VulkanBenchmarks: factor out code into separate files

* Move utility classes into separate hpp/cpp files
* Move utility functions into Util.hpp/cpp

Bug: b/176981107
Change-Id: If9046f12080f1eb09378586673d871867a836a1b
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/51868
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/tests/VulkanBenchmarks/Buffer.cpp b/tests/VulkanBenchmarks/Buffer.cpp
new file mode 100644
index 0000000..93e6b0e
--- /dev/null
+++ b/tests/VulkanBenchmarks/Buffer.cpp
@@ -0,0 +1,42 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Buffer.hpp"
+
+Buffer::Buffer(vk::Device device, vk::DeviceSize size, vk::BufferUsageFlags usage)
+    : device(device)
+    , size(size)
+{
+	vk::BufferCreateInfo bufferInfo{};
+	bufferInfo.size = size;
+	bufferInfo.usage = usage;
+	bufferInfo.sharingMode = vk::SharingMode::eExclusive;
+
+	buffer = device.createBuffer(bufferInfo);
+
+	auto memRequirements = device.getBufferMemoryRequirements(buffer);
+
+	vk::MemoryAllocateInfo allocInfo{};
+	allocInfo.allocationSize = memRequirements.size;
+	allocInfo.memoryTypeIndex = 0;  //TODO: getMemoryTypeIndex(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
+
+	bufferMemory = device.allocateMemory(allocInfo);
+	device.bindBufferMemory(buffer, bufferMemory, 0);
+}
+
+Buffer::~Buffer()
+{
+	device.freeMemory(bufferMemory);
+	device.destroyBuffer(buffer);
+}
diff --git a/tests/VulkanBenchmarks/Buffer.hpp b/tests/VulkanBenchmarks/Buffer.hpp
new file mode 100644
index 0000000..7db4eaf
--- /dev/null
+++ b/tests/VulkanBenchmarks/Buffer.hpp
@@ -0,0 +1,48 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_BUFFER_HPP_
+#define BENCHMARKS_BUFFER_HPP_
+
+#include "VulkanHeaders.hpp"
+
+class Buffer
+{
+public:
+	Buffer(vk::Device device, vk::DeviceSize size, vk::BufferUsageFlags usage);
+	~Buffer();
+
+	vk::Buffer getBuffer()
+	{
+		return buffer;
+	}
+
+	void *mapMemory()
+	{
+		return device.mapMemory(bufferMemory, 0, size);
+	}
+
+	void unmapMemory()
+	{
+		device.unmapMemory(bufferMemory);
+	}
+
+private:
+	const vk::Device device;
+	vk::DeviceSize size;
+	vk::Buffer buffer;              // Owning handle
+	vk::DeviceMemory bufferMemory;  // Owning handle
+};
+
+#endif  // BENCHMARKS_BUFFER_HPP_
diff --git a/tests/VulkanBenchmarks/CMakeLists.txt b/tests/VulkanBenchmarks/CMakeLists.txt
index 3585067..ca7708e 100644
--- a/tests/VulkanBenchmarks/CMakeLists.txt
+++ b/tests/VulkanBenchmarks/CMakeLists.txt
@@ -22,8 +22,22 @@
 )
 
 set(VULKAN_BENCHMARKS_SRC_FILES
-    VulkanBenchmarks.cpp
+    Buffer.cpp
+    Buffer.hpp
+    Framebuffer.cpp
+    Framebuffer.hpp
+    Image.cpp
+    Image.hpp
     main.cpp
+    Swapchain.cpp
+    Swapchain.hpp
+    Util.cpp
+    Util.hpp
+    VulkanBenchmarks.cpp
+    VulkanHeaders.cpp
+    VulkanHeaders.hpp
+    Window.cpp
+    Window.hpp
 )
 
 add_executable(VulkanBenchmarks
diff --git a/tests/VulkanBenchmarks/Framebuffer.cpp b/tests/VulkanBenchmarks/Framebuffer.cpp
new file mode 100644
index 0000000..ab62a10
--- /dev/null
+++ b/tests/VulkanBenchmarks/Framebuffer.cpp
@@ -0,0 +1,51 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Framebuffer.hpp"
+
+Framebuffer::Framebuffer(vk::Device device, vk::ImageView attachment, vk::Format colorFormat, vk::RenderPass renderPass, vk::Extent2D extent, bool multisample)
+    : device(device)
+{
+	std::vector<vk::ImageView> attachments(multisample ? 2 : 1);
+
+	if(multisample)
+	{
+		multisampleImage.reset(new Image(device, extent.width, extent.height, colorFormat, vk::SampleCountFlagBits::e4));
+
+		// We'll be rendering to attachment location 0
+		attachments[0] = multisampleImage->getImageView();
+		attachments[1] = attachment;  // Resolve attachment
+	}
+	else
+	{
+		attachments[0] = attachment;
+	}
+
+	vk::FramebufferCreateInfo framebufferCreateInfo;
+
+	framebufferCreateInfo.renderPass = renderPass;
+	framebufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+	framebufferCreateInfo.pAttachments = attachments.data();
+	framebufferCreateInfo.width = extent.width;
+	framebufferCreateInfo.height = extent.height;
+	framebufferCreateInfo.layers = 1;
+
+	framebuffer = device.createFramebuffer(framebufferCreateInfo);
+}
+
+Framebuffer::~Framebuffer()
+{
+	multisampleImage.reset();
+	device.destroyFramebuffer(framebuffer);
+}
diff --git a/tests/VulkanBenchmarks/Framebuffer.hpp b/tests/VulkanBenchmarks/Framebuffer.hpp
new file mode 100644
index 0000000..b3d1620
--- /dev/null
+++ b/tests/VulkanBenchmarks/Framebuffer.hpp
@@ -0,0 +1,38 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_FRAMEBUFFER_HPP_
+#define BENCHMARKS_FRAMEBUFFER_HPP_
+
+#include "Image.hpp"
+#include "VulkanHeaders.hpp"
+
+class Framebuffer
+{
+public:
+	Framebuffer(vk::Device device, vk::ImageView attachment, vk::Format colorFormat, vk::RenderPass renderPass, vk::Extent2D extent, bool multisample);
+	~Framebuffer();
+
+	vk::Framebuffer getFramebuffer()
+	{
+		return framebuffer;
+	}
+
+private:
+	const vk::Device device;
+	vk::Framebuffer framebuffer;  // Owning handle
+	std::unique_ptr<Image> multisampleImage;
+};
+
+#endif  // BENCHMARKS_FRAMEBUFFER_HPP_
diff --git a/tests/VulkanBenchmarks/Image.cpp b/tests/VulkanBenchmarks/Image.cpp
new file mode 100644
index 0000000..0ffc69b
--- /dev/null
+++ b/tests/VulkanBenchmarks/Image.cpp
@@ -0,0 +1,61 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Image.hpp"
+
+Image::Image(vk::Device device, uint32_t width, uint32_t height, vk::Format format, vk::SampleCountFlagBits sampleCount /*= vk::SampleCountFlagBits::e1*/)
+    : device(device)
+{
+	vk::ImageCreateInfo imageInfo;
+	imageInfo.imageType = vk::ImageType::e2D;
+	imageInfo.format = format;
+	imageInfo.tiling = vk::ImageTiling::eOptimal;
+	imageInfo.initialLayout = vk::ImageLayout::eGeneral;
+	imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment;
+	imageInfo.samples = sampleCount;
+	imageInfo.extent = vk::Extent3D(width, height, 1);
+	imageInfo.mipLevels = 1;
+	imageInfo.arrayLayers = 1;
+
+	image = device.createImage(imageInfo);
+
+	vk::MemoryRequirements memoryRequirements = device.getImageMemoryRequirements(image);
+
+	vk::MemoryAllocateInfo allocateInfo;
+	allocateInfo.allocationSize = memoryRequirements.size;
+	allocateInfo.memoryTypeIndex = 0;  //getMemoryTypeIndex(memoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
+
+	imageMemory = device.allocateMemory(allocateInfo);
+
+	device.bindImageMemory(image, imageMemory, 0);
+
+	vk::ImageViewCreateInfo imageViewInfo;
+	imageViewInfo.image = image;
+	imageViewInfo.viewType = vk::ImageViewType::e2D;
+	imageViewInfo.format = format;
+	imageViewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
+	imageViewInfo.subresourceRange.baseMipLevel = 0;
+	imageViewInfo.subresourceRange.levelCount = 1;
+	imageViewInfo.subresourceRange.baseArrayLayer = 0;
+	imageViewInfo.subresourceRange.layerCount = 1;
+
+	imageView = device.createImageView(imageViewInfo);
+}
+
+Image::~Image()
+{
+	device.destroyImageView(imageView);
+	device.freeMemory(imageMemory);
+	device.destroyImage(image);
+}
diff --git a/tests/VulkanBenchmarks/Image.hpp b/tests/VulkanBenchmarks/Image.hpp
new file mode 100644
index 0000000..3526ff2
--- /dev/null
+++ b/tests/VulkanBenchmarks/Image.hpp
@@ -0,0 +1,44 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_IMAGE_HPP_
+#define BENCHMARKS_IMAGE_HPP_
+
+#include "VulkanHeaders.hpp"
+
+class Image
+{
+public:
+	Image(vk::Device device, uint32_t width, uint32_t height, vk::Format format, vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1);
+	~Image();
+
+	vk::Image getImage()
+	{
+		return image;
+	}
+
+	vk::ImageView getImageView()
+	{
+		return imageView;
+	}
+
+private:
+	const vk::Device device;
+
+	vk::Image image;               // Owning handle
+	vk::DeviceMemory imageMemory;  // Owning handle
+	vk::ImageView imageView;       // Owning handle
+};
+
+#endif  // BENCHMARKS_IMAGE_HPP_
diff --git a/tests/VulkanBenchmarks/Swapchain.cpp b/tests/VulkanBenchmarks/Swapchain.cpp
new file mode 100644
index 0000000..843fc63
--- /dev/null
+++ b/tests/VulkanBenchmarks/Swapchain.cpp
@@ -0,0 +1,89 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Swapchain.hpp"
+#include "Window.hpp"
+
+Swapchain::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);
+	extent = surfaceCapabilities.currentExtent;
+
+	vk::SwapchainCreateInfoKHR swapchainCreateInfo;
+	swapchainCreateInfo.surface = surface;
+	swapchainCreateInfo.minImageCount = 2;  // double-buffered
+	swapchainCreateInfo.imageFormat = colorFormat;
+	swapchainCreateInfo.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
+	swapchainCreateInfo.imageExtent = extent;
+	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::~Swapchain()
+{
+	for(auto &imageView : imageViews)
+	{
+		device.destroyImageView(imageView, nullptr);
+	}
+
+	device.destroySwapchainKHR(swapchain, nullptr);
+}
+
+void Swapchain::acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t &imageIndex)
+{
+	auto result = device.acquireNextImageKHR(swapchain, UINT64_MAX, presentCompleteSemaphore, vk::Fence());
+	imageIndex = result.value;
+}
+
+void Swapchain::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);
+}
diff --git a/tests/VulkanBenchmarks/Swapchain.hpp b/tests/VulkanBenchmarks/Swapchain.hpp
new file mode 100644
index 0000000..d7ab6fc
--- /dev/null
+++ b/tests/VulkanBenchmarks/Swapchain.hpp
@@ -0,0 +1,59 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_SWAPCHAIN_HPP_
+#define BENCHMARKS_SWAPCHAIN_HPP_
+
+#include "VulkanHeaders.hpp"
+#include <vector>
+
+class Window;
+
+class Swapchain
+{
+public:
+	Swapchain(vk::PhysicalDevice physicalDevice, vk::Device device, Window &window);
+	~Swapchain();
+
+	void acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t &imageIndex);
+	void queuePresent(vk::Queue queue, uint32_t imageIndex, vk::Semaphore waitSemaphore);
+
+	size_t imageCount() const
+	{
+		return images.size();
+	}
+
+	vk::ImageView getImageView(size_t i) const
+	{
+		return imageViews[i];
+	}
+
+	vk::Extent2D getExtent() const
+	{
+		return extent;
+	}
+
+	const vk::Format colorFormat = vk::Format::eB8G8R8A8Unorm;
+
+private:
+	const vk::Device device;
+
+	vk::SwapchainKHR swapchain;  // Owning handle
+	vk::Extent2D extent;
+
+	std::vector<vk::Image> images;          // Weak pointers. Presentable images owned by swapchain object.
+	std::vector<vk::ImageView> imageViews;  // Owning handles
+};
+
+#endif  // BENCHMARKS_SWAPCHAIN_HPP_
diff --git a/tests/VulkanBenchmarks/Util.cpp b/tests/VulkanBenchmarks/Util.cpp
new file mode 100644
index 0000000..af2980c
--- /dev/null
+++ b/tests/VulkanBenchmarks/Util.cpp
@@ -0,0 +1,174 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Util.hpp"
+#include "SPIRV/GlslangToSpv.h"
+#include "StandAlone/ResourceLimits.h"
+
+namespace Util {
+
+uint32_t getMemoryTypeIndex(vk::PhysicalDevice physicalDevice, 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;
+}
+
+vk::CommandBuffer beginSingleTimeCommands(vk::Device device, vk::CommandPool commandPool)
+{
+	vk::CommandBufferAllocateInfo allocInfo{};
+	allocInfo.level = vk::CommandBufferLevel::ePrimary;
+	allocInfo.commandPool = commandPool;
+	allocInfo.commandBufferCount = 1;
+
+	auto commandBuffer = device.allocateCommandBuffers(allocInfo);
+
+	vk::CommandBufferBeginInfo beginInfo{};
+	beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
+
+	commandBuffer[0].begin(beginInfo);
+
+	return commandBuffer[0];
+}
+
+void endSingleTimeCommands(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::CommandBuffer commandBuffer)
+{
+	commandBuffer.end();
+
+	vk::SubmitInfo submitInfo{};
+	submitInfo.commandBufferCount = 1;
+	submitInfo.pCommandBuffers = &commandBuffer;
+
+	vk::Fence fence = {};  // TODO: pass in fence?
+	queue.submit(1, &submitInfo, fence);
+	queue.waitIdle();
+
+	device.freeCommandBuffers(commandPool, 1, &commandBuffer);
+}
+
+void transitionImageLayout(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout)
+{
+	vk::CommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
+
+	vk::ImageMemoryBarrier barrier{};
+	barrier.oldLayout = oldLayout;
+	barrier.newLayout = newLayout;
+	barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.image = image;
+	barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
+	barrier.subresourceRange.baseMipLevel = 0;
+	barrier.subresourceRange.levelCount = 1;
+	barrier.subresourceRange.baseArrayLayer = 0;
+	barrier.subresourceRange.layerCount = 1;
+
+	vk::PipelineStageFlags sourceStage;
+	vk::PipelineStageFlags destinationStage;
+
+	if(oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal)
+	{
+		barrier.srcAccessMask = {};
+		barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
+
+		sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
+		destinationStage = vk::PipelineStageFlagBits::eTransfer;
+	}
+	else if(oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal)
+	{
+		barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
+		barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
+
+		sourceStage = vk::PipelineStageFlagBits::eTransfer;
+		destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
+	}
+	else
+	{
+		assert(!"unsupported layout transition!");
+	}
+
+	commandBuffer.pipelineBarrier(sourceStage, destinationStage, vk::DependencyFlags{}, 0, nullptr, 0, nullptr, 1, &barrier);
+
+	endSingleTimeCommands(device, commandPool, queue, commandBuffer);
+}
+
+void copyBufferToImage(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height)
+{
+	vk::CommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
+
+	vk::BufferImageCopy region{};
+	region.bufferOffset = 0;
+	region.bufferRowLength = 0;
+	region.bufferImageHeight = 0;
+	region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
+	region.imageSubresource.mipLevel = 0;
+	region.imageSubresource.baseArrayLayer = 0;
+	region.imageSubresource.layerCount = 1;
+	region.imageOffset = vk::Offset3D{ 0, 0, 0 };
+	region.imageExtent = vk::Extent3D{ width, height, 1 };
+
+	commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, &region);
+
+	endSingleTimeCommands(device, commandPool, queue, commandBuffer);
+}
+
+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;
+}
+
+}  // namespace Util
diff --git a/tests/VulkanBenchmarks/Util.hpp b/tests/VulkanBenchmarks/Util.hpp
new file mode 100644
index 0000000..adfdedb
--- /dev/null
+++ b/tests/VulkanBenchmarks/Util.hpp
@@ -0,0 +1,39 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_UTIL_HPP_
+#define BENCHMARKS_UTIL_HPP_
+
+#include "VulkanHeaders.hpp"
+#include "glslang/Public/ShaderLang.h"
+
+#include <vector>
+
+namespace Util {
+
+uint32_t getMemoryTypeIndex(vk::PhysicalDevice physicalDevice, uint32_t typeBits, vk::MemoryPropertyFlags properties);
+
+vk::CommandBuffer beginSingleTimeCommands(vk::Device device, vk::CommandPool commandPool);
+
+void endSingleTimeCommands(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::CommandBuffer commandBuffer);
+
+void transitionImageLayout(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout);
+
+void copyBufferToImage(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height);
+
+std::vector<uint32_t> compileGLSLtoSPIRV(const char *glslSource, EShLanguage glslLanguage);
+
+}  // namespace Util
+
+#endif  // BENCHMARKS_UTIL_HPP_
diff --git a/tests/VulkanBenchmarks/VulkanBenchmarks.cpp b/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
index 2f566ce..9196dd9 100644
--- a/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
+++ b/tests/VulkanBenchmarks/VulkanBenchmarks.cpp
@@ -14,157 +14,19 @@
 
 #include "benchmark/benchmark.h"
 
