Add inter-process communcation to shader cache

This change allows running several instances of CTS which share a single
shader cache.

std::map which was used to hold the cache index was replaced by a simple
binary search tree implementation in order to play nice in a
pre-allocated buffer. Benchmarking gives approximately same performance
or better in random cases. Worst case behavior is terrible, but did not
occur while testing after integrating with CTS.

By default the size of the index is 1M entries, taking 16M of memory.
This memory pool is not resized dynamically, and will assert if over 1M
unique shaders are inserted to the cache.

Additional small improvement to the shader cache behavior avoids
re-hashing the cache key at cache miss.

For platforms that do not support IPC, the IPC code can be compiled out
using a predefined macro DISABLE_SHADERCACHE_IPC.

The IPC behavior is disabled by default, leading to old behavior, except
for the std::map to bst change and no re-hashing.

Affects:
*

VK-GL-CTS issue: 3565

Change-Id: I4f2433256968b76de26c9bd50981454bb4accae0
diff --git a/external/vulkancts/README.md b/external/vulkancts/README.md
index 33e0501..e585c48 100644
--- a/external/vulkancts/README.md
+++ b/external/vulkancts/README.md
@@ -605,6 +605,21 @@
 Do not truncate the shader cache file at startup. No shader compilation will
 occur on repeated runs of the CTS.
 
+	--deqp-shadercache-ipc=enable
+
+Enables the use of inter-process communication primitives to allow several
+instances of CTS to share a single cache file. All of the instances must
+use the same shader cache filename.
+
+Note that if one instance should crash while holding the cache file lock,
+the other instances will hang. The lock is only held while reading or
+writing to the cache, so crashes are unlikely.
+
+In case of a crash outside the cache file lock, the named shared memory
+and shared semaphore may be left behind. These will be re-used by CTS on
+subsequent runs, so additional memory leak will not occur. Shader cache
+truncate may not work in this case. On Windows, when all instances of
+CTS have terminated the shared resources get automatically cleaned up.
 
 RenderDoc
 ---------
