[Piglit] [PATCH 6/8] Import VkRunner

Neil Roberts nroberts at igalia.com
Wed Apr 4 22:56:53 UTC 2018


This adds a utility similar to shader_runner but that runs on Vulkan.
The code has been developed in a separate repo here:

https://github.com/igalia/vkrunner

The shaders can be specified in GLSL like with shader_runner. They are
converted to SPIR-V by invoking glslangValidatior which must be in the
path. Alternatively the script can contain SPIR-V sources which are
assembled with spirv-as. It is not yet a complete port of
shader_runner but it is enough to run a range of tests. The main
missing features are any sort of external buffer such as textures,
UBOs and SSBOs. It can however use push constants and it has an
additional feature of being more flexible with the framebuffer and
vertex data formats. As Vulkan doesn’t use names for the uniforms and
vertex data, they are instead referenced with byte offsets and
explicit locations. The framebuffer is entirely offscreen and no
window system extensions are used. See the included README for full
documentation.
---
 tests/vulkan/CMakeLists.txt                  |    1 +
 tests/vulkan/vkrunner/CMakeLists.txt         |    1 +
 tests/vulkan/vkrunner/CMakeLists.vulkan.txt  |   46 +
 tests/vulkan/vkrunner/README.md              |  125 ++
 tests/vulkan/vkrunner/main.c                 |  160 +++
 tests/vulkan/vkrunner/make-formats.py        |   93 ++
 tests/vulkan/vkrunner/vr-allocate-store.c    |  181 +++
 tests/vulkan/vkrunner/vr-allocate-store.h    |   48 +
 tests/vulkan/vkrunner/vr-buffer.c            |   87 ++
 tests/vulkan/vkrunner/vr-buffer.h            |   73 ++
 tests/vulkan/vkrunner/vr-config.c            |  117 ++
 tests/vulkan/vkrunner/vr-config.h            |   49 +
 tests/vulkan/vkrunner/vr-error-message.c     |   41 +
 tests/vulkan/vkrunner/vr-error-message.h     |   35 +
 tests/vulkan/vkrunner/vr-feature-offsets.c   |   92 ++
 tests/vulkan/vkrunner/vr-feature-offsets.h   |   39 +
 tests/vulkan/vkrunner/vr-flush-memory.c      |   52 +
 tests/vulkan/vkrunner/vr-flush-memory.h      |   37 +
 tests/vulkan/vkrunner/vr-format-table.h      | 1580 ++++++++++++++++++++++++++
 tests/vulkan/vkrunner/vr-format.c            |  289 +++++
 tests/vulkan/vkrunner/vr-format.h            |   86 ++
 tests/vulkan/vkrunner/vr-list.c              |   89 ++
 tests/vulkan/vkrunner/vr-list.h              |  122 ++
 tests/vulkan/vkrunner/vr-pipeline.c          |  668 +++++++++++
 tests/vulkan/vkrunner/vr-pipeline.h          |   54 +
 tests/vulkan/vkrunner/vr-script.c            |  948 ++++++++++++++++
 tests/vulkan/vkrunner/vr-script.h            |  146 +++
 tests/vulkan/vkrunner/vr-subprocess.c        |   61 +
 tests/vulkan/vkrunner/vr-subprocess.h        |   34 +
 tests/vulkan/vkrunner/vr-test.c              |  569 ++++++++++
 tests/vulkan/vkrunner/vr-test.h              |   40 +
 tests/vulkan/vkrunner/vr-vbo.c               |  637 +++++++++++
 tests/vulkan/vkrunner/vr-vbo.h               |   77 ++
 tests/vulkan/vkrunner/vr-vk-core-funcs.h     |    1 +
 tests/vulkan/vkrunner/vr-vk-device-funcs.h   |   56 +
 tests/vulkan/vkrunner/vr-vk-instance-funcs.h |    9 +
 tests/vulkan/vkrunner/vr-vk.c                |  148 +++
 tests/vulkan/vkrunner/vr-vk.h                |   56 +
 tests/vulkan/vkrunner/vr-window.c            |  678 +++++++++++
 tests/vulkan/vkrunner/vr-window.h            |   73 ++
 40 files changed, 7698 insertions(+)
 create mode 100644 tests/vulkan/vkrunner/CMakeLists.txt
 create mode 100644 tests/vulkan/vkrunner/CMakeLists.vulkan.txt
 create mode 100644 tests/vulkan/vkrunner/README.md
 create mode 100644 tests/vulkan/vkrunner/main.c
 create mode 100755 tests/vulkan/vkrunner/make-formats.py
 create mode 100644 tests/vulkan/vkrunner/vr-allocate-store.c
 create mode 100644 tests/vulkan/vkrunner/vr-allocate-store.h
 create mode 100644 tests/vulkan/vkrunner/vr-buffer.c
 create mode 100644 tests/vulkan/vkrunner/vr-buffer.h
 create mode 100644 tests/vulkan/vkrunner/vr-config.c
 create mode 100644 tests/vulkan/vkrunner/vr-config.h
 create mode 100644 tests/vulkan/vkrunner/vr-error-message.c
 create mode 100644 tests/vulkan/vkrunner/vr-error-message.h
 create mode 100644 tests/vulkan/vkrunner/vr-feature-offsets.c
 create mode 100644 tests/vulkan/vkrunner/vr-feature-offsets.h
 create mode 100644 tests/vulkan/vkrunner/vr-flush-memory.c
 create mode 100644 tests/vulkan/vkrunner/vr-flush-memory.h
 create mode 100644 tests/vulkan/vkrunner/vr-format-table.h
 create mode 100644 tests/vulkan/vkrunner/vr-format.c
 create mode 100644 tests/vulkan/vkrunner/vr-format.h
 create mode 100644 tests/vulkan/vkrunner/vr-list.c
 create mode 100644 tests/vulkan/vkrunner/vr-list.h
 create mode 100644 tests/vulkan/vkrunner/vr-pipeline.c
 create mode 100644 tests/vulkan/vkrunner/vr-pipeline.h
 create mode 100644 tests/vulkan/vkrunner/vr-script.c
 create mode 100644 tests/vulkan/vkrunner/vr-script.h
 create mode 100644 tests/vulkan/vkrunner/vr-subprocess.c
 create mode 100644 tests/vulkan/vkrunner/vr-subprocess.h
 create mode 100644 tests/vulkan/vkrunner/vr-test.c
 create mode 100644 tests/vulkan/vkrunner/vr-test.h
 create mode 100644 tests/vulkan/vkrunner/vr-vbo.c
 create mode 100644 tests/vulkan/vkrunner/vr-vbo.h
 create mode 100644 tests/vulkan/vkrunner/vr-vk-core-funcs.h
 create mode 100644 tests/vulkan/vkrunner/vr-vk-device-funcs.h
 create mode 100644 tests/vulkan/vkrunner/vr-vk-instance-funcs.h
 create mode 100644 tests/vulkan/vkrunner/vr-vk.c
 create mode 100644 tests/vulkan/vkrunner/vr-vk.h
 create mode 100644 tests/vulkan/vkrunner/vr-window.c
 create mode 100644 tests/vulkan/vkrunner/vr-window.h