-#if !defined(USE_HEADLESS_SURFACE)
-#	define USE_HEADLESS_SURFACE 0
-#endif
-
-#if !defined(_WIN32)
-// @TODO: implement native Window support for current platform. For now, always use HeadlessSurface.
-#	undef USE_HEADLESS_SURFACE
-#	define USE_HEADLESS_SURFACE 1
-#endif
-
-#if defined(_WIN32)
-#	define VK_USE_PLATFORM_WIN32_KHR
-#endif
-#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
-#define VULKAN_HPP_NO_NODISCARD_WARNINGS
-#include <vulkan/vulkan.hpp>
-VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
-
-#include "SPIRV/GlslangToSpv.h"
-#include "StandAlone/ResourceLimits.h"
-#include "glslang/Public/ShaderLang.h"
-
-#if defined(_WIN32)
-#	define WIN32_LEAN_AND_MEAN
-#	include <Windows.h>
-#endif
+#include "Buffer.hpp"
+#include "Framebuffer.hpp"
+#include "Image.hpp"
+#include "Swapchain.hpp"
+#include "Util.hpp"
+#include "VulkanHeaders.hpp"
+#include "Window.hpp"
 
 #include <cassert>
 #include <vector>
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
 
-namespace {
-uint32_t getMemoryTypeIndex(vk::PhysicalDevice physicalDevice, 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;
-}
-
-vk::CommandBuffer beginSingleTimeCommands(vk::Device device, vk::CommandPool commandPool)
-{
-	vk::CommandBufferAllocateInfo allocInfo{};
-	allocInfo.level = vk::CommandBufferLevel::ePrimary;
-	allocInfo.commandPool = commandPool;
-	allocInfo.commandBufferCount = 1;
-
-	auto commandBuffer = device.allocateCommandBuffers(allocInfo);
-
-	vk::CommandBufferBeginInfo beginInfo{};
-	beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
-
-	commandBuffer[0].begin(beginInfo);
-
-	return commandBuffer[0];
-}
-
-void endSingleTimeCommands(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::CommandBuffer commandBuffer)
-{
-	commandBuffer.end();
-
-	vk::SubmitInfo submitInfo{};
-	submitInfo.commandBufferCount = 1;
-	submitInfo.pCommandBuffers = &commandBuffer;
-
-	vk::Fence fence = {};  // TODO: pass in fence?
-	queue.submit(1, &submitInfo, fence);
-	queue.waitIdle();
-
-	device.freeCommandBuffers(commandPool, 1, &commandBuffer);
-}
-
-void transitionImageLayout(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout)
-{
-	vk::CommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
-
-	vk::ImageMemoryBarrier barrier{};
-	barrier.oldLayout = oldLayout;
-	barrier.newLayout = newLayout;
-	barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-	barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-	barrier.image = image;
-	barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
-	barrier.subresourceRange.baseMipLevel = 0;
-	barrier.subresourceRange.levelCount = 1;
-	barrier.subresourceRange.baseArrayLayer = 0;
-	barrier.subresourceRange.layerCount = 1;
-
-	vk::PipelineStageFlags sourceStage;
-	vk::PipelineStageFlags destinationStage;
-
-	if(oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal)
-	{
-		barrier.srcAccessMask = {};
-		barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
-
-		sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
-		destinationStage = vk::PipelineStageFlagBits::eTransfer;
-	}
-	else if(oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal)
-	{
-		barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
-		barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
-
-		sourceStage = vk::PipelineStageFlagBits::eTransfer;
-		destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
-	}
-	else
-	{
-		assert(!"unsupported layout transition!");
-	}
-
-	commandBuffer.pipelineBarrier(sourceStage, destinationStage, vk::DependencyFlags{}, 0, nullptr, 0, nullptr, 1, &barrier);
-
-	endSingleTimeCommands(device, commandPool, queue, commandBuffer);
-}
-
-void copyBufferToImage(vk::Device device, vk::CommandPool commandPool, vk::Queue queue, vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height)
-{
-	vk::CommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
-
-	vk::BufferImageCopy region{};
-	region.bufferOffset = 0;
-	region.bufferRowLength = 0;
-	region.bufferImageHeight = 0;
-	region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
-	region.imageSubresource.mipLevel = 0;
-	region.imageSubresource.baseArrayLayer = 0;
-	region.imageSubresource.layerCount = 1;
-	region.imageOffset = vk::Offset3D{ 0, 0, 0 };
-	region.imageExtent = vk::Extent3D{ width, height, 1 };
-
-	commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, &region);
-
-	endSingleTimeCommands(device, commandPool, queue, commandBuffer);
-}
-
-}  // namespace
-
 class VulkanBenchmark
 {
 public:
@@ -337,424 +199,6 @@
 	}
 }
 