diff --git a/external/vulkancts/framework/vulkan/vkIPC.inl b/external/vulkancts/framework/vulkan/vkIPC.inl
new file mode 100644
index 0000000..3ec4fab
--- /dev/null
+++ b/external/vulkancts/framework/vulkan/vkIPC.inl
@@ -0,0 +1,248 @@
+#if defined(_WIN32)
+typedef void *HANDLE;
+#else
+#include <semaphore.h>
+#endif
+
+typedef struct ipc_sharedmemory_
+{
+    char*			name;
+    unsigned char*	data;
+    size_t			size;
+#if defined(_WIN32)
+    HANDLE			handle;
+#else
+    int				fd;
+#endif
+} ipc_sharedmemory;
+
+typedef struct ipc_sharedsemaphore_
+{
+    char*			name;
+#if defined(_WIN32)
+    HANDLE			handle;
+#else
+    sem_t*			semaphore;
+#endif
+} ipc_sharedsemaphore;
+
+#if defined(_WIN32)
+#include <windows.h>
+#else // !_WIN32
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#endif // !_WIN32
+
+static char* ipc_strdup (char* src)
+{
+	int		i;
+	int		len = 0;
+    char*	dst = NULL;
+    while (src[len]) len++;
+#if !defined(_WIN32)
+    len++;
+#endif
+    dst = (char*)malloc(len + 1);
+    if (!dst) return NULL;
+    dst[len] = 0;
+
+#if defined(_WIN32)
+    for (i = 0; i < len; i++)
+        dst[i] = src[i];
+#else
+    dst[0] = '/';
+    for (i = 0; i < len - 1; i++)
+        dst[i + 1] = src[i];
+#endif
+    return dst;
+}
+
+void ipc_mem_init (ipc_sharedmemory* mem, char* name, size_t size)
+{
+    mem->name = ipc_strdup(name);
+
+    mem->size = size;
+    mem->data = NULL;
+#if defined(_WIN32)
+    mem->handle = 0;
+#else
+    mem->fd = -1;
+#endif
+}
+
+unsigned char* ipc_mem_access (ipc_sharedmemory* mem)
+{
+    return mem->data;
+}
+
+void ipc_sem_init (ipc_sharedsemaphore* sem, char* name)
+{
+    sem->name = ipc_strdup(name);
+#if defined(_WIN32)
+    sem->handle = 0;
+#else
+    sem->semaphore = NULL;
+#endif
+}
+
+#if defined(_WIN32)
+
+int ipc_mem_open_existing (ipc_sharedmemory* mem)
+{
+    mem->handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, mem->name);
+
+    if (!mem->handle)
+        return -1;
+
+    mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size);
+
+    if (!mem->data)
+        return -1;
+    return 0;
+}
+
+int ipc_mem_create (ipc_sharedmemory* mem)
+{
+    mem->handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)mem->size, mem->name);
+
+    if (!mem->handle)
+        return -1;
+
+    mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size);
+
+    if (!mem->data)
+        return -1;
+
+    return 0;
+}
+
+void ipc_mem_close (ipc_sharedmemory* mem)
+{
+    if (mem->data != NULL)
+    {
+        UnmapViewOfFile(mem->data);
+        mem->data = NULL;
+    }
+    free(mem->name);
+    mem->name = NULL;
+    mem->size = 0;
+}
+
+int ipc_sem_create (ipc_sharedsemaphore* sem, int initialvalue)
+{
+    sem->handle = CreateSemaphoreA(NULL, initialvalue, 0x7fffffff, sem->name);
+    if (!sem->handle)
+        return -1;
+    return 0;
+}
+
+void ipc_sem_close (ipc_sharedsemaphore* sem)
+{
+    CloseHandle(sem->handle);
+    free(sem->name);
+    sem->handle = 0;
+}
+
+void ipc_sem_increment (ipc_sharedsemaphore* sem)
+{
+    ReleaseSemaphore(sem->handle, 1, NULL);
+}
+
+void ipc_sem_decrement (ipc_sharedsemaphore* sem)
+{
+    WaitForSingleObject(sem->handle, INFINITE);
+}
+
+int ipc_sem_try_decrement (ipc_sharedsemaphore* sem)
+{
+    DWORD ret = WaitForSingleObject(sem->handle, 0);
+    if (ret == WAIT_OBJECT_0)
+        return 1;
+    return 0;
+}
+
+#else // !defined(_WIN32)
+
+int ipc_mem_open_existing (ipc_sharedmemory* mem)
+{
+    mem->fd = shm_open(mem->name, O_RDWR, 0755);
+    if (mem->fd < 0)
+        return -1;
+
+    mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0);
+    if (!mem->data)
+        return -1;
+
+    return 0;
+}
+
+int ipc_mem_create (ipc_sharedmemory* mem)
+{
+    int ret;
+    ret = shm_unlink(mem->name);
+    if (ret < 0 && errno != ENOENT)
+        return -1;
+
+    mem->fd = shm_open(mem->name, O_CREAT | O_RDWR, 0755);
+    if (mem->fd < 0)
+        return -1;
+
+    ftruncate(mem->fd, mem->size);
+
+    mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0);
+    if (!mem->data)
+        return -1;
+
+    return 0;
+}
+
+void ipc_mem_close (ipc_sharedmemory* mem)
+{
+    if (mem->data != NULL)
+    {
+        munmap(mem->data, mem->size);
+        close(mem->fd);
+        shm_unlink(mem->name);
+    }
+    free(mem->name);
+    mem->name = NULL;
+    mem->size = 0;
+}
+
+int ipc_sem_create (ipc_sharedsemaphore* sem, int initialvalue)
+{
+    sem->semaphore = sem_open(sem->name, O_CREAT, 0700, initialvalue);
+    if (sem->semaphore == SEM_FAILED)
+        return -1;
+    return 0;
+}
+
+void ipc_sem_close (ipc_sharedsemaphore* sem)
+{
+    sem_close(sem->semaphore);
+    sem_unlink(sem->name);
+    free(sem->name);
+}
+
+void ipc_sem_increment (ipc_sharedsemaphore* sem)
+{
+    sem_post(sem->semaphore);
+}
+
+void ipc_sem_decrement (ipc_sharedsemaphore* sem)
+{
+    sem_wait(sem->semaphore);
+}
+
+int ipc_sem_try_decrement(ipc_sharedsemaphore* sem)
+{
+    int res = sem_trywait(sem->semaphore);
+    if (res == 0)
+        return 1;
+    return 0;
+}
+
+#endif // !_WIN32
diff --git a/external/vulkancts/framework/vulkan/vkPrograms.cpp b/external/vulkancts/framework/vulkan/vkPrograms.cpp
index 77e839f..2b06e79 100644
--- a/external/vulkancts/framework/vulkan/vkPrograms.cpp
+++ b/external/vulkancts/framework/vulkan/vkPrograms.cpp
@@ -177,28 +177,218 @@
 	}
 }
 
