Add a triangle rendering benchmark
This benchmark renders a single triangle to a double-buffered swapchain.
It uses a passthrough vertex and fragment shader written in GLSL and
compiled into SPIR-V using glslang.
Bug: b/158231104
Change-Id: I21f00f8f8595e018ff0d05f5af898a1f64d4dd12
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/45629
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/tests/VulkanBenchmarks/VulkanBenchmarks.cpp b/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
index 5f8fd37..41d51a1 100644
--- a/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
+++ b/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
@@ -14,10 +14,18 @@
#include "benchmark/benchmark.h"
+#define VK_USE_PLATFORM_WIN32_KHR
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#include <vulkan/vulkan.hpp>
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
+#include "SPIRV/GlslangToSpv.h"
+#include "StandAlone/ResourceLimits.h"
+#include "glslang/Public/ShaderLang.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
#include <cassert>
#include <vector>
@@ -38,6 +46,7 @@
std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices();
assert(!physicalDevices.empty());
+ physicalDevice = physicalDevices[0];
const float defaultQueuePriority = 0.0f;
vk::DeviceQueueCreateInfo queueCreatInfo;
@@ -49,7 +58,7 @@
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreatInfo;
- device = physicalDevices[0].createDevice(deviceCreateInfo, nullptr);
+ device = physicalDevice.createDevice(deviceCreateInfo, nullptr);
queue = device.getQueue(queueFamilyIndex, 0);
}
@@ -62,9 +71,29 @@
}
protected:
+ uint32_t getMemoryTypeIndex(uint32_t typeBits, vk::MemoryPropertyFlags properties)
+ {
+ vk::PhysicalDeviceMemoryProperties deviceMemoryProperties = physicalDevice.getMemoryProperties();
+ for(uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++)
+ {
+ if((typeBits & 1) == 1)
+ {
+ if((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
+ {
+ return i;
+ }
+ }
+ typeBits >>= 1;
+ }
+
+ assert(false);
+ return -1;
+ }
+
const uint32_t queueFamilyIndex = 0;
vk::Instance instance;
+ vk::PhysicalDevice physicalDevice;
vk::Device device;
vk::Queue queue;
@@ -199,3 +228,642 @@
BENCHMARK_REGISTER_F(ClearImageBenchmark, Clear)
->Unit(benchmark::kMillisecond)
->DenseRange(0, clearBenchmarkVariants.size() - 1, 1);
+
+class Window
+{
+public:
+ Window(vk::Instance instance, vk::Extent2D windowSize)
+ {
+ moduleInstance = GetModuleHandle(NULL);
+
+ windowClass.cbSize = sizeof(WNDCLASSEX);
+ windowClass.style = CS_HREDRAW | CS_VREDRAW;
+ windowClass.lpfnWndProc = DefWindowProc;
+ windowClass.cbClsExtra = 0;
+ windowClass.cbWndExtra = 0;
+ windowClass.hInstance = moduleInstance;
+ windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
+ windowClass.lpszMenuName = NULL;
+ windowClass.lpszClassName = "Window";
+ windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
+
+ RegisterClassEx(&windowClass);
+
+ DWORD style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ DWORD extendedStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
+
+ RECT windowRect;
+ windowRect.left = 0L;
+ windowRect.top = 0L;
+ windowRect.right = (long)windowSize.width;
+ windowRect.bottom = (long)windowSize.height;
+
+ AdjustWindowRectEx(&windowRect, style, FALSE, extendedStyle);
+ uint32_t x = (GetSystemMetrics(SM_CXSCREEN) - windowRect.right) / 2;
+ uint32_t y = (GetSystemMetrics(SM_CYSCREEN) - windowRect.bottom) / 2;
+
+ window = CreateWindowEx(extendedStyle, "Window", "Hello",
+ style | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+ x, y,
+ windowRect.right - windowRect.left,
+ windowRect.bottom - windowRect.top,
+ NULL, NULL, moduleInstance, NULL);
+
+ SetForegroundWindow(window);
+ SetFocus(window);
+
+ // Create the Vulkan surface
+ vk::Win32SurfaceCreateInfoKHR surfaceCreateInfo;
+ surfaceCreateInfo.hinstance = moduleInstance;
+ surfaceCreateInfo.hwnd = window;
+ surface = instance.createWin32SurfaceKHR(surfaceCreateInfo);
+ assert(surface);
+ }
+
+ ~Window()
+ {
+ instance.destroySurfaceKHR(surface);
+
+ DestroyWindow(window);
+ UnregisterClass("Window", moduleInstance);
+ }
+
+ vk::SurfaceKHR getSurface()
+ {
+ return surface;
+ }
+
+ void show()
+ {
+ ShowWindow(window, SW_SHOW);
+ }
+
+private:
+ HWND window;
+ HINSTANCE moduleInstance;
+ WNDCLASSEX windowClass;
+
+ const vk::Instance instance;
+ vk::SurfaceKHR surface;
+};
+
+class Swapchain
+{
+public:
+ Swapchain(vk::PhysicalDevice physicalDevice, vk::Device device, Window *window)
+ : device(device)
+ {
+ vk::SurfaceKHR surface = window->getSurface();
+
+ // Create the swapchain
+ vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface);
+
+ vk::SwapchainCreateInfoKHR swapchainCreateInfo;
+ swapchainCreateInfo.surface = surface;
+ swapchainCreateInfo.minImageCount = 2; // double-buffered
+ swapchainCreateInfo.imageFormat = colorFormat;
+ swapchainCreateInfo.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
+ swapchainCreateInfo.imageExtent = surfaceCapabilities.currentExtent;
+ swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
+ swapchainCreateInfo.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
+ swapchainCreateInfo.imageArrayLayers = 1;
+ swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;
+ swapchainCreateInfo.presentMode = vk::PresentModeKHR::eFifo;
+ swapchainCreateInfo.clipped = VK_TRUE;
+ swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
+
+ swapchain = device.createSwapchainKHR(swapchainCreateInfo);
+
+ // Obtain the images and create views for them
+ images = device.getSwapchainImagesKHR(swapchain);
+
+ imageViews.resize(images.size());
+ for(size_t i = 0; i < imageViews.size(); i++)
+ {
+ vk::ImageViewCreateInfo colorAttachmentView;
+ colorAttachmentView.image = images[i];
+ colorAttachmentView.viewType = vk::ImageViewType::e2D;
+ colorAttachmentView.format = colorFormat;
+ colorAttachmentView.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
+ colorAttachmentView.subresourceRange.baseMipLevel = 0;
+ colorAttachmentView.subresourceRange.levelCount = 1;
+ colorAttachmentView.subresourceRange.baseArrayLayer = 0;
+ colorAttachmentView.subresourceRange.layerCount = 1;
+
+ imageViews[i] = device.createImageView(colorAttachmentView);
+ }
+ }
+
+ ~Swapchain()
+ {
+ for(auto &imageView : imageViews)
+ {
+ device.destroyImageView(imageView);
+ }
+
+ device.destroySwapchainKHR(swapchain);
+ }
+
+ void acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t &imageIndex)
+ {
+ auto result = device.acquireNextImageKHR(swapchain, UINT64_MAX, presentCompleteSemaphore, vk::Fence());
+ imageIndex = result.value;
+ }
+
+ void queuePresent(vk::Queue queue, uint32_t imageIndex, vk::Semaphore waitSemaphore)
+ {
+ vk::PresentInfoKHR presentInfo;
+ presentInfo.pWaitSemaphores = &waitSemaphore;
+ presentInfo.waitSemaphoreCount = 1;
+ presentInfo.swapchainCount = 1;
+ presentInfo.pSwapchains = &swapchain;
+ presentInfo.pImageIndices = &imageIndex;
+
+ queue.presentKHR(presentInfo);
+ }
+
+ size_t imageCount() const
+ {
+ return images.size();
+ }
+
+ vk::ImageView getImageView(size_t i) const
+ {
+ return imageViews[i];
+ }
+
+ const vk::Format colorFormat = vk::Format::eB8G8R8A8Unorm;
+
+private:
+ const vk::Device device;
+
+ vk::SwapchainKHR swapchain;
+
+ std::vector<vk::Image> images;
+ std::vector<vk::ImageView> imageViews;
+};
+
+static std::vector<uint32_t> compileGLSLtoSPIRV(const char *glslSource, EShLanguage glslLanguage)
+{
+ // glslang requires one-time initialization.
+ const struct GlslangProcessInitialiser
+ {
+ GlslangProcessInitialiser() { glslang::InitializeProcess(); }
+ ~GlslangProcessInitialiser() { glslang::FinalizeProcess(); }
+ } glslangInitialiser;
+
+ std::unique_ptr<glslang::TShader> glslangShader = std::make_unique<glslang::TShader>(glslLanguage);
+
+ glslangShader->setStrings(&glslSource, 1);
+ glslangShader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
+ glslangShader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
+
+ const int defaultVersion = 100;
+ EShMessages messages = static_cast<EShMessages>(EShMessages::EShMsgDefault | EShMessages::EShMsgSpvRules | EShMessages::EShMsgVulkanRules);
+ bool parseResult = glslangShader->parse(&glslang::DefaultTBuiltInResource, defaultVersion, false, messages);
+
+ if(!parseResult)
+ {
+ std::string debugLog = glslangShader->getInfoDebugLog();
+ std::string infoLog = glslangShader->getInfoLog();
+ assert(false);
+ }
+
+ glslang::TIntermediate *intermediateRepresentation = glslangShader->getIntermediate();
+ assert(intermediateRepresentation);
+
+ std::vector<uint32_t> spirv;
+ glslang::SpvOptions options;
+ glslang::GlslangToSpv(*intermediateRepresentation, spirv, &options);
+ assert(spirv.size() != 0);
+
+ return spirv;
+}
+
+class TriangleBenchmark : public VulkanBenchmark
+{
+public:
+ void SetUp(::benchmark::State &state) override
+ {
+ VulkanBenchmark::SetUp(state);
+
+ window = new Window(instance, windowSize);
+ swapchain = new Swapchain(physicalDevice, device, window);
+
+ renderPass = createRenderPass(swapchain->colorFormat);
+ framebuffers = createFramebuffers(renderPass);
+
+ prepareVertices();
+
+ pipeline = createGraphicsPipeline(renderPass);
+
+ createSynchronizationPrimitives();
+
+ createCommandBuffers(renderPass);
+ }
+
+ void TearDown(const ::benchmark::State &state) override
+ {
+ device.destroyPipelineLayout(pipelineLayout);
+ device.destroyPipelineCache(pipelineCache);
+
+ device.destroyBuffer(vertices.buffer);
+ device.freeMemory(vertices.memory);
+
+ device.destroySemaphore(presentCompleteSemaphore);
+ device.destroySemaphore(renderCompleteSemaphore);
+
+ for(auto &fence : waitFences)
+ {
+ device.destroyFence(fence);
+ }
+
+ for(auto &framebuffer : framebuffers)
+ {
+ device.destroyFramebuffer(framebuffer);
+ }
+
+ device.destroyRenderPass(renderPass);
+
+ device.freeCommandBuffers(commandPool, commandBuffers);
+ device.destroyCommandPool(commandPool);
+
+ delete swapchain;
+ delete window;
+
+ VulkanBenchmark::TearDown(state);
+ }
+
+protected:
+ void createSynchronizationPrimitives()
+ {
+ vk::SemaphoreCreateInfo semaphoreCreateInfo;
+ presentCompleteSemaphore = device.createSemaphore(semaphoreCreateInfo);
+ renderCompleteSemaphore = device.createSemaphore(semaphoreCreateInfo);
+
+ vk::FenceCreateInfo fenceCreateInfo;
+ fenceCreateInfo.flags = vk::FenceCreateFlagBits::eSignaled;
+ waitFences.resize(swapchain->imageCount());
+ for(auto &fence : waitFences)
+ {
+ fence = device.createFence(fenceCreateInfo);
+ }
+ }
+
+ void createCommandBuffers(vk::RenderPass renderPass)
+ {
+ vk::CommandPoolCreateInfo commandPoolCreateInfo;
+ commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex;
+ commandPoolCreateInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
+ commandPool = device.createCommandPool(commandPoolCreateInfo);
+
+ vk::CommandBufferAllocateInfo commandBufferAllocateInfo;
+ commandBufferAllocateInfo.commandPool = commandPool;
+ commandBufferAllocateInfo.commandBufferCount = swapchain->imageCount();
+ commandBufferAllocateInfo.level = vk::CommandBufferLevel::ePrimary;
+
+ commandBuffers = device.allocateCommandBuffers(commandBufferAllocateInfo);
+
+ for(size_t i = 0; i < commandBuffers.size(); i++)
+ {
+ vk::CommandBufferBeginInfo commandBufferBeginInfo;
+ commandBuffers[i].begin(commandBufferBeginInfo);
+
+ vk::ClearValue clearValues[1];
+ clearValues[0].color = vk::ClearColorValue(std::array<float, 4>{ 0.5f, 0.5f, 0.5f, 1.0f });
+
+ vk::RenderPassBeginInfo renderPassBeginInfo;
+ renderPassBeginInfo.framebuffer = framebuffers[i];
+ renderPassBeginInfo.renderPass = renderPass;
+ renderPassBeginInfo.renderArea.offset.x = 0;
+ renderPassBeginInfo.renderArea.offset.y = 0;
+ renderPassBeginInfo.renderArea.extent = windowSize;
+ renderPassBeginInfo.clearValueCount = 2;
+ renderPassBeginInfo.pClearValues = clearValues;
+ commandBuffers[i].beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline);
+
+ // Set dynamic state
+ vk::Viewport viewport(0.0f, 0.0f, windowSize.width, windowSize.height, 0.0f, 1.0f);
+ commandBuffers[i].setViewport(0, 1, &viewport);
+
+ vk::Rect2D scissor(vk::Offset2D(0, 0), windowSize);
+ commandBuffers[i].setScissor(0, 1, &scissor);
+
+ // Draw a triangle
+ commandBuffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get());
+ commandBuffers[i].bindVertexBuffers(0, { vertices.buffer }, { 0 });
+ commandBuffers[i].draw(3, 1, 0, 0);
+
+ commandBuffers[i].endRenderPass();
+ commandBuffers[i].end();
+ }
+ }
+
+ void renderFrame()
+ {
+ swapchain->acquireNextImage(presentCompleteSemaphore, currentFrameBuffer);
+
+ device.waitForFences(1, &waitFences[currentFrameBuffer], VK_TRUE, UINT64_MAX);
+ device.resetFences(1, &waitFences[currentFrameBuffer]);
+
+ vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
+
+ vk::SubmitInfo submitInfo;
+ submitInfo.pWaitDstStageMask = &waitStageMask;
+ submitInfo.pWaitSemaphores = &presentCompleteSemaphore;
+ submitInfo.waitSemaphoreCount = 1;
+ submitInfo.pSignalSemaphores = &renderCompleteSemaphore;
+ submitInfo.signalSemaphoreCount = 1;
+ submitInfo.pCommandBuffers = &commandBuffers[currentFrameBuffer];
+ submitInfo.commandBufferCount = 1;
+
+ queue.submit(1, &submitInfo, waitFences[currentFrameBuffer]);
+
+ swapchain->queuePresent(queue, currentFrameBuffer, renderCompleteSemaphore);
+ }
+
+ void prepareVertices()
+ {
+ struct Vertex
+ {
+ float position[3];
+ float color[3];
+ };
+
+ Vertex vertexBufferData[] = {
+ { { 1.0f, 1.0f, 0.05f }, { 1.0f, 0.0f, 0.0f } },
+ { { -1.0f, 1.0f, 0.5f }, { 0.0f, 1.0f, 0.0f } },
+ { { 0.0f, -1.0f, 0.5f }, { 0.0f, 0.0f, 1.0f } }
+ };
+
+ vk::BufferCreateInfo vertexBufferInfo;
+ vertexBufferInfo.size = sizeof(vertexBufferData);
+ vertexBufferInfo.usage = vk::BufferUsageFlagBits::eVertexBuffer;
+ vertices.buffer = device.createBuffer(vertexBufferInfo);
+
+ vk::MemoryAllocateInfo memoryAllocateInfo;
+ vk::MemoryRequirements memoryRequirements = device.getBufferMemoryRequirements(vertices.buffer);
+ memoryAllocateInfo.allocationSize = memoryRequirements.size;
+ memoryAllocateInfo.memoryTypeIndex = getMemoryTypeIndex(memoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
+ vk::DeviceMemory vertexBufferMemory = device.allocateMemory(memoryAllocateInfo);
+
+ void *data = device.mapMemory(vertexBufferMemory, 0, VK_WHOLE_SIZE);
+ memcpy(data, vertexBufferData, sizeof(vertexBufferData));
+ device.unmapMemory(vertexBufferMemory);
+ device.bindBufferMemory(vertices.buffer, vertexBufferMemory, 0);
+
+ vertices.inputBinding.binding = 0;
+ vertices.inputBinding.stride = sizeof(Vertex);
+ vertices.inputBinding.inputRate = vk::VertexInputRate::eVertex;
+
+ vertices.inputAttributes.push_back(vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, position)));
+ vertices.inputAttributes.push_back(vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)));
+
+ vertices.inputState.vertexBindingDescriptionCount = 1;
+ vertices.inputState.pVertexBindingDescriptions = &vertices.inputBinding;
+ vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.inputAttributes.size());
+ vertices.inputState.pVertexAttributeDescriptions = vertices.inputAttributes.data();
+ }
+
+ std::vector<vk::Framebuffer> createFramebuffers(vk::RenderPass renderPass)
+ {
+ std::vector<vk::Framebuffer> framebuffers(swapchain->imageCount());
+
+ for(size_t i = 0; i < framebuffers.size(); i++)
+ {
+ std::array<vk::ImageView, 1> attachments; // color only
+ attachments[0] = swapchain->getImageView(i);
+
+ vk::FramebufferCreateInfo framebufferCreateInfo;
+
+ framebufferCreateInfo.renderPass = renderPass;
+ framebufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+ framebufferCreateInfo.pAttachments = attachments.data();
+ framebufferCreateInfo.width = windowSize.width;
+ framebufferCreateInfo.height = windowSize.height;
+ framebufferCreateInfo.layers = 1;
+
+ framebuffers[i] = device.createFramebuffer(framebufferCreateInfo);
+ }
+
+ return framebuffers;
+ }
+
+ vk::RenderPass createRenderPass(vk::Format colorFormat)
+ {
+ std::array<vk::AttachmentDescription, 1> attachments;
+
+ attachments[0].format = colorFormat;
+ attachments[0].samples = vk::SampleCountFlagBits::e1;
+ attachments[0].loadOp = vk::AttachmentLoadOp::eClear;
+ attachments[0].storeOp = vk::AttachmentStoreOp::eStore;
+ attachments[0].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
+ attachments[0].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
+ attachments[0].initialLayout = vk::ImageLayout::eUndefined;
+ attachments[0].finalLayout = vk::ImageLayout::ePresentSrcKHR;
+
+ vk::AttachmentReference colorReference;
+ colorReference.attachment = 0;
+ colorReference.layout = vk::ImageLayout::eColorAttachmentOptimal;
+
+ vk::SubpassDescription subpassDescription;
+ subpassDescription.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
+ subpassDescription.colorAttachmentCount = 1;
+ subpassDescription.pColorAttachments = &colorReference;
+
+ std::array<vk::SubpassDependency, 2> dependencies;
+
+ dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+ dependencies[0].dstSubpass = 0;
+ dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eBottomOfPipe;
+ dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
+ dependencies[0].srcAccessMask = vk::AccessFlagBits::eMemoryRead;
+ dependencies[0].dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
+ dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
+
+ dependencies[1].srcSubpass = 0;
+ dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+ dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
+ dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eBottomOfPipe;
+ dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite;
+ dependencies[1].dstAccessMask = vk::AccessFlagBits::eMemoryRead;
+ dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
+
+ vk::RenderPassCreateInfo renderPassInfo;
+ renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+ renderPassInfo.pAttachments = attachments.data();
+ renderPassInfo.subpassCount = 1;
+ renderPassInfo.pSubpasses = &subpassDescription;
+ renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+ renderPassInfo.pDependencies = dependencies.data();
+
+ return device.createRenderPass(renderPassInfo);
+ }
+
+ vk::UniqueShaderModule createShaderModule(const char *glslSource, EShLanguage glslLanguage)
+ {
+ auto spirv = compileGLSLtoSPIRV(glslSource, glslLanguage);
+
+ vk::ShaderModuleCreateInfo moduleCreateInfo;
+ moduleCreateInfo.codeSize = spirv.size() * sizeof(uint32_t);
+ moduleCreateInfo.pCode = (uint32_t *)spirv.data();
+
+ return device.createShaderModuleUnique(moduleCreateInfo);
+ }
+
+ vk::UniquePipeline createGraphicsPipeline(vk::RenderPass renderPass)
+ {
+ vk::PipelineLayoutCreateInfo pPipelineLayoutCreateInfo;
+ pPipelineLayoutCreateInfo.setLayoutCount = 0;
+ pipelineLayout = device.createPipelineLayout(pPipelineLayoutCreateInfo);
+
+ vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
+ pipelineCreateInfo.layout = pipelineLayout;
+ pipelineCreateInfo.renderPass = renderPass;
+
+ vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState;
+ inputAssemblyState.topology = vk::PrimitiveTopology::eTriangleList;
+
+ vk::PipelineRasterizationStateCreateInfo rasterizationState;
+ rasterizationState.depthClampEnable = VK_FALSE;
+ rasterizationState.rasterizerDiscardEnable = VK_FALSE;
+ rasterizationState.polygonMode = vk::PolygonMode::eFill;
+ rasterizationState.cullMode = vk::CullModeFlagBits::eNone;
+ rasterizationState.frontFace = vk::FrontFace::eCounterClockwise;
+ rasterizationState.depthBiasEnable = VK_FALSE;
+ rasterizationState.lineWidth = 1.0f;
+
+ vk::PipelineColorBlendAttachmentState blendAttachmentState;
+ blendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
+ blendAttachmentState.blendEnable = VK_FALSE;
+ vk::PipelineColorBlendStateCreateInfo colorBlendState;
+ colorBlendState.attachmentCount = 1;
+ colorBlendState.pAttachments = &blendAttachmentState;
+
+ vk::PipelineViewportStateCreateInfo viewportState;
+ viewportState.viewportCount = 1;
+ viewportState.scissorCount = 1;
+
+ std::vector<vk::DynamicState> dynamicStateEnables;
+ dynamicStateEnables.push_back(vk::DynamicState::eViewport);
+ dynamicStateEnables.push_back(vk::DynamicState::eScissor);
+ vk::PipelineDynamicStateCreateInfo dynamicState = {};
+ dynamicState.pDynamicStates = dynamicStateEnables.data();
+ dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStateEnables.size());
+
+ vk::PipelineDepthStencilStateCreateInfo depthStencilState;
+ depthStencilState.depthTestEnable = VK_FALSE;
+ depthStencilState.depthWriteEnable = VK_FALSE;
+ depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual;
+ depthStencilState.depthBoundsTestEnable = VK_FALSE;
+ depthStencilState.back.failOp = vk::StencilOp::eKeep;
+ depthStencilState.back.passOp = vk::StencilOp::eKeep;
+ depthStencilState.back.compareOp = vk::CompareOp::eAlways;
+ depthStencilState.stencilTestEnable = VK_FALSE;
+ depthStencilState.front = depthStencilState.back;
+
+ vk::PipelineMultisampleStateCreateInfo multisampleState;
+ multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1;
+ multisampleState.pSampleMask = nullptr;
+
+ const char *vertexShader = R"(#version 310 es
+ layout(location = 0) in vec3 inPos;
+ layout(location = 1) in vec3 inColor;
+ layout(location = 0) out vec3 outColor;
+
+ void main()
+ {
+ outColor = inColor;
+ gl_Position = vec4(inPos.xyz, 1.0);
+ }
+ )";
+
+ const char *fragmentShader = R"(#version 310 es
+ precision highp float;
+
+ layout(location = 0) in vec3 inColor;
+ layout(location = 0) out vec4 outColor;
+
+ void main()
+ {
+ outColor = vec4(inColor, 1.0);
+ }
+ )";
+
+ vk::UniqueShaderModule vertexModule = createShaderModule(vertexShader, EShLanguage::EShLangVertex);
+ vk::UniqueShaderModule fragmentModule = createShaderModule(fragmentShader, EShLanguage::EShLangFragment);
+
+ std::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages;
+
+ shaderStages[0].module = vertexModule.get();
+ shaderStages[0].stage = vk::ShaderStageFlagBits::eVertex;
+ shaderStages[0].pName = "main";
+
+ shaderStages[1].module = fragmentModule.get();
+ shaderStages[1].stage = vk::ShaderStageFlagBits::eFragment;
+ shaderStages[1].pName = "main";
+
+ pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+ pipelineCreateInfo.pStages = shaderStages.data();
+ pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+ pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+ pipelineCreateInfo.pRasterizationState = &rasterizationState;
+ pipelineCreateInfo.pColorBlendState = &colorBlendState;
+ pipelineCreateInfo.pMultisampleState = &multisampleState;
+ pipelineCreateInfo.pViewportState = &viewportState;
+ pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+ pipelineCreateInfo.renderPass = renderPass;
+ pipelineCreateInfo.pDynamicState = &dynamicState;
+
+ return device.createGraphicsPipelineUnique(nullptr, pipelineCreateInfo);
+ }
+
+ const vk::Extent2D windowSize = { 1280, 720 };
+ Window *window = nullptr;
+
+ Swapchain *swapchain = nullptr;
+
+ vk::RenderPass renderPass;
+ std::vector<vk::Framebuffer> framebuffers;
+ uint32_t currentFrameBuffer = 0;
+
+ struct VertexBuffer
+ {
+ vk::Buffer buffer;
+ vk::DeviceMemory memory;
+
+ vk::PipelineVertexInputStateCreateInfo inputState;
+ vk::VertexInputBindingDescription inputBinding;
+ std::vector<vk::VertexInputAttributeDescription> inputAttributes;
+ } vertices;
+
+ vk::PipelineLayout pipelineLayout;
+ vk::PipelineCache pipelineCache;
+ vk::UniquePipeline pipeline;
+
+ vk::CommandPool commandPool;
+ std::vector<vk::CommandBuffer> commandBuffers;
+
+ vk::Semaphore presentCompleteSemaphore;
+ vk::Semaphore renderCompleteSemaphore;
+
+ std::vector<vk::Fence> waitFences;
+};
+
+BENCHMARK_DEFINE_F(TriangleBenchmark, Triangle)
+(benchmark::State &state)
+{
+ if(false) window->show(); // Enable for visual verification.
+
+ // Warmup
+ renderFrame();
+
+ for(auto _ : state)
+ {
+ renderFrame();
+ }
+}
+BENCHMARK_REGISTER_F(TriangleBenchmark, Triangle)
+ ->Unit(benchmark::kMillisecond);