[Mesa-dev] [PATCHv2 08/13] mesa: add infrastructure for threaded shader compilation
Chia-I Wu
olvaffe at gmail.com
Wed Jul 9 00:47:49 PDT 2014
Add _mesa_enable_glsl_threadpool to enable the thread pool for a context, and
add ctx->Const.DeferCompileShader and ctx->Const.DeferLinkProgram to
fine-control what gets threaded.
Setting DeferCompileShader to true will make _mesa_glsl_compile_shader be
executed in a worker thread. The function is thread-safe so there is no
restriction on DeferCompileShader.
Setting DeferLinkProgram to true will make _mesa_glsl_link_shader be executed
in a worker thread. The function is thread-safe only when certain driver
functions (as documented in struct gl_constants) are thread-safe. It is
drivers' responsibility to fix those driver functions before setting
DeferLinkProgram.
When DeferLinkProgram is set, drivers are not supposed to inspect the context
in their LinkShader callbacks. Instead, NotifyLinkShader is added. Drivers
should inspect the context in NotifyLinkShader and save what they need for
LinkShader in gl_shader_program.
As a final note, most applications will not benefit from threaded shader
compilation because they check GL_COMPILE_STATUS/GL_LINK_STATUS immediately,
giving the worker threads no time to do their jobs. A possible improvement is
to split LinkShader into two parts: the first part links and error checks
while the second part optimizes and generates the machine code. With the
split, we can always defer the second part to the thread pool.
Signed-off-by: Chia-I Wu <olv at lunarg.com>
---
src/mesa/main/context.c | 29 +++++++++++
src/mesa/main/context.h | 3 ++
src/mesa/main/dd.h | 8 +++
src/mesa/main/mtypes.h | 34 ++++++++++++
src/mesa/main/pipelineobj.c | 18 +++++++
src/mesa/main/shaderapi.c | 122 +++++++++++++++++++++++++++++++++++++++-----
src/mesa/main/shaderobj.c | 74 +++++++++++++++++++++++++--
src/mesa/main/shaderobj.h | 55 ++++++++++++++++++--
8 files changed, 322 insertions(+), 21 deletions(-)
diff --git a/src/mesa/main/context.c b/src/mesa/main/context.c
index b082159..e27450c 100644
--- a/src/mesa/main/context.c
+++ b/src/mesa/main/context.c
@@ -112,6 +112,7 @@
#include "points.h"
#include "polygon.h"
#include "queryobj.h"
+#include "shaderapi.h"
#include "syncobj.h"
#include "rastpos.h"
#include "remap.h"
@@ -139,6 +140,7 @@
#endif
#include "glsl_parser_extras.h"
+#include "threadpool.h"
#include <stdbool.h>
@@ -1187,6 +1189,27 @@ _mesa_create_context(gl_api api,
}
}
+void
+_mesa_enable_glsl_threadpool(struct gl_context *ctx, int max_threads)
+{
+ if (!ctx->ThreadPool)
+ ctx->ThreadPool = _mesa_glsl_get_threadpool(max_threads);
+}
+
+static void
+wait_shader_object_cb(GLuint id, void *data, void *userData)
+{
+ struct gl_context *ctx = (struct gl_context *) userData;
+ struct gl_shader *sh = (struct gl_shader *) data;
+
+ if (_mesa_validate_shader_target(ctx, sh->Type)) {
+ _mesa_wait_shaders(ctx, &sh, 1);
+ }
+ else {
+ struct gl_shader_program *shProg = (struct gl_shader_program *) data;
+ _mesa_wait_shader_program(ctx, shProg);
+ }
+}
/**
* Free the data associated with the given context.
@@ -1205,6 +1228,12 @@ _mesa_free_context_data( struct gl_context *ctx )
_mesa_make_current(ctx, NULL, NULL);
}
+ if (ctx->ThreadPool) {
+ _mesa_HashWalk(ctx->Shared->ShaderObjects, wait_shader_object_cb, ctx);
+ _mesa_threadpool_unref(ctx->ThreadPool);
+ ctx->ThreadPool = NULL;
+ }
+
/* unreference WinSysDraw/Read buffers */
_mesa_reference_framebuffer(&ctx->WinSysDrawBuffer, NULL);
_mesa_reference_framebuffer(&ctx->WinSysReadBuffer, NULL);
diff --git a/src/mesa/main/context.h b/src/mesa/main/context.h
index 792ab4c..b23f9fa 100644
--- a/src/mesa/main/context.h
+++ b/src/mesa/main/context.h
@@ -118,6 +118,9 @@ _mesa_create_context(gl_api api,
const struct dd_function_table *driverFunctions);
extern void
+_mesa_enable_glsl_threadpool(struct gl_context *ctx, int max_threads);
+
+extern void
_mesa_free_context_data( struct gl_context *ctx );
extern void
diff --git a/src/mesa/main/dd.h b/src/mesa/main/dd.h
index 633ea2c..38f8c68 100644
--- a/src/mesa/main/dd.h
+++ b/src/mesa/main/dd.h
@@ -447,6 +447,14 @@ struct dd_function_table {
*/
/*@{*/
/**
+ * Called when a shader program is to be linked.
+ *
+ * This is optional and gives drivers an opportunity to inspect the context
+ * and prepare for LinkShader, which may be deferred to another thread.
+ */
+ void (*NotifyLinkShader)(struct gl_context *ctx,
+ struct gl_shader_program *shader);
+ /**
* Called when a shader program is linked.
*
* This gives drivers an opportunity to clone the IR and make their
diff --git a/src/mesa/main/mtypes.h b/src/mesa/main/mtypes.h
index 5964576..316da23 100644
--- a/src/mesa/main/mtypes.h
+++ b/src/mesa/main/mtypes.h
@@ -71,6 +71,8 @@ typedef GLuint64 GLbitfield64;
*/
/*@{*/
struct _mesa_HashTable;
+struct _mesa_threadpool;
+struct _mesa_threadpool_task;
struct gl_attrib_node;
struct gl_list_extensions;
struct gl_meta_state;
@@ -2510,6 +2512,14 @@ struct gl_shader
*/
unsigned LocalSize[3];
} Comp;
+
+ /**
+ * Deferred task of glCompileShader. We should extend the mutex, not only
+ * to protect the deferred task, but to protect the entire gl_shader.
+ */
+ mtx_t Mutex;
+ struct _mesa_threadpool_task *Task;
+ void *TaskData;
};
@@ -2770,6 +2780,15 @@ struct gl_shader_program
* #extension ARB_fragment_coord_conventions: enable
*/
GLboolean ARB_fragment_coord_conventions_enable;
+
+ /**
+ * Deferred task of glLinkProgram. We should extend the mutex, not only
+ * to protect the deferred task, but to protect the entire
+ * gl_shader_program.
+ */
+ mtx_t Mutex;
+ struct _mesa_threadpool_task *Task;
+ void *TaskData;
};
@@ -3489,6 +3508,18 @@ struct gl_constants
GLfloat MaxFragmentInterpolationOffset;
GLboolean FakeSWMSAA;
+
+ /*
+ * Defer certain operations to a thread pool.
+ *
+ * When DeferLinkProgram is set, these functions must be thread-safe
+ *
+ * ctx->Driver.NewShader
+ * ctx->Driver.DeleteShader
+ * ctx->Driver.LinkShader
+ */
+ GLboolean DeferCompileShader;
+ GLboolean DeferLinkProgram;
};
@@ -4262,6 +4293,9 @@ struct gl_context
* Once this field becomes true, it is never reset to false.
*/
GLboolean ShareGroupReset;
+
+ /* A thread pool for threaded shader compilation */
+ struct _mesa_threadpool *ThreadPool;
};
diff --git a/src/mesa/main/pipelineobj.c b/src/mesa/main/pipelineobj.c
index 90c1d00..ae4f80a 100644
--- a/src/mesa/main/pipelineobj.c
+++ b/src/mesa/main/pipelineobj.c
@@ -211,6 +211,24 @@ _mesa_reference_pipeline_object_(struct gl_context *ctx,
}
else {
obj->RefCount++;
+
+ /*
+ * The current pipeline object (as set by glUseProgram or
+ * glBindProgramPipeline) cannot have unfinished tasks.
+ */
+ if (ptr == &ctx->_Shader) {
+ if (obj->ActiveProgram)
+ _mesa_wait_shader_program(ctx, obj->ActiveProgram);
+ if (obj->Name) {
+ int i;
+
+ for (i = 0; i < MESA_SHADER_STAGES; i++) {
+ if (obj->CurrentProgram[i])
+ _mesa_wait_shader_program(ctx, obj->CurrentProgram[i]);
+ }
+ }
+ }
+
*ptr = obj;
}
mtx_unlock(&obj->Mutex);
diff --git a/src/mesa/main/shaderapi.c b/src/mesa/main/shaderapi.c
index 2bbef35..339d7a2 100644
--- a/src/mesa/main/shaderapi.c
+++ b/src/mesa/main/shaderapi.c
@@ -53,6 +53,7 @@
#include "program/prog_print.h"
#include "program/prog_parameter.h"
#include "ralloc.h"
+#include "threadpool.h"
#include <stdbool.h>
#include "../glsl/glsl_parser_extras.h"
#include "../glsl/ir.h"
@@ -210,7 +211,8 @@ _mesa_validate_shader_target(const struct gl_context *ctx, GLenum type)
static GLboolean
is_program(struct gl_context *ctx, GLuint name)
{
- struct gl_shader_program *shProg = _mesa_lookup_shader_program(ctx, name);
+ struct gl_shader_program *shProg =
+ _mesa_lookup_shader_program_no_wait(ctx, name);
return shProg ? GL_TRUE : GL_FALSE;
}
@@ -218,7 +220,7 @@ is_program(struct gl_context *ctx, GLuint name)
static GLboolean
is_shader(struct gl_context *ctx, GLuint name)
{
- struct gl_shader *shader = _mesa_lookup_shader(ctx, name);
+ struct gl_shader *shader = _mesa_lookup_shader_no_wait(ctx, name);
return shader ? GL_TRUE : GL_FALSE;
}
@@ -239,7 +241,7 @@ attach_shader(struct gl_context *ctx, GLuint program, GLuint shader)
if (!shProg)
return;
- sh = _mesa_lookup_shader_err(ctx, shader, "glAttachShader");
+ sh = _mesa_lookup_shader_err_no_wait(ctx, shader, "glAttachShader");
if (!sh) {
return;
}
@@ -341,7 +343,9 @@ delete_shader_program(struct gl_context *ctx, GLuint name)
*/
struct gl_shader_program *shProg;
- shProg = _mesa_lookup_shader_program_err(ctx, name, "glDeleteProgram");
+ /* no waiting until _mesa_delete_shader_program() */
+ shProg = _mesa_lookup_shader_program_err_no_wait(ctx, name,
+ "glDeleteProgram");
if (!shProg)
return;
@@ -359,7 +363,8 @@ delete_shader(struct gl_context *ctx, GLuint shader)
{
struct gl_shader *sh;
- sh = _mesa_lookup_shader_err(ctx, shader, "glDeleteShader");
+ /* no waiting until _mesa_delete_shader() */
+ sh = _mesa_lookup_shader_err_no_wait(ctx, shader, "glDeleteShader");
if (!sh)
return;
@@ -812,6 +817,48 @@ shader_source(struct gl_context *ctx, GLuint shader, const GLchar *source)
#endif
}
+static bool
+can_queue_task(struct gl_context *ctx)
+{
+ if (!ctx->ThreadPool)
+ return false;
+
+ /* MESA_GLSL is set */
+ if (ctx->_Shader->Flags)
+ return false;
+
+ /* context requires synchronized compiler warnings and errors */
+ if (_mesa_get_debug_state_int(ctx, GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB))
+ return false;
+
+ return true;
+}
+
+static void
+deferred_compile_shader(void *data)
+{
+ struct gl_shader *sh = (struct gl_shader *) data;
+ struct gl_context *ctx = (struct gl_context *) sh->TaskData;
+
+ _mesa_glsl_compile_shader(ctx, sh, false, false);
+}
+
+static bool
+queue_compile_shader(struct gl_context *ctx, struct gl_shader *sh)
+{
+ if (!can_queue_task(ctx))
+ return false;
+ if (!ctx->Const.DeferCompileShader)
+ return false;
+
+ sh->TaskData = (void *) ctx;
+ sh->Task = _mesa_threadpool_queue_task(ctx->ThreadPool,
+ deferred_compile_shader, (void *) sh);
+ if (!sh->Task)
+ sh->TaskData = NULL;
+
+ return (sh->Task != NULL);
+}
/**
* Compile a shader.
@@ -844,10 +891,12 @@ compile_shader(struct gl_context *ctx, GLuint shaderObj)
fflush(stderr);
}
- /* this call will set the shader->CompileStatus field to indicate if
- * compilation was successful.
+ /* This call will set the shader->CompileStatus field to indicate if
+ * compilation was successful. But if queueing succeeded, the field
+ * will be set at a later point.
*/
- _mesa_glsl_compile_shader(ctx, sh, false, false);
+ if (!queue_compile_shader(ctx, sh))
+ _mesa_glsl_compile_shader(ctx, sh, false, false);
if (ctx->_Shader->Flags & GLSL_LOG) {
_mesa_write_shader_to_file(sh);
@@ -870,7 +919,7 @@ compile_shader(struct gl_context *ctx, GLuint shaderObj)
}
- if (!sh->CompileStatus) {
+ if (ctx->_Shader->Flags && !sh->CompileStatus) {
if (ctx->_Shader->Flags & GLSL_DUMP_ON_ERROR) {
fprintf(stderr, "GLSL source for %s shader %d:\n",
_mesa_shader_stage_to_string(sh->Stage), sh->Name);
@@ -887,6 +936,49 @@ compile_shader(struct gl_context *ctx, GLuint shaderObj)
}
+static void
+deferred_link_program(void *data)
+{
+ struct gl_shader_program *shProg = (struct gl_shader_program *) data;
+ struct gl_context *ctx = (struct gl_context *) shProg->TaskData;
+
+ _mesa_wait_shaders(ctx, shProg->Shaders, shProg->NumShaders);
+ _mesa_glsl_link_shader(ctx, shProg);
+}
+
+static bool
+queue_link_program(struct gl_context *ctx, struct gl_shader_program *shProg)
+{
+ int i;
+
+ if (!can_queue_task(ctx))
+ return false;
+ if (!ctx->Const.DeferLinkProgram)
+ return false;
+
+ /*
+ * Rather than adding _mesa_wait_shader_program calls here and there, we
+ * simply disallow threaded linking when the program is current or active.
+ */
+ if (ctx->_Shader->ActiveProgram == shProg)
+ return false;
+ /* be careful for separate programs */
+ if (ctx->_Shader->Name != 0) {
+ for (i = 0; i < MESA_SHADER_STAGES; i++) {
+ if (ctx->_Shader->CurrentProgram[i] == shProg)
+ return false;
+ }
+ }
+
+ shProg->TaskData = (void *) ctx;
+ shProg->Task = _mesa_threadpool_queue_task(ctx->ThreadPool,
+ deferred_link_program, (void *) shProg);
+ if (!shProg->Task)
+ shProg->TaskData = NULL;
+
+ return (shProg->Task != NULL);
+}
+
/**
* Link a program's shaders.
*/
@@ -912,10 +1004,16 @@ link_program(struct gl_context *ctx, GLuint program)
FLUSH_VERTICES(ctx, _NEW_PROGRAM);
- _mesa_glsl_link_shader(ctx, shProg);
+ if (ctx->Driver.NotifyLinkShader)
+ ctx->Driver.NotifyLinkShader(ctx, shProg);
+
+ if (!queue_link_program(ctx, shProg)) {
+ _mesa_wait_shaders(ctx, shProg->Shaders, shProg->NumShaders);
+ _mesa_glsl_link_shader(ctx, shProg);
+ }
- if (shProg->LinkStatus == GL_FALSE &&
- (ctx->_Shader->Flags & GLSL_REPORT_ERRORS)) {
+ if ((ctx->_Shader->Flags & GLSL_REPORT_ERRORS) &&
+ shProg->LinkStatus == GL_FALSE) {
_mesa_debug(ctx, "Error linking program %u:\n%s\n",
shProg->Name, shProg->InfoLog);
}
diff --git a/src/mesa/main/shaderobj.c b/src/mesa/main/shaderobj.c
index b9feff4..ab94d14 100644
--- a/src/mesa/main/shaderobj.c
+++ b/src/mesa/main/shaderobj.c
@@ -41,6 +41,7 @@
#include "program/prog_parameter.h"
#include "program/hash_table.h"
#include "ralloc.h"
+#include "threadpool.h"
/**********************************************************************/
/*** Shader object functions ***/
@@ -95,6 +96,7 @@ _mesa_reference_shader(struct gl_context *ctx, struct gl_shader **ptr,
void
_mesa_init_shader(struct gl_context *ctx, struct gl_shader *shader)
{
+ mtx_init(&shader->Mutex, mtx_plain);
shader->RefCount = 1;
}
@@ -125,18 +127,51 @@ _mesa_new_shader(struct gl_context *ctx, GLuint name, GLenum type)
static void
_mesa_delete_shader(struct gl_context *ctx, struct gl_shader *sh)
{
+ _mesa_wait_shaders(ctx, &sh, 1);
+
free((void *)sh->Source);
free(sh->Label);
_mesa_reference_program(ctx, &sh->Program, NULL);
+ mtx_destroy(&sh->Mutex);
ralloc_free(sh);
}
/**
+ * Wait for the threaded compile tasks to complete.
+ */
+void
+_mesa_wait_shaders(struct gl_context *ctx,
+ struct gl_shader **shaders,
+ int num_shaders)
+{
+ int i;
+
+ for (i = 0; i < num_shaders; i++) {
+ struct gl_shader *sh = shaders[i];
+
+ mtx_lock(&sh->Mutex);
+
+ if (sh->Task) {
+ struct gl_context *task_ctx = (struct gl_context *) sh->TaskData;
+
+ if (!_mesa_threadpool_complete_task(task_ctx->ThreadPool, sh->Task))
+ sh->CompileStatus = GL_FALSE;
+
+ sh->Task = NULL;
+ sh->TaskData = NULL;
+ }
+
+ mtx_unlock(&sh->Mutex);
+ }
+}
+
+
+/**
* Lookup a GLSL shader object.
*/
struct gl_shader *
-_mesa_lookup_shader(struct gl_context *ctx, GLuint name)
+_mesa_lookup_shader_no_wait(struct gl_context *ctx, GLuint name)
{
if (name) {
struct gl_shader *sh = (struct gl_shader *)
@@ -158,7 +193,8 @@ _mesa_lookup_shader(struct gl_context *ctx, GLuint name)
* As above, but record an error if shader is not found.
*/
struct gl_shader *
-_mesa_lookup_shader_err(struct gl_context *ctx, GLuint name, const char *caller)
+_mesa_lookup_shader_err_no_wait(struct gl_context *ctx, GLuint name,
+ const char *caller)
{
if (!name) {
_mesa_error(ctx, GL_INVALID_VALUE, "%s", caller);
@@ -238,6 +274,8 @@ _mesa_reference_shader_program(struct gl_context *ctx,
void
_mesa_init_shader_program(struct gl_context *ctx, struct gl_shader_program *prog)
{
+ mtx_init(&prog->Mutex, mtx_plain);
+
prog->Type = GL_SHADER_PROGRAM_MESA;
prog->RefCount = 1;
@@ -373,17 +411,43 @@ _mesa_free_shader_program_data(struct gl_context *ctx,
static void
_mesa_delete_shader_program(struct gl_context *ctx, struct gl_shader_program *shProg)
{
+ _mesa_wait_shader_program(ctx, shProg);
+
_mesa_free_shader_program_data(ctx, shProg);
+ mtx_destroy(&shProg->Mutex);
ralloc_free(shProg);
}
/**
+ * Wait for the threaded linking task to complete.
+ */
+void
+_mesa_wait_shader_program(struct gl_context *ctx,
+ struct gl_shader_program *shProg)
+{
+ mtx_lock(&shProg->Mutex);
+
+ if (shProg->Task) {
+ struct gl_context *task_ctx = (struct gl_context *) shProg->TaskData;
+
+ if (!_mesa_threadpool_complete_task(task_ctx->ThreadPool,
+ shProg->Task))
+ shProg->LinkStatus = GL_FALSE;
+ shProg->Task = NULL;
+ shProg->TaskData = NULL;
+ }
+
+ mtx_unlock(&shProg->Mutex);
+}
+
+
+/**
* Lookup a GLSL program object.
*/
struct gl_shader_program *
-_mesa_lookup_shader_program(struct gl_context *ctx, GLuint name)
+_mesa_lookup_shader_program_no_wait(struct gl_context *ctx, GLuint name)
{
struct gl_shader_program *shProg;
if (name) {
@@ -406,8 +470,8 @@ _mesa_lookup_shader_program(struct gl_context *ctx, GLuint name)
* As above, but record an error if program is not found.
*/
struct gl_shader_program *
-_mesa_lookup_shader_program_err(struct gl_context *ctx, GLuint name,
- const char *caller)
+_mesa_lookup_shader_program_err_no_wait(struct gl_context *ctx, GLuint name,
+ const char *caller)
{
if (!name) {
_mesa_error(ctx, GL_INVALID_VALUE, "%s", caller);
diff --git a/src/mesa/main/shaderobj.h b/src/mesa/main/shaderobj.h
index fae8be8..c2f8a0d 100644
--- a/src/mesa/main/shaderobj.h
+++ b/src/mesa/main/shaderobj.h
@@ -53,13 +53,35 @@ extern void
_mesa_reference_shader(struct gl_context *ctx, struct gl_shader **ptr,
struct gl_shader *sh);
+extern void
+_mesa_wait_shaders(struct gl_context *ctx,
+ struct gl_shader **shaders,
+ int num_shaders);
+
extern struct gl_shader *
-_mesa_lookup_shader(struct gl_context *ctx, GLuint name);
+_mesa_lookup_shader_no_wait(struct gl_context *ctx, GLuint name);
extern struct gl_shader *
-_mesa_lookup_shader_err(struct gl_context *ctx, GLuint name, const char *caller);
+_mesa_lookup_shader_err_no_wait(struct gl_context *ctx, GLuint name,
+ const char *caller);
+static inline struct gl_shader *
+_mesa_lookup_shader(struct gl_context *ctx, GLuint name)
+{
+ struct gl_shader *sh = _mesa_lookup_shader_no_wait(ctx, name);
+ if (sh)
+ _mesa_wait_shaders(ctx, &sh, 1);
+ return sh;
+}
+static inline struct gl_shader *
+_mesa_lookup_shader_err(struct gl_context *ctx, GLuint name, const char *caller)
+{
+ struct gl_shader *sh = _mesa_lookup_shader_err_no_wait(ctx, name, caller);
+ if (sh)
+ _mesa_wait_shaders(ctx, &sh, 1);
+ return sh;
+}
extern void
_mesa_reference_shader_program(struct gl_context *ctx,
@@ -74,12 +96,37 @@ _mesa_new_shader(struct gl_context *ctx, GLuint name, GLenum type);
extern void
_mesa_init_shader_program(struct gl_context *ctx, struct gl_shader_program *prog);
+extern void
+_mesa_wait_shader_program(struct gl_context *ctx,
+ struct gl_shader_program *shProg);
+
extern struct gl_shader_program *
-_mesa_lookup_shader_program(struct gl_context *ctx, GLuint name);
+_mesa_lookup_shader_program_no_wait(struct gl_context *ctx, GLuint name);
extern struct gl_shader_program *
+_mesa_lookup_shader_program_err_no_wait(struct gl_context *ctx, GLuint name,
+ const char *caller);
+
+static inline struct gl_shader_program *
+_mesa_lookup_shader_program(struct gl_context *ctx, GLuint name)
+{
+ struct gl_shader_program *shProg =
+ _mesa_lookup_shader_program_no_wait(ctx, name);
+ if (shProg)
+ _mesa_wait_shader_program(ctx, shProg);
+ return shProg;
+}
+
+static inline struct gl_shader_program *
_mesa_lookup_shader_program_err(struct gl_context *ctx, GLuint name,
- const char *caller);
+ const char *caller)
+{
+ struct gl_shader_program *shProg =
+ _mesa_lookup_shader_program_err_no_wait(ctx, name, caller);
+ if (shProg)
+ _mesa_wait_shader_program(ctx, shProg);
+ return shProg;
+}
extern void
_mesa_clear_shader_program_data(struct gl_context *ctx,
--
2.0.0.rc2
More information about the mesa-dev
mailing list