-#if USE_HEADLESS_SURFACE
-class Window
-{
-public:
-	Window(vk::Instance instance, vk::Extent2D windowSize)
-	{
-		vk::HeadlessSurfaceCreateInfoEXT surfaceCreateInfo;
-		surface = instance.createHeadlessSurfaceEXT(surfaceCreateInfo);
-		assert(surface);
-	}
-
-	~Window()
-	{
-		instance.destroySurfaceKHR(surface, nullptr);
-	}
-
-	vk::SurfaceKHR getSurface()
-	{
-		return surface;
-	}
-
-	void show()
-	{
-	}
-
-private:
-	const vk::Instance instance;
-	vk::SurfaceKHR surface;
-};
-#elif defined(_WIN32)
-class Window
-{
-public:
-	Window(vk::Instance instance, vk::Extent2D windowSize)
-	{
-		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, nullptr);
-		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;
-};
-#else
-#	error Window class unimplemented for this platform
-#endif
-
-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);
-		extent = surfaceCapabilities.currentExtent;
-
-		vk::SwapchainCreateInfoKHR swapchainCreateInfo;
-		swapchainCreateInfo.surface = surface;
-		swapchainCreateInfo.minImageCount = 2;  // double-buffered
-		swapchainCreateInfo.imageFormat = colorFormat;
-		swapchainCreateInfo.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
-		swapchainCreateInfo.imageExtent = extent;
-		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, nullptr);
-		}
-
-		device.destroySwapchainKHR(swapchain, nullptr);
-	}
-
-	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];
-	}
-
-	vk::Extent2D getExtent() const
-	{
-		return extent;
-	}
-
-	const vk::Format colorFormat = vk::Format::eB8G8R8A8Unorm;
-
-private:
-	const vk::Device device;
-
-	vk::SwapchainKHR swapchain;  // Owning handle
-	vk::Extent2D extent;
-
-	std::vector<vk::Image> images;          // Weak pointers. Presentable images owned by swapchain object.
-	std::vector<vk::ImageView> imageViews;  // Owning handles
-};
-
-class Buffer
-{
-public:
-	Buffer(vk::Device device, vk::DeviceSize size, vk::BufferUsageFlags usage)
-	    : device(device)
-	    , size(size)
-	{
-		vk::BufferCreateInfo bufferInfo{};
-		bufferInfo.size = size;
-		bufferInfo.usage = usage;
-		bufferInfo.sharingMode = vk::SharingMode::eExclusive;
-
-		buffer = device.createBuffer(bufferInfo);
-
-		auto memRequirements = device.getBufferMemoryRequirements(buffer);
-
-		vk::MemoryAllocateInfo allocInfo{};
-		allocInfo.allocationSize = memRequirements.size;
-		allocInfo.memoryTypeIndex = 0;  //TODO: getMemoryTypeIndex(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
-
-		bufferMemory = device.allocateMemory(allocInfo);
-		device.bindBufferMemory(buffer, bufferMemory, 0);
-	}
-
-	~Buffer()
-	{
-		device.freeMemory(bufferMemory);
-		device.destroyBuffer(buffer);
-	}
-
-	vk::Buffer getBuffer()
-	{
-		return buffer;
-	}
-
-	void *mapMemory()
-	{
-		return device.mapMemory(bufferMemory, 0, size);
-	}
-
-	void unmapMemory()
-	{
-		device.unmapMemory(bufferMemory);
-	}
-
-private:
-	const vk::Device device;
-	vk::DeviceSize size;
-	vk::Buffer buffer;              // Owning handle
-	vk::DeviceMemory bufferMemory;  // Owning handle
-};
-
-class Image
-{
-public:
-	Image(vk::Device device, uint32_t width, uint32_t height, vk::Format format, vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1)
-	    : device(device)
-	{
-		vk::ImageCreateInfo imageInfo;
-		imageInfo.imageType = vk::ImageType::e2D;
-		imageInfo.format = format;
-		imageInfo.tiling = vk::ImageTiling::eOptimal;
-		imageInfo.initialLayout = vk::ImageLayout::eGeneral;
-		imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment;
-		imageInfo.samples = sampleCount;
-		imageInfo.extent = vk::Extent3D(width, height, 1);
-		imageInfo.mipLevels = 1;
-		imageInfo.arrayLayers = 1;
-
-		image = device.createImage(imageInfo);
-
-		vk::MemoryRequirements memoryRequirements = device.getImageMemoryRequirements(image);
-
-		vk::MemoryAllocateInfo allocateInfo;
-		allocateInfo.allocationSize = memoryRequirements.size;
-		allocateInfo.memoryTypeIndex = 0;  //getMemoryTypeIndex(memoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
-
-		imageMemory = device.allocateMemory(allocateInfo);
-
-		device.bindImageMemory(image, imageMemory, 0);
-
-		vk::ImageViewCreateInfo imageViewInfo;
-		imageViewInfo.image = image;
-		imageViewInfo.viewType = vk::ImageViewType::e2D;
-		imageViewInfo.format = format;
-		imageViewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
-		imageViewInfo.subresourceRange.baseMipLevel = 0;
-		imageViewInfo.subresourceRange.levelCount = 1;
-		imageViewInfo.subresourceRange.baseArrayLayer = 0;
-		imageViewInfo.subresourceRange.layerCount = 1;
-
-		imageView = device.createImageView(imageViewInfo);
-	}
-
-	~Image()
-	{
-		device.destroyImageView(imageView);
-		device.freeMemory(imageMemory);
-		device.destroyImage(image);
-	}
-
-	vk::Image getImage()
-	{
-		return image;
-	}
-
-	vk::ImageView getImageView()
-	{
-		return imageView;
-	}
-
-private:
-	const vk::Device device;
-
-	vk::Image image;               // Owning handle
-	vk::DeviceMemory imageMemory;  // Owning handle
-	vk::ImageView imageView;       // Owning handle
-};
-
-class Framebuffer
-{
-public:
-	Framebuffer(vk::Device device, vk::ImageView attachment, vk::Format colorFormat, vk::RenderPass renderPass, vk::Extent2D extent, bool multisample)
-	    : device(device)
-	{
-		std::vector<vk::ImageView> attachments(multisample ? 2 : 1);
-
-		if(multisample)
-		{
-			multisampleImage.reset(new Image(device, extent.width, extent.height, colorFormat, vk::SampleCountFlagBits::e4));
-
-			// We'll be rendering to attachment location 0
-			attachments[0] = multisampleImage->getImageView();
-			attachments[1] = attachment;  // Resolve attachment
-		}
-		else
-		{
-			attachments[0] = attachment;
-		}
-
-		vk::FramebufferCreateInfo framebufferCreateInfo;
-
-		framebufferCreateInfo.renderPass = renderPass;
-		framebufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
-		framebufferCreateInfo.pAttachments = attachments.data();
-		framebufferCreateInfo.width = extent.width;
-		framebufferCreateInfo.height = extent.height;
-		framebufferCreateInfo.layers = 1;
-
-		framebuffer = device.createFramebuffer(framebufferCreateInfo);
-	}
-
-	~Framebuffer()
-	{
-		multisampleImage.reset();
-		device.destroyFramebuffer(framebuffer);
-	}
-
-	vk::Framebuffer getFramebuffer()
-	{
-		return framebuffer;
-	}
-
-private:
-	const vk::Device device;
-	vk::Framebuffer framebuffer;  // Owning handle
-	std::unique_ptr<Image> multisampleImage;
-};
-
-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:
@@ -871,9 +315,9 @@
 		memset(data, 255, bufferSize);
 		buffer.unmapMemory();
 
