Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Dinges <dingto>2022-09-22 18:27:51 +0300
committerClément Foucault <foucault.clem@gmail.com>2022-09-22 18:32:43 +0300
commit697b447c2069bbbbaa9929aab0ea1f66ef8bf4d0 (patch)
treef5c78b102b5c1478fb1dbd262b23508f5f072e33 /source/blender/gpu/intern/gpu_material.c
parentbb63b98d1ff5acfd24dff9b5e72175f82f5bca26 (diff)
Metal: MTLContext implementation and immediate mode rendering support.
MTLContext provides functionality for command encoding, binding management and graphics device management. MTLImmediate provides simple draw enablement with dynamically encoded data. These draws utilise temporary scratch buffer memory to provide minimal bandwidth overhead during workload submission. This patch also contains empty placeholders for MTLBatch and MTLDrawList to enable testing of first pixels on-screen without failure. The Metal API also requires access to the GHOST_Context to ensure the same pre-initialized Metal GPU device is used by the viewport. Given the explicit nature of Metal, explicit control is also needed over presentation, to ensure correct work scheduling and rendering pipeline state. Authored by Apple: Michael Parkin-White Ref T96261 (The diff is based on 043f59cb3b5835ba1a0bbf6f1cbad080b527f7f6) Reviewed By: fclem Differential Revision: https://developer.blender.org/D15953
Diffstat (limited to 'source/blender/gpu/intern/gpu_material.c')
-rw-r--r--source/blender/gpu/intern/gpu_material.c215
1 files changed, 208 insertions, 7 deletions
diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c
index 96809db1587..991cb229eda 100644
--- a/source/blender/gpu/intern/gpu_material.c
+++ b/source/blender/gpu/intern/gpu_material.c
@@ -34,6 +34,8 @@
#include "DRW_engine.h"
+#include "PIL_time.h"
+
#include "gpu_codegen.h"
#include "gpu_node_graph.h"
@@ -43,6 +45,17 @@
#define MAX_COLOR_BAND 128
#define MAX_GPU_SKIES 8
+/** Whether the optimized variant of the GPUPass should be created asynchronously.
+ * Usage of this depends on whether there are possible threading challenges of doing so.
+ * Currently, the overhead of GPU_generate_pass is relatively small in comparison to shader
+ * compilation, though this option exists in case any potential scenarios for material graph
+ * optimization cause a slow down on the main thread.
+ *
+ * NOTE: The actual shader program for the optimized pass will alwaysbe compiled asynchronously,
+ * this flag controls whether shader node graph source serialization happens on the compilation
+ * worker thread. */
+#define ASYNC_OPTIMIZED_PASS_CREATION 0
+
typedef struct GPUColorBandBuilder {
float pixels[MAX_COLOR_BAND][CM_TABLE + 1][4];
int current_layer;
@@ -57,6 +70,27 @@ struct GPUMaterial {
/* Contains GPUShader and source code for deferred compilation.
* Can be shared between similar material (i.e: sharing same nodetree topology). */
GPUPass *pass;
+ /* Optimized GPUPass, situationally compiled after initial pass for optimal realtime performance.
+ * This shader variant bakes dynamic uniform data as constant. This variant will not use
+ * the ubo, and instead bake constants directly into the shader source. */
+ GPUPass *optimized_pass;
+ /* Optimization status.
+ * We also use this status to determine whether this material should be considered for
+ * optimization. Only sufficiently complex shaders benefit from constant-folding optimizations.
+ * `GPU_MAT_OPTIMIZATION_READY` -> shader should be optimized and is ready for optimization.
+ * `GPU_MAT_OPTIMIZATION_SKIP` -> Shader should not be optimized as it would not benefit
+ * performance to do so, based on the heuristic.
+ */
+ eGPUMaterialOptimizationStatus optimization_status;
+ double creation_time;
+#if ASYNC_OPTIMIZED_PASS_CREATION == 1
+ struct DeferredOptimizePass {
+ GPUCodegenCallbackFn callback;
+ void *thunk;
+ } DeferredOptimizePass;
+ struct DeferredOptimizePass optimize_pass_info;
+#endif
+
/** UBOs for this material parameters. */
GPUUniformBuf *ubo;
/** Compilation status. Do not use if shader is not GPU_MAT_SUCCESS. */
@@ -209,6 +243,9 @@ void GPU_material_free_single(GPUMaterial *material)
gpu_node_graph_free(&material->graph);
+ if (material->optimized_pass != NULL) {
+ GPU_pass_release(material->optimized_pass);
+ }
if (material->pass != NULL) {
GPU_pass_release(material->pass);
}
@@ -247,12 +284,15 @@ Scene *GPU_material_scene(GPUMaterial *material)
GPUPass *GPU_material_get_pass(GPUMaterial *material)
{
- return material->pass;
+ return (material->optimized_pass) ? material->optimized_pass : material->pass;
}
GPUShader *GPU_material_get_shader(GPUMaterial *material)
{
- return material->pass ? GPU_pass_shader_get(material->pass) : NULL;
+ /* First attempt to select optimized shader. If not available, fetch original. */
+ GPUShader *shader = (material->optimized_pass) ? GPU_pass_shader_get(material->optimized_pass) :
+ NULL;
+ return (shader) ? shader : ((material->pass) ? GPU_pass_shader_get(material->pass) : NULL);
}
const char *GPU_material_get_name(GPUMaterial *material)
@@ -665,6 +705,29 @@ void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status)
mat->status = status;
}
+eGPUMaterialOptimizationStatus GPU_material_optimization_status(GPUMaterial *mat)
+{
+ return mat->optimization_status;
+}
+
+void GPU_material_optimization_status_set(GPUMaterial *mat, eGPUMaterialOptimizationStatus status)
+{
+ mat->optimization_status = status;
+ if (mat->optimization_status == GPU_MAT_OPTIMIZATION_READY) {
+ /* Reset creation timer to delay optimization pass. */
+ mat->creation_time = PIL_check_seconds_timer();
+ }
+}
+
+bool GPU_material_optimization_ready(GPUMaterial *mat)
+{
+ /* Timer threshold before optimizations will be queued.
+ * When materials are frequently being modified, optimization
+ * can incur CPU overhead from excessive compilation. */
+ const double optimization_time_threshold_s = 5.0;
+ return ((PIL_check_seconds_timer() - mat->creation_time) >= optimization_time_threshold_s);
+}
+
/* Code generation */
bool GPU_material_has_surface_output(GPUMaterial *mat)
@@ -730,6 +793,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
mat->uuid = shader_uuid;
mat->flag = GPU_MATFLAG_UPDATED;
mat->status = GPU_MAT_CREATED;
+ mat->optimization_status = GPU_MAT_OPTIMIZATION_SKIP;
mat->is_volume_shader = is_volume_shader;
mat->graph.used_libraries = BLI_gset_new(
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "GPUNodeGraph.used_libraries");
@@ -748,7 +812,7 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
{
/* Create source code and search pass cache for an already compiled version. */
- mat->pass = GPU_generate_pass(mat, &mat->graph, callback, thunk);
+ mat->pass = GPU_generate_pass(mat, &mat->graph, callback, thunk, false);
if (mat->pass == NULL) {
/* We had a cache hit and the shader has already failed to compile. */
@@ -756,11 +820,44 @@ GPUMaterial *GPU_material_from_nodetree(Scene *scene,
gpu_node_graph_free(&mat->graph);
}
else {
+ /* Determine whether we should generate an optimized variant of the graph.
+ * Heuristic is based on complexity of default material pass and shader node graph. */
+ if (GPU_pass_should_optimize(mat->pass)) {
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_READY);
+ }
+
GPUShader *sh = GPU_pass_shader_get(mat->pass);
if (sh != NULL) {
/* We had a cache hit and the shader is already compiled. */
mat->status = GPU_MAT_SUCCESS;
- gpu_node_graph_free_nodes(&mat->graph);
+
+ if (mat->optimization_status == GPU_MAT_OPTIMIZATION_SKIP) {
+ gpu_node_graph_free_nodes(&mat->graph);
+ }
+ }
+
+ /* Generate optimized pass. */
+ if (mat->optimization_status == GPU_MAT_OPTIMIZATION_READY) {
+#if ASYNC_OPTIMIZED_PASS_CREATION == 1
+ mat->optimized_pass = NULL;
+ mat->optimize_pass_info.callback = callback;
+ mat->optimize_pass_info.thunk = thunk;
+#else
+ mat->optimized_pass = GPU_generate_pass(mat, &mat->graph, callback, thunk, true);
+ if (mat->optimized_pass == NULL) {
+ /* Failed to create optimized pass. */
+ gpu_node_graph_free_nodes(&mat->graph);
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SKIP);
+ }
+ else {
+ GPUShader *optimized_sh = GPU_pass_shader_get(mat->optimized_pass);
+ if (optimized_sh != NULL) {
+ /* Optimized shader already available. */
+ gpu_node_graph_free_nodes(&mat->graph);
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SUCCESS);
+ }
+ }
+#endif
}
}
}
@@ -811,7 +908,11 @@ void GPU_material_compile(GPUMaterial *mat)
GPUShader *sh = GPU_pass_shader_get(mat->pass);
if (sh != NULL) {
mat->status = GPU_MAT_SUCCESS;
- gpu_node_graph_free_nodes(&mat->graph);
+
+ if (mat->optimization_status == GPU_MAT_OPTIMIZATION_SKIP) {
+ /* Only free node graph nodes if not required by secondary optimization pass. */
+ gpu_node_graph_free_nodes(&mat->graph);
+ }
}
else {
mat->status = GPU_MAT_FAILED;
@@ -825,6 +926,71 @@ void GPU_material_compile(GPUMaterial *mat)
}
}
+void GPU_material_optimize(GPUMaterial *mat)
+{
+ /* If shader is flagged for skipping optimization or has already been successfully
+ * optimized, skip. */
+ if (ELEM(mat->optimization_status, GPU_MAT_OPTIMIZATION_SKIP, GPU_MAT_OPTIMIZATION_SUCCESS)) {
+ return;
+ }
+
+ /* If original shader has not been fully compiled, we are not
+ * ready to perform optimization. */
+ if (mat->status != GPU_MAT_SUCCESS) {
+ /* Reset optimization status. */
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_READY);
+ return;
+ }
+
+#if ASYNC_OPTIMIZED_PASS_CREATION == 1
+ /* If the optimized pass is not valid, first generate optimized pass.
+ * NOTE(Threading): Need to verify if GPU_generate_pass can cause side-effects, especially when
+ * used with "thunk". So far, this appears to work, and deferring optimized pass creation is more
+ * optimal, as these do not benefit from caching, due to baked constants. However, this could
+ * possibly be cause for concern for certain cases. */
+ if (!mat->optimized_pass) {
+ mat->optimized_pass = GPU_generate_pass(
+ mat, &mat->graph, mat->optimize_pass_info.callback, mat->optimize_pass_info.thunk, true);
+ BLI_assert(mat->optimized_pass);
+ }
+#else
+ if (!mat->optimized_pass) {
+ /* Optimized pass has not been created, skip future optimization attempts. */
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SKIP);
+ return;
+ }
+#endif
+
+ bool success;
+ /* NOTE: The shader may have already been compiled here since we are
+ * sharing GPUShader across GPUMaterials. In this case it's a no-op. */
+#ifndef NDEBUG
+ success = GPU_pass_compile(mat->optimized_pass, mat->name);
+#else
+ success = GPU_pass_compile(mat->optimized_pass, __func__);
+#endif
+
+ if (success) {
+ GPUShader *sh = GPU_pass_shader_get(mat->optimized_pass);
+ if (sh != NULL) {
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SUCCESS);
+ }
+ else {
+ /* Optimized pass failed to compile. Disable any future optimization attempts. */
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SKIP);
+ }
+ }
+ else {
+ /* Optimization pass generation failed. Disable future attempts to optimize. */
+ GPU_pass_release(mat->optimized_pass);
+ mat->optimized_pass = NULL;
+ GPU_material_optimization_status_set(mat, GPU_MAT_OPTIMIZATION_SKIP);
+ }
+
+ /* Release node graph as no longer needed. */
+ gpu_node_graph_free_nodes(&mat->graph);
+}
+
void GPU_materials_free(Main *bmain)
{
LISTBASE_FOREACH (Material *, ma, &bmain->materials) {
@@ -848,6 +1014,8 @@ GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_functi
material->graph.used_libraries = BLI_gset_new(
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "GPUNodeGraph.used_libraries");
material->refcount = 1;
+ material->optimization_status = GPU_MAT_OPTIMIZATION_SKIP;
+ material->optimized_pass = NULL;
/* Construct the material graph by adding and linking the necessary GPU material nodes. */
construct_function_cb(thunk, material);
@@ -856,7 +1024,9 @@ GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_functi
gpu_material_ramp_texture_build(material);
/* Lookup an existing pass in the cache or generate a new one. */
- material->pass = GPU_generate_pass(material, &material->graph, generate_code_function_cb, thunk);
+ material->pass = GPU_generate_pass(
+ material, &material->graph, generate_code_function_cb, thunk, false);
+ material->optimized_pass = NULL;
/* The pass already exists in the pass cache but its shader already failed to compile. */
if (material->pass == NULL) {
@@ -865,11 +1035,42 @@ GPUMaterial *GPU_material_from_callbacks(ConstructGPUMaterialFn construct_functi
return material;
}
+ /* Generate optimized pass. */
+ if (GPU_pass_should_optimize(material->pass)) {
+
+#if ASYNC_OPTIMIZED_PASS_CREATION == 1
+ mmaterial->optimized_pass = NULL;
+ material->optimize_pass_info.callback = generate_code_function_cb;
+ material->optimize_pass_info.thunk = thunk;
+ GPU_material_optimization_status_set(GPU_MAT_OPTIMIZATION_READY);
+#else
+ material->optimized_pass = GPU_generate_pass(
+ material, &material->graph, generate_code_function_cb, thunk, true);
+
+ if (material->optimized_pass == NULL) {
+ /* Failed to create optimized pass. */
+ gpu_node_graph_free_nodes(&material->graph);
+ GPU_material_optimization_status_set(material, GPU_MAT_OPTIMIZATION_SKIP);
+ }
+ else {
+ GPUShader *optimized_sh = GPU_pass_shader_get(material->optimized_pass);
+ if (optimized_sh != NULL) {
+ /* Optimized shader already available. */
+ gpu_node_graph_free_nodes(&material->graph);
+ GPU_material_optimization_status_set(material, GPU_MAT_OPTIMIZATION_SUCCESS);
+ }
+ }
+#endif
+ }
+
/* The pass already exists in the pass cache and its shader is already compiled. */
GPUShader *shader = GPU_pass_shader_get(material->pass);
if (shader != NULL) {
material->status = GPU_MAT_SUCCESS;
- gpu_node_graph_free_nodes(&material->graph);
+ if (material->optimization_status == GPU_MAT_OPTIMIZATION_SKIP) {
+ /* Only free node graph if not required by secondary optimization pass. */
+ gpu_node_graph_free_nodes(&material->graph);
+ }
return material;
}