There is one FIXME left where I need advice: The treatment of not 8 byte aligned inputs in vkd3d_shader_cache_hash_key(), in particular regarding endianness. I think it is fairly theoretical as there are few big endian systems left that matter (and I don't have any), but I am not even sure if I am treating this right on little endian machines.
From: Stefan Dösinger stefan@codeweavers.com
---
No flags parameter for put and get for now. The internal use I had (a flag to prevent replacing existing values) is actually the default of native ID3D12ShaderCacheSession and for the time being fine for internal uses. We should probably add a flags parameter before making the API public. --- include/vkd3d_types.h | 2 + libs/vkd3d-common/error.c | 2 + libs/vkd3d/cache.c | 144 +++++++++++++++++++++++++++++++++++++ libs/vkd3d/device.c | 16 ++++- libs/vkd3d/vkd3d_private.h | 2 + 5 files changed, 163 insertions(+), 3 deletions(-)
diff --git a/include/vkd3d_types.h b/include/vkd3d_types.h index 9060fa087..4334f28f0 100644 --- a/include/vkd3d_types.h +++ b/include/vkd3d_types.h @@ -53,6 +53,8 @@ enum vkd3d_result VKD3D_ERROR_INVALID_SHADER = -4, /** The operation is not implemented in this version of vkd3d. */ VKD3D_ERROR_NOT_IMPLEMENTED = -5, + /** The key already exists in the cache. */ + VKD3D_ERROR_KEY_ALREADY_EXISTS = -6,
VKD3D_FORCE_32_BIT_ENUM(VKD3D_RESULT), }; diff --git a/libs/vkd3d-common/error.c b/libs/vkd3d-common/error.c index b8350a540..a6efeddec 100644 --- a/libs/vkd3d-common/error.c +++ b/libs/vkd3d-common/error.c @@ -35,6 +35,8 @@ HRESULT hresult_from_vkd3d_result(int vkd3d_result) return E_INVALIDARG; case VKD3D_ERROR_NOT_IMPLEMENTED: return E_NOTIMPL; + case VKD3D_ERROR_KEY_ALREADY_EXISTS: + return DXGI_ERROR_ALREADY_EXISTS; default: FIXME("Unhandled vkd3d result %d.\n", vkd3d_result); return E_FAIL; diff --git a/libs/vkd3d/cache.c b/libs/vkd3d/cache.c index 56ba69904..1ff567e9b 100644 --- a/libs/vkd3d/cache.c +++ b/libs/vkd3d/cache.c @@ -18,11 +18,64 @@
#include "vkd3d_private.h"
+struct vkd3d_cache_entry_header +{ + uint64_t hash; + uint64_t key_size; + uint64_t value_size; +}; + struct vkd3d_shader_cache { unsigned int refcount; + struct rb_tree tree; +}; + +struct shader_cache_entry +{ + struct vkd3d_cache_entry_header h; + struct rb_entry entry; + uint8_t *payload; };
+struct shader_cache_key +{ + uint64_t hash; + const void *key; + uint64_t key_size; +}; + +static int vkd3d_shader_cache_compare_key(const void *key, const struct rb_entry *entry) +{ + const struct shader_cache_entry *e = RB_ENTRY_VALUE(entry, struct shader_cache_entry, entry); + const struct shader_cache_key *k = key; + int ret; + + if (k->hash < e->h.hash) + return -1; + if (k->hash > e->h.hash) + return 1; + + if (k->key_size < e->h.key_size) + return -1; + if (k->key_size > e->h.key_size) + return 1; + + /* Until now we have not seen an actual hash collission. If the key didn't match it was always + * due to a bug in the serialization code or memory corruption. If you see this FIXME please + * investigate. */ + ret = memcmp(k->key, e->payload, k->key_size); + if (ret) + FIXME("Actual case of a hash collission found.\n"); + return ret; +} + +static void vkd3d_shader_cache_add_item(struct vkd3d_shader_cache *cache, + struct shader_cache_entry *e) +{ + rb_put(&cache->tree, &e->h.hash, &e->entry); +} + int vkd3d_shader_open_cache(struct vkd3d_shader_cache **cache) { struct vkd3d_shader_cache *object; @@ -34,6 +87,8 @@ int vkd3d_shader_open_cache(struct vkd3d_shader_cache **cache) return VKD3D_ERROR_OUT_OF_MEMORY;
object->refcount = 1; + rb_init(&object->tree, vkd3d_shader_cache_compare_key); + *cache = object;
return VKD3D_OK; @@ -46,6 +101,13 @@ unsigned int vkd3d_shader_cache_incref(struct vkd3d_shader_cache *cache) return refcount; }
+static void vkd3d_shader_cache_clear(struct rb_entry *entry, void *context) +{ + struct shader_cache_entry *e = RB_ENTRY_VALUE(entry, struct shader_cache_entry, entry); + vkd3d_free(e->payload); + vkd3d_free(e); +} + unsigned int vkd3d_shader_cache_decref(struct vkd3d_shader_cache *cache) { unsigned int refcount = vkd3d_atomic_decrement_u32(&cache->refcount); @@ -54,6 +116,88 @@ unsigned int vkd3d_shader_cache_decref(struct vkd3d_shader_cache *cache) if (refcount) return refcount;
+ rb_destroy(&cache->tree, vkd3d_shader_cache_clear, NULL); + vkd3d_free(cache); return 0; } + +static const uint64_t fnv_offset = 0xcbf29ce484222325; +static const uint64_t fnv_prime = 0x00000100000001b3; + +static inline uint64_t fnv_1a_64(const uint64_t *values, size_t count, uint64_t seed) +{ + size_t i; + + for (i = 0; i < count; ++i) + seed = (seed ^ values[i]) * fnv_prime; + + return seed; +} + +static uint64_t vkd3d_shader_cache_hash_key(const void *key, size_t size) +{ + uint64_t last = 0, ret; + + ret = fnv_1a_64(key, size / sizeof(uint64_t), fnv_offset); + if (size % sizeof(uint64_t)) + { + const char *c = key; + /* FIXME: Endianess? */ + c += align(size, sizeof(uint64_t)) - sizeof(uint64_t); + memcpy(&last, c, size % sizeof(uint64_t)); + ret = fnv_1a_64(&last, 1, ret); + } + return ret; +} + +int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, + const void *key, size_t key_size, const void *value, size_t value_size) +{ + struct shader_cache_entry *e; + struct shader_cache_key k; + struct rb_entry *entry; + enum vkd3d_result ret; + + TRACE("%p, %p, %#zx, %p, %#zx.\n", cache, key, key_size, value, value_size); + + k.hash = vkd3d_shader_cache_hash_key(key, key_size); + k.key = key; + k.key_size = key_size; + entry = rb_get(&cache->tree, &k); + e = entry ? RB_ENTRY_VALUE(entry, struct shader_cache_entry, entry) : NULL; + + if (e) + { + WARN("Key already exists, returning VKD3D_ERROR_KEY_ALREADY_EXISTS.\n"); + ret = VKD3D_ERROR_KEY_ALREADY_EXISTS; + goto done; + } + + e = vkd3d_malloc(sizeof(*e)); + if (!e) + { + ret = VKD3D_ERROR_OUT_OF_MEMORY; + goto done; + } + e->payload = vkd3d_malloc(key_size + value_size); + if (!e->payload) + { + vkd3d_free(e); + ret = VKD3D_ERROR_OUT_OF_MEMORY; + goto done; + } + + e->h.key_size = key_size; + e->h.value_size = value_size; + e->h.hash = k.hash; + memcpy(e->payload, key, key_size); + memcpy(e->payload + key_size, value, value_size); + + vkd3d_shader_cache_add_item(cache, e); + TRACE("Cache item %#"PRIx64" stored.\n", k.hash); + ret = VKD3D_OK; + +done: + return ret; +} diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index 90de27c53..4f88f6e6f 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -2674,10 +2674,20 @@ static HRESULT STDMETHODCALLTYPE d3d12_cache_session_FindValue(ID3D12ShaderCache static HRESULT STDMETHODCALLTYPE d3d12_cache_session_StoreValue(ID3D12ShaderCacheSession *iface, const void *key, UINT key_size, const void *value, UINT value_size) { - FIXME("iface %p, key %p, key_size %#x, value %p, value_size %u stub!\n", iface, key, key_size, - value, value_size); + struct d3d12_cache_session *session = impl_from_ID3D12ShaderCacheSession(iface); + enum vkd3d_result ret;
- return E_NOTIMPL; + TRACE("iface %p, key %p, key_size %#x, value %p, value_size %u.\n", + iface, key, key_size, value, value_size); + + if (!key || !key_size || !value || !value_size) + { + WARN("Invalid input parameters, returning E_INVALIDARG.\n"); + return E_INVALIDARG; + } + + ret = vkd3d_shader_cache_put(session->cache, key, key_size, value, value_size); + return hresult_from_vkd3d_result(ret); }
static void STDMETHODCALLTYPE d3d12_cache_session_SetDeleteOnDestroy(ID3D12ShaderCacheSession *iface) diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h index 91ffa6e98..9d8658608 100644 --- a/libs/vkd3d/vkd3d_private.h +++ b/libs/vkd3d/vkd3d_private.h @@ -1932,5 +1932,7 @@ struct vkd3d_shader_cache; int vkd3d_shader_open_cache(struct vkd3d_shader_cache **cache); unsigned int vkd3d_shader_cache_incref(struct vkd3d_shader_cache *cache); unsigned int vkd3d_shader_cache_decref(struct vkd3d_shader_cache *cache); +int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, + const void *key, size_t key_size, const void *value, size_t value_size);
#endif /* __VKD3D_PRIVATE_H */
From: Stefan Dösinger stefan@codeweavers.com
--- include/vkd3d_types.h | 4 +++ libs/vkd3d-common/error.c | 4 +++ libs/vkd3d/cache.c | 51 ++++++++++++++++++++++++++++++++++++++ libs/vkd3d/device.c | 18 ++++++++++++-- libs/vkd3d/vkd3d_private.h | 2 ++ 5 files changed, 77 insertions(+), 2 deletions(-)
diff --git a/include/vkd3d_types.h b/include/vkd3d_types.h index 4334f28f0..2bab30fc8 100644 --- a/include/vkd3d_types.h +++ b/include/vkd3d_types.h @@ -55,6 +55,10 @@ enum vkd3d_result VKD3D_ERROR_NOT_IMPLEMENTED = -5, /** The key already exists in the cache. */ VKD3D_ERROR_KEY_ALREADY_EXISTS = -6, + /** The requested shader cache key was not found. */ + VKD3D_ERROR_NOT_FOUND = -7, + /** The requested shader cache value was bigger than the passed buffer. */ + VKD3D_ERROR_MORE_DATA = -8,
VKD3D_FORCE_32_BIT_ENUM(VKD3D_RESULT), }; diff --git a/libs/vkd3d-common/error.c b/libs/vkd3d-common/error.c index a6efeddec..2f978c497 100644 --- a/libs/vkd3d-common/error.c +++ b/libs/vkd3d-common/error.c @@ -37,6 +37,10 @@ HRESULT hresult_from_vkd3d_result(int vkd3d_result) return E_NOTIMPL; case VKD3D_ERROR_KEY_ALREADY_EXISTS: return DXGI_ERROR_ALREADY_EXISTS; + case VKD3D_ERROR_NOT_FOUND: + return DXGI_ERROR_NOT_FOUND; + case VKD3D_ERROR_MORE_DATA: + return DXGI_ERROR_MORE_DATA; default: FIXME("Unhandled vkd3d result %d.\n", vkd3d_result); return E_FAIL; diff --git a/libs/vkd3d/cache.c b/libs/vkd3d/cache.c index 1ff567e9b..458067d0f 100644 --- a/libs/vkd3d/cache.c +++ b/libs/vkd3d/cache.c @@ -201,3 +201,54 @@ int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, done: return ret; } + +int vkd3d_shader_cache_get(struct vkd3d_shader_cache *cache, + const void *key, size_t key_size, void *value, size_t *value_size) +{ + struct shader_cache_entry *e; + struct shader_cache_key k; + struct rb_entry *entry; + enum vkd3d_result ret; + size_t size_in; + + TRACE("%p, %p, %#zx, %p, %p.\n", cache, key, key_size, value, value_size); + + size_in = *value_size; + + k.hash = vkd3d_shader_cache_hash_key(key, key_size); + k.key = key; + k.key_size = key_size; + entry = rb_get(&cache->tree, &k); + if (!entry) + { + WARN("Entry not found.\n"); + ret = VKD3D_ERROR_NOT_FOUND; + goto done; + } + + e = RB_ENTRY_VALUE(entry, struct shader_cache_entry, entry); + + *value_size = e->h.value_size; + if (!value) + { + TRACE("Found item %#"PRIx64", returning needed size %#"PRIx64".\n", + e->h.hash, e->h.value_size); + ret = VKD3D_OK; + goto done; + } + + if (size_in < e->h.value_size) + { + WARN("Output buffer is too small for item %#"PRIx64", got %#zx want %#"PRIx64".\n", + e->h.hash, size_in, e->h.value_size); + ret = VKD3D_ERROR_MORE_DATA; + goto done; + } + + memcpy(value, e->payload + e->h.key_size, e->h.value_size); + ret = VKD3D_OK; + TRACE("Returning cached item %#"PRIx64".\n", e->h.hash); + +done: + return ret; +} diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index 4f88f6e6f..6e8836eee 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -2665,10 +2665,24 @@ static HRESULT STDMETHODCALLTYPE d3d12_cache_session_GetDevice(ID3D12ShaderCache static HRESULT STDMETHODCALLTYPE d3d12_cache_session_FindValue(ID3D12ShaderCacheSession *iface, const void *key, UINT key_size, void *value, UINT *value_size) { - FIXME("iface %p, key %p, key_size %#x, value %p, value_size %p stub!\n", + struct d3d12_cache_session *session = impl_from_ID3D12ShaderCacheSession(iface); + enum vkd3d_result ret; + size_t size; + + TRACE("iface %p, key %p, key_size %x, value %p, value_size %p.\n", iface, key, key_size, value, value_size);
- return DXGI_ERROR_NOT_FOUND; + if (!value_size) + { + WARN("value_size is NULL, returning E_INVALIDARG.\n"); + return E_INVALIDARG; + } + + size = *value_size; + ret = vkd3d_shader_cache_get(session->cache, key, key_size, value, &size); + *value_size = size; + + return hresult_from_vkd3d_result(ret); }
static HRESULT STDMETHODCALLTYPE d3d12_cache_session_StoreValue(ID3D12ShaderCacheSession *iface, diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h index 9d8658608..56558bac7 100644 --- a/libs/vkd3d/vkd3d_private.h +++ b/libs/vkd3d/vkd3d_private.h @@ -1934,5 +1934,7 @@ unsigned int vkd3d_shader_cache_incref(struct vkd3d_shader_cache *cache); unsigned int vkd3d_shader_cache_decref(struct vkd3d_shader_cache *cache); int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, const void *key, size_t key_size, const void *value, size_t value_size); +int vkd3d_shader_cache_get(struct vkd3d_shader_cache *cache, + const void *key, size_t key_size, void *value, size_t *value_size);
#endif /* __VKD3D_PRIVATE_H */
From: Stefan Dösinger stefan@codeweavers.com
---
The is no VKD3D_SHADER_CACHE_FLAGS_NO_SERIALIZE flag for now because we don't have internal use for it yet. I think we should add it before making the API public. --- include/private/vkd3d_common.h | 16 ++++++++++++++++ include/vkd3d_types.h | 2 ++ libs/vkd3d/cache.c | 28 ++++++++++++++++++++++++++++ libs/vkd3d/device.c | 11 +++++++++-- 4 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/include/private/vkd3d_common.h b/include/private/vkd3d_common.h index 10e2afd5d..4b339cd4a 100644 --- a/include/private/vkd3d_common.h +++ b/include/private/vkd3d_common.h @@ -32,6 +32,7 @@ #include <stdlib.h> #ifndef _WIN32 #include <pthread.h> +#include <errno.h> #endif
#ifdef _MSC_VER @@ -468,6 +469,21 @@ static inline void vkd3d_mutex_lock(struct vkd3d_mutex *lock) #endif }
+static inline bool vkd3d_mutex_trylock(struct vkd3d_mutex *lock) +{ +#ifdef _WIN32 + return TryEnterCriticalSection(&lock->lock); +#else + int ret; + + ret = pthread_mutex_trylock(&lock->lock); + if (ret && errno != EBUSY) + ERR("Could not lock the mutex, error %d.\n", ret); + + return !ret; +#endif +} + static inline void vkd3d_mutex_unlock(struct vkd3d_mutex *lock) { #ifdef _WIN32 diff --git a/include/vkd3d_types.h b/include/vkd3d_types.h index 2bab30fc8..de9248a67 100644 --- a/include/vkd3d_types.h +++ b/include/vkd3d_types.h @@ -59,6 +59,8 @@ enum vkd3d_result VKD3D_ERROR_NOT_FOUND = -7, /** The requested shader cache value was bigger than the passed buffer. */ VKD3D_ERROR_MORE_DATA = -8, + /** The cache lock is contended. */ + VKD3D_ERROR_LOCK_NOT_AVAILABLE = -9,
VKD3D_FORCE_32_BIT_ENUM(VKD3D_RESULT), }; diff --git a/libs/vkd3d/cache.c b/libs/vkd3d/cache.c index 458067d0f..5c1e9e658 100644 --- a/libs/vkd3d/cache.c +++ b/libs/vkd3d/cache.c @@ -28,6 +28,8 @@ struct vkd3d_cache_entry_header struct vkd3d_shader_cache { unsigned int refcount; + struct vkd3d_mutex lock; + struct rb_tree tree; };
@@ -88,6 +90,7 @@ int vkd3d_shader_open_cache(struct vkd3d_shader_cache **cache)
object->refcount = 1; rb_init(&object->tree, vkd3d_shader_cache_compare_key); + vkd3d_mutex_init(&object->lock);
*cache = object;
@@ -117,6 +120,7 @@ unsigned int vkd3d_shader_cache_decref(struct vkd3d_shader_cache *cache) return refcount;
rb_destroy(&cache->tree, vkd3d_shader_cache_clear, NULL); + vkd3d_mutex_destroy(&cache->lock);
vkd3d_free(cache); return 0; @@ -151,6 +155,16 @@ static uint64_t vkd3d_shader_cache_hash_key(const void *key, size_t size) return ret; }
+static bool vkd3d_shader_cache_trylock(struct vkd3d_shader_cache *cache) +{ + return vkd3d_mutex_trylock(&cache->lock); +} + +static void vkd3d_shader_cache_unlock(struct vkd3d_shader_cache *cache) +{ + vkd3d_mutex_unlock(&cache->lock); +} + int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, const void *key, size_t key_size, const void *value, size_t value_size) { @@ -161,6 +175,12 @@ int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache,
TRACE("%p, %p, %#zx, %p, %#zx.\n", cache, key, key_size, value, value_size);
+ if (!vkd3d_shader_cache_trylock(cache)) + { + WARN("Cache lock not available.\n"); + return VKD3D_ERROR_LOCK_NOT_AVAILABLE; + } + k.hash = vkd3d_shader_cache_hash_key(key, key_size); k.key = key; k.key_size = key_size; @@ -199,6 +219,7 @@ int vkd3d_shader_cache_put(struct vkd3d_shader_cache *cache, ret = VKD3D_OK;
done: + vkd3d_shader_cache_unlock(cache); return ret; }
@@ -213,6 +234,12 @@ int vkd3d_shader_cache_get(struct vkd3d_shader_cache *cache,
TRACE("%p, %p, %#zx, %p, %p.\n", cache, key, key_size, value, value_size);
+ if (!vkd3d_shader_cache_trylock(cache)) + { + WARN("Cache lock not available.\n"); + return VKD3D_ERROR_LOCK_NOT_AVAILABLE; + } + size_in = *value_size;
k.hash = vkd3d_shader_cache_hash_key(key, key_size); @@ -250,5 +277,6 @@ int vkd3d_shader_cache_get(struct vkd3d_shader_cache *cache, TRACE("Returning cached item %#"PRIx64".\n", e->h.hash);
done: + vkd3d_shader_cache_unlock(cache); return ret; } diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c index 6e8836eee..ba210b372 100644 --- a/libs/vkd3d/device.c +++ b/libs/vkd3d/device.c @@ -2679,7 +2679,10 @@ static HRESULT STDMETHODCALLTYPE d3d12_cache_session_FindValue(ID3D12ShaderCache }
size = *value_size; - ret = vkd3d_shader_cache_get(session->cache, key, key_size, value, &size); + do + { + ret = vkd3d_shader_cache_get(session->cache, key, key_size, value, &size); + } while (ret == VKD3D_ERROR_LOCK_NOT_AVAILABLE); *value_size = size;
return hresult_from_vkd3d_result(ret); @@ -2700,7 +2703,11 @@ static HRESULT STDMETHODCALLTYPE d3d12_cache_session_StoreValue(ID3D12ShaderCach return E_INVALIDARG; }
- ret = vkd3d_shader_cache_put(session->cache, key, key_size, value, value_size); + do + { + ret = vkd3d_shader_cache_put(session->cache, key, key_size, value, value_size); + } while (ret == VKD3D_ERROR_LOCK_NOT_AVAILABLE); + return hresult_from_vkd3d_result(ret); }
From: Stefan Dösinger stefan@codeweavers.com
--- tests/d3d12.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-)
diff --git a/tests/d3d12.c b/tests/d3d12.c index aaea5e316..f5c55e0c2 100644 --- a/tests/d3d12.c +++ b/tests/d3d12.c @@ -38325,9 +38325,9 @@ static void test_unused_interpolated_input(void)
static void test_shader_cache(void) { + unsigned int refcount, base_refcount, value_size; ID3D12ShaderCacheSession *session, *session2; D3D12_SHADER_CACHE_SESSION_DESC desc = {0}; - unsigned int refcount, base_refcount; struct test_context context; ID3D12Device9 *device; IUnknown *unk, *unk2; @@ -38336,6 +38336,13 @@ static void test_shader_cache(void) static const GUID test_guid = {0xfdb37466, 0x428f, 0x4edf, {0xa3, 0x7f, 0x9b, 0x1d, 0xf4, 0x88, 0xc5, 0xfc}};
+ static const char key1[] = "1234567890"; + static const char key2[] = "ABCDEFGHIJ"; + char blob1[20 * 1024]; + char blob2[20 * 1024]; + char blob3[30 * 1024]; + char blob4[40 * 1024]; + if (!init_test_context(&context, NULL)) return;
@@ -38510,6 +38517,159 @@ static void test_shader_cache(void) ok(session2 != session, "Expected different interface pointers, got %p for both.\n", session); ID3D12ShaderCacheSession_Release(session2); + + memset(blob1, '1', sizeof(blob1)); + memset(blob2, '2', sizeof(blob2)); + memset(blob3, '3', sizeof(blob3)); + memset(blob4, '4', sizeof(blob4)); + + /* Basic store and retrieval. + * + * Adding a key a second time is not allowed (unless the entry has been evicted), but there + * seems to be a bug in native: Despite returning an error and not changing the cache contents, + * the "added" entry counts towards the cache size, filling up the quota. Eventually native + * returns DXGI_ERROR_CACHE_FULL. + * + * To avoid relying on this bug or being hit by it in the eviction tests below, destroy the + * cache and recreate it after verifying the value. */ + hr = ID3D12ShaderCacheSession_StoreValue(session, key1, sizeof(key1), blob1, sizeof(blob1)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key1, sizeof(key1), blob1, sizeof(blob1)); + ok(hr == DXGI_ERROR_ALREADY_EXISTS, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key1, sizeof(key1), blob2, sizeof(blob2)); + ok(hr == DXGI_ERROR_ALREADY_EXISTS, "Got unexpected hr %#x.\n", hr); + + value_size = sizeof(blob3); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), blob3, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob1), "Got unexpected size %x.\n", value_size); + ok(!memcmp(blob3, blob1, sizeof(blob1)), "Unexpected value retrieved.\n"); + ok(blob3[sizeof(blob1)] == '3', "Output buffer was modified beyond the stored value.\n"); + memset(blob3, '3', sizeof(blob3)); + + ID3D12ShaderCacheSession_Release(session); + hr = ID3D12Device9_CreateShaderCacheSession(device, &desc, + &IID_ID3D12ShaderCacheSession, (void **)&session); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key1, sizeof(key1), blob1, sizeof(blob1)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + /* Try to retrieve the value from another session sharing the same name. */ + hr = ID3D12Device9_CreateShaderCacheSession(device, &desc, + &IID_ID3D12ShaderCacheSession, (void **)&session2); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + hr = ID3D12ShaderCacheSession_FindValue(session2, key1, sizeof(key1), blob3, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob1), "Got unexpected size %x.\n", value_size); + ok(!memcmp(blob3, blob1, sizeof(blob1)), "Unexpected value retrieved.\n"); + ok(blob3[sizeof(blob1)] == '3', "Output buffer was modified beyond the stored value.\n"); + hr = ID3D12ShaderCacheSession_FindValue(session2, key1, sizeof(key1), blob3, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + ID3D12ShaderCacheSession_Release(session2); + + /* value_size must be set. */ + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), NULL, NULL); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), blob3, NULL); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), NULL, NULL); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), blob3, NULL); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + + /* Test how the output size is handled. - A NULL data ptr returns S_OK and sets + * *value_size to the required size. */ + value_size = 0; + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob1), "Got unexpected size %x.\n", value_size); + value_size = sizeof(blob1) + 1; + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob1), "Got unexpected size %x.\n", value_size); + /* It remains untouched if the item was not found. */ + value_size = 0xdeadbeef; + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), NULL, &value_size); + ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr); + ok(value_size == 0xdeadbeef, "Got unexpected size %x.\n", value_size); + /* If a too small data pointer is passed, *value_size is set, but no data is written. */ + value_size = sizeof(blob1) - 1; + memset(blob3, 'C', sizeof(blob3)); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), blob3, &value_size); + ok(hr == DXGI_ERROR_MORE_DATA, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob1), "Got unexpected size %x.\n", value_size); + ok(blob3[0] == 'C', "Output buffer was modified.\n"); + + /* Test evicition due to lack of room in the cache. */ + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, sizeof(key2), blob2, sizeof(blob2)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + value_size = sizeof(blob3); + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), blob3, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(blob2), "Got unexpected size %x.\n", value_size); + value_size = sizeof(blob3); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), blob3, &value_size); + todo ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr); + + /* The full key is stored as well. Use a huge key for a small value. It counts towards the + * size and will evict existing data. */ + hr = ID3D12ShaderCacheSession_StoreValue(session, blob1, sizeof(blob1), &desc, sizeof(desc)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + value_size = 0xdeadbeef; + hr = ID3D12ShaderCacheSession_FindValue(session, blob1, sizeof(blob1), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + ok(value_size == sizeof(desc), "Got unexpected size %x.\n", value_size); + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), NULL, &value_size); + todo ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr); + + /* Keys are not truncated. */ + blob1[sizeof(blob1) - 1] = 'X'; + hr = ID3D12ShaderCacheSession_FindValue(session, blob1, sizeof(blob1), NULL, &value_size); + ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr); + /* Make sure the huge key was not accidentally evicted. */ + blob1[sizeof(blob1) - 1] = '1'; + hr = ID3D12ShaderCacheSession_FindValue(session, blob1, sizeof(blob1), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + /* Reset the cache so we don't get collisions on 'key1' if eviction failed. */ + ID3D12ShaderCacheSession_Release(session); + desc.Mode = D3D12_SHADER_CACHE_MODE_MEMORY; + hr = ID3D12Device9_CreateShaderCacheSession(device, &desc, + &IID_ID3D12ShaderCacheSession, (void **)&session); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + /* Store a blob that is too big. It goes in at first but gets evicted next time. + * + * Why are we not getting DXGI_ERROR_CACHE_FULL here? I don't know. */ + hr = ID3D12ShaderCacheSession_StoreValue(session, key1, sizeof(key1), blob4, sizeof(blob4)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, sizeof(key2), &desc, sizeof(desc)); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key1, sizeof(key1), NULL, &value_size); + todo ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_FindValue(session, key2, sizeof(key2), NULL, &value_size); + ok(hr == S_OK, "Got unexpected hr %#x.\n", hr); + + /* Can we delete specific entries? It doesn't look like it. In fact, anything with a zero length + * or pointer is invalid. There is device::ShaderCacheControl with CONTROL_FLAG_CLEAR and + * SetDeleteOnDestroy, but those nuke the entire cache, not single entries. */ + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, sizeof(key2), NULL, 0); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, sizeof(key2), &desc, 0); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, sizeof(key2), NULL, sizeof(desc)); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, NULL, sizeof(key2), &desc, sizeof(desc)); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, key2, 0, &desc, 1); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + hr = ID3D12ShaderCacheSession_StoreValue(session, NULL, sizeof(key2), &desc, sizeof(desc)); + ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr); + ID3D12ShaderCacheSession_Release(session);
ID3D12Device9_Release(device);