-de::Mutex							cacheFileMutex;
-map<deUint32, vector<deUint32> >	cacheFileIndex;
-bool								cacheFileFirstRun = true;
+// IPC functions
+#ifndef DISABLE_SHADERCACHE_IPC
+#include "vkIPC.inl"
+#endif
 
-void shaderCacheFirstRunCheck (const char* shaderCacheFile, bool truncate)
+// Overridable wrapper for de::Mutex
+class cacheMutex
 {
-	cacheFileMutex.lock();
-	if (cacheFileFirstRun)
+public:
+	cacheMutex () {}
+	virtual ~cacheMutex () {}
+	virtual void lock () { localMutex.lock(); }
+	virtual void unlock () { localMutex.unlock(); }
+private:
+	de::Mutex localMutex;
+};
+
+#ifndef DISABLE_SHADERCACHE_IPC
+// Overriden cacheMutex that uses IPC instead
+class cacheMutexIPC : public cacheMutex
+{
+public:
+	cacheMutexIPC ()
 	{
-		cacheFileFirstRun = false;
-		if (truncate)
+		ipc_sem_init(&guard, "cts_shadercache_ipc_guard");
+		ipc_sem_create(&guard, 1);
+	}
+	virtual ~cacheMutexIPC ()
+	{
+		ipc_sem_close(&guard);
+	}
+	virtual void lock () { ipc_sem_decrement(&guard); }
+	virtual void unlock () { ipc_sem_increment(&guard); }
+private:
+	ipc_sharedsemaphore guard;
+};
+#endif
+
+// Each cache node takes 4 * 4 = 16 bytes; 1M items takes 16M memory.
+const deUint32						cacheMaxItems		= 1024 * 1024;
+cacheMutex*							cacheFileMutex		= 0;
+bool								cacheFileFirstRun	= true;
+deUint32*							cacheMempool		= 0;
+#ifndef DISABLE_SHADERCACHE_IPC
+ipc_sharedmemory					cacheIPCMemory;
+#endif
+
+struct cacheNode
+{
+	deUint32 key;
+	deUint32 data;
+	deUint32 right_child;
+	deUint32 left_child;
+};
+
+cacheNode* cacheSearch (deUint32 key)
+{
+	cacheNode*		r = (cacheNode*)(cacheMempool + 1);
+	unsigned int	p = 0;
+
+	while (1)
+	{
+		if (r[p].key == key)
+			return &r[p];
+
+		if (key > r[p].key)
+			p = r[p].right_child;
+		else
+			p = r[p].left_child;
+
+		if (p == 0)
+			return 0;
+	}
+}
+
+void cacheInsert (deUint32 key, deUint32 data)
+{
+	cacheNode*	r		= (cacheNode*)(cacheMempool + 1);
+	int*		tail	= (int*)cacheMempool;
+	int			newnode	= *tail;
+
+	DE_ASSERT(newnode < cacheMaxItems);
+
+	// If we run out of cache space, reset the cache index.
+	if (newnode >= cacheMaxItems)
+	{
+		*tail = 0;
+		newnode = 0;
+	}
+
+	r[*tail].data = data;
+	r[*tail].key = key;
+	r[*tail].left_child = 0;
+	r[*tail].right_child = 0;
+
+	(*tail)++;
+
+	if (newnode == 0)
+	{
+		// first
+		return;
+	}
+
+	int p = 0;
+	while (1)
+	{
+		if (r[p].key == key)
+		{
+			// collision; use the latest data
+			r[p].data = data;
+			(*tail)--;
+			return;
+		}
+
+		if (key > r[p].key)
+		{
+			if (r[p].right_child != 0)
+			{
+				p = r[p].right_child;
+			}
+			else
+			{
+				r[p].right_child = newnode;
+				return;
+			}
+		}
+		else
+		{
+			if (r[p].left_child != 0)
+			{
+				p = r[p].left_child;
+			}
+			else
+			{
+				r[p].left_child = newnode;
+				return;
+			}
+		}
+	}
+}
+
+// Called via atexit()
+void shaderCacheClean ()
+{
+	delete cacheFileMutex;
+	delete[] cacheMempool;
+}
+
+#ifndef DISABLE_SHADERCACHE_IPC
+// Called via atexit()
+void shaderCacheCleanIPC ()
+{
+	delete cacheFileMutex;
+	ipc_mem_close(&cacheIPCMemory);
+}
+#endif
+
+void shaderCacheFirstRunCheck (const tcu::CommandLine& commandLine)
+{
+	bool first = true;
+
+	if (!cacheFileFirstRun)
+		return;
+
+	cacheFileFirstRun = false;
+
+#ifndef DISABLE_SHADERCACHE_IPC
+	if (commandLine.isShaderCacheIPCEnabled())
+	{
+		// IPC path, allocate shared mutex and shared memory
+		cacheFileMutex = new cacheMutexIPC;
+		cacheFileMutex->lock();
+		ipc_mem_init(&cacheIPCMemory, "cts_shadercache_memory", sizeof(deUint32) * (cacheMaxItems * 4 + 1));
+		if (ipc_mem_open_existing(&cacheIPCMemory) != 0)
+		{
+			ipc_mem_create(&cacheIPCMemory);
+			cacheMempool = (deUint32*)ipc_mem_access(&cacheIPCMemory);
+			cacheMempool[0] = 0;
+		}
+		else
+		{
+			cacheMempool = (deUint32*)ipc_mem_access(&cacheIPCMemory);
+			first = false;
+		}
+		atexit(shaderCacheCleanIPC);
+	}
+	else
+#endif
+	{
+		// Non-IPC path, allocate local mutex and memory
+		cacheFileMutex = new cacheMutex;
+		cacheFileMutex->lock();
+		cacheMempool = new deUint32[cacheMaxItems * 4 + 1];
+		cacheMempool[0] = 0;
+
+		atexit(shaderCacheClean);
+	}
+
+	if (first)
+	{
+		if (commandLine.isShaderCacheTruncateEnabled())
 		{
 			// Open file with "w" access to truncate it
-			FILE* f = fopen(shaderCacheFile, "wb");
+			FILE* f = fopen(commandLine.getShaderCacheFilename(), "wb");
 			if (f)
 				fclose(f);
 		}
 		else
 		{
 			// Parse chunked shader cache file for hashes and offsets
-			FILE* file = fopen(shaderCacheFile, "rb");
-			int count = 0;
+			FILE* file	= fopen(commandLine.getShaderCacheFilename(), "rb");
+			int count	= 0;
 			if (file)
 			{
 				deUint32 chunksize	= 0;
@@ -210,7 +400,7 @@
 					offset = (deUint32)ftell(file);
 					if (ok) ok = fread(&chunksize, 1, 4, file)				== 4;
 					if (ok) ok = fread(&hash, 1, 4, file)					== 4;
-					if (ok) cacheFileIndex[hash].push_back(offset);
+					if (ok) cacheInsert(hash, offset);
 					if (ok) ok = fseek(file, offset + chunksize, SEEK_SET)	== 0;
 					count++;
 				}
@@ -218,7 +408,7 @@
 			}
 		}
 	}
