gallium/util: add a cache of live shaders for shader CSO deduplication
authorMarek Olšák <marek.olsak@amd.com>
Sat, 30 Nov 2019 02:01:19 +0000 (21:01 -0500)
committerMarek Olšák <marek.olsak@amd.com>
Sat, 25 Jan 2020 01:29:29 +0000 (20:29 -0500)
Reviewed-by: Pierre-Eric Pelloux-Prayer <pierre-eric.pelloux-prayer@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/merge_requests/2929>

src/gallium/auxiliary/Makefile.sources
src/gallium/auxiliary/meson.build
src/gallium/auxiliary/util/u_live_shader_cache.c [new file with mode: 0644]
src/gallium/auxiliary/util/u_live_shader_cache.h [new file with mode: 0644]

index d31f71aaf99e933505ec26e0034adfe4decd855e..2cba524f2bcca1679ea2e5c4ea0f185d40d247b8 100644 (file)
@@ -262,6 +262,8 @@ C_SOURCES := \
        util/u_inlines.h \
        util/u_linear.c \
        util/u_linear.h \
+       util/u_live_shader_cache.c \
+       util/u_live_shader_cache.h \
        util/u_log.c \
        util/u_log.h \
        util/u_network.c \
index d9e91216211518b2bd8d6b193d8980f645346c64..c36f30837392c2a138e07209ea55f94a9286249c 100644 (file)
@@ -282,6 +282,8 @@ files_libgallium = files(
   'util/u_inlines.h',
   'util/u_linear.c',
   'util/u_linear.h',
+  'util/u_live_shader_cache.c',
+  'util/u_live_shader_cache.h',
   'util/u_log.c',
   'util/u_log.h',
   'util/u_network.c',
diff --git a/src/gallium/auxiliary/util/u_live_shader_cache.c b/src/gallium/auxiliary/util/u_live_shader_cache.c
new file mode 100644 (file)
index 0000000..9299348
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2019 Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * 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
+ * THE AUTHOR(S) 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 "util/u_live_shader_cache.h"
+
+#include "util/u_inlines.h"
+#include "tgsi/tgsi_from_mesa.h"
+#include "tgsi/tgsi_parse.h"
+
+#include "compiler/nir/nir_serialize.h"
+
+#include "util/blob.h"
+#include "util/hash_table.h"
+#include "util/mesa-sha1.h"
+
+static uint32_t key_hash(const void *key)
+{
+   /* Take the first dword of SHA1. */
+   return *(uint32_t*)key;
+}
+
+static bool key_equals(const void *a, const void *b)
+{
+   /* Compare SHA1s. */
+   return memcmp(a, b, 20) == 0;
+}
+
+void
+util_live_shader_cache_init(struct util_live_shader_cache *cache,
+                            void *(*create_shader)(struct pipe_context *,
+                                                   const struct pipe_shader_state *state),
+                            void (*destroy_shader)(struct pipe_context *, void *))
+{
+   simple_mtx_init(&cache->lock, mtx_plain);
+   cache->hashtable = _mesa_hash_table_create(NULL, key_hash, key_equals);
+   cache->create_shader = create_shader;
+   cache->destroy_shader = destroy_shader;
+}
+
+void
+util_live_shader_cache_deinit(struct util_live_shader_cache *cache)
+{
+   if (cache->hashtable) {
+      /* The hash table should be empty at this point. */
+      _mesa_hash_table_destroy(cache->hashtable, NULL);
+      simple_mtx_destroy(&cache->lock);
+   }
+}
+
+void *
+util_live_shader_cache_get(struct pipe_context *ctx,
+                           struct util_live_shader_cache *cache,
+                           const struct pipe_shader_state *state)
+{
+   struct blob blob = {0};
+   unsigned ir_size;
+   const void *ir_binary;
+   enum pipe_shader_type stage;
+
+   /* Get the shader binary and shader stage. */
+   if (state->type == PIPE_SHADER_IR_TGSI) {
+      ir_binary = state->tokens;
+      ir_size = tgsi_num_tokens(state->tokens) *
+                sizeof(struct tgsi_token);
+      stage = tgsi_get_processor_type(state->tokens);
+   } else if (state->type == PIPE_SHADER_IR_NIR) {
+      blob_init(&blob);
+      nir_serialize(&blob, state->ir.nir, true);
+      ir_binary = blob.data;
+      ir_size = blob.size;
+      stage = pipe_shader_type_from_mesa(((nir_shader*)state->ir.nir)->info.stage);
+   } else {
+      assert(0);
+      return NULL;
+   }
+
+   /* Compute SHA1 of pipe_shader_state. */
+   struct mesa_sha1 sha1_ctx;
+   unsigned char sha1[20];
+   _mesa_sha1_init(&sha1_ctx);
+   _mesa_sha1_update(&sha1_ctx, ir_binary, ir_size);
+   if ((stage == PIPE_SHADER_VERTEX ||
+        stage == PIPE_SHADER_TESS_EVAL ||
+        stage == PIPE_SHADER_GEOMETRY) &&
+       state->stream_output.num_outputs) {
+      _mesa_sha1_update(&sha1_ctx, &state->stream_output,
+                        sizeof(state->stream_output));
+   }
+   _mesa_sha1_final(&sha1_ctx, sha1);
+
+   if (ir_binary == blob.data)
+      blob_finish(&blob);
+
+   /* Find the shader in the live cache. */
+   simple_mtx_lock(&cache->lock);
+   struct hash_entry *entry = _mesa_hash_table_search(cache->hashtable, sha1);
+   struct util_live_shader *shader = entry ? entry->data : NULL;
+
+   /* Increase the refcount. */
+   if (shader)
+      pipe_reference(NULL, &shader->reference);
+   simple_mtx_unlock(&cache->lock);
+
+   /* Return if the shader already exists. */
+   if (shader)
+      return shader;
+
+   /* The cache mutex is unlocked to allow multiple create_shader
+    * invocations to run simultaneously.
+    */
+   shader = (struct util_live_shader*)cache->create_shader(ctx, state);
+   pipe_reference_init(&shader->reference, 1);
+   memcpy(shader->sha1, sha1, sizeof(sha1));
+
+   simple_mtx_lock(&cache->lock);
+   /* The same shader might have been created in parallel. This is rare.
+    * If so, keep the one already in cache.
+    */
+   struct hash_entry *entry2 = _mesa_hash_table_search(cache->hashtable, sha1);
+   struct util_live_shader *shader2 = entry2 ? entry2->data : NULL;
+
+   if (shader2) {
+      cache->destroy_shader(ctx, shader);
+      shader = shader2;
+      /* Increase the refcount. */
+      pipe_reference(NULL, &shader->reference);
+   } else {
+      _mesa_hash_table_insert(cache->hashtable, shader->sha1, shader);
+   }
+   simple_mtx_unlock(&cache->lock);
+
+   return shader;
+}
+
+void
+util_shader_reference(struct pipe_context *ctx,
+                      struct util_live_shader_cache *cache,
+                      void **dst, void *src)
+{
+   if (*dst == src)
+      return;
+
+   struct util_live_shader *dst_shader = (struct util_live_shader*)*dst;
+   struct util_live_shader *src_shader = (struct util_live_shader*)src;
+
+   simple_mtx_lock(&cache->lock);
+   bool destroy = pipe_reference(&dst_shader->reference, &src_shader->reference);
+   if (destroy) {
+      struct hash_entry *entry = _mesa_hash_table_search(cache->hashtable,
+                                                         dst_shader->sha1);
+      assert(entry);
+      _mesa_hash_table_remove(cache->hashtable, entry);
+   }
+   simple_mtx_unlock(&cache->lock);
+
+   if (destroy)
+      cache->destroy_shader(ctx, dst_shader);
+
+   *dst = src;
+}
diff --git a/src/gallium/auxiliary/util/u_live_shader_cache.h b/src/gallium/auxiliary/util/u_live_shader_cache.h
new file mode 100644 (file)
index 0000000..9ee0803
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * 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
+ * THE AUTHOR(S) 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.
+ */
+
+/* This deduplicates live shader CSOs, meaning that creating 2 shaders with
+ * the same IR will return the same CSO.
+ *
+ * How to use this:
+ *
+ * - create_xx_state should only call util_live_shader_cache_get.
+ *
+ * - delete_xx_state should only call util_shader_reference(&shader, NULL).
+ *   This will decrease the reference count.
+ *
+ * - Driver shaders must inherit util_live_shader. They don't have to
+ *   initialize it.
+ *
+ * - Declare struct util_live_shader_cache in your pipe_screen (no pointer) if
+ *   you support shareable shaders. If not, declare it in your pipe_context.
+ *
+ * - Set your create_shader and destroy_shader driver callbacks with
+ *   util_live_shader_cache_init. These are your driver versions of
+ *   create_xx_state and delete_xx_state. There is no distinction between
+ *   vs, tcs, tes, gs, fs. Instead, get the shader type from the IR.
+ *
+ * - Call util_live_shader_cache_deinit when you destroy your screen or context.
+ */
+
+#ifndef U_LIVE_SHADER_CACHE_H
+#define U_LIVE_SHADER_CACHE_H
+
+#include "util/simple_mtx.h"
+#include "pipe/p_state.h"
+
+struct util_live_shader_cache {
+   simple_mtx_t lock;
+   struct hash_table *hashtable;
+
+   void *(*create_shader)(struct pipe_context *,
+                          const struct pipe_shader_state *state);
+   void (*destroy_shader)(struct pipe_context *, void *);
+};
+
+struct util_live_shader {
+   struct pipe_reference reference;
+   unsigned char sha1[20];
+};
+
+void
+util_live_shader_cache_init(struct util_live_shader_cache *cache,
+                              void *(*create_shader)(struct pipe_context *,
+                                                     const struct pipe_shader_state *state),
+                              void (*destroy_shader)(struct pipe_context *, void *));
+
+void
+util_live_shader_cache_deinit(struct util_live_shader_cache *cache);
+
+void *
+util_live_shader_cache_get(struct pipe_context *ctx,
+                           struct util_live_shader_cache *cache,
+                           const struct pipe_shader_state *state);
+
+void
+util_shader_reference(struct pipe_context *ctx,
+                      struct util_live_shader_cache *cache,
+                      void **dst, void *src);
+
+#endif