-		transitionImageLayout(device, commandPool, queue, texture->getImage(), vk::Format::eR8G8B8A8Unorm, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
-		copyBufferToImage(device, commandPool, queue, buffer.getBuffer(), texture->getImage(), 16, 16);
-		transitionImageLayout(device, commandPool, queue, texture->getImage(), vk::Format::eR8G8B8A8Unorm, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal);
+		Util::transitionImageLayout(device, commandPool, queue, texture->getImage(), vk::Format::eR8G8B8A8Unorm, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
+		Util::copyBufferToImage(device, commandPool, queue, buffer.getBuffer(), texture->getImage(), 16, 16);
+		Util::transitionImageLayout(device, commandPool, queue, texture->getImage(), vk::Format::eR8G8B8A8Unorm, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal);
 
 		vk::SamplerCreateInfo samplerInfo;
 		samplerInfo.magFilter = vk::Filter::eLinear;
@@ -993,7 +437,7 @@
 		vk::MemoryAllocateInfo memoryAllocateInfo;
 		vk::MemoryRequirements memoryRequirements = device.getBufferMemoryRequirements(vertices.buffer);
 		memoryAllocateInfo.allocationSize = memoryRequirements.size;
-		memoryAllocateInfo.memoryTypeIndex = getMemoryTypeIndex(physicalDevice, memoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
+		memoryAllocateInfo.memoryTypeIndex = Util::getMemoryTypeIndex(physicalDevice, memoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
 		vertices.memory = device.allocateMemory(memoryAllocateInfo);
 
 		void *data = device.mapMemory(vertices.memory, 0, VK_WHOLE_SIZE);
@@ -1108,7 +552,7 @@
 
 	vk::ShaderModule createShaderModule(const char *glslSource, EShLanguage glslLanguage)
 	{
-		auto spirv = compileGLSLtoSPIRV(glslSource, glslLanguage);
+		auto spirv = Util::compileGLSLtoSPIRV(glslSource, glslLanguage);
 
 		vk::ShaderModuleCreateInfo moduleCreateInfo;
 		moduleCreateInfo.codeSize = spirv.size() * sizeof(uint32_t);
diff --git a/tests/VulkanBenchmarks/VulkanHeaders.cpp b/tests/VulkanBenchmarks/VulkanHeaders.cpp
new file mode 100644
index 0000000..e8750df
--- /dev/null
+++ b/tests/VulkanBenchmarks/VulkanHeaders.cpp
@@ -0,0 +1,3 @@
+#include "VulkanHeaders.hpp"
+
+VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
diff --git a/tests/VulkanBenchmarks/VulkanHeaders.hpp b/tests/VulkanBenchmarks/VulkanHeaders.hpp
new file mode 100644
index 0000000..69d79f1
--- /dev/null
+++ b/tests/VulkanBenchmarks/VulkanHeaders.hpp
@@ -0,0 +1,35 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_VULKAN_HEADERS_HPP_
+#define BENCHMARKS_VULKAN_HEADERS_HPP_
+
+#if !defined(USE_HEADLESS_SURFACE)
+#	define USE_HEADLESS_SURFACE 0
+#endif
+
+#if !defined(_WIN32)
+// @TODO: implement native Window support for current platform. For now, always use HeadlessSurface.
+#	undef USE_HEADLESS_SURFACE
+#	define USE_HEADLESS_SURFACE 1
+#endif
+
+#if defined(_WIN32)
+#	define VK_USE_PLATFORM_WIN32_KHR
+#endif
+#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
+#define VULKAN_HPP_NO_NODISCARD_WARNINGS
+#include <vulkan/vulkan.hpp>
+
+#endif  // BENCHMARKS_VULKAN_HEADERS_HPP_
diff --git a/tests/VulkanBenchmarks/Window.cpp b/tests/VulkanBenchmarks/Window.cpp
new file mode 100644
index 0000000..1cfdbe6
--- /dev/null
+++ b/tests/VulkanBenchmarks/Window.cpp
@@ -0,0 +1,109 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Window.hpp"
+
+#if USE_HEADLESS_SURFACE
+
+Window::Window(vk::Instance instance, vk::Extent2D windowSize)
+{
+	vk::HeadlessSurfaceCreateInfoEXT surfaceCreateInfo;
+	surface = instance.createHeadlessSurfaceEXT(surfaceCreateInfo);
+	assert(surface);
+}
+
+Window::~Window()
+{
+	instance.destroySurfaceKHR(surface, nullptr);
+}
+
+vk::SurfaceKHR Window::getSurface()
+{
+	return surface;
+}
+
+void Window::show()
+{
+}
+
+#elif defined(_WIN32)
+
+Window::Window(vk::Instance instance, vk::Extent2D windowSize)
+{
+	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::~Window()
+{
+	instance.destroySurfaceKHR(surface, nullptr);
+	DestroyWindow(window);
+	UnregisterClass("Window", moduleInstance);
+}
+
+vk::SurfaceKHR Window::getSurface()
+{
+	return surface;
+}
+
+void Window::show()
+{
+	ShowWindow(window, SW_SHOW);
+}
+
+#else
+#	error Window class unimplemented for this platform
+#endif
diff --git a/tests/VulkanBenchmarks/Window.hpp b/tests/VulkanBenchmarks/Window.hpp
new file mode 100644
index 0000000..b4e8626
--- /dev/null
+++ b/tests/VulkanBenchmarks/Window.hpp
@@ -0,0 +1,61 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BENCHMARKS_WINDOW_HPP_
+#define BENCHMARKS_WINDOW_HPP_
+
+#include "VulkanHeaders.hpp"
+
+#if defined(_WIN32)
+#	define WIN32_LEAN_AND_MEAN
+#	include <Windows.h>
+#endif
+
+#if USE_HEADLESS_SURFACE
+class Window
+{
+public:
+	Window(vk::Instance instance, vk::Extent2D windowSize);
+	~Window();
+	vk::SurfaceKHR getSurface();
+	void show();
+
+private:
+	const vk::Instance instance;
+	vk::SurfaceKHR surface;
+};
+
+#elif defined(_WIN32)
+
+class Window
+{
+public:
+	Window(vk::Instance instance, vk::Extent2D windowSize);
+	~Window();
+	vk::SurfaceKHR getSurface();
+	void show();
+
+private:
+	HWND window;
+	HINSTANCE moduleInstance;
+	WNDCLASSEX windowClass;
+	const vk::Instance instance;
+	vk::SurfaceKHR surface;
+};
+
+#else
+#	error Window class unimplemented for this platform
+#endif
+
+#endif  // BENCHMARKS_WINDOW_HPP_