-	cacheFileMutex.unlock();
+	cacheFileMutex->unlock();
 }
 
 std::string intToString (deUint32 integer)
@@ -243,122 +433,117 @@
 	return hash;
 }
 
-vk::ProgramBinary* shadercacheLoad (const std::string& shaderstring, const char* shaderCacheFilename)
+vk::ProgramBinary* shadercacheLoad (const std::string& shaderstring, const char* shaderCacheFilename, deUint32 hash)
 {
-	deUint32		hash		= shadercacheHash(shaderstring.c_str());
 	deInt32			format;
 	deInt32			length;
 	deInt32			sourcelength;
-	deUint32		i;
 	deUint32		temp;
 	deUint8*		bin			= 0;
 	char*			source		= 0;
 	deBool			ok			= true;
 	deBool			diff		= true;
-	cacheFileMutex.lock();
+	cacheNode*		node		= 0;
+	cacheFileMutex->lock();
 
-	if (cacheFileIndex.count(hash) == 0)
+	node = cacheSearch(hash);
+	if (node == 0)
 	{
-		cacheFileMutex.unlock();
+		cacheFileMutex->unlock();
 		return 0;
 	}
 	FILE*			file		= fopen(shaderCacheFilename, "rb");
-	ok				= file											!= 0;
+	ok				= file										!= 0;
 
-	for (i = 0; i < cacheFileIndex[hash].size(); i++)
+	if (ok) ok = fseek(file, node->data, SEEK_SET)				== 0;
+	if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
+	if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
+	if (ok) ok = temp											== hash; // Double check
+	if (ok) ok = fread(&format, 1, 4, file)						== 4;
+	if (ok) ok = fread(&length, 1, 4, file)						== 4;
+	if (ok) ok = length											> 0; // sanity check
+	if (ok) bin = new deUint8[length];
+	if (ok) ok = fread(bin, 1, length, file)					== (size_t)length;
+	if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
+	if (ok && sourcelength > 0)
 	{
-		if (ok) ok = fseek(file, cacheFileIndex[hash][i], SEEK_SET)	== 0;
-		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
-		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
-		if (ok) ok = temp											== hash; // Double check
-		if (ok) ok = fread(&format, 1, 4, file)						== 4;
-		if (ok) ok = fread(&length, 1, 4, file)						== 4;
-		if (ok) ok = length											> 0; // sanity check
-		if (ok) bin = new deUint8[length];
-		if (ok) ok = fread(bin, 1, length, file)					== (size_t)length;
-		if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
-		if (ok && sourcelength > 0)
-		{
-			source = new char[sourcelength + 1];
-			ok = fread(source, 1, sourcelength, file)				== (size_t)sourcelength;
-			source[sourcelength] = 0;
-			diff = shaderstring != std::string(source);
-		}
-		if (!ok || diff)
-		{
-			// Mismatch, but may still exist in cache if there were hash collisions
-			delete[] source;
-			delete[] bin;
-		}
-		else
-		{
-			delete[] source;
-			if (file) fclose(file);
-			cacheFileMutex.unlock();
-			vk::ProgramBinary* res = new vk::ProgramBinary((vk::ProgramFormat)format, length, bin);
-			delete[] bin;
-			return res;
-		}
+		source = new char[sourcelength + 1];
+		ok = fread(source, 1, sourcelength, file)				== (size_t)sourcelength;
+		source[sourcelength] = 0;
+		diff = shaderstring != std::string(source);
+	}
+	if (!ok || diff)
+	{
+		// Mismatch
+		delete[] source;
+		delete[] bin;
+	}
+	else
+	{
+		delete[] source;
+		if (file) fclose(file);
+		cacheFileMutex->unlock();
+		vk::ProgramBinary* res = new vk::ProgramBinary((vk::ProgramFormat)format, length, bin);
+		delete[] bin;
+		return res;
 	}
 	if (file) fclose(file);
-	cacheFileMutex.unlock();
+	cacheFileMutex->unlock();
 	return 0;
 }
 