diff --git a/tests/vulkan/CMakeLists.txt b/tests/vulkan/CMakeLists.txt
index e69de29bb..6fe023b14 100644
--- a/tests/vulkan/CMakeLists.txt
+++ b/tests/vulkan/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(vkrunner)
diff --git a/tests/vulkan/vkrunner/CMakeLists.txt b/tests/vulkan/vkrunner/CMakeLists.txt
new file mode 100644
index 000000000..144a306f4
--- /dev/null
+++ b/tests/vulkan/vkrunner/CMakeLists.txt
@@ -0,0 +1 @@
+piglit_include_target_api()
diff --git a/tests/vulkan/vkrunner/CMakeLists.vulkan.txt b/tests/vulkan/vkrunner/CMakeLists.vulkan.txt
new file mode 100644
index 000000000..4e1a48d0d
--- /dev/null
+++ b/tests/vulkan/vkrunner/CMakeLists.vulkan.txt
@@ -0,0 +1,46 @@
+include_directories(
+        ${VULKAN_INCLUDE_DIR}
+)
+
+link_libraries (
+	piglitutil_${piglit_target_api}
+)
+
+add_executable(vkrunner
+        vr-allocate-store.c
+        vr-allocate-store.h
+        vr-buffer.c
+        vr-buffer.h
+        vr-config.c
+        vr-config.h
+        vr-error-message.c
+        vr-error-message.h
+        vr-feature-offsets.c
+        vr-feature-offsets.h
+        vr-flush-memory.c
+        vr-flush-memory.h
+        vr-format-table.h
+        vr-format.h
+        vr-format.c
+        vr-list.c
+        vr-list.h
+        vr-script.c
+        vr-script.h
+        vr-vk.c
+        vr-vk.h
+        vr-vk-core-funcs.h
+        vr-vk-device-funcs.h
+        vr-vk-instance-funcs.h
+        vr-pipeline.c
+        vr-pipeline.h
+        vr-subprocess.c
+        vr-subprocess.h
+        vr-test.c
+        vr-test.h
+        vr-vbo.c
+        vr-vbo.h
+        vr-window.c
+        vr-window.h
+        main.c)
+
+# vim: ft=cmake:
diff --git a/tests/vulkan/vkrunner/README.md b/tests/vulkan/vkrunner/README.md
new file mode 100644
index 000000000..2bdea713c
--- /dev/null
+++ b/tests/vulkan/vkrunner/README.md
@@ -0,0 +1,125 @@
+# VkRunner
+
+VkRunner is a Vulkan shader tester based on `shader_runner`. The goal
+is to make it be able to test scripts as similar to shader_test format
+as possible.
+
+## Running
+
+VkRunner requires glslangValidator to compile GLSL to SPIR-V. It is
+invoked on the fly while executing the test. It must either be
+available in your path or you can set the variable
+`PIGLIT_GLSLANG_VALIDATOR_BINARY` to point to it. It can be obtained
+from [here](https://github.com/KhronosGroup/glslang/).
+
+## [test] section:
+
+The `[test]` currently only supports the following commands:
+
+> draw rect _x_ _y_ _width_ _height_
+
+Draws a rectangle at the given normalised coordinates. The vertices
+will be uploaded at vertex input location 0 as a vec3. Remember that
+Vulkan’s normalised coordinate system is different from OpenGL’s.
+
+> draw arrays [instanced] _topology_ _firstVertex_ _vertexCount_ [_instanceCount_]
+
+Calls `vkCmdDraw` with the given parameters. The vertex data will be
+sourced from the `[vertex data]` section. The _topology_ should be one
+of the values of VkPrimitiveTopology minus the VK\_PRIMITIVE\_TOPOLOGY
+prefix. Alternatively it can be a GLenum value as used in
+shader_runner.
+
+> [relative] probe [rect] (rgb|rgba) (_x_, _y_[, _width_, _height_]) (_r_, _g_, _b_[, _a_])
+
+Verifies that a given rectangle matches the given colour. If the
+command begins with the keyword `relative` then the coordinates are
+normalised from 0.0 to 1.0, otherwise they are pixel coordinates.
+Either way the origin is the top-left corner of the image. If `rect`
+is not specified then the width and height are set to 1 pixel. The
+alpha component of the image can be ignored or not by specifying
+either `rgb` or `rgba`.
+
+> probe all (rgb|rgba) _r_ _g_ _b_ [_a_]
+
+The same as above except that it probes the entire window.
+
+> uniform _type_ _offset_ _values_…
+
+Sets a push constant at the given offset. Note that unlike
+shader_runner, the offset is a byte offset into the push constant
+buffer rather than a uniform location. The type can be one of int,
+float, double, vec[234], dvec[234] or ivec[234].
+
+> clear color _r_ _g_ _b_ _a_
+
+Sets the color to use for subsequent clear commands. Defaults to all
+zeros.
+
+> clear
+
+Clears the entire framebuffer to the previously set clear color.
+
+## [require] section
+
+> _feature_
+
+The `[require]` section can contain names of members from
+VkPhysicalDeviceFeatures. These will be searched for when deciding
+which physical device to open. If no physical device with the
+corresponding requirements can be found then it will report an error.
+
+> framebuffer _format_
+
+Use this to specify the format of the framebuffer using a format from
+VkFormat minus the VK_FORMAT prefix.
+
+## Shader sections
+
+Shaders can be stored in sections like `[vertex shader]` just like in
+`shader_runner`. Multiple GLSL shaders can be given for a single stage
+and they will be linked together via glslangValidator.
+
+Alternatively, the disassembly of the SPIR-V source can be provided
+with a section like `[vertex shader spirv]`. This will be assembled
+with `spirv-as`. If a SPIR-V section is given for a stage there can be
+no other shaders for that stage.
+
+The vertex shader can also be skipped with an empty section called
+`[vertex shader passthrough]`. That will create a simple vertex shader
+than just copies a vec4 for input location 0 to `gl_Position`.
+
+## [vertex data] section
+
+The `[vertex data]` section is used to specify vertex attributes and
+data for use with the draw arrays command. It is similar to
+shader_runner except that integer locations are used instead of names
+and matrices are specifed by using a location within the matrix rather
+than having a separate field.
+
+The format consists of a row of column headers followed by any number
+of rows of data. Each column header has the form _ATTRLOC_/_FORMAT_
+where _ATTRLOC_ is the location of the vertex attribute to be bound to
+this column and _FORMAT_ is the name of a VkFormat minus the VK_FORMAT
+prefix.
+
+Alternatively the column header can use something closer the
+shader_runner format like _ATTRLOC_/_GL\_TYPE_/_GLSL\_TYPE_.
+_GL\_TYPE_ is the GL type of data that follows (“half”, “float”,
+“double”, “byte”, “ubyte”, “short”, “ushort”, “int” or “uint”),
+_GLSL\_TYPE_ is the GLSL type of the data (“int”, “uint”, “float”,
+“double”, “ivec”\*, “uvec”\*, “vec”\*, “dvec”\*).
+
+The data follows the column headers in space-separated form. “#” can
+be used for comments, as in shell scripts. See the
+`vertex-data.shader_test` file as an example.
+
+## Command line arguments
+
+    usage: vkrunner [OPTION]... SCRIPT...
+    Runs the shader test script SCRIPT
+    
+    Options:
+      -h            Show this help message
+      -i IMG        Write the final rendering to IMG as a PPM image
+      -d            Show the SPIR-V disassembly
diff --git a/tests/vulkan/vkrunner/main.c b/tests/vulkan/vkrunner/main.c
new file mode 100644
index 000000000..898e18609
--- /dev/null
+++ b/tests/vulkan/vkrunner/main.c
@@ -0,0 +1,160 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+
+#include "vr-vk.h"
+#include "vr-window.h"
+#include "vr-script.h"
+#include "vr-pipeline.h"
+#include "vr-test.h"
+#include "vr-config.h"
+#include "vr-error-message.h"
+
+static bool
+write_ppm(struct vr_window *window,
+	  const char *filename)
+{
+	const struct vr_format *format = window->framebuffer_format;
+	int format_size = vr_format_get_size(format);
+	FILE *out = fopen(filename, "w");
+
+	if (out == NULL) {
+		vr_error_message("%s: %s", filename, strerror(errno));
+		return false;
+	}
+
+	fprintf(out,
+		"P6\n"
+		"%i %i\n"
+		"255\n",
+		VR_WINDOW_WIDTH,
+		VR_WINDOW_HEIGHT);
+
+	for (int y = 0; y < VR_WINDOW_HEIGHT; y++) {
+		const uint8_t *p = ((uint8_t *) window->linear_memory_map +
+				    y * window->linear_memory_stride);
+
+		for (int x = 0; x < VR_WINDOW_WIDTH; x++) {
+			double pixel[4];
+
+			vr_format_load_pixel(format, p, pixel);
+
+			for (int i = 0; i < 3; i++) {
+				double v = pixel[i];
+
+				if (v < 0.0)
+					v = 0.0;
+				else if (v > 1.0)
+					v = 1.0;
+
+				fputc(round(v * 255.0), out);
+			}
+			p += format_size;
+		}
+	}
+
+	fclose(out);
+
+	return true;
+}
+
+static enum piglit_result
+process_script(struct vr_config *config,
+	       const char *filename)
+{
+	enum piglit_result res = PIGLIT_PASS;
+	struct vr_script *script = NULL;
+	struct vr_window *window = NULL;
+	struct vr_pipeline *pipeline = NULL;
+
+	script = vr_script_load(filename);
+	if (script == NULL) {
+		res = PIGLIT_FAIL;
+		goto out;
+	}
+
+	res = vr_window_new(&script->required_features,
+			    script->framebuffer_format,
+			    &window);
+	if (res != PIGLIT_PASS)
+		goto out;
+
+	pipeline = vr_pipeline_create(config, window, script);
+
+	if (pipeline == NULL) {
+		res = PIGLIT_FAIL;
+		goto out;
+	}
+
+	if (!vr_test_run(window, pipeline, script))
+		res = PIGLIT_FAIL;
+
+	if (config->image_filename) {
+		if (!write_ppm(window, config->image_filename))
+			res = PIGLIT_FAIL;
+	}
+
+out:
+	if (pipeline)
+		vr_pipeline_free(pipeline);
+	if (window)
+		vr_window_free(window);
+	if (script)
+		vr_script_free(script);
+
+	return res;
+}
+
+int
+main(int argc, char **argv)
+{
+	enum piglit_result overall_result = PIGLIT_SKIP;
+
+	struct vr_config *config = vr_config_new(argc, argv);
+	if (config == NULL)
+		return EXIT_FAILURE;
+
+	struct vr_config_script *script;
+	vr_list_for_each(script, &config->scripts, link) {
+		if (config->scripts.next->next != &config->scripts)
+			printf("%s\n", script->filename);
+
+		enum piglit_result res =
+			process_script(config, script->filename);
+		piglit_merge_result(&overall_result, res);
+	}
+
+	vr_config_free(config);
+
+	piglit_report_result(overall_result);
+
+	return EXIT_FAILURE;
+}
diff --git a/tests/vulkan/vkrunner/make-formats.py b/tests/vulkan/vkrunner/make-formats.py
new file mode 100755
index 000000000..6bb1030c0
--- /dev/null
+++ b/tests/vulkan/vkrunner/make-formats.py
@@ -0,0 +1,93 @@
+#!/usr/bin/python3
+
+import re
+import sys
+
+format_re = re.compile(r'\bVK_FORMAT_([A-Z0-9_]+)\b')
+skip_re = re.compile(r'(?:_BLOCK(?:_IMG)?|_KHR|^UNDEFINED|'
+                     r'^RANGE_SIZE|^MAX_ENUM|_RANGE)$')
+component_re = re.compile('([A-Z]+)([0-9]+)')
+pack_re = re.compile('PACK([0-9]+)$')
+
+swizzles = [ 'RGBA', 'BGRA', 'ARGB', 'ABGR' ]
+
+def get_formats(data):
+    in_enum = False
+
+    for line in data:
+        if line.startswith('typedef enum VkFormat '):
+            in_enum = True
+        elif line.startswith('}'):
+            in_enum = False
+        if not in_enum:
+            continue
+
+        md = format_re.search(line)
+        if md is None:
+            continue
+        name = md.group(1)
+        if skip_re.search(name):
+            continue
+        yield(name)
+
+def get_components(name):
+    components = [(md.group(1), int(md.group(2)))
+                  for md in component_re.finditer(name)]
+    for letter, size in components:
+        if "RGBA".find(letter) == -1:
+            return None
+    return components
+
+def get_swizzle(components):
+    component_letters = "".join((letter for letter, size in components))
+    for swizzle in swizzles:
+        if swizzle.startswith(component_letters):
+            return swizzle
+    print("Unknown swizzle {}".format(component_letters),
+          file=sys.stderr)
+    sys.exit(1)
+
+formats = sorted(set(get_formats(sys.stdin)))
+
+print("""/* Automatically generated by make-formats.py */
+static const struct vr_format
+formats[] = {""")
+
+for name in formats:
+    parts = name.split('_')
+
+    components = get_components(parts[0])
+
+    if components is None:
+        continue
+
+    swizzle = get_swizzle(components)
+
+    if len(parts) >= 3:
+        md = pack_re.match(parts[2])
+        packed_size = int(md.group(1))
+    else:
+        packed_size = 0
+
+    print("""	{{
+		.vk_format = VK_FORMAT_{},
+		.name = "{}",
+		.packed_size = {},
+		.swizzle = VR_FORMAT_SWIZZLE_{},
+		.mode = VR_FORMAT_MODE_{},
+		.n_components = {},
+		.components = {{""".format(
+                    name,
+                    name,
+                    packed_size,
+                    swizzle,
+                    parts[1],
+                    len(components)))
+
+    for letter, size in components:
+        print("\t\t\t{{ .bits = {} }},".format(size))
+
+    print("""		}
+	},""")
+
+print("};")
diff --git a/tests/vulkan/vkrunner/vr-allocate-store.c b/tests/vulkan/vkrunner/vr-allocate-store.c
new file mode 100644
index 000000000..c711ab4bd
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-allocate-store.c
@@ -0,0 +1,181 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2016, 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdlib.h>
+
+#include "vr-allocate-store.h"
+#include "piglit-util.h"
+
+static int
+find_memory_type(struct vr_window *window,
+		 uint32_t usable_memory_types,
+		 uint32_t memory_type_flags)
+{
+	int i;
+
+	while (usable_memory_types) {
+		i = ffs(usable_memory_types) - 1;
+
+		if ((window->memory_properties.memoryTypes[i].propertyFlags &
+		     memory_type_flags) == memory_type_flags)
+			return i;
+
+		usable_memory_types &= ~(1 << i);
+	}
+
+	return -1;
+}
+
+VkResult
+vr_allocate_store_buffer(struct vr_window *window,
+			 uint32_t memory_type_flags,
+			 int n_buffers,
+			 const VkBuffer *buffers,
+			 VkDeviceMemory *memory_out,
+			 int *memory_type_index_out,
+			 int *offsets)
+{
+	VkDeviceMemory memory;
+	VkMemoryRequirements reqs;
+	VkResult res;
+	int offset = 0;
+	int memory_type_index;
+	uint32_t usable_memory_types = UINT32_MAX;
+	VkDeviceSize granularity;
+	int i;
+
+	if (offsets == NULL)
+		offsets = alloca(sizeof *offsets * n_buffers);
+
+	granularity = window->device_properties.limits.bufferImageGranularity;
+
+	for (i = 0; i < n_buffers; i++) {
+		vr_vk.vkGetBufferMemoryRequirements(window->device,
+						    buffers[i],
+						    &reqs);
+		offset = ALIGN(offset, granularity);
+		offset = ALIGN(offset, reqs.alignment);
+		offsets[i] = offset;
+		offset += reqs.size;
+
+		usable_memory_types &= reqs.memoryTypeBits;
+	}
+
+	memory_type_index = find_memory_type(window,
+					     usable_memory_types,
+					     memory_type_flags);
+	if (memory_type_index == -1)
+		return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+
+	VkMemoryAllocateInfo allocate_info = {
+		.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+		.allocationSize = offset,
+		.memoryTypeIndex = memory_type_index
+	};
+	res = vr_vk.vkAllocateMemory(window->device,
+				     &allocate_info,
+				     NULL, /* allocator */
+				     &memory);
+	if (res != VK_SUCCESS)
+		return res;
+
+	for (i = 0; i < n_buffers; i++) {
+		vr_vk.vkBindBufferMemory(window->device,
+					 buffers[i],
+					 memory,
+					 offsets[i]);
+	}
+
+	*memory_out = memory;
+	if (memory_type_index_out)
+		*memory_type_index_out = memory_type_index;
+
+	return VK_SUCCESS;
+}
+
+VkResult
+vr_allocate_store_image(struct vr_window *window,
+			uint32_t memory_type_flags,
+			int n_images,
+			const VkImage *images,
+			VkDeviceMemory *memory_out,
+			int *memory_type_index_out)
+{
+	VkDeviceMemory memory;
+	VkMemoryRequirements reqs;
+	VkResult res;
+	int offset = 0;
+	int *offsets = alloca(sizeof *offsets * n_images);
+	int memory_type_index;
+	uint32_t usable_memory_types = UINT32_MAX;
+	VkDeviceSize granularity;
+	int i;
+
+	granularity = window->device_properties.limits.bufferImageGranularity;
+
+	for (i = 0; i < n_images; i++) {
+		vr_vk.vkGetImageMemoryRequirements(window->device,
+						   images[i],
+						   &reqs);
+		offset = ALIGN(offset, granularity);
+		offset = ALIGN(offset, reqs.alignment);
+		offsets[i] = offset;
+		offset += reqs.size;
+
+		usable_memory_types &= reqs.memoryTypeBits;
+	}
+
+	memory_type_index = find_memory_type(window,
+					     usable_memory_types,
+					     memory_type_flags);
+	if (memory_type_index == -1)
+		return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+
+	VkMemoryAllocateInfo allocate_info = {
+		.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+		.allocationSize = offset,
+		.memoryTypeIndex = memory_type_index
+	};
+	res = vr_vk.vkAllocateMemory(window->device,
+				     &allocate_info,
+				     NULL, /* allocator */
+				     &memory);
+	if (res != VK_SUCCESS)
+		return res;
+
+	for (i = 0; i < n_images; i++) {
+		vr_vk.vkBindImageMemory(window->device,
+					images[i],
+					memory,
+					offsets[i]);
+	}
+
+	*memory_out = memory;
+	if (memory_type_index_out)
+		*memory_type_index_out = memory_type_index;
+
+	return VK_SUCCESS;
+}
diff --git a/tests/vulkan/vkrunner/vr-allocate-store.h b/tests/vulkan/vkrunner/vr-allocate-store.h
new file mode 100644
index 000000000..94f93f8c1
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-allocate-store.h
@@ -0,0 +1,48 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_ALLOCATE_STORE_H
+#define VR_ALLOCATE_STORE_H
+
+#include "vr-window.h"
+
+VkResult
+vr_allocate_store_image(struct vr_window *window,
+			uint32_t memory_type_flags,
+			int n_images,
+			const VkImage *images,
+			VkDeviceMemory *memory_out,
+			int *memory_type_index_out);
+
+VkResult
+vr_allocate_store_buffer(struct vr_window *window,
+			 uint32_t memory_type_flags,
+			 int n_buffers,
+			 const VkBuffer *buffers,
+			 VkDeviceMemory *memory_out,
+			 int *memory_type_index_out,
+			 int *offsets);
+
+#endif /* VR_ALLOCATE_STORE_H */
diff --git a/tests/vulkan/vkrunner/vr-buffer.c b/tests/vulkan/vkrunner/vr-buffer.c
new file mode 100644
index 000000000..4c20b7722
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-buffer.c
@@ -0,0 +1,87 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2013, 2014 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "vr-buffer.h"
+#include "piglit-util.h"
+
+void
+vr_buffer_init(struct vr_buffer *buffer)
+{
+	static const struct vr_buffer init = VR_BUFFER_STATIC_INIT;
+
+	*buffer = init;
+}
+
+void
+vr_buffer_ensure_size(struct vr_buffer *buffer,
+		      size_t size)
+{
+	size_t new_size = MAX2(buffer->size, 1);
+
+	while (new_size < size)
+		new_size *= 2;
+
+	if (new_size != buffer->size) {
+		buffer->data = realloc(buffer->data, new_size);
+		buffer->size = new_size;
+	}
+}
+
+void
+vr_buffer_set_length(struct vr_buffer *buffer,
+		     size_t length)
+{
+	vr_buffer_ensure_size(buffer, length);
+	buffer->length = length;
+}
+
+void
+vr_buffer_append(struct vr_buffer *buffer,
+		 const void *data,
+		 size_t length)
+{
+	vr_buffer_ensure_size(buffer, buffer->length + length);
+	memcpy(buffer->data + buffer->length, data, length);
+	buffer->length += length;
+}
+
+void
+vr_buffer_append_string(struct vr_buffer *buffer,
+			const char *str)
+{
+	vr_buffer_append(buffer, str, strlen(str) + 1);
+	buffer->length--;
+}
+
+void
+vr_buffer_destroy(struct vr_buffer *buffer)
+{
+	free(buffer->data);
+}
diff --git a/tests/vulkan/vkrunner/vr-buffer.h b/tests/vulkan/vkrunner/vr-buffer.h
new file mode 100644
index 000000000..cc355f3fa
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-buffer.h
@@ -0,0 +1,73 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2013, 2014 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_BUFFER_H
+#define VR_BUFFER_H
+
+#include <stdint.h>
+#include <stdarg.h>
+
+struct vr_buffer {
+	uint8_t *data;
+	size_t length;
+	size_t size;
+};
+
+#define VR_BUFFER_STATIC_INIT { .data = NULL, .length = 0, .size = 0 }
+
+void
+vr_buffer_init(struct vr_buffer *buffer);
+
+void
+vr_buffer_ensure_size(struct vr_buffer *buffer,
+		      size_t size);
+
+void
+vr_buffer_set_length(struct vr_buffer *buffer,
+		     size_t length);
+
+void
+vr_buffer_append(struct vr_buffer *buffer,
+		 const void *data,
+		 size_t length);
+
+static inline void
+vr_buffer_append_c(struct vr_buffer *buffer,
+		   char c)
+{
+	if (buffer->size > buffer->length)
+		buffer->data[buffer->length++] = c;
+	else
+		vr_buffer_append(buffer, &c, 1);
+}
+
+void
+vr_buffer_append_string(struct vr_buffer *buffer,
+			const char *str);
+
+void
+vr_buffer_destroy(struct vr_buffer *buffer);
+
+#endif /* VR_BUFFER_H */
diff --git a/tests/vulkan/vkrunner/vr-config.c b/tests/vulkan/vkrunner/vr-config.c
new file mode 100644
index 000000000..02fbb7c4c
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-config.c
@@ -0,0 +1,117 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-config.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+show_help(void)
+{
+	printf("usage: vkrunner [OPTION]... SCRIPT...\n"
+	       "Runs the shader test script SCRIPT\n"
+	       "\n"
+	       "Options:\n"
+	       "  -h		Show this help message\n"
+	       "  -i IMG	Write the final rendering to IMG as a "
+	       "PPM image\n"
+	       "  -d		Show the SPIR-V disassembly\n");
+}
+
+static void
+add_script(struct vr_config *config,
+	   const char *filename)
+{
+	struct vr_config_script *script =
+		malloc(sizeof *script + strlen(filename) + 1);
+	strcpy(script->filename, filename);
+	vr_list_insert(config->scripts.prev, &script->link);
+}
+
+struct vr_config *
+vr_config_new(int argc, char **argv)
+{
+	struct vr_config *config = calloc(sizeof *config, 1);
+
+	vr_list_init(&config->scripts);
+
+	while (true) {
+		int opt = getopt(argc, argv, "-hi:d");
+
+		if (opt == -1)
+			break;
+
+		switch (opt) {
+		case 'h':
+			show_help();
+			goto error;
+		case 'i':
+			if (config->image_filename) {
+				fprintf(stderr,
+					"duplicate -i option\n");
+				goto error;
+			}
+			config->image_filename = strdup(optarg);
+			break;
+		case 'd':
+			config->show_disassembly = true;
+			break;
+		case 1:
+			add_script(config, optarg);
+			break;
+		case '?':
+			goto error;
+		}
+	}
+
+	if (vr_list_empty(&config->scripts)) {
+		fprintf(stderr, "no script specified\n");
+		show_help();
+		goto error;
+	}
+
+	return config;
+
+error:
+	vr_config_free(config);
+	return NULL;
+}
+
+void
+vr_config_free(struct vr_config *config)
+{
+	struct vr_config_script *script, *tmp;
+
+	vr_list_for_each_safe(script, tmp, &config->scripts, link)
+		free(script);
+
+	free(config->image_filename);
+
+	free(config);
+}
diff --git a/tests/vulkan/vkrunner/vr-config.h b/tests/vulkan/vkrunner/vr-config.h
new file mode 100644
index 000000000..ebee67cb3
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-config.h
@@ -0,0 +1,49 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_CONFIG_H
+#define VR_CONFIG_H
+
+#include <stdbool.h>
+#include "vr-list.h"
+
+struct vr_config_script {
+	struct vr_list link;
+	char filename[];
+};
+
+struct vr_config {
+	char *image_filename;
+	struct vr_list scripts;
+	bool show_disassembly;
+};
+
+struct vr_config *
+vr_config_new(int argc, char **argv);
+
+void
+vr_config_free(struct vr_config *config);
+
+#endif /* VR_CONFIG_H */
diff --git a/tests/vulkan/vkrunner/vr-error-message.c b/tests/vulkan/vkrunner/vr-error-message.c
new file mode 100644
index 000000000..e0368e732
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-error-message.c
@@ -0,0 +1,41 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "vr-error-message.h"
+
+void
+vr_error_message(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vfprintf(stderr, format, ap);
+	fputc('\n', stderr);
+	va_end(ap);
+}
diff --git a/tests/vulkan/vkrunner/vr-error-message.h b/tests/vulkan/vkrunner/vr-error-message.h
new file mode 100644
index 000000000..c81b3d4c4
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-error-message.h
@@ -0,0 +1,35 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_ERROR_MESSAGE_H
+#define VR_ERROR_MESSAGE_H
+
+#include "piglit-util.h"
+
+PRINTFLIKE(1, 2)
+void
+vr_error_message(const char *format, ...);
+
+#endif /* VR_ERROR_MESSAGE_H */
diff --git a/tests/vulkan/vkrunner/vr-feature-offsets.c b/tests/vulkan/vkrunner/vr-feature-offsets.c
new file mode 100644
index 000000000..4f63b5562
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-feature-offsets.c
@@ -0,0 +1,92 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-feature-offsets.h"
+#include "vr-vk.h"
+#include "piglit-util.h"
+
+#define VR_FEATURE(n)							\
+	{ PIGLIT_STRINGIFY(n), offsetof(VkPhysicalDeviceFeatures, n) },
+
+const struct vr_feature_offset
+vr_feature_offsets[] = {
+	VR_FEATURE(robustBufferAccess)
+	VR_FEATURE(fullDrawIndexUint32)
+	VR_FEATURE(imageCubeArray)
+	VR_FEATURE(independentBlend)
+	VR_FEATURE(geometryShader)
+	VR_FEATURE(tessellationShader)
+	VR_FEATURE(sampleRateShading)
+	VR_FEATURE(dualSrcBlend)
+	VR_FEATURE(logicOp)
+	VR_FEATURE(multiDrawIndirect)
+	VR_FEATURE(drawIndirectFirstInstance)
+	VR_FEATURE(depthClamp)
+	VR_FEATURE(depthBiasClamp)
+	VR_FEATURE(fillModeNonSolid)
+	VR_FEATURE(depthBounds)
+	VR_FEATURE(wideLines)
+	VR_FEATURE(largePoints)
+	VR_FEATURE(alphaToOne)
+	VR_FEATURE(multiViewport)
+	VR_FEATURE(samplerAnisotropy)
+	VR_FEATURE(textureCompressionETC2)
+	VR_FEATURE(textureCompressionASTC_LDR)
+	VR_FEATURE(textureCompressionBC)
+	VR_FEATURE(occlusionQueryPrecise)
+	VR_FEATURE(pipelineStatisticsQuery)
+	VR_FEATURE(vertexPipelineStoresAndAtomics)
+	VR_FEATURE(fragmentStoresAndAtomics)
+	VR_FEATURE(shaderTessellationAndGeometryPointSize)
+	VR_FEATURE(shaderImageGatherExtended)
+	VR_FEATURE(shaderStorageImageExtendedFormats)
+	VR_FEATURE(shaderStorageImageMultisample)
+	VR_FEATURE(shaderStorageImageReadWithoutFormat)
+	VR_FEATURE(shaderStorageImageWriteWithoutFormat)
+	VR_FEATURE(shaderUniformBufferArrayDynamicIndexing)
+	VR_FEATURE(shaderSampledImageArrayDynamicIndexing)
+	VR_FEATURE(shaderStorageBufferArrayDynamicIndexing)
+	VR_FEATURE(shaderStorageImageArrayDynamicIndexing)
+	VR_FEATURE(shaderClipDistance)
+	VR_FEATURE(shaderCullDistance)
+	VR_FEATURE(shaderFloat64)
+	VR_FEATURE(shaderInt64)
+	VR_FEATURE(shaderInt16)
+	VR_FEATURE(shaderResourceResidency)
+	VR_FEATURE(shaderResourceMinLod)
+	VR_FEATURE(sparseBinding)
+	VR_FEATURE(sparseResidencyBuffer)
+	VR_FEATURE(sparseResidencyImage2D)
+	VR_FEATURE(sparseResidencyImage3D)
+	VR_FEATURE(sparseResidency2Samples)
+	VR_FEATURE(sparseResidency4Samples)
+	VR_FEATURE(sparseResidency8Samples)
+	VR_FEATURE(sparseResidency16Samples)
+	VR_FEATURE(sparseResidencyAliased)
+	VR_FEATURE(variableMultisampleRate)
+	VR_FEATURE(inheritedQueries)
+	{ NULL, 0 }
+};
diff --git a/tests/vulkan/vkrunner/vr-feature-offsets.h b/tests/vulkan/vkrunner/vr-feature-offsets.h
new file mode 100644
index 000000000..664d585f4
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-feature-offsets.h
@@ -0,0 +1,39 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_FEATURE_OFFSETS_H
+#define VR_FEATURE_OFFSETS_H
+
+#include <stdlib.h>
+
+struct vr_feature_offset {
+	const char *name;
+	size_t offset;
+};
+
+extern const struct vr_feature_offset
+vr_feature_offsets[];
+
+#endif /* VRFEATURE_OFFSETS_H */
diff --git a/tests/vulkan/vkrunner/vr-flush-memory.c b/tests/vulkan/vkrunner/vr-flush-memory.c
new file mode 100644
index 000000000..5a924a373
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-flush-memory.c
@@ -0,0 +1,52 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-flush-memory.h"
+
+VkResult
+vr_flush_memory(struct vr_window *window,
+		int memory_type_index,
+		VkDeviceMemory memory,
+		VkDeviceSize size)
+{
+	const VkMemoryType *memory_type =
+		&window->memory_properties.memoryTypes[memory_type_index];
+
+	/* We don’t need to do anything if the memory is already
+	 * coherent */
+	if ((memory_type->propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
+		return VK_SUCCESS;
+
+	VkMappedMemoryRange mapped_memory_range = {
+		.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+		.memory = memory,
+		.offset = 0,
+		.size = size
+	};
+	return vr_vk.vkFlushMappedMemoryRanges(window->device,
+					       1, /* memoryRangeCount */
+					       &mapped_memory_range);
+}
diff --git a/tests/vulkan/vkrunner/vr-flush-memory.h b/tests/vulkan/vkrunner/vr-flush-memory.h
new file mode 100644
index 000000000..225dd9942
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-flush-memory.h
@@ -0,0 +1,37 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_FLUSH_MEMORY_H
+#define VR_FLUSH_MEMORY_H
+
+#include "vr-window.h"
+
+VkResult
+vr_flush_memory(struct vr_window *window,
+		int memory_type_index,
+		VkDeviceMemory memory,
+		VkDeviceSize size);
+
+#endif /* VR_FLUSH_MEMORY_H */
diff --git a/tests/vulkan/vkrunner/vr-format-table.h b/tests/vulkan/vkrunner/vr-format-table.h
new file mode 100644
index 000000000..d35805f17
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-format-table.h
@@ -0,0 +1,1580 @@
+/* Automatically generated by make-formats.py */
+static const struct vr_format
+formats[] = {
+	{
+		.vk_format = VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+		.name = "A1R5G5B5_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 1 },
+			{ .bits = 5 },
+			{ .bits = 5 },
+			{ .bits = 5 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_SINT_PACK32,
+		.name = "A2B10G10R10_SINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_SNORM_PACK32,
+		.name = "A2B10G10R10_SNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_SSCALED_PACK32,
+		.name = "A2B10G10R10_SSCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_UINT_PACK32,
+		.name = "A2B10G10R10_UINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+		.name = "A2B10G10R10_UNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2B10G10R10_USCALED_PACK32,
+		.name = "A2B10G10R10_USCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_SINT_PACK32,
+		.name = "A2R10G10B10_SINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_SNORM_PACK32,
+		.name = "A2R10G10B10_SNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_SSCALED_PACK32,
+		.name = "A2R10G10B10_SSCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_UINT_PACK32,
+		.name = "A2R10G10B10_UINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_UNORM_PACK32,
+		.name = "A2R10G10B10_UNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A2R10G10B10_USCALED_PACK32,
+		.name = "A2R10G10B10_USCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ARGB,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 2 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+			{ .bits = 10 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_SINT_PACK32,
+		.name = "A8B8G8R8_SINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+		.name = "A8B8G8R8_SNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+		.name = "A8B8G8R8_SRGB_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_SSCALED_PACK32,
+		.name = "A8B8G8R8_SSCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_UINT_PACK32,
+		.name = "A8B8G8R8_UINT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+		.name = "A8B8G8R8_UNORM_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_A8B8G8R8_USCALED_PACK32,
+		.name = "A8B8G8R8_USCALED_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_ABGR,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+		.name = "B10G11R11_UFLOAT_PACK32",
+		.packed_size = 32,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UFLOAT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 10 },
+			{ .bits = 11 },
+			{ .bits = 11 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B4G4R4A4_UNORM_PACK16,
+		.name = "B4G4R4A4_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 4 },
+			{ .bits = 4 },
+			{ .bits = 4 },
+			{ .bits = 4 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B5G5R5A1_UNORM_PACK16,
+		.name = "B5G5R5A1_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 5 },
+			{ .bits = 5 },
+			{ .bits = 5 },
+			{ .bits = 1 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B5G6R5_UNORM_PACK16,
+		.name = "B5G6R5_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 5 },
+			{ .bits = 6 },
+			{ .bits = 5 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_SINT,
+		.name = "B8G8R8A8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_SNORM,
+		.name = "B8G8R8A8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_SRGB,
+		.name = "B8G8R8A8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_SSCALED,
+		.name = "B8G8R8A8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_UINT,
+		.name = "B8G8R8A8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_UNORM,
+		.name = "B8G8R8A8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8A8_USCALED,
+		.name = "B8G8R8A8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_SINT,
+		.name = "B8G8R8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_SNORM,
+		.name = "B8G8R8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_SRGB,
+		.name = "B8G8R8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_SSCALED,
+		.name = "B8G8R8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_UINT,
+		.name = "B8G8R8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_UNORM,
+		.name = "B8G8R8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_B8G8R8_USCALED,
+		.name = "B8G8R8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_BGRA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_SFLOAT,
+		.name = "R16G16B16A16_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_SINT,
+		.name = "R16G16B16A16_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_SNORM,
+		.name = "R16G16B16A16_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_SSCALED,
+		.name = "R16G16B16A16_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_UINT,
+		.name = "R16G16B16A16_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_UNORM,
+		.name = "R16G16B16A16_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16A16_USCALED,
+		.name = "R16G16B16A16_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_SFLOAT,
+		.name = "R16G16B16_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_SINT,
+		.name = "R16G16B16_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_SNORM,
+		.name = "R16G16B16_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_SSCALED,
+		.name = "R16G16B16_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_UINT,
+		.name = "R16G16B16_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_UNORM,
+		.name = "R16G16B16_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16B16_USCALED,
+		.name = "R16G16B16_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_SFLOAT,
+		.name = "R16G16_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_SINT,
+		.name = "R16G16_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_SNORM,
+		.name = "R16G16_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_SSCALED,
+		.name = "R16G16_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_UINT,
+		.name = "R16G16_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_UNORM,
+		.name = "R16G16_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16G16_USCALED,
+		.name = "R16G16_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 2,
+		.components = {
+			{ .bits = 16 },
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_SFLOAT,
+		.name = "R16_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_SINT,
+		.name = "R16_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_SNORM,
+		.name = "R16_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_SSCALED,
+		.name = "R16_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_UINT,
+		.name = "R16_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_UNORM,
+		.name = "R16_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R16_USCALED,
+		.name = "R16_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 1,
+		.components = {
+			{ .bits = 16 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32A32_SFLOAT,
+		.name = "R32G32B32A32_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32A32_SINT,
+		.name = "R32G32B32A32_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32A32_UINT,
+		.name = "R32G32B32A32_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32_SFLOAT,
+		.name = "R32G32B32_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32_SINT,
+		.name = "R32G32B32_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32B32_UINT,
+		.name = "R32G32B32_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32_SFLOAT,
+		.name = "R32G32_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32_SINT,
+		.name = "R32G32_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32G32_UINT,
+		.name = "R32G32_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 32 },
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32_SFLOAT,
+		.name = "R32_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32_SINT,
+		.name = "R32_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R32_UINT,
+		.name = "R32_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 32 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+		.name = "R4G4B4A4_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 4 },
+			{ .bits = 4 },
+			{ .bits = 4 },
+			{ .bits = 4 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R4G4_UNORM_PACK8,
+		.name = "R4G4_UNORM_PACK8",
+		.packed_size = 8,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 2,
+		.components = {
+			{ .bits = 4 },
+			{ .bits = 4 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R5G5B5A1_UNORM_PACK16,
+		.name = "R5G5B5A1_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 5 },
+			{ .bits = 5 },
+			{ .bits = 5 },
+			{ .bits = 1 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R5G6B5_UNORM_PACK16,
+		.name = "R5G6B5_UNORM_PACK16",
+		.packed_size = 16,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 5 },
+			{ .bits = 6 },
+			{ .bits = 5 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64A64_SFLOAT,
+		.name = "R64G64B64A64_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64A64_SINT,
+		.name = "R64G64B64A64_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64A64_UINT,
+		.name = "R64G64B64A64_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64_SFLOAT,
+		.name = "R64G64B64_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64_SINT,
+		.name = "R64G64B64_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64B64_UINT,
+		.name = "R64G64B64_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64_SFLOAT,
+		.name = "R64G64_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64_SINT,
+		.name = "R64G64_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64G64_UINT,
+		.name = "R64G64_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 64 },
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64_SFLOAT,
+		.name = "R64_SFLOAT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SFLOAT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64_SINT,
+		.name = "R64_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R64_UINT,
+		.name = "R64_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 64 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_SINT,
+		.name = "R8G8B8A8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_SNORM,
+		.name = "R8G8B8A8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_SRGB,
+		.name = "R8G8B8A8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_SSCALED,
+		.name = "R8G8B8A8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_UINT,
+		.name = "R8G8B8A8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_UNORM,
+		.name = "R8G8B8A8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8A8_USCALED,
+		.name = "R8G8B8A8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 4,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_SINT,
+		.name = "R8G8B8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_SNORM,
+		.name = "R8G8B8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_SRGB,
+		.name = "R8G8B8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_SSCALED,
+		.name = "R8G8B8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_UINT,
+		.name = "R8G8B8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_UNORM,
+		.name = "R8G8B8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8B8_USCALED,
+		.name = "R8G8B8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 3,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_SINT,
+		.name = "R8G8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_SNORM,
+		.name = "R8G8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_SRGB,
+		.name = "R8G8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_SSCALED,
+		.name = "R8G8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_UINT,
+		.name = "R8G8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_UNORM,
+		.name = "R8G8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8G8_USCALED,
+		.name = "R8G8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 2,
+		.components = {
+			{ .bits = 8 },
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_SINT,
+		.name = "R8_SINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_SNORM,
+		.name = "R8_SNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SNORM,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_SRGB,
+		.name = "R8_SRGB",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SRGB,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_SSCALED,
+		.name = "R8_SSCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_SSCALED,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_UINT,
+		.name = "R8_UINT",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UINT,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_UNORM,
+		.name = "R8_UNORM",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_UNORM,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+	{
+		.vk_format = VK_FORMAT_R8_USCALED,
+		.name = "R8_USCALED",
+		.packed_size = 0,
+		.swizzle = VR_FORMAT_SWIZZLE_RGBA,
+		.mode = VR_FORMAT_MODE_USCALED,
+		.n_components = 1,
+		.components = {
+			{ .bits = 8 },
+		}
+	},
+};
diff --git a/tests/vulkan/vkrunner/vr-format.c b/tests/vulkan/vkrunner/vr-format.c
new file mode 100644
index 000000000..7808df965
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-format.c
@@ -0,0 +1,289 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-format.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "vr-format-table.h"
+#include "piglit-util.h"
+
+const struct vr_format *
+vr_format_lookup_by_name(const char *name)
+{
+	for (int i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (!strcasecmp(formats[i].name, name))
+			return formats + i;
+	}
+
+	return NULL;
+}
+
+const struct vr_format *
+vr_format_lookup_by_vk_format(VkFormat vk_format)
+{
+	for (int i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].vk_format == vk_format)
+			return formats + i;
+	}
+
+	return NULL;
+}
+
+const struct vr_format *
+vr_format_lookup_by_details(int bit_size,
+			    enum vr_format_mode mode,
+			    int n_components)
+{
+	for (int i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].n_components != n_components ||
+		    formats[i].mode != mode ||
+		    formats[i].packed_size != 0 ||
+		    formats[i].swizzle != VR_FORMAT_SWIZZLE_RGBA)
+			continue;
+
+		for (int j = 0; j < n_components; j++) {
+			if (formats[i].components[j].bits != bit_size)
+				goto bad_format;
+		}
+
+		return formats + i;
+
+	bad_format:
+		continue;
+	}
+
+	return NULL;
+}
+
+int
+vr_format_get_size(const struct vr_format *format)
+{
+	if (format->packed_size)
+		return format->packed_size / 8;
+
+	int total_size = 0;
+
+	for (int i = 0; i < format->n_components; i++)
+		total_size += format->components[i].bits;
+
+	return total_size / 8;
+}
+
+static int32_t
+sign_extend(uint32_t part, int bits)
+{
+	if (part & (1 << (bits - 1)))
+		return (UINT32_MAX << bits) | part;
+	else
+		return part;
+}
+
+static double
+load_packed_part(uint32_t part,
+		 int bits,
+		 enum vr_format_mode mode)
+{
+	assert(bits < 32);
+
+	switch (mode) {
+	case VR_FORMAT_MODE_SRGB:
+	case VR_FORMAT_MODE_UNORM:
+		return part / (double) ((1 << bits) - 1);
+	case VR_FORMAT_MODE_SNORM:
+		return (sign_extend(part, bits) /
+			(double) ((1 << (bits - 1)) - 1));
+	case VR_FORMAT_MODE_UINT:
+	case VR_FORMAT_MODE_USCALED:
+		return part;
+	case VR_FORMAT_MODE_SSCALED:
+	case VR_FORMAT_MODE_SINT:
+		return sign_extend(part, bits);
+	case VR_FORMAT_MODE_UFLOAT:
+		piglit_fatal("FIXME: load from packed UFLOAT format");
+	case VR_FORMAT_MODE_SFLOAT:
+		piglit_fatal("Unexpected packed SFLOAT format");
+	}
+
+	piglit_fatal("Unknown packed format");
+}
+
+static void
+load_packed_parts(const struct vr_format *format,
+		  const uint8_t *fb,
+		  double *parts)
+{
+	uint64_t packed_parts;
+
+	switch (format->packed_size) {
+	case 8:
+		packed_parts = *fb;
+		break;
+	case 16:
+		packed_parts = *(uint16_t *) fb;
+		break;
+	case 32:
+		packed_parts = *(uint32_t *) fb;
+		break;
+	default:
+		piglit_fatal("Unknown packed bit size: %i", format->packed_size);
+	}
+
+	for (int i = format->n_components - 1; i >= 0; i--) {
+		int bits = format->components[i].bits;
+		uint32_t part = packed_parts & ((1 << bits) - 1);
+
+		parts[i] = load_packed_part(part, bits, format->mode);
+		packed_parts >>= bits;
+	}
+}
+
+static double
+load_part(const struct vr_format *format,
+	  int bits,
+	  const uint8_t *fb)
+{
+	switch (format->mode) {
+	case VR_FORMAT_MODE_SRGB:
+	case VR_FORMAT_MODE_UNORM:
+		switch (bits) {
+		case 8:
+			return *fb / (double) UINT8_MAX;
+		case 16:
+			return (*(uint16_t *) fb) / (double) UINT16_MAX;
+		case 32:
+			return (*(uint32_t *) fb) / (double) UINT32_MAX;
+		case 64:
+			return (*(uint64_t *) fb) / (double) UINT64_MAX;
+		}
+		break;
+	case VR_FORMAT_MODE_SNORM:
+		switch (bits) {
+		case 8:
+			return (*(int8_t *) fb) / (double) INT8_MAX;
+		case 16:
+			return (*(int16_t *) fb) / (double) INT16_MAX;
+		case 32:
+			return (*(int32_t *) fb) / (double) INT32_MAX;
+		case 64:
+			return (*(int64_t *) fb) / (double) INT64_MAX;
+		}
+		break;
+	case VR_FORMAT_MODE_UINT:
+	case VR_FORMAT_MODE_USCALED:
+		switch (bits) {
+		case 8:
+			return *fb;
+		case 16:
+			return *(uint16_t *) fb;
+		case 32:
+			return *(uint32_t *) fb;
+		case 64:
+			return *(uint64_t *) fb;
+		}
+		break;
+	case VR_FORMAT_MODE_SINT:
+	case VR_FORMAT_MODE_SSCALED:
+		switch (bits) {
+		case 8:
+			return *(int8_t *) fb;
+		case 16:
+			return *(int16_t *) fb;
+		case 32:
+			return *(int32_t *) fb;
+		case 64:
+			return *(int64_t *) fb;
+		}
+		break;
+	case VR_FORMAT_MODE_UFLOAT:
+		break;
+	case VR_FORMAT_MODE_SFLOAT:
+		switch (bits) {
+		case 16:
+			piglit_fatal("FIXME: load pixel from half-float format");
+		case 32:
+			return *(float *) fb;
+		case 64:
+			return *(double *) fb;
+		}
+		break;
+	}
+
+	piglit_fatal("Unknown format bit size combination");
+}
+
+void
+vr_format_load_pixel(const struct vr_format *format,
+		     const uint8_t *p,
+		     double *pixel)
+{
+	double parts[4] = { 0.0f };
+
+	/* Alpha component defaults to 1.0 if not contained in the format */
+	switch (format->swizzle) {
+	case VR_FORMAT_SWIZZLE_BGRA:
+	case VR_FORMAT_SWIZZLE_RGBA:
+		parts[3] = 1.0f;
+		break;
+	case VR_FORMAT_SWIZZLE_ARGB:
+	case VR_FORMAT_SWIZZLE_ABGR:
+		parts[0] = 1.0f;
+		break;
+	}
+
+	if (format->packed_size) {
+		load_packed_parts(format, p, parts);
+	} else {
+		for (int i = 0; i < format->n_components; i++) {
+			int bits = format->components[i].bits;
+			parts[i] = load_part(format, bits, p);
+			p += bits / 8;
+		}
+	}
+
+	switch (format->swizzle) {
+	case VR_FORMAT_SWIZZLE_RGBA:
+		memcpy(pixel, parts, sizeof parts);
+		break;
+	case VR_FORMAT_SWIZZLE_ARGB:
+		memcpy(pixel, parts + 1, sizeof (double) * 3);
+		pixel[2] = parts[0];
+		break;
+	case VR_FORMAT_SWIZZLE_BGRA:
+		pixel[0] = parts[2];
+		pixel[1] = parts[1];
+		pixel[2] = parts[0];
+		pixel[3] = parts[3];
+		break;
+	case VR_FORMAT_SWIZZLE_ABGR:
+		pixel[0] = parts[3];
+		pixel[1] = parts[2];
+		pixel[2] = parts[1];
+		pixel[3] = parts[0];
+		break;
+	}
+}
diff --git a/tests/vulkan/vkrunner/vr-format.h b/tests/vulkan/vkrunner/vr-format.h
new file mode 100644
index 000000000..d0a595778
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-format.h
@@ -0,0 +1,86 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_FORMAT_H
+#define VR_FORMAT_H
+
+#include "vr-vk.h"
+
+enum vr_format_mode {
+	VR_FORMAT_MODE_UNORM,
+	VR_FORMAT_MODE_SNORM,
+	VR_FORMAT_MODE_USCALED,
+	VR_FORMAT_MODE_SSCALED,
+	VR_FORMAT_MODE_UINT,
+	VR_FORMAT_MODE_SINT,
+	VR_FORMAT_MODE_UFLOAT,
+	VR_FORMAT_MODE_SFLOAT,
+	VR_FORMAT_MODE_SRGB,
+};
+
+enum vr_format_swizzle {
+	VR_FORMAT_SWIZZLE_RGBA,
+	VR_FORMAT_SWIZZLE_BGRA,
+	VR_FORMAT_SWIZZLE_ARGB,
+	VR_FORMAT_SWIZZLE_ABGR,
+};
+
+struct vr_format_component {
+	int bits;
+};
+
+struct vr_format {
+	VkFormat vk_format;
+	const char *name;
+	/* If the format is packed, this is the total number of bits.
+	 * Otherwise it is zero.
+	 */
+	int packed_size;
+	enum vr_format_swizzle swizzle;
+	enum vr_format_mode mode;
+	int n_components;
+	struct vr_format_component components[4];
+};
+
+const struct vr_format *
+vr_format_lookup_by_name(const char *name);
+
+const struct vr_format *
+vr_format_lookup_by_vk_format(VkFormat vk_format);
+
+const struct vr_format *
+vr_format_lookup_by_details(int bit_size,
+			    enum vr_format_mode mode,
+			    int n_components);
+
+int
+vr_format_get_size(const struct vr_format *format);
+
+void
+vr_format_load_pixel(const struct vr_format *format,
+		     const uint8_t *p,
+		     double *pixel);
+
+#endif /* VR_FORMAT_H */
diff --git a/tests/vulkan/vkrunner/vr-list.c b/tests/vulkan/vkrunner/vr-list.c
new file mode 100644
index 000000000..2fa4c5137
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-list.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2011, 2012, 2013 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+/* This list implementation is based on the Wayland source code */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "vr-list.h"
+
+void
+vr_list_init(struct vr_list *list)
+{
+	list->prev = list;
+	list->next = list;
+}
+
+void
+vr_list_insert(struct vr_list *list, struct vr_list *elm)
+{
+	elm->prev = list;
+	elm->next = list->next;
+	list->next = elm;
+	elm->next->prev = elm;
+}
+
+void
+vr_list_remove(struct vr_list *elm)
+{
+	elm->prev->next = elm->next;
+	elm->next->prev = elm->prev;
+	elm->next = NULL;
+	elm->prev = NULL;
+}
+
+int
+vr_list_length(const struct vr_list *list)
+{
+	struct vr_list *e;
+	int count;
+
+	count = 0;
+	e = list->next;
+	while (e != list) {
+		e = e->next;
+		count++;
+	}
+
+	return count;
+}
+
+int
+vr_list_empty(const struct vr_list *list)
+{
+	return list->next == list;
+}
+
+void
+vr_list_insert_list(struct vr_list *list, struct vr_list *other)
+{
+	if (vr_list_empty(other))
+		return;
+
+	other->next->prev = list;
+	other->prev->next = list->next;
+	list->next->prev = other->prev;
+	list->next = other->next;
+}
diff --git a/tests/vulkan/vkrunner/vr-list.h b/tests/vulkan/vkrunner/vr-list.h
new file mode 100644
index 000000000..166d64246
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-list.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2012, 2013 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+/* This list implementation is based on the Wayland source code */
+
+#ifndef VR_LIST_H
+#define VR_LIST_H
+
+/**
+ * vr_list - linked list
+ *
+ * The list head is of "vr_list" type, and must be initialized
+ * using vr_list_init().  All entries in the list must be of the same
+ * type.  The item type must have a "vr_list" member. This
+ * member will be initialized by vr_list_insert(). There is no need to
+ * call vr_list_init() on the individual item. To query if the list is
+ * empty in O(1), use vr_list_empty().
+ *
+ * Let's call the list reference "vr_list foo_list", the item type as
+ * "item_t", and the item member as "vr_list link". The following code
+ *
+ * The following code will initialize a list:
+ *
+ *	vr_list_init (foo_list);
+ *	vr_list_insert (foo_list, item1);      Pushes item1 at the head
+ *	vr_list_insert (foo_list, item2);      Pushes item2 at the head
+ *	vr_list_insert (item2, item3);	       Pushes item3 after item2
+ *
+ * The list now looks like [item2, item3, item1]
+ *
+ * Will iterate the list in ascending order:
+ *
+ *	item_t *item;
+ *	vr_list_for_each(item, foo_list, link) {
+ *		Do_something_with_item(item);
+ *	}
+ */
+
+struct vr_list {
+	struct vr_list *prev;
+	struct vr_list *next;
+};
+
+void
+vr_list_init(struct vr_list *list);
+
+void
+vr_list_insert(struct vr_list *list, struct vr_list *elm);
+
+void
+vr_list_remove(struct vr_list *elm);
+
+int
+vr_list_length(const struct vr_list *list);
+
+int
+vr_list_empty(const struct vr_list *list);
+
+void
+vr_list_insert_list(struct vr_list *list, struct vr_list *other);
+
+#ifdef __cplusplus
+#define VR_LIST_TYPECAST(iterator) (decltype(iterator))
+#else
+#define VR_LIST_TYPECAST(iterator) (void *)
+#endif
+
+/* This assigns to iterator first so that taking a reference to it
+ * later in the second step won't be an undefined operation. It
+ * assigns the value of list_node rather than 0 so that it is possible
+ * have list_node be based on the previous value of iterator. In that
+ * respect iterator is just used as a convenient temporary variable.
+ * The compiler optimises all of this down to a single subtraction by
+ * a constant */
+#define vr_list_set_iterator(list_node, iterator, member)	\
+	((iterator) = VR_LIST_TYPECAST(iterator) (list_node),	\
+	 (iterator) = VR_LIST_TYPECAST(iterator)		\
+	 ((char *) (iterator) -					\
+	  (((char *) &(iterator)->member) -			\
+	   (char *) (iterator))))
+
+#define vr_container_of(ptr, type, member)			\
+	(type *) ((char *) (ptr) - offsetof (type, member))
+
+#define vr_list_for_each(pos, head, member)				\
+	for (vr_list_set_iterator((head)->next, pos, member);		\
+	     &pos->member != (head);					\
+	     vr_list_set_iterator(pos->member.next, pos, member))
+
+#define vr_list_for_each_safe(pos, tmp, head, member)			\
+	for (vr_list_set_iterator((head)->next, pos, member),		\
+		     vr_list_set_iterator((pos)->member.next, tmp, member); \
+	     &pos->member != (head);					\
+	     pos = tmp,							\
+		     vr_list_set_iterator(pos->member.next, tmp, member))
+
+#define vr_list_for_each_reverse(pos, head, member)			\
+	for (vr_list_set_iterator((head)->prev, pos, member);		\
+	     &pos->member != (head);					\
+	     vr_list_set_iterator(pos->member.prev, pos, member))
+
+#endif /* VR_LIST_H */
diff --git a/tests/vulkan/vkrunner/vr-pipeline.c b/tests/vulkan/vkrunner/vr-pipeline.c
new file mode 100644
index 000000000..62f25741b
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-pipeline.c
@@ -0,0 +1,668 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-pipeline.h"
+#include "vr-subprocess.h"
+#include "vr-script.h"
+#include "vr-error-message.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+static const char *
+stage_names[VR_SCRIPT_N_STAGES] = {
+	[VR_SCRIPT_SHADER_STAGE_VERTEX] = "vert",
+	[VR_SCRIPT_SHADER_STAGE_TESS_CTRL] = "tesc",
+	[VR_SCRIPT_SHADER_STAGE_TESS_EVAL] = "tese",
+	[VR_SCRIPT_SHADER_STAGE_GEOMETRY] = "geom",
+	[VR_SCRIPT_SHADER_STAGE_FRAGMENT] = "frag",
+	[VR_SCRIPT_SHADER_STAGE_COMPUTE] = "comp",
+};
+
+static const VkViewport
+base_viewports[] = {
+	{
+		.width = VR_WINDOW_WIDTH,
+		.height = VR_WINDOW_HEIGHT,
+		.minDepth = 0.0f,
+		.maxDepth = 1.0f
+	}
+};
+
+static const VkRect2D
+base_scissors[] = {
+	{
+		.extent = { VR_WINDOW_WIDTH, VR_WINDOW_HEIGHT }
+	}
+};
+
+static const VkPipelineViewportStateCreateInfo
+base_viewport_state = {
+	.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+	.viewportCount = ARRAY_SIZE(base_viewports),
+	.pViewports = base_viewports,
+	.scissorCount = ARRAY_SIZE(base_scissors),
+	.pScissors = base_scissors
+};
+
+static const VkPipelineRasterizationStateCreateInfo
+base_rasterization_state = {
+	.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+	.polygonMode = VK_POLYGON_MODE_FILL,
+	.cullMode = VK_CULL_MODE_NONE,
+	.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
+	.lineWidth = 1.0f
+};
+
+static const VkPipelineMultisampleStateCreateInfo
+base_multisample_state = {
+	.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+	.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+};
+
+static const VkPipelineDepthStencilStateCreateInfo
+base_depth_stencil_state = {
+	.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+	.depthTestEnable = false,
+	.depthWriteEnable = false,
+	.depthCompareOp = VK_COMPARE_OP_LESS
+};
+
+static const VkPipelineColorBlendAttachmentState base_blend_attachments[] = {
+	{
+		.blendEnable = false,
+		.colorWriteMask = (VK_COLOR_COMPONENT_R_BIT |
+				   VK_COLOR_COMPONENT_G_BIT |
+				   VK_COLOR_COMPONENT_B_BIT |
+				   VK_COLOR_COMPONENT_A_BIT)
+	}
+};
+
+static const VkPipelineColorBlendStateCreateInfo base_color_blend_state = {
+	.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+	.attachmentCount = ARRAY_SIZE(base_blend_attachments),
+	.pAttachments = base_blend_attachments
+};
+
+static bool
+create_named_temp_file(FILE **stream_out,
+		       char **filename_out)
+{
+	char filename[] = "/tmp/vkrunner-XXXXXX";
+	int fd = mkstemp(filename);
+	FILE *stream;
+
+	if (fd == -1) {
+		vr_error_message("mkstemp: %s", strerror(errno));
+		return false;
+	}
+
+	stream = fdopen(fd, "r+");
+	if (stream == NULL) {
+		vr_error_message("%s: %s", filename, strerror(errno));
+		close(fd);
+		return false;
+	}
+
+	*filename_out = strdup(filename);
+	*stream_out = stream;
+
+	return true;
+}
+
+static char *
+create_file_for_shader(const struct vr_script_shader *shader)
+{
+	char *filename;
+	FILE *out;
+
+	if (!create_named_temp_file(&out, &filename))
+		return NULL;
+
+	fwrite(shader->source, 1, shader->length, out);
+
+	fclose(out);
+
+	return filename;
+}
+
+static bool
+load_stream_contents(FILE *stream,
+		     uint8_t **contents,
+		     size_t *size)
+{
+	ssize_t got;
+	long pos;
+
+	fseek(stream, 0, SEEK_END);
+	pos = ftell(stream);
+
+	if (pos == -1) {
+		vr_error_message("ftell failed");
+		return false;
+	}
+
+	*size = pos;
+	rewind(stream);
+	*contents = malloc(*size);
+
+	got = fread(*contents, 1, *size, stream);
+	if (got != *size) {
+		vr_error_message("Error reading file contents");
+		free(contents);
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+show_disassembly(const char *filename)
+{
+	char *args[] = {
+		getenv("PIGLIT_SPIRV_DIS_BINARY"),
+		(char *) filename,
+		NULL
+	};
+
+	if (args[0] == NULL)
+		args[0] = "spirv-dis";
+
+	return vr_subprocess_command(args);
+}
+
+static VkShaderModule
+compile_stage(const struct vr_config *config,
+	      struct vr_window *window,
+	      const struct vr_script *script,
+	      enum vr_script_shader_stage stage)
+{
+	const int n_base_args = 6;
+	int n_shaders = vr_list_length(&script->stages[stage]);
+	char **args = alloca((n_base_args + n_shaders + 1) * sizeof args[0]);
+	const struct vr_script_shader *shader;
+	VkShaderModule module = NULL;
+	FILE *module_stream = NULL;
+	char *module_filename;
+	uint8_t *module_binary = NULL;
+	size_t module_size;
+	bool res;
+	int i;
+
+	if (!create_named_temp_file(&module_stream, &module_filename))
+		goto out;
+
+	args[0] = getenv("PIGLIT_GLSLANG_VALIDATOR_BINARY");
+	if (args[0] == NULL)
+		args[0] = "glslangValidator";
+
+	args[1] = "-V";
+	args[2] = "-S";
+	args[3] = (char *) stage_names[stage];
+	args[4] = "-o";
+	args[5] = module_filename;
+	memset(args + n_base_args, 0, (n_shaders + 1) * sizeof args[0]);
+
+	i = n_base_args;
+	vr_list_for_each(shader, &script->stages[stage], link) {
+		args[i] = create_file_for_shader(shader);
+		if (args[i] == 0)
+			goto out;
+		i++;
+	}
+
+	res = vr_subprocess_command(args);
+	if (!res) {
+		vr_error_message("glslangValidator failed");
+		goto out;
+	}
+
+	if (config->show_disassembly)
+		show_disassembly(module_filename);
+
+	if (!load_stream_contents(module_stream, &module_binary, &module_size))
+		goto out;
+
+	VkShaderModuleCreateInfo shader_module_create_info = {
+			.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+			.codeSize = module_size,
+			.pCode = (const uint32_t *) module_binary
+	};
+	res = vr_vk.vkCreateShaderModule(window->device,
+					 &shader_module_create_info,
+					 NULL, /* allocator */
+					 &module);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkCreateShaderModule failed");
+		module = NULL;
+		goto out;
+	}
+
+out:
+	for (i = 0; i < n_shaders; i++) {
+		if (args[i + n_base_args]) {
+			unlink(args[i + n_base_args]);
+			free(args[i + n_base_args]);
+		}
+	}
+
+	if (module_stream) {
+		fclose(module_stream);
+		unlink(module_filename);
+		free(module_filename);
+	}
+
+	if (module_binary)
+		free(module_binary);
+
+	return module;
+}
+
+static VkShaderModule
+assemble_stage(const struct vr_config *config,
+	       struct vr_window *window,
+	       const struct vr_script_shader *shader)
+{
+	FILE *module_stream = NULL;
+	char *module_filename;
+	char *source_filename = NULL;
+	uint8_t *module_binary = NULL;
+	VkShaderModule module = NULL;
+	size_t module_size;
+	bool res;
+
+	if (!create_named_temp_file(&module_stream, &module_filename))
+		goto out;
+
+	source_filename = create_file_for_shader(shader);
+	if (source_filename == NULL)
+		goto out;
+
+	char *args[] = {
+		getenv("PIGLIT_SPIRV_AS_BINARY"),
+		"-o", module_filename,
+		source_filename,
+		NULL
+	};
+
+	if (args[0] == NULL)
+		args[0] = "spirv-as";
+
+	res = vr_subprocess_command(args);
+	if (!res) {
+		vr_error_message("spirv-as failed");
+		goto out;
+	}
+
+	if (config->show_disassembly)
+		show_disassembly(module_filename);
+
+	if (!load_stream_contents(module_stream, &module_binary, &module_size))
+		goto out;
+
+	VkShaderModuleCreateInfo shader_module_create_info = {
+			.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+			.codeSize = module_size,
+			.pCode = (const uint32_t *) module_binary
+	};
+	res = vr_vk.vkCreateShaderModule(window->device,
+					 &shader_module_create_info,
+					 NULL, /* allocator */
+					 &module);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkCreateShaderModule failed");
+		module = NULL;
+		goto out;
+	}
+
+out:
+	if (source_filename) {
+		unlink(source_filename);
+		free(source_filename);
+	}
+
+	if (module_stream) {
+		fclose(module_stream);
+		unlink(module_filename);
+		free(module_filename);
+	}
+
+	if (module_binary)
+		free(module_binary);
+
+	return module;
+}
+
+static VkShaderModule
+build_stage(const struct vr_config *config,
+	    struct vr_window *window,
+	    const struct vr_script *script,
+	    enum vr_script_shader_stage stage)
+{
+	assert(!vr_list_empty(&script->stages[stage]));
+
+	struct vr_script_shader *shader =
+		vr_container_of(script->stages[stage].next,
+				struct vr_script_shader,
+				link);
+
+	switch (shader->source_type) {
+	case VR_SCRIPT_SOURCE_TYPE_GLSL:
+		return compile_stage(config, window, script, stage);
+	case VR_SCRIPT_SOURCE_TYPE_SPIRV:
+		return assemble_stage(config, window, shader);
+	}
+
+	piglit_fatal("should not be reached");
+}
+
+static VkPrimitiveTopology
+get_topology(const struct vr_script *script)
+{
+	for (int i = 0; i < script->n_commands; i++) {
+		if (script->commands[i].op == VR_SCRIPT_OP_DRAW_ARRAYS)
+			return script->commands[i].draw_arrays.topology;
+	}
+
+	return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+}
+
+static void
+set_vertex_input_state(const struct vr_script *script,
+		       VkPipelineVertexInputStateCreateInfo *state)
+{
+	VkVertexInputBindingDescription *input_binding =
+		calloc(sizeof *input_binding, 1);
+
+	input_binding[0].binding = 0;
+	input_binding[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+	memset(state, 0, sizeof *state);
+
+	state->sType =
+		VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+	state->vertexBindingDescriptionCount = 1;
+	state->pVertexBindingDescriptions = input_binding;
+
+	if (script->vertex_data == NULL) {
+		VkVertexInputAttributeDescription *attrib =
+			calloc(sizeof *attrib, 1);
+		state->vertexAttributeDescriptionCount = 1;
+		state->pVertexAttributeDescriptions = attrib;
+
+		input_binding[0].stride =
+			sizeof (struct vr_pipeline_vertex);
+
+		attrib->location = 0;
+		attrib->binding = 0;
+		attrib->format = VK_FORMAT_R32G32B32_SFLOAT;
+		attrib->offset = offsetof(struct vr_pipeline_vertex, x);
+
+		return;
+	}
+
+	int n_attribs = vr_list_length(&script->vertex_data->attribs);
+	VkVertexInputAttributeDescription *attrib_desc =
+		calloc(sizeof *attrib_desc, n_attribs);
+	const struct vr_vbo_attrib *attrib;
+
+	state->vertexAttributeDescriptionCount = n_attribs;
+	state->pVertexAttributeDescriptions = attrib_desc;
+	input_binding[0].stride = script->vertex_data->stride;
+
+	vr_list_for_each(attrib, &script->vertex_data->attribs, link) {
+		attrib_desc->location = attrib->location;
+		attrib_desc->binding = 0;
+		attrib_desc->format = attrib->format->vk_format,
+		attrib_desc->offset = attrib->offset;
+		attrib_desc++;
+	};
+}
+
+static VkPipeline
+create_vk_pipeline(struct vr_pipeline *pipeline,
+		   const struct vr_script *script)
+{
+	struct vr_window *window = pipeline->window;
+	VkResult res;
+	int num_stages = 0;
+
+	VkPipelineShaderStageCreateInfo stages[VR_SCRIPT_N_STAGES] = { };
+
+	for (int i = 0; i < VR_SCRIPT_N_STAGES; i++) {
+		if (pipeline->modules[i] == NULL)
+			continue;
+		stages[num_stages].sType =
+			VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+		stages[num_stages].stage = VK_SHADER_STAGE_VERTEX_BIT << i;
+		stages[num_stages].module = pipeline->modules[i];
+		stages[num_stages].pName = "main";
+		num_stages++;
+	}
+
+	VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {
+		.sType =
+		VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+		.topology = get_topology(script),
+		.primitiveRestartEnable = false
+	};
+
+	VkPipelineVertexInputStateCreateInfo vertex_input_state;
+	set_vertex_input_state(script, &vertex_input_state);
+
+	VkGraphicsPipelineCreateInfo info = {
+		.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+		.pViewportState = &base_viewport_state,
+		.pRasterizationState = &base_rasterization_state,
+		.pMultisampleState = &base_multisample_state,
+		.pDepthStencilState = &base_depth_stencil_state,
+		.pColorBlendState = &base_color_blend_state,
+		.subpass = 0,
+		.basePipelineHandle = NULL,
+		.basePipelineIndex = -1,
+
+		.stageCount = num_stages,
+		.pStages = stages,
+		.pVertexInputState = &vertex_input_state,
+		.pInputAssemblyState = &input_assembly_state,
+		.layout = pipeline->layout,
+		.renderPass = window->render_pass
+	};
+
+	VkPipeline vk_pipeline;
+
+	res = vr_vk.vkCreateGraphicsPipelines(window->device,
+					      pipeline->pipeline_cache,
+					      1, /* nCreateInfos */
+					      &info,
+					      NULL, /* allocator */
+					      &vk_pipeline);
+
+	free((void *) vertex_input_state.pVertexBindingDescriptions);
+	free((void *) vertex_input_state.pVertexAttributeDescriptions);
+
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkPipeline");
+		return NULL;
+	}
+
+	return vk_pipeline;
+}
+
+static size_t
+get_push_constant_size(const struct vr_script *script)
+{
+	size_t max = 0;
+
+	for (int i = 0; i < script->n_commands; i++) {
+		const struct vr_script_command *command =
+			script->commands + i;
+		if (command->op != VR_SCRIPT_OP_SET_PUSH_CONSTANT)
+			continue;
+
+		enum vr_script_type type =
+			command->set_push_constant.value.type;
+		size_t value_size = vr_script_type_size(type);
+		size_t end = command->set_push_constant.offset + value_size;
+
+		if (end > max)
+			max = end;
+	}
+
+	return max;
+}
+
+static VkShaderStageFlags
+get_script_stages(const struct vr_script *script)
+{
+	VkShaderStageFlags flags = 0;
+
+	for (int i = 0; i < VR_SCRIPT_N_STAGES; i++) {
+		if (!vr_list_empty(script->stages + i))
+			flags |= VK_SHADER_STAGE_VERTEX_BIT << i;
+	}
+
+	return flags;
+}
+
+static VkPipelineLayout
+create_vk_layout(struct vr_pipeline *pipeline,
+		 const struct vr_script *script)
+{
+	VkResult res;
+
+	VkPushConstantRange push_constant_range = {
+		.stageFlags = pipeline->stages,
+		.offset = 0,
+		.size = get_push_constant_size(script)
+	};
+	VkPipelineLayoutCreateInfo pipeline_layout_create_info = {
+		.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+		.setLayoutCount = 0
+	};
+
+	if (push_constant_range.size > 0) {
+		pipeline_layout_create_info.pushConstantRangeCount = 1;
+		pipeline_layout_create_info.pPushConstantRanges =
+			&push_constant_range;
+	}
+
+	VkPipelineLayout layout;
+	res = vr_vk.vkCreatePipelineLayout(pipeline->window->device,
+					   &pipeline_layout_create_info,
+					   NULL, /* allocator */
+					   &layout);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating empty layout");
+		return NULL;
+	}
+
+	return layout;
+}
+
+struct vr_pipeline *
+vr_pipeline_create(const struct vr_config *config,
+		   struct vr_window *window,
+		   const struct vr_script *script)
+{
+	VkResult res;
+	struct vr_pipeline *pipeline = calloc(sizeof *pipeline, 1);
+
+	pipeline->window = window;
+
+	for (int i = 0; i < VR_SCRIPT_N_STAGES; i++) {
+		if (vr_list_empty(&script->stages[i]))
+			continue;
+
+		pipeline->modules[i] = build_stage(config, window, script, i);
+		if (pipeline->modules[i] == NULL)
+			goto error;
+	}
+
+	VkPipelineCacheCreateInfo pipeline_cache_create_info = {
+		.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO
+	};
+	res = vr_vk.vkCreatePipelineCache(window->device,
+					  &pipeline_cache_create_info,
+					  NULL, /* allocator */
+					  &pipeline->pipeline_cache);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating pipeline cache");
+		goto error;
+	}
+
+	pipeline->stages = get_script_stages(script);
+
+	pipeline->layout = create_vk_layout(pipeline, script);
+	if (pipeline->layout == NULL)
+		goto error;
+
+	pipeline->pipeline = create_vk_pipeline(pipeline, script);
+	if (pipeline->pipeline == NULL)
+		goto error;
+
+	return pipeline;
+
+error:
+	vr_pipeline_free(pipeline);
+	return NULL;
+}
+
+void
+vr_pipeline_free(struct vr_pipeline *pipeline)
+{
+	struct vr_window *window = pipeline->window;
+
+	if (pipeline->pipeline) {
+		vr_vk.vkDestroyPipeline(window->device,
+					pipeline->pipeline,
+					NULL /* allocator */);
+	}
+
+	if (pipeline->pipeline_cache) {
+		vr_vk.vkDestroyPipelineCache(window->device,
+					     pipeline->pipeline_cache,
+					     NULL /* allocator */);
+	}
+
+	if (pipeline->layout) {
+		vr_vk.vkDestroyPipelineLayout(window->device,
+					      pipeline->layout,
+					      NULL /* allocator */);
+	}
+
+	for (int i = 0; i < VR_SCRIPT_N_STAGES; i++) {
+		if (pipeline->modules[i] == NULL)
+			continue;
+		vr_vk.vkDestroyShaderModule(window->device,
+					    pipeline->modules[i],
+					    NULL /* allocator */);
+	}
+
+	free(pipeline);
+}
diff --git a/tests/vulkan/vkrunner/vr-pipeline.h b/tests/vulkan/vkrunner/vr-pipeline.h
new file mode 100644
index 000000000..2c9058d7a
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-pipeline.h
@@ -0,0 +1,54 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_PIPELINE_H
+#define VR_PIPELINE_H
+
+#include "vr-script.h"
+#include "vr-window.h"
+#include "vr-config.h"
+
+struct vr_pipeline {
+	struct vr_window *window;
+	VkPipelineLayout layout;
+	VkPipeline pipeline;
+	VkPipelineCache pipeline_cache;
+	VkShaderModule modules[VR_SCRIPT_N_STAGES];
+	VkShaderStageFlagBits stages;
+};
+
+struct vr_pipeline_vertex {
+	float x, y, z;
+};
+
+struct vr_pipeline *
+vr_pipeline_create(const struct vr_config *config,
+		   struct vr_window *window,
+		   const struct vr_script *script);
+
+void
+vr_pipeline_free(struct vr_pipeline *pipeline);
+
+#endif /* VR_PIPELINE_H */
diff --git a/tests/vulkan/vkrunner/vr-script.c b/tests/vulkan/vkrunner/vr-script.c
new file mode 100644
index 000000000..414a23cbc
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-script.c
@@ -0,0 +1,948 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "vr-script.h"
+#include "vr-list.h"
+#include "vr-buffer.h"
+#include "vr-error-message.h"
+#include "vr-feature-offsets.h"
+#include "vr-window.h"
+#include "piglit-util.h"
+
+enum section {
+	SECTION_NONE,
+	SECTION_REQUIRE,
+	SECTION_SHADER,
+	SECTION_VERTEX_DATA,
+	SECTION_TEST
+};
+
+struct load_state {
+	const char *filename;
+	int line_num;
+	struct vr_script *script;
+	struct vr_buffer buffer;
+	char *line;
+	size_t len;
+	ssize_t nread;
+	enum vr_script_shader_stage current_stage;
+	enum vr_script_source_type current_source_type;
+	enum section current_section;
+	struct vr_buffer commands;
+};
+
+static const char *
+stage_names[VR_SCRIPT_N_STAGES] = {
+	"vertex shader",
+	"tessellation control shader",
+	"tessellation evaluation shader",
+	"geometry shader",
+	"fragment shader",
+	"compute shader",
+};
+
+static const char
+vertex_shader_passthrough[] =
+	"#version 430\n"
+	"\n"
+	"layout(location = 0) in vec4 piglit_vertex;\n"
+	"\n"
+	"void\n"
+	"main()\n"
+	"{\n"
+	"	 gl_Position = piglit_vertex;\n"
+	"}\n";
+
+static void
+add_shader(struct load_state *data,
+	   enum vr_script_shader_stage stage,
+	   enum vr_script_source_type source_type,
+	   size_t length,
+	   const char *source)
+{
+	struct vr_script_shader *shader;
+
+	shader = malloc(sizeof *shader + length);
+	shader->length = length;
+	shader->source_type = source_type;
+	memcpy(shader->source, source, length);
+
+	vr_list_insert(data->script->stages[stage].prev, &shader->link);
+}
+
+static void
+end_shader(struct load_state *data)
+{
+	add_shader(data,
+		   data->current_stage,
+		   data->current_source_type,
+		   data->buffer.length,
+		   (const char *) data->buffer.data);
+}
+
+static bool
+end_vertex_data(struct load_state *data)
+{
+	data->script->vertex_data = vr_vbo_parse((const char *)
+						 data->buffer.data,
+						 data->buffer.length);
+	return data->script->vertex_data != NULL;
+}
+
+static bool
+end_section(struct load_state *data)
+{
+	switch (data->current_section) {
+	case SECTION_NONE:
+		break;
+
+	case SECTION_REQUIRE:
+		break;
+
+	case SECTION_SHADER:
+		end_shader(data);
+		break;
+
+	case SECTION_VERTEX_DATA:
+		return end_vertex_data(data);
+
+	case SECTION_TEST:
+		break;
+	}
+
+	data->current_section = SECTION_NONE;
+
+	return true;
+}
+
+static bool
+is_string(const char *string,
+	  const char *start,
+	  const char *end)
+{
+	return (end - start == strlen(string) &&
+		!memcmp(start, string, end - start));
+}
+
+static bool
+looking_at(const char **p,
+	   const char *string)
+{
+	int len = strlen(string);
+
+	if (strncmp(*p, string, len) == 0) {
+		*p += len;
+		return true;
+	}
+
+	return false;
+}
+
+static bool
+is_end(const char *p)
+{
+	while (*p && isspace(*p))
+		p++;
+
+	return *p == '\0';
+}
+
+static bool
+parse_floats(const char **p,
+	     float *out,
+	     int n_floats,
+	     const char *sep)
+{
+	char *tail;
+
+	for (int i = 0; i < n_floats; i++) {
+		while (isspace(**p))
+			(*p)++;
+
+		errno = 0;
+		*(out++) = strtof(*p, &tail);
+		if (errno != 0 || tail == *p)
+			return false;
+		*p = tail;
+
+		if (sep && i < n_floats - 1) {
+			while (isspace(**p))
+				(*p)++;
+			if (!looking_at(p, sep))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool
+parse_doubles(const char **p,
+	      double *out,
+	      int n_doubles,
+	      const char *sep)
+{
+	char *tail;
+
+	for (int i = 0; i < n_doubles; i++) {
+		while (isspace(**p))
+			(*p)++;
+
+		errno = 0;
+		*(out++) = strtod(*p, &tail);
+		if (errno != 0 || tail == *p)
+			return false;
+		*p = tail;
+
+		if (sep && i < n_doubles - 1) {
+			while (isspace(**p))
+				(*p)++;
+			if (!looking_at(p, sep))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool
+parse_ints(const char **p,
+	   int *out,
+	   int n_ints,
+	   const char *sep)
+{
+	long v;
+	char *tail;
+
+	for (int i = 0; i < n_ints; i++) {
+		while (isspace(**p))
+			(*p)++;
+
+		errno = 0;
+		v = strtol(*p, &tail, 10);
+		if (errno != 0 || tail == *p ||
+		    v < INT_MIN || v > INT_MAX)
+			return false;
+		*(out++) = (int) v;
+		*p = tail;
+
+		if (sep && i < n_ints - 1) {
+			while (isspace(**p))
+				(*p)++;
+			if (!looking_at(p, sep))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool
+parse_size_t(const char **p,
+	     size_t *out)
+{
+	unsigned long v;
+	char *tail;
+
+	errno = 0;
+	v = strtoul(*p, &tail, 10);
+	if (errno != 0 || tail == *p || v > SIZE_MAX)
+		return false;
+	*out = v;
+	*p = tail;
+
+	return true;
+}
+
+static bool
+parse_value_type(const char **p,
+		 enum vr_script_type *type)
+{
+	static const struct {
+		const char *name;
+		enum vr_script_type type;
+	} types[] = {
+		{ "int ", VR_SCRIPT_TYPE_INT },
+		{ "float ", VR_SCRIPT_TYPE_FLOAT },
+		{ "double ", VR_SCRIPT_TYPE_DOUBLE },
+		{ "vec2 ", VR_SCRIPT_TYPE_VEC2 },
+		{ "vec3 ", VR_SCRIPT_TYPE_VEC3 },
+		{ "vec4 ", VR_SCRIPT_TYPE_VEC4 },
+		{ "dvec2 ", VR_SCRIPT_TYPE_DVEC2 },
+		{ "dvec3 ", VR_SCRIPT_TYPE_DVEC3 },
+		{ "dvec4 ", VR_SCRIPT_TYPE_DVEC4 },
+		{ "ivec2 ", VR_SCRIPT_TYPE_IVEC2 },
+		{ "ivec3 ", VR_SCRIPT_TYPE_IVEC3 },
+		{ "ivec4 ", VR_SCRIPT_TYPE_IVEC4 },
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(types); i++) {
+		if (looking_at(p, types[i].name)) {
+			*type = types[i].type;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static bool
+parse_value(const char **p,
+	    struct vr_script_value *value)
+{
+	switch (value->type) {
+	case VR_SCRIPT_TYPE_INT:
+		return parse_ints(p, &value->i, 1, NULL);
+	case VR_SCRIPT_TYPE_FLOAT:
+		return parse_floats(p, &value->f, 1, NULL);
+	case VR_SCRIPT_TYPE_DOUBLE:
+		return parse_doubles(p, &value->d, 1, NULL);
+	case VR_SCRIPT_TYPE_VEC2:
+		return parse_floats(p, value->vec, 2, NULL);
+	case VR_SCRIPT_TYPE_VEC3:
+		return parse_floats(p, value->vec, 3, NULL);
+	case VR_SCRIPT_TYPE_VEC4:
+		return parse_floats(p, value->vec, 4, NULL);
+	case VR_SCRIPT_TYPE_DVEC2:
+		return parse_doubles(p, value->dvec, 2, NULL);
+	case VR_SCRIPT_TYPE_DVEC3:
+		return parse_doubles(p, value->dvec, 3, NULL);
+	case VR_SCRIPT_TYPE_DVEC4:
+		return parse_doubles(p, value->dvec, 4, NULL);
+	case VR_SCRIPT_TYPE_IVEC2:
+		return parse_ints(p, value->ivec, 2, NULL);
+	case VR_SCRIPT_TYPE_IVEC3:
+		return parse_ints(p, value->ivec, 3, NULL);
+	case VR_SCRIPT_TYPE_IVEC4:
+		return parse_ints(p, value->ivec, 4, NULL);
+	}
+
+	piglit_fatal("should not be reached");
+}
+
+static bool
+process_none_line(struct load_state *data)
+{
+	const char *start = data->line;
+
+	while (*start && isspace(*start))
+		start++;
+
+	if (*start != '#' && *start != '\0') {
+		vr_error_message("%s:%i expected empty line",
+				 data->filename,
+				 data->line_num);
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+process_require_line(struct load_state *data)
+{
+	const char *start = data->line, *p;
+
+	while (*start && isspace(*start))
+		start++;
+
+	if (*start == '#' || *start == '\0')
+		return true;
+
+	for (int i = 0; vr_feature_offsets[i].name; i++) {
+		p = start;
+		if (!looking_at(&p, vr_feature_offsets[i].name) || !is_end(p))
+			continue;
+		*(VkBool32 *) ((uint8_t *) &data->script->required_features +
+			       vr_feature_offsets[i].offset) = true;
+		return true;
+	}
+
+	if (looking_at(&p, "framebuffer ")) {
+		while (isspace(*p))
+			p++;
+		const char *end = p;
+		while (*end && !isspace(*end))
+			end++;
+
+		if (is_end(end)) {
+			char *format_name = strndup(p, end - p);
+			const struct vr_format *format =
+				vr_format_lookup_by_name(format_name);
+			bool ret;
+
+			if (format == NULL) {
+				vr_error_message("%s:%i: Unknown format: %s",
+						 data->filename,
+						 data->line_num,
+						 format_name);
+				ret = false;
+			} else {
+				data->script->framebuffer_format = format;
+				ret = true;
+			}
+
+			free(format_name);
+
+			return ret;
+		}
+	}
+
+	vr_error_message("%s:%i: Invalid require line",
+			 data->filename,
+			 data->line_num);
+
+	return false;
+}
+
+static bool
+process_probe_command(const char **p,
+		      struct vr_script_command *command)
+{
+	bool relative = false;
+	enum { POINT, RECT, ALL } region_type = POINT;
+	int n_components;
+
+	if (looking_at(p, "relative "))
+		relative = true;
+
+	if (!looking_at(p, "probe "))
+		return false;
+
+	if (looking_at(p, "rect "))
+		region_type = RECT;
+	else if (looking_at(p, "all "))
+		region_type = ALL;
+
+	if (looking_at(p, "rgb "))
+		n_components = 3;
+	else if (looking_at(p, "rgba "))
+		n_components = 4;
+	else
+		return false;
+
+	command->op = VR_SCRIPT_OP_PROBE_RECT;
+	command->probe_rect.n_components = n_components;
+
+	if (region_type == ALL) {
+		if (relative)
+			return false;
+		if (!parse_doubles(p,
+				   command->probe_rect.color,
+				   n_components,
+				   NULL))
+			return false;
+		if (!is_end(*p))
+			return false;
+		command->probe_rect.x = 0;
+		command->probe_rect.y = 0;
+		command->probe_rect.w = VR_WINDOW_WIDTH;
+		command->probe_rect.h = VR_WINDOW_HEIGHT;
+		return true;
+	}
+
+	while (isspace(**p))
+		(*p)++;
+	if (**p != '(')
+		return false;
+	(*p)++;
+
+	if (region_type == POINT) {
+		if (relative) {
+			float rel_pos[2];
+			if (!parse_floats(p, rel_pos, 2, ","))
+				return false;
+			command->probe_rect.x = rel_pos[0] * VR_WINDOW_WIDTH;
+			command->probe_rect.y = rel_pos[1] * VR_WINDOW_HEIGHT;
+		} else if (!parse_ints(p, &command->probe_rect.x, 2, ",")) {
+			return false;
+		}
+		command->probe_rect.w = 1;
+		command->probe_rect.h = 1;
+	} else {
+		assert(region_type == RECT);
+
+		if (relative) {
+			float rel_pos[4];
+			if (!parse_floats(p, rel_pos, 4, ","))
+				return false;
+			command->probe_rect.x = rel_pos[0] * VR_WINDOW_WIDTH;
+			command->probe_rect.y = rel_pos[1] * VR_WINDOW_HEIGHT;
+			command->probe_rect.w = rel_pos[2] * VR_WINDOW_WIDTH;
+			command->probe_rect.h = rel_pos[3] * VR_WINDOW_HEIGHT;
+		} else if (!parse_ints(p, &command->probe_rect.x, 4, ",")) {
+			return false;
+		}
+	}
+
+	while (isspace(**p))
+		(*p)++;
+	if (**p != ')')
+		return false;
+	(*p)++;
+
+	while (isspace(**p))
+		(*p)++;
+	if (**p != '(')
+		return false;
+	(*p)++;
+
+	if (!parse_doubles(p, command->probe_rect.color, n_components, ","))
+		return false;
+
+	while (isspace(**p))
+		(*p)++;
+	if (**p != ')')
+		return false;
+	(*p)++;
+
+	if (!is_end(*p))
+		return false;
+
+	return true;
+}
+
+static bool
+process_draw_arrays_command(struct load_state *data,
+			    const char *p,
+			    struct vr_script_command *command)
+{
+	int args[3];
+	int n_args;
+
+	if (looking_at(&p, "instanced ")) {
+		n_args = 3;
+	} else {
+		n_args = 2;
+		args[2] = 1;
+	}
+
+	static const struct {
+		const char *name;
+		VkPrimitiveTopology topology;
+	} topologies[] = {
+		/* GL names used in Piglit */
+		{ "GL_POINTS", VK_PRIMITIVE_TOPOLOGY_POINT_LIST },
+		{ "GL_LINES", VK_PRIMITIVE_TOPOLOGY_LINE_LIST },
+		{ "GL_LINE_STRIP", VK_PRIMITIVE_TOPOLOGY_LINE_STRIP },
+		{ "GL_TRIANGLES", VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST },
+		{ "GL_TRIANGLE_STRIP", VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP },
+		{ "GL_TRIANGLE_FAN", VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN },
+		{ "GL_LINES_ADJACENCY",
+		  VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY },
+		{ "GL_LINE_STRIP_ADJACENCY",
+		  VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY },
+		{ "GL_TRIANGLES_ADJACENCY",
+		  VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY },
+		{ "GL_TRIANGLE_STRIP_ADJACENCY",
+		  VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY },
+		{ "GL_PATCHES", VK_PRIMITIVE_TOPOLOGY_PATCH_LIST },
+		/* Vulkan names */
+#define vkname(x) { PIGLIT_STRINGIFY(x), VK_PRIMITIVE_TOPOLOGY_ ## x }
+		vkname(POINT_LIST),
+		vkname(LINE_LIST),
+		vkname(LINE_STRIP),
+		vkname(TRIANGLE_LIST),
+		vkname(TRIANGLE_STRIP),
+		vkname(TRIANGLE_FAN),
+		vkname(LINE_LIST_WITH_ADJACENCY),
+		vkname(LINE_STRIP_WITH_ADJACENCY),
+		vkname(TRIANGLE_LIST_WITH_ADJACENCY),
+		vkname(TRIANGLE_STRIP_WITH_ADJACENCY),
+		vkname(PATCH_LIST),
+#undef vkname
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(topologies); i++) {
+		if (looking_at(&p, topologies[i].name)) {
+			command->draw_arrays.topology = topologies[i].topology;
+			goto found_topology;
+		}
+	}
+
+	vr_error_message("%s:%i: Unknown topology in draw arrays command",
+			 data->filename,
+			 data->line_num);
+	return false;
+
+found_topology:
+	if (!parse_ints(&p, args, n_args, NULL) ||
+	    !is_end(p)) {
+		vr_error_message("%s:%i: Invalid draw arrays command",
+				 data->filename,
+				 data->line_num);
+		return false;
+	}
+
+	command->op = VR_SCRIPT_OP_DRAW_ARRAYS;
+	command->draw_arrays.first_vertex = args[0];
+	command->draw_arrays.vertex_count = args[1];
+	command->draw_arrays.first_instance = 0;
+	command->draw_arrays.instance_count = args[2];
+
+	return true;
+}
+
+static bool
+process_test_line(struct load_state *data)
+{
+	const char *p = data->line;
+
+	while (*p && isspace(*p))
+		p++;
+
+	if (*p == '#' || *p == '\0')
+		return true;
+
+	vr_buffer_set_length(&data->commands,
+			     data->commands.length +
+			     sizeof (struct vr_script_command));
+	struct vr_script_command *command =
+		(struct vr_script_command *)
+		(data->commands.data +
+		 data->commands.length -
+		 sizeof (struct vr_script_command));
+
+	command->line_num = data->line_num;
+
+	if (looking_at(&p, "draw rect ")) {
+		if (!parse_floats(&p, &command->draw_rect.x, 4, NULL) ||
+		    !is_end(p))
+			goto error;
+		command->op = VR_SCRIPT_OP_DRAW_RECT;
+		return true;
+	}
+
+	if (process_probe_command(&p, command))
+		return true;
+
+	if (looking_at(&p, "draw arrays "))
+		return process_draw_arrays_command(data, p, command);
+
+	if (looking_at(&p, "uniform ")) {
+		while (isspace(*p))
+			p++;
+		if (!parse_value_type(&p,
+				      &command->set_push_constant.value.type))
+			goto error;
+		if (!parse_size_t(&p, &command->set_push_constant.offset))
+			goto error;
+		if (!parse_value(&p, &command->set_push_constant.value))
+			goto error;
+		if (!is_end(p))
+			goto error;
+		command->op = VR_SCRIPT_OP_SET_PUSH_CONSTANT;
+		return true;
+	}
+
+	if (looking_at(&p, "clear color ")) {
+		if (!parse_floats(&p, command->clear_color.color, 4, NULL))
+			goto error;
+		if (!is_end(p))
+			goto error;
+		command->op = VR_SCRIPT_OP_CLEAR_COLOR;
+		return true;
+	}
+
+	if (looking_at(&p, "clear")) {
+		if (!is_end(p))
+			goto error;
+		command->op = VR_SCRIPT_OP_CLEAR;
+		return true;
+	}
+
+error:
+	vr_error_message("%s:%i: Invalid test command",
+			 data->filename,
+			 data->line_num);
+	return false;
+}
+
+static bool
+is_stage_section(struct load_state *data,
+		 const char *start,
+		 const char *end)
+{
+	int stage;
+
+	for (stage = 0; stage < VR_SCRIPT_N_STAGES; stage++) {
+		if (is_string(stage_names[stage], start, end)) {
+			data->current_source_type = VR_SCRIPT_SOURCE_TYPE_GLSL;
+			goto found;
+		}
+	}
+
+	if (end - start <= 6 || memcmp(" spirv", end - 6, 6))
+		return false;
+
+	end -= 6;
+
+	for (stage = 0; stage < VR_SCRIPT_N_STAGES; stage++) {
+		if (is_string(stage_names[stage], start, end)) {
+			data->current_source_type = VR_SCRIPT_SOURCE_TYPE_SPIRV;
+			goto found;
+		}
+	}
+
+	return false;
+
+found:
+	data->current_section = SECTION_SHADER;
+	data->current_stage = stage;
+	data->buffer.length = 0;
+	return true;
+}
+
+static bool
+process_section_header(struct load_state *data)
+{
+	if (!end_section(data))
+		return false;
+
+	const char *start = data->line + 1;
+	const char *end = strchr(start, ']');
+	if (end == NULL) {
+		vr_error_message("%s:%i: Missing ']'",
+				 data->filename,
+				 data->line_num);
+		return false;
+	}
+
+	if (is_stage_section(data, start, end)) {
+		const struct vr_script *script = data->script;
+		if (data->current_source_type == VR_SCRIPT_SOURCE_TYPE_SPIRV &&
+		    !vr_list_empty(&script->stages[data->current_stage])) {
+			vr_error_message("%s:%i: SPIR-V source can not be "
+					 "linked with other shaders in the "
+					 "same stage",
+					 data->filename,
+					 data->line_num);
+			return false;
+		}
+
+		return true;
+	}
+
+	if (is_string("vertex shader passthrough", start, end)) {
+		data->current_section = SECTION_NONE;
+		add_shader(data,
+			   VR_SCRIPT_SHADER_STAGE_VERTEX,
+			   VR_SCRIPT_SOURCE_TYPE_GLSL,
+			   (sizeof vertex_shader_passthrough) - 1,
+			   vertex_shader_passthrough);
+		return true;
+	}
+
+	if (is_string("require", start, end)) {
+		data->current_section = SECTION_REQUIRE;
+		return true;
+	}
+
+	if (is_string("test", start, end)) {
+		data->current_section = SECTION_TEST;
+		return true;
+	}
+
+	if (is_string("vertex data", start, end)) {
+		if (data->script->vertex_data) {
+			vr_error_message("%s:%i: Duplicate vertex data section",
+					 data->filename,
+					 data->line_num);
+			return false;
+		}
+		data->current_section = SECTION_VERTEX_DATA;
+		data->buffer.length = 0;
+		return true;
+	}
+
+	vr_error_message("%s:%i: Unknown section “%.*s”",
+			 data->filename,
+			 data->line_num,
+			 (int) (end - start),
+			 start);
+	return false;
+}
+
+static bool
+process_line(struct load_state *data)
+{
+	if (data->line[0] == '[')
+		return process_section_header(data);
+
+	switch (data->current_section) {
+	case SECTION_NONE:
+		return process_none_line(data);
+
+	case SECTION_REQUIRE:
+		return process_require_line(data);
+
+	case SECTION_SHADER:
+	case SECTION_VERTEX_DATA:
+		vr_buffer_append(&data->buffer, data->line, data->nread);
+		return true;
+
+	case SECTION_TEST:
+		return process_test_line(data);
+	}
+
+	return true;
+}
+
+static struct vr_script *
+load_script_from_stream(const char *filename,
+			FILE *f)
+{
+	struct load_state data = {
+		.filename = filename,
+		.line_num = 1,
+		.script = calloc(sizeof (struct vr_script), 1),
+		.line = NULL,
+		.len = 0,
+		.buffer = VR_BUFFER_STATIC_INIT,
+		.commands = VR_BUFFER_STATIC_INIT,
+		.current_stage = -1,
+		.current_section = SECTION_NONE
+	};
+	bool res = true;
+	int stage;
+
+	data.script->filename = strdup(filename);
+	data.script->framebuffer_format =
+		vr_format_lookup_by_vk_format(VK_FORMAT_B8G8R8A8_UNORM);
+	assert(data.script->framebuffer_format != NULL);
+
+	for (stage = 0; stage < VR_SCRIPT_N_STAGES; stage++)
+		vr_list_init(&data.script->stages[stage]);
+
+	do {
+		data.nread = getline(&data.line, &data.len, f);
+		if (data.nread == -1)
+			break;
+
+		res = process_line(&data);
+
+		data.line_num++;
+	} while (res);
+
+	if (res)
+		res = end_section(&data);
+
+	data.script->commands = malloc(data.commands.length);
+	memcpy(data.script->commands, data.commands.data, data.commands.length);
+	data.script->n_commands = (data.commands.length /
+				   sizeof (struct vr_script_command));
+
+	vr_buffer_destroy(&data.commands);
+	vr_buffer_destroy(&data.buffer);
+	free(data.line);
+
+	if (res) {
+		return data.script;
+	} else {
+		vr_script_free(data.script);
+		return NULL;
+	}
+}
+
+struct vr_script *
+vr_script_load(const char *filename)
+{
+	struct vr_script *script;
+	FILE *f = fopen(filename, "r");
+
+	if (f == NULL) {
+		vr_error_message("%s: %s", filename, strerror(errno));
+		return NULL;
+	}
+
+	script = load_script_from_stream(filename, f);
+
+	fclose(f);
+
+	return script;
+}
+
+void
+vr_script_free(struct vr_script *script)
+{
+	int stage;
+	struct vr_script_shader *shader, *tmp;
+
+	for (stage = 0; stage < VR_SCRIPT_N_STAGES; stage++) {
+		vr_list_for_each_safe(shader,
+				      tmp,
+				      &script->stages[stage],
+				      link) {
+			free(shader);
+		}
+	}
+
+	if (script->vertex_data)
+		vr_vbo_free(script->vertex_data);
+
+	free(script->filename);
+
+	free(script->commands);
+
+	free(script);
+}
+
+size_t
+vr_script_type_size(enum vr_script_type type)
+{
+	switch (type) {
+	case VR_SCRIPT_TYPE_INT:
+	case VR_SCRIPT_TYPE_FLOAT:
+		return 4;
+	case VR_SCRIPT_TYPE_DOUBLE:
+		return 8;
+	case VR_SCRIPT_TYPE_VEC2:
+	case VR_SCRIPT_TYPE_IVEC2:
+		return 4 * 2;
+	case VR_SCRIPT_TYPE_VEC3:
+	case VR_SCRIPT_TYPE_IVEC3:
+		return 4 * 3;
+	case VR_SCRIPT_TYPE_VEC4:
+	case VR_SCRIPT_TYPE_IVEC4:
+		return 4 * 4;
+	case VR_SCRIPT_TYPE_DVEC2:
+		return 8 * 2;
+	case VR_SCRIPT_TYPE_DVEC3:
+		return 8 * 3;
+	case VR_SCRIPT_TYPE_DVEC4:
+		return 8 * 4;
+	}
+
+	piglit_fatal("should not be reached");
+}
diff --git a/tests/vulkan/vkrunner/vr-script.h b/tests/vulkan/vkrunner/vr-script.h
new file mode 100644
index 000000000..e21146faf
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-script.h
@@ -0,0 +1,146 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_SCRIPT_H
+#define VR_SCRIPT_H
+
+#include "vr-list.h"
+#include "vr-vk.h"
+#include "vr-vbo.h"
+#include "vr-format.h"
+
+enum vr_script_shader_stage {
+	VR_SCRIPT_SHADER_STAGE_VERTEX,
+	VR_SCRIPT_SHADER_STAGE_TESS_CTRL,
+	VR_SCRIPT_SHADER_STAGE_TESS_EVAL,
+	VR_SCRIPT_SHADER_STAGE_GEOMETRY,
+	VR_SCRIPT_SHADER_STAGE_FRAGMENT,
+	VR_SCRIPT_SHADER_STAGE_COMPUTE
+};
+
+#define VR_SCRIPT_N_STAGES 6
+
+enum vr_script_op {
+	VR_SCRIPT_OP_DRAW_RECT,
+	VR_SCRIPT_OP_DRAW_ARRAYS,
+	VR_SCRIPT_OP_PROBE_RECT,
+	VR_SCRIPT_OP_SET_PUSH_CONSTANT,
+	VR_SCRIPT_OP_CLEAR_COLOR,
+	VR_SCRIPT_OP_CLEAR
+};
+
+enum vr_script_source_type {
+	VR_SCRIPT_SOURCE_TYPE_GLSL,
+	VR_SCRIPT_SOURCE_TYPE_SPIRV
+};
+
+struct vr_script_shader {
+	struct vr_list link;
+	enum vr_script_source_type source_type;
+	size_t length;
+	char source[];
+};
+
+enum vr_script_type {
+	VR_SCRIPT_TYPE_INT,
+	VR_SCRIPT_TYPE_FLOAT,
+	VR_SCRIPT_TYPE_DOUBLE,
+	VR_SCRIPT_TYPE_VEC2,
+	VR_SCRIPT_TYPE_VEC3,
+	VR_SCRIPT_TYPE_VEC4,
+	VR_SCRIPT_TYPE_DVEC2,
+	VR_SCRIPT_TYPE_DVEC3,
+	VR_SCRIPT_TYPE_DVEC4,
+	VR_SCRIPT_TYPE_IVEC2,
+	VR_SCRIPT_TYPE_IVEC3,
+	VR_SCRIPT_TYPE_IVEC4,
+};
+
+struct vr_script_value {
+	enum vr_script_type type;
+	union {
+		int i;
+		float f;
+		double d;
+		float vec[4];
+		double dvec[4];
+		int ivec[4];
+	};
+};
+
+struct vr_script_command {
+	enum vr_script_op op;
+	int line_num;
+
+	union {
+		struct {
+			float x, y, w, h;
+		} draw_rect;
+
+		struct {
+			int n_components;
+			int x, y, w, h;
+			double color[4];
+		} probe_rect;
+
+		struct {
+			size_t offset;
+			struct vr_script_value value;
+		} set_push_constant;
+
+		struct {
+			float color[4];
+		} clear_color;
+
+		struct {
+			VkPrimitiveTopology topology;
+			uint32_t vertex_count;
+			uint32_t instance_count;
+			uint32_t first_vertex;
+			uint32_t first_instance;
+		} draw_arrays;
+	};
+};
+
+struct vr_script {
+	char *filename;
+	struct vr_list stages[VR_SCRIPT_N_STAGES];
+	size_t n_commands;
+	struct vr_script_command *commands;
+	VkPhysicalDeviceFeatures required_features;
+	const struct vr_format *framebuffer_format;
+	struct vr_vbo *vertex_data;
+};
+
+struct vr_script *
+vr_script_load(const char *filename);
+
+void
+vr_script_free(struct vr_script *script);
+
+size_t
+vr_script_type_size(enum vr_script_type type);
+
+#endif /* VR_SCRIPT_H */
diff --git a/tests/vulkan/vkrunner/vr-subprocess.c b/tests/vulkan/vkrunner/vr-subprocess.c
new file mode 100644
index 000000000..1c91ff9e2
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-subprocess.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEM, IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "vr-subprocess.h"
+#include "vr-error-message.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <limits.h>
+
+bool
+vr_subprocess_command(char * const *arguments)
+{
+	pid_t pid;
+
+	pid = fork();
+
+	if (pid < 0) {
+		vr_error_message("fork failed: %s\n", strerror(errno));
+		return false;
+	} else if (pid == 0) {
+		for (int i = 3; i < 256; i++)
+			close(i);
+		execvp(arguments[0], arguments);
+		fprintf(stderr, "%s: %s\n", arguments[0], strerror(errno));
+		exit(EXIT_FAILURE);
+	} else {
+		int status;
+		while (waitpid(pid, &status, 0 /* options */) == -1);
+
+		if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+			return false;
+	}
+
+	return true;
+}
diff --git a/tests/vulkan/vkrunner/vr-subprocess.h b/tests/vulkan/vkrunner/vr-subprocess.h
new file mode 100644
index 000000000..72719e246
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-subprocess.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEM, IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __VR_SUBPROCESS_H__
+#define __VR_SUBPROCESS_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+bool
+vr_subprocess_command(char * const *arguments);
+
+#endif /* __VR_SUBPROCESS_H__ */
diff --git a/tests/vulkan/vkrunner/vr-test.c b/tests/vulkan/vkrunner/vr-test.c
new file mode 100644
index 000000000..d2046a380
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-test.c
@@ -0,0 +1,569 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Neil Roberts
+ * Copyright (C) 2018 Intel Coporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "vr-test.h"
+#include "vr-list.h"
+#include "vr-error-message.h"
+#include "vr-allocate-store.h"
+#include "vr-flush-memory.h"
+
+#include <math.h>
+#include <stdio.h>
+
+struct test_buffer {
+	struct vr_list link;
+	VkBuffer buffer;
+	VkDeviceMemory memory;
+	void *memory_map;
+	int memory_type_index;
+};
+
+struct test_data {
+	struct vr_window *window;
+	struct vr_pipeline *pipeline;
+	struct vr_list buffers;
+	const struct vr_script *script;
+	float clear_color[4];
+	struct test_buffer *vbo_buffer;
+};
+
+static const double
+tolerance[4] = { 0.01, 0.01, 0.01, 0.01 };
+
+static struct test_buffer *
+allocate_test_buffer(struct test_data *data,
+		     size_t size,
+		     VkBufferUsageFlagBits usage)
+{
+	struct test_buffer *buffer = calloc(sizeof *buffer, 1);
+	VkResult res;
+
+	vr_list_insert(data->buffers.prev, &buffer->link);
+
+	VkBufferCreateInfo buffer_create_info = {
+		.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+		.size = size,
+		.usage = usage,
+		.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+	};
+	res = vr_vk.vkCreateBuffer(data->window->device,
+				   &buffer_create_info,
+				   NULL, /* allocator */
+				   &buffer->buffer);
+	if (res != VK_SUCCESS) {
+		buffer->buffer = NULL;
+		vr_error_message("Error creating buffer");
+		return NULL;
+	}
+
+	res = vr_allocate_store_buffer(data->window,
+				       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+				       1, /* n_buffers */
+				       &buffer->buffer,
+				       &buffer->memory,
+				       &buffer->memory_type_index,
+				       NULL /* offsets */);
+	if (res != VK_SUCCESS) {
+		buffer->memory = NULL;
+		vr_error_message("Error allocating memory");
+		return NULL;
+	}
+
+	res = vr_vk.vkMapMemory(data->window->device,
+				buffer->memory,
+				0, /* offset */
+				VK_WHOLE_SIZE,
+				0, /* flags */
+				&buffer->memory_map);
+	if (res != VK_SUCCESS) {
+		buffer->memory_map = NULL;
+		vr_error_message("Error mapping memory");
+		return NULL;
+	}
+
+	return buffer;
+}
+
+static void
+free_test_buffer(struct test_data *data,
+		 struct test_buffer *buffer)
+{
+	struct vr_window *window = data->window;
+
+	if (buffer->memory_map) {
+		vr_vk.vkUnmapMemory(window->device,
+				    buffer->memory);
+	}
+	if (buffer->memory) {
+		vr_vk.vkFreeMemory(window->device,
+				   buffer->memory,
+				   NULL /* allocator */);
+	}
+	if (buffer->buffer) {
+		vr_vk.vkDestroyBuffer(window->device,
+				      buffer->buffer,
+				      NULL /* allocator */);
+	}
+
+	vr_list_remove(&buffer->link);
+	free(buffer);
+}
+
+static bool
+begin_paint(struct test_data *data)
+{
+	VkResult res;
+
+	VkCommandBufferBeginInfo begin_command_buffer_info = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+	};
+	res = vr_vk.vkBeginCommandBuffer(data->window->command_buffer,
+					 &begin_command_buffer_info);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkBeginCommandBuffer failed");
+		return false;
+	}
+
+	VkRenderPassBeginInfo render_pass_begin_info = {
+		.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+		.renderPass = data->window->render_pass,
+		.framebuffer = data->window->framebuffer,
+		.renderArea = {
+			.offset = { 0, 0 },
+			.extent = {
+				VR_WINDOW_WIDTH, VR_WINDOW_HEIGHT
+			}
+		},
+	};
+	vr_vk.vkCmdBeginRenderPass(data->window->command_buffer,
+				   &render_pass_begin_info,
+				   VK_SUBPASS_CONTENTS_INLINE);
+
+	vr_vk.vkCmdBindPipeline(data->window->command_buffer,
+				VK_PIPELINE_BIND_POINT_GRAPHICS,
+				data->pipeline->pipeline);
+
+	return true;
+}
+
+static bool
+end_paint(struct test_data *data)
+{
+	struct vr_window *window = data->window;
+	VkResult res;
+
+	vr_vk.vkCmdEndRenderPass(window->command_buffer);
+
+	VkImageCopy copy_region = {
+		.srcSubresource = {
+			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+			.mipLevel = 0,
+			.baseArrayLayer = 0,
+			.layerCount = 1
+		},
+		.srcOffset = { 0, 0, 0 },
+		.dstSubresource = {
+			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+			.mipLevel = 0,
+			.baseArrayLayer = 0,
+			.layerCount = 1
+		},
+		.dstOffset = { 0, 0, 0 },
+		.extent = { VR_WINDOW_WIDTH, VR_WINDOW_HEIGHT, 1 }
+	};
+	vr_vk.vkCmdCopyImage(window->command_buffer,
+			     window->color_image,
+			     VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			     window->linear_image,
+			     VK_IMAGE_LAYOUT_GENERAL,
+			     1, /* regionCount */
+			     &copy_region);
+
+	res = vr_vk.vkEndCommandBuffer(window->command_buffer);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkEndCommandBuffer failed");
+		return false;
+	}
+
+	vr_vk.vkResetFences(window->device,
+			    1, /* fenceCount */
+			    &window->vk_fence);
+
+	VkSubmitInfo submit_info = {
+		.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+		.commandBufferCount = 1,
+		.pCommandBuffers = &window->command_buffer,
+		.pWaitDstStageMask =
+		(VkPipelineStageFlagBits[])
+		{ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT }
+	};
+	res = vr_vk.vkQueueSubmit(window->queue,
+				  1, /* submitCount */
+				  &submit_info,
+				  window->vk_fence);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkQueueSubmit failed");
+		return false;
+	}
+
+	res = vr_vk.vkWaitForFences(window->device,
+				    1, /* fenceCount */
+				    &window->vk_fence,
+				    VK_TRUE, /* waitAll */
+				    UINT64_MAX);
+	if (res != VK_SUCCESS) {
+		vr_error_message("vkWaitForFences failed");
+		return false;
+	}
+
+	if (window->need_linear_memory_invalidate) {
+		VkMappedMemoryRange memory_range = {
+			.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+			.memory = window->linear_memory,
+			.offset = 0,
+			.size = VK_WHOLE_SIZE
+		};
+		vr_vk.vkInvalidateMappedMemoryRanges(window->device,
+						     1, /* memoryRangeCount */
+						     &memory_range);
+	}
+
+	return true;
+}
+
+static void
+print_command_fail(const struct vr_script_command *command)
+{
+	printf("Command failed at line %i\n",
+	       command->line_num);
+}
+
+static bool
+draw_rect(struct test_data *data,
+	  const struct vr_script_command *command)
+{
+	struct test_buffer *buffer;
+
+	buffer = allocate_test_buffer(data,
+				      sizeof (struct vr_pipeline_vertex) * 6,
+				      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+	if (buffer == NULL)
+		return false;
+
+	struct vr_pipeline_vertex *v = buffer->memory_map;
+
+	v->x = command->draw_rect.x;
+	v->y = command->draw_rect.y;
+	v->z = 0.0f;
+	v++;
+
+	v->x = command->draw_rect.x + command->draw_rect.w;
+	v->y = command->draw_rect.y;
+	v->z = 0.0f;
+	v++;
+
+	v->x = command->draw_rect.x;
+	v->y = command->draw_rect.y + command->draw_rect.h;
+	v->z = 0.0f;
+	v++;
+
+	v->x = command->draw_rect.x;
+	v->y = command->draw_rect.y + command->draw_rect.h;
+	v->z = 0.0f;
+	v++;
+
+	v->x = command->draw_rect.x + command->draw_rect.w;
+	v->y = command->draw_rect.y;
+	v->z = 0.0f;
+	v++;
+
+	v->x = command->draw_rect.x + command->draw_rect.w;
+	v->y = command->draw_rect.y + command->draw_rect.h;
+	v->z = 0.0f;
+	v++;
+
+	vr_flush_memory(data->window,
+			buffer->memory_type_index,
+			buffer->memory,
+			VK_WHOLE_SIZE);
+
+	vr_vk.vkCmdBindVertexBuffers(data->window->command_buffer,
+				     0, /* firstBinding */
+				     1, /* bindingCount */
+				     &buffer->buffer,
+				     (VkDeviceSize[]) { 0 });
+	vr_vk.vkCmdDraw(data->window->command_buffer,
+			6, /* vertexCount */
+			1, /* instanceCount */
+			0, /* firstVertex */
+			0 /* firstInstance */);
+
+	return true;
+}
+
+static bool
+draw_arrays(struct test_data *data,
+	    const struct vr_script_command *command)
+{
+	struct vr_vbo *vbo = data->script->vertex_data;
+
+	if (vbo == NULL) {
+		print_command_fail(command);
+		vr_error_message("draw arrays command used with no vertex "
+				 "data section");
+		return false;
+	}
+
+	if (data->vbo_buffer == NULL) {
+		data->vbo_buffer =
+			allocate_test_buffer(data,
+					     vbo->stride *
+					     vbo->num_rows,
+					     VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+		if (data->vbo_buffer == NULL)
+			return false;
+
+		memcpy(data->vbo_buffer->memory_map,
+		       vbo->raw_data,
+		       vbo->stride * vbo->num_rows);
+
+		vr_flush_memory(data->window,
+				data->vbo_buffer->memory_type_index,
+				data->vbo_buffer->memory,
+				VK_WHOLE_SIZE);
+	}
+
+	vr_vk.vkCmdBindVertexBuffers(data->window->command_buffer,
+				     0, /* firstBinding */
+				     1, /* bindingCount */
+				     &data->vbo_buffer->buffer,
+				     (VkDeviceSize[]) { 0 });
+	vr_vk.vkCmdDraw(data->window->command_buffer,
+			command->draw_arrays.vertex_count,
+			command->draw_arrays.instance_count,
+			command->draw_arrays.first_vertex,
+			command->draw_arrays.first_instance);
+
+	return true;
+}
+
+static bool
+compare_pixels(const double *color1,
+	       const double *color2,
+	       const double *tolerance,
+	       int n_components)
+{
+	for (int p = 0; p < n_components; ++p)
+		if (fabsf(color1[p] - color2[p]) > tolerance[p])
+			return false;
+	return true;
+}
+
+static void
+print_components_double(const double *pixel,
+			int n_components)
+{
+	int p;
+	for (p = 0; p < n_components; ++p)
+		printf(" %f", pixel[p]);
+}
+
+static void
+print_bad_pixel(int x, int y,
+		int n_components,
+		const double *expected,
+		const double *observed)
+{
+	printf("Probe color at (%i,%i)\n"
+	       "  Expected:",
+	       x, y);
+	print_components_double(expected, n_components);
+	printf("\n"
+	       "  Observed:");
+	print_components_double(observed, n_components);
+	printf("\n");
+}
+
+static bool
+probe_rect(struct test_data *data,
+	   const struct vr_script_command *command)
+{
+	int n_components = command->probe_rect.n_components;
+	const struct vr_format *format = data->window->framebuffer_format;
+	int format_size = vr_format_get_size(format);
+	bool ret = true;
+
+	/* End the paint to copy the framebuffer into the linear buffer */
+	if (!end_paint(data))
+		ret = false;
+
+	for (int y = 0; y < command->probe_rect.h; y++) {
+		const uint8_t *p =
+			((y + command->probe_rect.y) *
+			 data->window->linear_memory_stride +
+			 command->probe_rect.x * format_size +
+			 (uint8_t *) data->window->linear_memory_map);
+		for (int x = 0; x < command->probe_rect.w; x++) {
+			double pixel[4];
+			vr_format_load_pixel(format, p, pixel);
+			p += format_size;
+
+			if (!compare_pixels(pixel,
+					    command->probe_rect.color,
+					    tolerance,
+					    n_components)) {
+				ret = false;
+				print_command_fail(command);
+				print_bad_pixel(x + command->probe_rect.x,
+						y + command->probe_rect.y,
+						n_components,
+						command->probe_rect.color,
+						pixel);
+				goto done;
+			}
+		}
+	}
+done:
+
+	if (!begin_paint(data))
+		ret = false;
+
+	return ret;
+}
+
+static bool
+set_push_constant(struct test_data *data,
+		  const struct vr_script_command *command)
+{
+	const struct vr_script_value *value =
+		&command->set_push_constant.value;
+
+	vr_vk.vkCmdPushConstants(data->window->command_buffer,
+				 data->pipeline->layout,
+				 data->pipeline->stages,
+				 command->set_push_constant.offset,
+				 vr_script_type_size(value->type),
+				 &value->i);
+
+	return true;
+}
+
+static bool
+clear_color(struct test_data *data,
+	    const struct vr_script_command *command)
+{
+	memcpy(data->clear_color,
+	       command->clear_color.color,
+	       sizeof data->clear_color);
+	return true;
+}
+
+static bool
+clear(struct test_data *data,
+      const struct vr_script_command *command)
+{
+	VkClearAttachment color_clear_attachment = {
+		.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+		.colorAttachment = 0,
+	};
+	VkClearRect color_clear_rect = {
+		.rect = {
+			.offset = { 0, 0 },
+			.extent = { VR_WINDOW_WIDTH, VR_WINDOW_HEIGHT }
+		},
+		.baseArrayLayer = 0,
+		.layerCount = 1
+	};
+	memcpy(color_clear_attachment.clearValue.color.float32,
+	       data->clear_color,
+	       sizeof data->clear_color);
+	vr_vk.vkCmdClearAttachments(data->window->command_buffer,
+				    1, /* attachmentCount */
+				    &color_clear_attachment,
+				    1,
+				    &color_clear_rect);
+	return true;
+}
+
+bool
+vr_test_run(struct vr_window *window,
+	    struct vr_pipeline *pipeline,
+	    const struct vr_script *script)
+{
+	struct test_data data = {
+		.window = window,
+		.pipeline = pipeline,
+		.script = script
+	};
+	bool ret = true;
+
+	vr_list_init(&data.buffers);
+
+	if (!begin_paint(&data))
+		ret = false;
+
+	for (int i = 0; i < script->n_commands; i++) {
+		const struct vr_script_command *command = script->commands + i;
+
+		switch (command->op) {
+		case VR_SCRIPT_OP_DRAW_RECT:
+			if (!draw_rect(&data, command))
+				ret = false;
+			break;
+		case VR_SCRIPT_OP_DRAW_ARRAYS:
+			if (!draw_arrays(&data, command))
+				ret = false;
+			break;
+		case VR_SCRIPT_OP_PROBE_RECT:
+			if (!probe_rect(&data, command))
+				ret = false;
+			break;
+		case VR_SCRIPT_OP_SET_PUSH_CONSTANT:
+			if (!set_push_constant(&data, command))
+				ret = false;
+			break;
+		case VR_SCRIPT_OP_CLEAR_COLOR:
+			if (!clear_color(&data, command))
+				ret = false;
+			break;
+		case VR_SCRIPT_OP_CLEAR:
+			if (!clear(&data, command))
+				ret = false;
+			break;
+		}
+	}
+
+	if (!end_paint(&data))
+		ret = false;
+
+	struct test_buffer *buffer, *tmp;
+	vr_list_for_each_safe(buffer, tmp, &data.buffers, link) {
+		free_test_buffer(&data, buffer);
+	}
+
+	return ret;
+}
diff --git a/tests/vulkan/vkrunner/vr-test.h b/tests/vulkan/vkrunner/vr-test.h
new file mode 100644
index 000000000..7b2f5aad0
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-test.h
@@ -0,0 +1,40 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_TEST_H
+#define VR_TEST_H
+
+#include <stdbool.h>
+
+#include "vr-pipeline.h"
+#include "vr-script.h"
+#include "vr-window.h"
+
+bool
+vr_test_run(struct vr_window *window,
+	    struct vr_pipeline *pipeline,
+	    const struct vr_script *script);
+
+#endif /* VR_TEST_H */
diff --git a/tests/vulkan/vkrunner/vr-vbo.c b/tests/vulkan/vkrunner/vr-vbo.c
new file mode 100644
index 000000000..7274b63b6
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vbo.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright © 2011, 2016, 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/* Based on piglit-vbo.cpp */
+
+/**
+ * This file adds the facility for specifying vertex data to piglit
+ * tests using a columnar text format, for example:
+ *
+ *   \verbatim
+ *   0/r32g32b32_sfloat 1/r32_uint	3/int/int	4/int/int
+ *   0.0 0.0 0.0	10		0		0	# comment
+ *   0.0 1.0 0.0	 5		1		1
+ *   1.0 1.0 0.0	 0		0		1
+ *   \endverbatim
+ *
+ * The format consists of a row of column headers followed by any
+ * number of rows of data. Each column header has the form
+ * ATTRLOC/FORMAT where ATTRLOC is the location of the vertex
+ * attribute to be bound to this column and FORMAT is the name of a
+ * VkFormat minus the VK_FORMAT prefix.
+ *
+ * Alternatively the column header can use something closer the Piglit
+ * format like ATTRLOC/GL_TYPE/GLSL_TYPE. GL_TYPE is the GL type of
+ * data that follows (“half”, “float”, “double”, “byte”, “ubyte”,
+ * “short”, “ushort”, “int” or “uint”), GLSL_TYPE is the GLSL type of
+ * the data (“int”, “uint”, “float”, “double”, “ivec”\*, “uvec”\*,
+ * “vec”\*, “dvec”\*).
+ *
+ * The data follows the column headers in space-separated form. “#”
+ * can be used for comments, as in shell scripts.
+ */
+
+
+#include "vr-vbo.h"
+#include "vr-error-message.h"
+#include "vr-buffer.h"
+#include "piglit-util.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Convert from Piglit style formats to a VkFormat
+ */
+static const struct vr_format *
+decode_type(const char *gl_type,
+	    const char *glsl_type)
+{
+	static const struct {
+		const char *name;
+		enum vr_format_mode mode;
+		int bit_size;
+	} gl_types[] = {
+		{ "byte", VR_FORMAT_MODE_SINT, 8 },
+		{ "ubyte", VR_FORMAT_MODE_UINT, 8 },
+		{ "short", VR_FORMAT_MODE_SINT, 16 },
+		{ "ushort", VR_FORMAT_MODE_UINT, 16 },
+		{ "int", VR_FORMAT_MODE_SINT, 32 },
+		{ "uint", VR_FORMAT_MODE_UINT, 32 },
+		{ "half", VR_FORMAT_MODE_SFLOAT, 16 },
+		{ "float", VR_FORMAT_MODE_SFLOAT, 32 },
+		{ "double", VR_FORMAT_MODE_SFLOAT, 64 },
+	};
+
+	enum vr_format_mode mode;
+	int bit_size;
+	int n_components;
+
+	for (int i = 0; i < ARRAY_SIZE(gl_types); i++) {
+		if (!strcmp(gl_types[i].name, gl_type)) {
+			mode = gl_types[i].mode;
+			bit_size = gl_types[i].bit_size;
+			goto found_gl_type;
+		}
+	}
+
+	vr_error_message("Unknown gl_type: %s", gl_type);
+	return NULL;
+
+found_gl_type:
+	if (!strcmp(glsl_type, "int") ||
+	    !strcmp(glsl_type, "uint") ||
+	    !strcmp(glsl_type, "float") ||
+	    !strcmp(glsl_type, "double")) {
+		n_components = 1;
+	} else {
+		if (!strncmp(glsl_type, "vec", 3)) {
+			n_components = glsl_type[3] - '0';
+		} else if (strchr("iud", glsl_type[0]) &&
+			   !strncmp(glsl_type + 1, "vec", 3)) {
+			n_components = glsl_type[4] - '0';
+		} else {
+			vr_error_message("Unknown glsl_type: %s",
+					 glsl_type);
+			return NULL;
+		}
+
+		if (n_components < 2 || n_components > 4) {
+			vr_error_message("Invalid components: %s",
+					 glsl_type);
+			return NULL;
+		}
+	}
+
+	const struct vr_format *format =
+		vr_format_lookup_by_details(bit_size,
+					    mode,
+					    n_components);
+
+	if (format == NULL) {
+		vr_error_message("Invalid type combo: %s/%s",
+				 gl_type,
+				 glsl_type);
+		return NULL;
+	}
+
+	return format;
+}
+
+static bool
+get_attrib_location(const char *name,
+		    unsigned *index)
+{
+	errno = 0;
+
+	char *end;
+	unsigned long ul = strtoul(name, &end, 10);
+
+	if (errno == 0 && end > name && ul <= UINT_MAX) {
+		*index = ul;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+/**
+ * Build a vertex_attrib_description from a column header, by
+ * interpreting the location, type, dimensions and mattrix_column
+ * parts of the header.
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return false.
+ */
+static bool
+parse_vertex_attrib(struct vr_vbo_attrib *attrib,
+		    const char *text)
+{
+	char *name = NULL;
+	bool ret = true;
+
+	/* Split the column header into location/type/dimensions
+	 * fields.
+	 */
+	const char *first_slash = strchr(text, '/');
+	if (first_slash == NULL) {
+		vr_error_message("Column headers must be in the form"
+				 " location/format.\n"
+				 "Got: %s",
+				 text);
+		ret = false;
+		goto out;
+	}
+
+	size_t name_size = first_slash - text;
+	name = strndup(text, name_size);
+
+	const struct vr_format *format;
+
+	const char *second_slash = strchr(first_slash + 1, '/');
+	if (second_slash == NULL) {
+		format = vr_format_lookup_by_name(first_slash + 1);
+		if (format == NULL) {
+			vr_error_message("Unknown format: %s", first_slash + 1);
+			ret = false;
+			goto out;
+		}
+	} else {
+		char *gl_type = strndup(first_slash + 1,
+					   second_slash - first_slash - 1);
+		format = decode_type(gl_type, second_slash + 1);
+		free(gl_type);
+
+		if (format == NULL) {
+			ret = false;
+			goto out;
+		}
+	}
+
+	if (!get_attrib_location(name, &attrib->location)) {
+		vr_error_message("Unexpected vbo column name.  Got: %s",
+				 name);
+		ret = false;
+		goto out;
+	}
+
+	attrib->format = format;
+
+out:
+	free(name);
+
+	return ret;
+}
+
+/**
+ * Parse a single number (floating point or integral) from one of the
+ * data rows, and store it in the location pointed to by \c data.
+ * Update \c text to point to the next character of input.
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return false.  Otherwise return true.
+ */
+static bool
+parse_datum(enum vr_format_mode mode,
+	    int bit_size,
+	    const char **text,
+	    void *data)
+{
+	char *endptr;
+	errno = 0;
+	switch (mode) {
+	case VR_FORMAT_MODE_SFLOAT:
+		switch (bit_size) {
+		case 16: {
+			unsigned short value = strtohf_hex(*text, &endptr);
+			if (errno == ERANGE) {
+				vr_error_message("Could not parse as "
+						 "half float");
+				return false;
+			}
+			*((uint16_t *) data) = value;
+			goto handled;
+		}
+		case 32: {
+			float value = strtof_hex(*text, &endptr);
+			if (errno == ERANGE) {
+				vr_error_message("Could not parse as float");
+				return false;
+			}
+			*((float *) data) = value;
+			goto handled;
+		}
+		case 64: {
+			double value = strtod_hex(*text, &endptr);
+			if (errno == ERANGE) {
+				vr_error_message("Could not parse as double");
+				return false;
+			}
+			*((double *) data) = value;
+			goto handled;
+		}
+		}
+	case VR_FORMAT_MODE_UNORM:
+	case VR_FORMAT_MODE_USCALED:
+	case VR_FORMAT_MODE_UINT:
+	case VR_FORMAT_MODE_SRGB:
+		switch (bit_size) {
+		case 8: {
+			unsigned long value = strtoul(*text, &endptr, 0);
+			if (errno == ERANGE || value > UINT8_MAX) {
+				vr_error_message("Could not parse as unsigned "
+						 "byte");
+				return false;
+			}
+			*((uint8_t *) data) = (uint8_t) value;
+			goto handled;
+		}
+		case 16: {
+			unsigned long value = strtoul(*text, &endptr, 0);
+			if (errno == ERANGE || value > UINT16_MAX) {
+				vr_error_message("Could not parse as unsigned "
+						 "short");
+				return false;
+			}
+			*((uint16_t *) data) = (uint16_t) value;
+			goto handled;
+		}
+		case 32: {
+			unsigned long value = strtoul(*text, &endptr, 0);
+			if (errno == ERANGE || value > UINT32_MAX) {
+				vr_error_message("Could not parse as "
+						 "unsigned integer");
+				return false;
+			}
+			*((uint32_t *) data) = (uint32_t) value;
+			goto handled;
+		}
+		case 64: {
+			unsigned long value = strtoul(*text, &endptr, 0);
+			if (errno == ERANGE || value > UINT64_MAX) {
+				vr_error_message("Could not parse as "
+						 "unsigned long");
+				return false;
+			}
+			*((uint64_t *) data) = (uint64_t) value;
+			goto handled;
+		}
+		}
+	case VR_FORMAT_MODE_SNORM:
+	case VR_FORMAT_MODE_SSCALED:
+	case VR_FORMAT_MODE_SINT:
+		switch (bit_size) {
+		case 8: {
+			long value = strtol(*text, &endptr, 0);
+			if (errno == ERANGE ||
+			    value > INT8_MAX || value < INT8_MIN) {
+				vr_error_message("Could not parse as signed "
+						 "byte");
+				return false;
+			}
+			*((int8_t *) data) = (int8_t) value;
+			goto handled;
+		}
+		case 16: {
+			long value = strtol(*text, &endptr, 0);
+			if (errno == ERANGE ||
+			    value > INT16_MAX || value < INT16_MIN) {
+				vr_error_message("Could not parse as signed "
+						 "short");
+				return false;
+			}
+			*((int16_t *) data) = (int16_t) value;
+			goto handled;
+		}
+		case 32: {
+			long value = strtol(*text, &endptr, 0);
+			if (errno == ERANGE ||
+			    value > INT32_MAX || value < INT32_MIN) {
+				vr_error_message("Could not parse as "
+						 "signed integer");
+				return false;
+			}
+			*((int32_t *) data) = (int32_t) value;
+			goto handled;
+		}
+		case 64: {
+			long value = strtol(*text, &endptr, 0);
+			if (errno == ERANGE ||
+			    value > INT64_MAX || value < INT64_MIN) {
+				vr_error_message("Could not parse as "
+						 "signed long");
+				return false;
+			}
+			*((int64_t *) data) = (int64_t) value;
+			goto handled;
+		}
+		}
+	case VR_FORMAT_MODE_UFLOAT:
+		break;
+	}
+
+	piglit_fatal("Unexpected format");
+
+handled:
+	*text = endptr;
+
+	return true;
+}
+
+struct vbo_data {
+	/**
+	 * True if the header line has already been parsed.
+	 */
+	bool header_seen;
+
+	struct vr_buffer raw_data;
+
+	struct vr_vbo *vbo;
+
+	unsigned line_num;
+};
+
+static int
+get_alignment(const struct vr_format *format)
+{
+	if (format->packed_size)
+		return format->packed_size / 8;
+
+	int max_size = 8;
+
+	for (int i = 0; i < format->n_components; i++) {
+		if (format->components[i].bits > max_size)
+			max_size = format->components[i].bits;
+	}
+
+	return max_size / 8;
+}
+
+/**
+ * Populate this->attribs and compute this->stride based on column
+ * headers.
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return false
+ */
+static bool
+parse_header_line(struct vbo_data *data,
+		  const char *line)
+{
+	struct vr_vbo *vbo = data->vbo;
+	size_t pos = 0;
+	size_t line_size;
+	const char *newline = strchr(line, '\n');
+
+	if (newline)
+		line_size = newline - line;
+	else
+		line_size = strlen(line);
+
+	vbo->stride = 0;
+
+	int max_alignment = 1;
+
+	while (pos < line_size) {
+		if (isspace(line[pos])) {
+			++pos;
+			continue;
+		}
+
+		size_t column_header_end = pos;
+		while (column_header_end < line_size &&
+		       !isspace(line[column_header_end]))
+			++column_header_end;
+
+		char *column_header =
+			strndup(line + pos, column_header_end - pos);
+
+		struct vr_vbo_attrib *attrib =
+			calloc(sizeof *attrib, 1);
+		vr_list_insert(vbo->attribs.prev, &attrib->link);
+
+		bool res = parse_vertex_attrib(attrib, column_header);
+
+		free(column_header);
+
+		if (!res)
+			return false;
+
+		int alignment = get_alignment(attrib->format);
+
+		vbo->stride = ALIGN(vbo->stride, alignment);
+		attrib->offset = vbo->stride;
+		vbo->stride += vr_format_get_size(attrib->format);
+		pos = column_header_end + 1;
+
+		if (alignment > max_alignment)
+			max_alignment = alignment;
+	}
+
+	vbo->stride = ALIGN(vbo->stride, max_alignment);
+
+	return true;
+}
+
+
+/**
+ * Convert a data row into binary form and append it to data->raw_data.
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return false.
+ */
+static bool
+parse_data_line(struct vbo_data *data,
+		const char *line)
+{
+	struct vr_vbo *vbo = data->vbo;
+
+	/* Allocate space in raw_data for this line */
+	size_t old_length = data->raw_data.length;
+	vr_buffer_set_length(&data->raw_data, old_length + vbo->stride);
+
+	const char *line_ptr = line;
+	const struct vr_vbo_attrib *attrib;
+
+	vr_list_for_each(attrib, &vbo->attribs, link) {
+		uint8_t *data_ptr = (data->raw_data.data +
+				     old_length +
+				     attrib->offset);
+
+		if (attrib->format->packed_size) {
+			if (!parse_datum(VR_FORMAT_MODE_UINT,
+					 attrib->format->packed_size,
+					 &line_ptr,
+					 data_ptr))
+				goto error;
+			continue;
+		}
+
+		for (size_t j = 0; j < attrib->format->n_components; ++j) {
+			if (!parse_datum(attrib->format->mode,
+					 attrib->format->components[j].bits,
+					 &line_ptr,
+					 data_ptr))
+				goto error;
+
+			data_ptr += attrib->format->components[j].bits / 8;
+		}
+	}
+
+	++vbo->num_rows;
+
+	return true;
+
+error:
+	vr_error_message("At line %u of [vertex data] section. "
+			 "Offending text: %s",
+			 data->line_num,
+			 line_ptr);
+	return false;
+}
+
+
+/**
+ * Parse a line of input text.
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return false
+ */
+static bool
+parse_line(struct vbo_data *data,
+	   const char *line,
+	   const char *text_end)
+{
+	while (line < text_end && *line != '\n' && isspace(*line))
+		line++;
+
+	const char *line_end;
+
+	for (line_end = line;
+	     /* Ignore end-of-line comments */
+	     (line_end < text_end &&
+	      *line_end != '#' &&
+	      *line_end != '\n' &&
+	      *line_end);
+	     line_end++);
+
+	/* Ignore blank or comment-only lines */
+	if (line_end == line)
+		return true;
+
+	char *line_copy = strndup(line, line_end - line);
+	bool ret;
+
+	if (data->header_seen) {
+		ret = parse_data_line(data, line_copy);
+	} else {
+		data->header_seen = true;
+		ret = parse_header_line(data, line_copy);
+	}
+
+	free(line_copy);
+
+	return ret;
+}
+
+
+/**
+ * Parse the input
+ *
+ * If there is a parse failure, print a description of the problem and
+ * then return NULL
+ */
+struct vr_vbo *
+vr_vbo_parse(const char *text, size_t text_length)
+{
+	const char *text_end = text + text_length;
+	struct vbo_data data = {
+		.line_num = 1,
+		.vbo = calloc(sizeof (struct vr_vbo), 1),
+		.raw_data = VR_BUFFER_STATIC_INIT
+	};
+
+	vr_list_init(&data.vbo->attribs);
+
+	const char *line = text;
+	while (line < text_end) {
+		if (!parse_line(&data, line, text_end)) {
+			vr_vbo_free(data.vbo);
+			data.vbo = NULL;
+			break;
+		}
+
+		data.line_num++;
+
+		const char *line_end =
+			memchr(line, '\n', text_end - line);
+		if (line_end)
+			line = line_end + 1;
+		else
+			break;
+	}
+
+	if (data.vbo)
+		data.vbo->raw_data = data.raw_data.data;
+	else
+		vr_buffer_destroy(&data.raw_data);
+
+	return data.vbo;
+}
+
+void
+vr_vbo_free(struct vr_vbo *vbo)
+{
+	struct vr_vbo_attrib *attrib, *tmp;
+
+	vr_list_for_each_safe(attrib, tmp, &vbo->attribs, link)
+		free(attrib);
+
+	free(vbo->raw_data);
+	free(vbo);
+}
diff --git a/tests/vulkan/vkrunner/vr-vbo.h b/tests/vulkan/vkrunner/vr-vbo.h
new file mode 100644
index 000000000..6d7a90e6e
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vbo.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright © 2011, 2016, 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_VBO_H
+#define VR_VBO_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "vr-list.h"
+#include "vr-format.h"
+
+struct vr_vbo_attrib {
+	struct vr_list link;
+
+	const struct vr_format *format;
+
+	/**
+	 * Vertex location
+	 */
+	unsigned location;
+
+	/**
+	 * Byte offset into the vertex data of this attribute.
+	 */
+	size_t offset;
+};
+
+struct vr_vbo {
+	/**
+	 * Description of each attribute.
+	 */
+	struct vr_list attribs;
+
+	/**
+	 * Raw data buffer containing parsed numbers.
+	 */
+	uint8_t *raw_data;
+
+	/**
+	 * Number of bytes in each row of raw_data.
+	 */
+	size_t stride;
+
+	/**
+	 * Number of rows in raw_data.
+	 */
+	size_t num_rows;
+};
+
+struct vr_vbo *
+vr_vbo_parse(const char *text, size_t text_length);
+
+void
+vr_vbo_free(struct vr_vbo *vbo);
+
+#endif /* VR_VBO_H */
diff --git a/tests/vulkan/vkrunner/vr-vk-core-funcs.h b/tests/vulkan/vkrunner/vr-vk-core-funcs.h
new file mode 100644
index 000000000..ad6e09d67
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vk-core-funcs.h
@@ -0,0 +1 @@
+VR_VK_FUNC(vkCreateInstance)
diff --git a/tests/vulkan/vkrunner/vr-vk-device-funcs.h b/tests/vulkan/vkrunner/vr-vk-device-funcs.h
new file mode 100644
index 000000000..20b054bdc
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vk-device-funcs.h
@@ -0,0 +1,56 @@
+VR_VK_FUNC(vkAllocateCommandBuffers)
+VR_VK_FUNC(vkAllocateMemory)
+VR_VK_FUNC(vkBeginCommandBuffer)
+VR_VK_FUNC(vkBindBufferMemory)
+VR_VK_FUNC(vkBindImageMemory)
+VR_VK_FUNC(vkCmdBeginRenderPass)
+VR_VK_FUNC(vkCmdBindPipeline)
+VR_VK_FUNC(vkCmdBindVertexBuffers)
+VR_VK_FUNC(vkCmdClearAttachments)
+VR_VK_FUNC(vkCmdCopyImage)
+VR_VK_FUNC(vkCmdDraw)
+VR_VK_FUNC(vkCmdEndRenderPass)
+VR_VK_FUNC(vkCmdPipelineBarrier)
+VR_VK_FUNC(vkCmdPushConstants)
+VR_VK_FUNC(vkCreateBuffer)
+VR_VK_FUNC(vkCreateCommandPool)
+VR_VK_FUNC(vkCreateDescriptorPool)
+VR_VK_FUNC(vkCreateFence)
+VR_VK_FUNC(vkCreateFramebuffer)
+VR_VK_FUNC(vkCreateGraphicsPipelines)
+VR_VK_FUNC(vkCreateImage)
+VR_VK_FUNC(vkCreateImageView)
+VR_VK_FUNC(vkCreatePipelineCache)
+VR_VK_FUNC(vkCreatePipelineLayout)
+VR_VK_FUNC(vkCreateRenderPass)
+VR_VK_FUNC(vkCreateShaderModule)
+VR_VK_FUNC(vkDestroyBuffer)
+VR_VK_FUNC(vkDestroyCommandPool)
+VR_VK_FUNC(vkDestroyDescriptorPool)
+VR_VK_FUNC(vkDestroyDevice)
+VR_VK_FUNC(vkDestroyFence)
+VR_VK_FUNC(vkDestroyFramebuffer)
+VR_VK_FUNC(vkDestroyImage)
+VR_VK_FUNC(vkDestroyImageView)
+VR_VK_FUNC(vkDestroyPipeline)
+VR_VK_FUNC(vkDestroyPipelineCache)
+VR_VK_FUNC(vkDestroyPipelineLayout)
+VR_VK_FUNC(vkDestroyRenderPass)
+VR_VK_FUNC(vkDestroySampler)
+VR_VK_FUNC(vkDestroyShaderModule)
+VR_VK_FUNC(vkEndCommandBuffer)
+VR_VK_FUNC(vkFlushMappedMemoryRanges)
+VR_VK_FUNC(vkFreeCommandBuffers)
+VR_VK_FUNC(vkFreeDescriptorSets)
+VR_VK_FUNC(vkFreeMemory)
+VR_VK_FUNC(vkGetBufferMemoryRequirements)
+VR_VK_FUNC(vkGetDeviceQueue)
+VR_VK_FUNC(vkGetImageMemoryRequirements)
+VR_VK_FUNC(vkGetImageSubresourceLayout)
+VR_VK_FUNC(vkInvalidateMappedMemoryRanges)
+VR_VK_FUNC(vkMapMemory)
+VR_VK_FUNC(vkQueueSubmit)
+VR_VK_FUNC(vkQueueWaitIdle)
+VR_VK_FUNC(vkResetFences)
+VR_VK_FUNC(vkUnmapMemory)
+VR_VK_FUNC(vkWaitForFences)
diff --git a/tests/vulkan/vkrunner/vr-vk-instance-funcs.h b/tests/vulkan/vkrunner/vr-vk-instance-funcs.h
new file mode 100644
index 000000000..5f72f5856
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vk-instance-funcs.h
@@ -0,0 +1,9 @@
+VR_VK_FUNC(vkCreateDevice)
+VR_VK_FUNC(vkDestroyInstance)
+VR_VK_FUNC(vkEnumeratePhysicalDevices)
+VR_VK_FUNC(vkGetDeviceProcAddr)
+VR_VK_FUNC(vkGetPhysicalDeviceFeatures)
+VR_VK_FUNC(vkGetPhysicalDeviceFormatProperties)
+VR_VK_FUNC(vkGetPhysicalDeviceMemoryProperties)
+VR_VK_FUNC(vkGetPhysicalDeviceProperties)
+VR_VK_FUNC(vkGetPhysicalDeviceQueueFamilyProperties)
diff --git a/tests/vulkan/vkrunner/vr-vk.c b/tests/vulkan/vkrunner/vr-vk.c
new file mode 100644
index 000000000..2bd03b59e
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vk.c
@@ -0,0 +1,148 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2016 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+#include "vr-vk.h"
+#include "vr-error-message.h"
+
+struct vr_vk vr_vk;
+
+static void *lib_vulkan;
+
+struct function {
+	const char *name;
+	size_t offset;
+};
+
+#define VR_VK_FUNC(func_name) \
+	{ .name = #func_name, .offset = offsetof(struct vr_vk, func_name) },
+
+static const struct function
+core_functions[] = {
+#include "vr-vk-core-funcs.h"
+};
+
+static const struct function
+instance_functions[] = {
+#include "vr-vk-instance-funcs.h"
+};
+
+static const struct function
+device_functions[] = {
+#include "vr-vk-device-funcs.h"
+};
+
+#undef VR_VK_FUNC
+
+typedef void *
+(* func_getter)(void *object,
+		const char *func_name);
+
+static void
+init_functions(func_getter getter,
+	       void *object,
+	       const struct function *functions,
+	       int n_functions)
+{
+	const struct function *function;
+	int i;
+
+	for (i = 0; i < n_functions; i++) {
+		function = functions + i;
+		*(void **) ((char *) &vr_vk + function->offset) =
+			getter(object, function->name);
+	}
+}
+
+bool
+vr_vk_load_libvulkan(void)
+{
+#ifdef WIN32
+
+	lib_vulkan = LoadLibrary("vulkan-1.dll");
+
+	if (lib_vulkan == NULL) {
+		vr_error_message("Error openining vulkan-1.dll");
+		return false;
+	}
+
+	vr_vk.vkGetInstanceProcAddr = (void *)
+		GetProcAddress(lib_vulkan, "vkGetInstanceProcAddr");
+
+#else
+	lib_vulkan = dlopen("libvulkan.so.1", RTLD_LAZY | RTLD_GLOBAL);
+
+	if (lib_vulkan == NULL) {
+		vr_error_message("Error openining libvulkan.so.1: %s",
+				 dlerror());
+		return false;
+	}
+
+	vr_vk.vkGetInstanceProcAddr =
+		dlsym(lib_vulkan, "vkGetInstanceProcAddr");
+
+#endif /* WIN32 */
+
+	init_functions((func_getter) vr_vk.vkGetInstanceProcAddr,
+		       NULL, /* object */
+		       core_functions,
+		       ARRAY_SIZE(core_functions));
+
+	return true;
+}
+
+void
+vr_vk_init_instance(VkInstance instance)
+{
+	init_functions((func_getter) vr_vk.vkGetInstanceProcAddr,
+		       instance,
+		       instance_functions,
+		       ARRAY_SIZE(instance_functions));
+}
+
+void
+vr_vk_init_device(VkDevice device)
+{
+	init_functions((func_getter) vr_vk.vkGetDeviceProcAddr,
+		       device,
+		       device_functions,
+		       ARRAY_SIZE(device_functions));
+}
+
+void
+vr_vk_unload_libvulkan(void)
+{
+#ifdef WIN32
+	FreeLibrary(lib_vulkan);
+#else
+	dlclose(lib_vulkan);
+#endif
+}
diff --git a/tests/vulkan/vkrunner/vr-vk.h b/tests/vulkan/vkrunner/vr-vk.h
new file mode 100644
index 000000000..40400c48e
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-vk.h
@@ -0,0 +1,56 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2016 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_VK_H
+#define VR_VK_H
+
+#include <vulkan/vulkan.h>
+#include <stdbool.h>
+
+struct vr_vk {
+	PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+
+#define VR_VK_FUNC(name) PFN_ ## name name;
+#include "vr-vk-core-funcs.h"
+#include "vr-vk-instance-funcs.h"
+#include "vr-vk-device-funcs.h"
+#undef VR_VK_FUNC
+};
+
+extern struct vr_vk vr_vk;
+
+bool
+vr_vk_load_libvulkan(void);
+
+void
+vr_vk_init_instance(VkInstance instance);
+
+void
+vr_vk_init_device(VkDevice device);
+
+void
+vr_vk_unload_libvulkan(void);
+
+#endif /* VR_VK_H */
diff --git a/tests/vulkan/vkrunner/vr-window.c b/tests/vulkan/vkrunner/vr-window.c
new file mode 100644
index 000000000..bb03e397f
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-window.c
@@ -0,0 +1,678 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2013, 2014, 2015, 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <stdbool.h>
+#include <assert.h>
+
+#include "vr-window.h"
+#include "vr-error-message.h"
+#include "vr-allocate-store.h"
+#include "vr-feature-offsets.h"
+
+static int
+find_queue_family(struct vr_window *window,
+		  VkPhysicalDevice physical_device)
+{
+	VkQueueFamilyProperties *queues;
+	uint32_t count = 0;
+	uint32_t i;
+
+	vr_vk.vkGetPhysicalDeviceQueueFamilyProperties(physical_device,
+						       &count,
+						       NULL /* queues */);
+
+	queues = malloc(sizeof *queues * count);
+
+	vr_vk.vkGetPhysicalDeviceQueueFamilyProperties(physical_device,
+						       &count,
+						       queues);
+
+	for (i = 0; i < count; i++) {
+		if ((queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) &&
+		    queues[i].queueCount >= 1)
+			break;
+	}
+
+	free(queues);
+
+	if (i >= count)
+		return -1;
+	else
+		return i;
+}
+
+static void
+destroy_framebuffer_resources(struct vr_window *window)
+{
+	if (window->color_image_view) {
+		vr_vk.vkDestroyImageView(window->device,
+					 window->color_image_view,
+					 NULL /* allocator */);
+		window->color_image_view = NULL;
+	}
+	if (window->framebuffer) {
+		vr_vk.vkDestroyFramebuffer(window->device,
+					   window->framebuffer,
+					   NULL /* allocator */);
+		window->framebuffer = NULL;
+	}
+	if (window->linear_memory_map) {
+		vr_vk.vkUnmapMemory(window->device,
+				    window->linear_memory);
+		window->linear_memory_map = NULL;
+	}
+	if (window->linear_memory) {
+		vr_vk.vkFreeMemory(window->device,
+				   window->linear_memory,
+				   NULL /* allocator */);
+		window->linear_memory = NULL;
+	}
+	if (window->memory) {
+		vr_vk.vkFreeMemory(window->device,
+				   window->memory,
+				   NULL /* allocator */);
+		window->memory = NULL;
+	}
+	if (window->linear_image) {
+		vr_vk.vkDestroyImage(window->device,
+				     window->linear_image,
+				     NULL /* allocator */);
+		window->linear_image = NULL;
+	}
+	if (window->color_image) {
+		vr_vk.vkDestroyImage(window->device,
+				     window->color_image,
+				     NULL /* allocator */);
+		window->color_image = NULL;
+	}
+}
+
+static bool
+init_framebuffer_resources(struct vr_window *window)
+{
+	VkResult res;
+	int linear_memory_type;
+
+	VkImageCreateInfo image_create_info = {
+		.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+		.imageType = VK_IMAGE_TYPE_2D,
+		.format = window->framebuffer_format->vk_format,
+		.extent = {
+			.width = VR_WINDOW_WIDTH,
+			.height = VR_WINDOW_HEIGHT,
+			.depth = 1
+		},
+		.mipLevels = 1,
+		.arrayLayers = 1,
+		.samples = VK_SAMPLE_COUNT_1_BIT,
+		.tiling = VK_IMAGE_TILING_OPTIMAL,
+		.usage = (VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+			  VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
+		.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+		.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+	};
+	res = vr_vk.vkCreateImage(window->device,
+				  &image_create_info,
+				  NULL, /* allocator */
+				  &window->color_image);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkImage");
+		return false;
+	}
+
+	image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+	image_create_info.tiling = VK_IMAGE_TILING_LINEAR;
+	res = vr_vk.vkCreateImage(window->device,
+				  &image_create_info,
+				  NULL, /* allocator */
+				  &window->linear_image);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkImage");
+		return false;
+	}
+
+	res = vr_allocate_store_image(window,
+				      0, /* memory_type_flags */
+				      1, /* n_images */
+				      (VkImage[]) { window->color_image },
+				      &window->memory,
+				      NULL /* memory_type_index */);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error allocating framebuffer memory");
+		return false;
+	}
+
+	res = vr_allocate_store_image(window,
+				      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+				      1, /* n_images */
+				      &window->linear_image,
+				      &window->linear_memory,
+				      &linear_memory_type);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error allocating framebuffer memory");
+		return false;
+	}
+
+	window->need_linear_memory_invalidate =
+		(window->memory_properties.
+		 memoryTypes[linear_memory_type].propertyFlags &
+		 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0;
+
+	VkImageSubresource linear_subresource = {
+		.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+		.mipLevel = 0,
+		.arrayLayer = 0
+	};
+	VkSubresourceLayout linear_layout;
+	vr_vk.vkGetImageSubresourceLayout(window->device,
+					  window->linear_image,
+					  &linear_subresource,
+					  &linear_layout);
+	window->linear_memory_stride = linear_layout.rowPitch;
+
+	res = vr_vk.vkMapMemory(window->device,
+				window->linear_memory,
+				0, /* offset */
+				VK_WHOLE_SIZE,
+				0, /* flags */
+				&window->linear_memory_map);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error mapping linear memory");
+		return false;
+	}
+
+	VkImageViewCreateInfo image_view_create_info = {
+		.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+		.image = window->color_image,
+		.viewType = VK_IMAGE_VIEW_TYPE_2D,
+		.format = window->framebuffer_format->vk_format,
+		.components = {
+			.r = VK_COMPONENT_SWIZZLE_R,
+			.g = VK_COMPONENT_SWIZZLE_G,
+			.b = VK_COMPONENT_SWIZZLE_B,
+			.a = VK_COMPONENT_SWIZZLE_A
+		},
+		.subresourceRange = {
+			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+			.baseMipLevel = 0,
+			.levelCount = 1,
+			.baseArrayLayer = 0,
+			.layerCount = 1
+		}
+	};
+	res = vr_vk.vkCreateImageView(window->device,
+				      &image_view_create_info,
+				      NULL, /* allocator */
+				      &window->color_image_view);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating image view");
+		return false;
+	}
+
+	VkImageView attachments[] = {
+		window->color_image_view,
+	};
+	VkFramebufferCreateInfo framebuffer_create_info = {
+		.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+		.renderPass = window->render_pass,
+		.attachmentCount = ARRAY_SIZE(attachments),
+		.pAttachments = attachments,
+		.width = VR_WINDOW_WIDTH,
+		.height = VR_WINDOW_HEIGHT,
+		.layers = 1
+	};
+	res = vr_vk.vkCreateFramebuffer(window->device,
+					&framebuffer_create_info,
+					NULL, /* allocator */
+					&window->framebuffer);
+	if (res != VK_SUCCESS) {
+		window->framebuffer = NULL;
+		vr_error_message("Error creating framebuffer");
+		return false;
+	}
+
+	VkCommandBufferBeginInfo command_buffer_begin_info = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+		.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+	};
+	vr_vk.vkBeginCommandBuffer(window->command_buffer,
+				   &command_buffer_begin_info);
+
+	VkImageMemoryBarrier image_memory_barrier = {
+		.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+		.srcAccessMask = 0,
+		.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+		.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+		.newLayout = VK_IMAGE_LAYOUT_GENERAL,
+		.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+		.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+		.image = window->linear_image,
+		.subresourceRange = {
+			.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+			.baseMipLevel = 0,
+			.levelCount = 1,
+			.layerCount = 1
+		}
+	};
+	vr_vk.vkCmdPipelineBarrier(window->command_buffer,
+				   VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+				   VK_PIPELINE_STAGE_TRANSFER_BIT,
+				   0, /* dependencyFlags */
+				   0, /* memoryBarrierCount */
+				   NULL, /* pMemoryBarriers */
+				   0, /* bufferMemoryBarrierCount */
+				   NULL, /* pBufferMemoryBarriers */
+				   1, /* imageMemoryBarrierCount */
+				   &image_memory_barrier);
+
+	vr_vk.vkEndCommandBuffer(window->command_buffer);
+
+	VkSubmitInfo submitInfo = {
+		.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+		.commandBufferCount = 1,
+		.pCommandBuffers = &window->command_buffer
+	};
+	vr_vk.vkQueueSubmit(window->queue,
+			    1,
+			    &submitInfo,
+			    VK_NULL_HANDLE);
+
+	vr_vk.vkQueueWaitIdle(window->queue);
+
+	return true;
+}
+
+static void
+deinit_vk(struct vr_window *window)
+{
+	destroy_framebuffer_resources(window);
+
+	if (window->vk_fence) {
+		vr_vk.vkDestroyFence(window->device,
+				     window->vk_fence,
+				     NULL /* allocator */);
+		window->vk_fence = NULL;
+	}
+	if (window->render_pass) {
+		vr_vk.vkDestroyRenderPass(window->device,
+					  window->render_pass,
+					  NULL /* allocator */);
+		window->render_pass = NULL;
+	}
+	if (window->descriptor_pool) {
+		vr_vk.vkDestroyDescriptorPool(window->device,
+					      window->descriptor_pool,
+					      NULL /* allocator */);
+		window->descriptor_pool = NULL;
+	}
+	if (window->command_buffer) {
+		vr_vk.vkFreeCommandBuffers(window->device,
+					   window->command_pool,
+					   1, /* commandBufferCount */
+					   &window->command_buffer);
+		window->command_buffer = NULL;
+	}
+	if (window->command_pool) {
+		vr_vk.vkDestroyCommandPool(window->device,
+					   window->command_pool,
+					   NULL /* allocator */);
+		window->command_pool = NULL;
+	}
+	if (window->device) {
+		vr_vk.vkDestroyDevice(window->device,
+				      NULL /* allocator */);
+		window->device = NULL;
+	}
+	if (window->vk_instance) {
+		vr_vk.vkDestroyInstance(window->vk_instance,
+					NULL /* allocator */);
+		window->vk_instance = NULL;
+	}
+}
+
+static bool
+get_feature(const VkPhysicalDeviceFeatures *features,
+	    int feature_num)
+{
+	const struct vr_feature_offset *fo =
+		vr_feature_offsets + feature_num;
+	return *(const VkBool32 *) ((const uint8_t *) features + fo->offset);
+}
+
+static bool
+check_features(const VkPhysicalDeviceFeatures *features,
+	       const VkPhysicalDeviceFeatures *requires)
+{
+	for (int i = 0; vr_feature_offsets[i].name; i++) {
+		if (get_feature(requires, i) &&
+		    !get_feature(features, i)) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static enum piglit_result
+find_physical_device(struct vr_window *window,
+		     const VkPhysicalDeviceFeatures *requires)
+{
+	VkResult res;
+	uint32_t count;
+	VkPhysicalDevice *devices;
+	int i, queue_family;
+
+	res = vr_vk.vkEnumeratePhysicalDevices(window->vk_instance,
+					       &count,
+					       NULL);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error enumerating VkPhysicalDevices");
+		return PIGLIT_FAIL;
+	}
+
+	devices = alloca(count * sizeof *devices);
+
+	res = vr_vk.vkEnumeratePhysicalDevices(window->vk_instance,
+					       &count,
+					       devices);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error enumerating VkPhysicalDevices");
+		return PIGLIT_FAIL;
+	}
+
+	for (i = 0; i < count; i++) {
+		VkPhysicalDeviceFeatures features;
+		vr_vk.vkGetPhysicalDeviceFeatures(devices[i], &features);
+		if (!check_features(&features, requires))
+			continue;
+
+		queue_family = find_queue_family(window, devices[i]);
+		if (queue_family == -1)
+			continue;
+
+		window->physical_device = devices[i];
+		window->queue_family = queue_family;
+
+		return PIGLIT_PASS;
+	}
+
+	vr_error_message("No suitable device and queue family found");
+
+	return PIGLIT_SKIP;
+}
+
+static enum piglit_result
+init_vk(struct vr_window *window,
+	const VkPhysicalDeviceFeatures *requires)
+{
+	VkPhysicalDeviceMemoryProperties *memory_properties =
+		&window->memory_properties;
+	enum piglit_result vres = PIGLIT_PASS;
+	VkResult res;
+
+	struct VkInstanceCreateInfo instance_create_info = {
+		.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+		.pApplicationInfo = &(VkApplicationInfo) {
+			.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+			.pApplicationName = "vkrunner",
+			.apiVersion = VK_MAKE_VERSION(1, 0, 2)
+		},
+	};
+	res = vr_vk.vkCreateInstance(&instance_create_info,
+				     NULL, /* allocator */
+				     &window->vk_instance);
+
+	if (res != VK_SUCCESS) {
+		vr_error_message("Failed to create VkInstance");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	vr_vk_init_instance(window->vk_instance);
+
+	vres = find_physical_device(window, requires);
+	if (vres != PIGLIT_PASS)
+		goto error;
+
+	vr_vk.vkGetPhysicalDeviceProperties(window->physical_device,
+					    &window->device_properties);
+	vr_vk.vkGetPhysicalDeviceMemoryProperties(window->physical_device,
+						  memory_properties);
+	vr_vk.vkGetPhysicalDeviceFeatures(window->physical_device,
+					  &window->features);
+
+	VkFormatProperties format_properties;
+	VkFormat framebuffer_format = window->framebuffer_format->vk_format;
+	vr_vk.vkGetPhysicalDeviceFormatProperties(window->physical_device,
+						  framebuffer_format,
+						  &format_properties);
+	if ((format_properties.optimalTilingFeatures &
+	     (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
+	      VK_FORMAT_FEATURE_BLIT_SRC_BIT)) !=
+	    (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
+	     VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
+		vr_error_message("Format %s is not supported as a color "
+				 "attachment and blit source",
+				 window->framebuffer_format->name);
+		vres = PIGLIT_SKIP;
+		goto error;
+	}
+	if ((format_properties.linearTilingFeatures &
+	     VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) {
+		vr_error_message("Format %s is not supported as a linear "
+				 "blit destination",
+				 window->framebuffer_format->name);
+		vres = PIGLIT_SKIP;
+		goto error;
+	}
+
+	VkDeviceCreateInfo device_create_info = {
+		.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+		.queueCreateInfoCount = 1,
+		.pQueueCreateInfos = &(VkDeviceQueueCreateInfo) {
+			.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+			.queueFamilyIndex = window->queue_family,
+			.queueCount = 1,
+			.pQueuePriorities = (float[]) { 1.0f }
+		},
+		.enabledExtensionCount = 1,
+		.ppEnabledExtensionNames = (const char * const []) {
+			VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+		},
+		.pEnabledFeatures = requires
+	};
+	res = vr_vk.vkCreateDevice(window->physical_device,
+				   &device_create_info,
+				   NULL, /* allocator */
+				   &window->device);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkDevice");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	vr_vk_init_device(window->device);
+
+	vr_vk.vkGetDeviceQueue(window->device,
+			       window->queue_family,
+			       0, /* queueIndex */
+			       &window->queue);
+
+	VkCommandPoolCreateInfo command_pool_create_info = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+		.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+		.queueFamilyIndex = window->queue_family
+	};
+	res = vr_vk.vkCreateCommandPool(window->device,
+					&command_pool_create_info,
+					NULL, /* allocator */
+					&window->command_pool);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkCommandPool");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	VkCommandBufferAllocateInfo command_buffer_allocate_info = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+		.commandPool = window->command_pool,
+		.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+		.commandBufferCount = 1
+	};
+	res = vr_vk.vkAllocateCommandBuffers(window->device,
+					     &command_buffer_allocate_info,
+					     &window->command_buffer);
+
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating command buffer");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	VkDescriptorPoolSize pool_size = {
+		.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+		.descriptorCount = 4
+	};
+	VkDescriptorPoolCreateInfo descriptor_pool_create_info = {
+		.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+		.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+		.maxSets = 4,
+		.poolSizeCount = 1,
+		.pPoolSizes = &pool_size
+	};
+	res = vr_vk.vkCreateDescriptorPool(window->device,
+					   &descriptor_pool_create_info,
+					   NULL, /* allocator */
+					   &window->descriptor_pool);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating VkDescriptorPool");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	VkAttachmentDescription attachment_descriptions[] = {
+		{
+			.format = window->framebuffer_format->vk_format,
+			.samples = VK_SAMPLE_COUNT_1_BIT,
+			.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+			.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+			.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+			.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+			.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+			.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+		},
+	};
+	VkSubpassDescription subpass_descriptions[] = {
+		{
+			.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+			.colorAttachmentCount = 1,
+			.pColorAttachments = &(VkAttachmentReference) {
+				.attachment = 0,
+				.layout =
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+			},
+		}
+	};
+	VkRenderPassCreateInfo render_pass_create_info = {
+		.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+		.attachmentCount = ARRAY_SIZE(attachment_descriptions),
+		.pAttachments = attachment_descriptions,
+		.subpassCount = ARRAY_SIZE(subpass_descriptions),
+		.pSubpasses = subpass_descriptions
+	};
+	res = vr_vk.vkCreateRenderPass(window->device,
+				       &render_pass_create_info,
+				       NULL, /* allocator */
+				       &window->render_pass);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating render pass");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	VkFenceCreateInfo fence_create_info = {
+		.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
+	};
+	res = vr_vk.vkCreateFence(window->device,
+				  &fence_create_info,
+				  NULL, /* allocator */
+				  &window->vk_fence);
+	if (res != VK_SUCCESS) {
+		vr_error_message("Error creating fence");
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	if (!init_framebuffer_resources(window)) {
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	return PIGLIT_PASS;
+
+error:
+	deinit_vk(window);
+	return vres;
+}
+
+enum piglit_result
+vr_window_new(const VkPhysicalDeviceFeatures *requires,
+	      const struct vr_format *framebuffer_format,
+	      struct vr_window **window_out)
+{
+	struct vr_window *window = calloc(sizeof *window, 1);
+	enum piglit_result vres;
+
+	if (!vr_vk_load_libvulkan()) {
+		vres = PIGLIT_FAIL;
+		goto error;
+	}
+
+	window->libvulkan_loaded = true;
+	window->framebuffer_format = framebuffer_format;
+
+	vres = init_vk(window, requires);
+	if (vres != PIGLIT_PASS)
+		goto error;
+
+	*window_out = window;
+	return PIGLIT_PASS;
+
+error:
+	vr_window_free(window);
+	return vres;
+}
+
+void
+vr_window_free(struct vr_window *window)
+{
+	deinit_vk(window);
+
+	if (window->libvulkan_loaded)
+		vr_vk_unload_libvulkan();
+
+	free(window);
+}
diff --git a/tests/vulkan/vkrunner/vr-window.h b/tests/vulkan/vkrunner/vr-window.h
new file mode 100644
index 000000000..a11cd7df3
--- /dev/null
+++ b/tests/vulkan/vkrunner/vr-window.h
@@ -0,0 +1,73 @@
+/*
+ * vkrunner
+ *
+ * Copyright (C) 2017 Neil Roberts
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VR_WINDOW_H
+#define VR_WINDOW_H
+
+#include <stdbool.h>
+#include "vr-vk.h"
+#include "vr-format.h"
+
+#define VR_WINDOW_WIDTH 250
+#define VR_WINDOW_HEIGHT 250
+
+struct vr_window {
+	VkDevice device;
+	VkPhysicalDevice physical_device;
+	VkPhysicalDeviceMemoryProperties memory_properties;
+	VkPhysicalDeviceProperties device_properties;
+	VkPhysicalDeviceFeatures features;
+	VkDescriptorPool descriptor_pool;
+	VkCommandPool command_pool;
+	VkCommandBuffer command_buffer;
+	VkRenderPass render_pass;
+	VkQueue queue;
+	int queue_family;
+	VkInstance vk_instance;
+	VkFence vk_fence;
+
+	VkImage color_image;
+	VkImage linear_image;
+	VkDeviceMemory memory;
+	VkDeviceMemory linear_memory;
+	bool need_linear_memory_invalidate;
+	void *linear_memory_map;
+	VkDeviceSize linear_memory_stride;
+	VkImageView color_image_view;
+	VkFramebuffer framebuffer;
+	const struct vr_format *framebuffer_format;
+
+	bool libvulkan_loaded;
+};
+
+enum piglit_result
+vr_window_new(const VkPhysicalDeviceFeatures *requires,
+	      const struct vr_format *framebuffer_format,
+	      struct vr_window **window_out);
+
+void
+vr_window_free(struct vr_window *window);
+
+#endif /* VR_WINDOW_H */
-- 
2.14.3



More information about the Piglit mailing list