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