-void shadercacheSave (const vk::ProgramBinary* binary, const std::string& shaderstring, const char* shaderCacheFilename)
+void shadercacheSave (const vk::ProgramBinary* binary, const std::string& shaderstring, const char* shaderCacheFilename, deUint32 hash)
 {
 	if (binary == 0)
 		return;
-	deUint32			hash		= shadercacheHash(shaderstring.c_str());
 	deInt32				format		= binary->getFormat();
 	deUint32			length		= (deUint32)binary->getSize();
 	deUint32			chunksize;
 	deUint32			offset;
 	const deUint8*		bin			= binary->getBinary();
 	const de::FilePath	filePath	(shaderCacheFilename);
+	cacheNode*			node		= 0;
 
-	cacheFileMutex.lock();
+	cacheFileMutex->lock();
 
-	if (cacheFileIndex[hash].size())
+	node = cacheSearch(hash);
+
+	if (node)
 	{
 		FILE*			file		= fopen(shaderCacheFilename, "rb");
 		deBool			ok			= (file != 0);
 		deBool			diff		= DE_TRUE;
 		deInt32			sourcelength;
-		deUint32		i;
 		deUint32		temp;
 
-		for (i = 0; i < cacheFileIndex[hash].size(); i++)
+		deUint32	cachedLength	= 0;
+
+		if (ok) ok = fseek(file, node->data, SEEK_SET)				== 0;
+		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
+		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
+		if (ok) ok = temp											== hash; // Double check
+		if (ok) ok = fread(&temp, 1, 4, file)						== 4;
+		if (ok) ok = fread(&cachedLength, 1, 4, file)				== 4;
+		if (ok) ok = cachedLength									> 0; // sanity check
+		if (ok) fseek(file, cachedLength, SEEK_CUR); // skip binary
+		if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
+
+		if (ok && sourcelength > 0)
 		{
-			deUint32	cachedLength	= 0;
+			char* source;
+			source	= new char[sourcelength + 1];
+			ok		= fread(source, 1, sourcelength, file)			== (size_t)sourcelength;
+			source[sourcelength] = 0;
+			diff	= shaderstring != std::string(source);
+			delete[] source;
+		}
 
-			if (ok) ok = fseek(file, cacheFileIndex[hash][i], SEEK_SET)	== 0;
-			if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
-			if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
-			if (ok) ok = temp											== hash; // Double check
-			if (ok) ok = fread(&temp, 1, 4, file)						== 4;
-			if (ok) ok = fread(&cachedLength, 1, 4, file)				== 4;
-			if (ok) ok = cachedLength									> 0; // sanity check
-			if (ok) fseek(file, cachedLength, SEEK_CUR); // skip binary
-			if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
-
-			if (ok && sourcelength > 0)
-			{
-				char* source;
-				source	= new char[sourcelength + 1];
-				ok		= fread(source, 1, sourcelength, file)			== (size_t)sourcelength;
-				source[sourcelength] = 0;
-				diff	= shaderstring != std::string(source);
-				delete[] source;
-			}
-
-			if (ok && !diff)
-			{
-				// Already in cache (written by another thread, probably)
-				fclose(file);
-				cacheFileMutex.unlock();
-				return;
-			}
+		if (ok && !diff)
+		{
+			// Already in cache (written by another thread, probably)
+			fclose(file);
+			cacheFileMutex->unlock();
+			return;
 		}
 		fclose(file);
 	}
@@ -369,7 +554,7 @@
 	FILE*				file		= fopen(shaderCacheFilename, "ab");
 	if (!file)
 	{
-		cacheFileMutex.unlock();
+		cacheFileMutex->unlock();
 		return;
 	}
 	// Append mode starts writing from the end of the file,
@@ -386,9 +571,9 @@
 	fwrite(&length, 1, 4, file);
 	fwrite(shaderstring.c_str(), 1, length, file);
 	fclose(file);
-	cacheFileIndex[hash].push_back(offset);
+	cacheInsert(hash, offset);
 
-	cacheFileMutex.unlock();
+	cacheFileMutex->unlock();
 }
 
 // Insert any information that may affect compilation into the shader string.
@@ -430,10 +615,11 @@
 	std::string			shaderstring;
 	vk::ProgramBinary*	res					= 0;
 	const int			optimizationRecipe	= commandLine.getOptimizationRecipe();
+	deUint32			hash				= 0;
 
 	if (commandLine.isShadercacheEnabled())
 	{
-		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
+		shaderCacheFirstRunCheck(commandLine);
 		getCompileEnvironment(cachekey);
 		getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
 
@@ -450,7 +636,9 @@
 
 		cachekey = cachekey + shaderstring;
 
-		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
+		hash = shadercacheHash(cachekey.c_str());
+
+		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
 
 		if (res)
 		{
@@ -501,7 +689,7 @@
 
 		res = createProgramBinaryFromSpirV(binary);
 		if (commandLine.isShadercacheEnabled())
-			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
+			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
 	}
 	return res;
 }
@@ -515,10 +703,11 @@
 	std::string			shaderstring;
 	vk::ProgramBinary*	res					= 0;
 	const int			optimizationRecipe	= commandLine.getOptimizationRecipe();
+	deInt32				hash				= 0;
 
 	if (commandLine.isShadercacheEnabled())
 	{
-		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
+		shaderCacheFirstRunCheck(commandLine);
 		getCompileEnvironment(cachekey);
 		getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
 
@@ -535,7 +724,9 @@
 
 		cachekey = cachekey + shaderstring;
 
-		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
+		hash = shadercacheHash(cachekey.c_str());
+
+		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
 
 		if (res)
 		{
@@ -586,7 +777,9 @@
 
 		res = createProgramBinaryFromSpirV(binary);
 		if (commandLine.isShadercacheEnabled())
-			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
+		{
+			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
+		}
 	}
 	return res;
 }
@@ -599,10 +792,11 @@
 	vk::ProgramBinary*	res					= 0;
 	std::string			cachekey;
 	const int			optimizationRecipe	= commandLine.isSpirvOptimizationEnabled() ? commandLine.getOptimizationRecipe() : 0;
+	deUint32			hash				= 0;
 
 	if (commandLine.isShadercacheEnabled())
 	{
-		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
+		shaderCacheFirstRunCheck(commandLine);
 		getCompileEnvironment(cachekey);
 		cachekey += "Target Spir-V ";
 		cachekey += getSpirvVersionName(spirvVersion);
@@ -616,7 +810,9 @@
 
 		cachekey += program.source;
 
-		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
+		hash = shadercacheHash(cachekey.c_str());
+
+		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
 
 		if (res)
 		{
@@ -646,7 +842,9 @@
 
 		res = createProgramBinaryFromSpirV(binary);
 		if (commandLine.isShadercacheEnabled())
-			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
+		{
+			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
+		}
 	}
 	return res;
 }
diff --git a/framework/common/tcuCommandLine.cpp b/framework/common/tcuCommandLine.cpp
index 0fff299..600aa9e 100644
--- a/framework/common/tcuCommandLine.cpp
+++ b/framework/common/tcuCommandLine.cpp
@@ -99,6 +99,7 @@
 DE_DECLARE_COMMAND_LINE_OPT(Optimization,				int);
 DE_DECLARE_COMMAND_LINE_OPT(OptimizeSpirv,				bool);
 DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheTruncate,		bool);
+DE_DECLARE_COMMAND_LINE_OPT(ShaderCacheIPC,				bool);
 DE_DECLARE_COMMAND_LINE_OPT(RenderDoc,					bool);
 DE_DECLARE_COMMAND_LINE_OPT(CaseFraction,				std::vector<int>);
 DE_DECLARE_COMMAND_LINE_OPT(CaseFractionMandatoryTests,	std::string);
@@ -226,6 +227,7 @@
 		<< Option<ShaderCache>					(DE_NULL,	"deqp-shadercache",							"Enable or disable shader cache",					s_enableNames,		"enable")
 		<< Option<ShaderCacheFilename>			(DE_NULL,	"deqp-shadercache-filename",				"Write shader cache to given file",										"shadercache.bin")
 		<< Option<ShaderCacheTruncate>			(DE_NULL,	"deqp-shadercache-truncate",				"Truncate shader cache before running tests",		s_enableNames,		"enable")
+		<< Option<ShaderCacheIPC>				(DE_NULL,	"deqp-shadercache-ipc",						"Should shader cache use inter process comms",		s_enableNames,		"disable")
 		<< Option<RenderDoc>					(DE_NULL,	"deqp-renderdoc",							"Enable RenderDoc frame markers",					s_enableNames,		"disable")
 		<< Option<CaseFraction>					(DE_NULL,	"deqp-fraction",							"Run a fraction of the test cases (e.g. N,M means run group%M==N)",	parseIntList,	"")
 		<< Option<CaseFractionMandatoryTests>	(DE_NULL,	"deqp-fraction-mandatory-caselist-file",	"Case list file that must be run for each fraction",					"")
@@ -964,6 +966,7 @@
 bool					CommandLine::isShadercacheEnabled			(void) const	{ return m_cmdLine.getOption<opt::ShaderCache>();							}
 const char*				CommandLine::getShaderCacheFilename			(void) const	{ return m_cmdLine.getOption<opt::ShaderCacheFilename>().c_str();			}
 bool					CommandLine::isShaderCacheTruncateEnabled	(void) const	{ return m_cmdLine.getOption<opt::ShaderCacheTruncate>();					}
+bool					CommandLine::isShaderCacheIPCEnabled		(void) const	{ return m_cmdLine.getOption<opt::ShaderCacheIPC>();						}
 int						CommandLine::getOptimizationRecipe			(void) const	{ return m_cmdLine.getOption<opt::Optimization>();							}
 bool					CommandLine::isSpirvOptimizationEnabled		(void) const	{ return m_cmdLine.getOption<opt::OptimizeSpirv>();							}
 bool					CommandLine::isRenderDocEnabled				(void) const	{ return m_cmdLine.getOption<opt::RenderDoc>();								}
diff --git a/framework/common/tcuCommandLine.hpp b/framework/common/tcuCommandLine.hpp
index f845789..1701c39 100644
--- a/framework/common/tcuCommandLine.hpp
+++ b/framework/common/tcuCommandLine.hpp
@@ -247,6 +247,9 @@
 	//! Should the shader cache be truncated before run (--deqp-shadercache-truncate)
 	bool							isShaderCacheTruncateEnabled	(void) const;
 
+	//! Should the shader cache use inter process communication (IPC) (--deqp-shadercache-ipc)
+	bool							isShaderCacheIPCEnabled	(void) const;
+
 	//! Get shader optimization recipe (--deqp-optimization-recipe)
 	int								getOptimizationRecipe		(void) const;