GPU: Batch Compilation: Show compiled GPUMaterials as soon as possible

Request one separate compilation batch for each GPUPass so users can
get a better sense of the compilation progress, and to better distribute
texture loading over time.

Pull Request: https://projects.blender.org/blender/blender/pulls/125012
This commit is contained in:
Miguel Pozo
2024-07-25 19:18:07 +02:00
parent 5bf44c6eb4
commit 4919221456
5 changed files with 65 additions and 84 deletions

View File

@@ -84,8 +84,7 @@ static void drw_deferred_shader_compilation_exec(void *custom_data,
WM_system_gpu_context_activate(system_gpu_context);
GPU_context_active_set(blender_gpu_context);
Vector<GPUMaterial *> next_batch;
Map<BatchHandle, Vector<GPUMaterial *>> batches;
Vector<GPUMaterial *> async_mats;
while (true) {
if (worker_status->stop) {
@@ -106,38 +105,26 @@ static void drw_deferred_shader_compilation_exec(void *custom_data,
if (mat) {
/* We have a new material that must be compiled,
* we either compile it directly or add it to a parallel compilation batch. */
* we either compile it directly or add it to the async compilation list. */
if (use_parallel_compilation) {
next_batch.append(mat);
GPU_material_async_compile(mat);
async_mats.append(mat);
}
else {
GPU_material_compile(mat);
GPU_material_release(mat);
}
}
else if (!next_batch.is_empty()) {
else if (!async_mats.is_empty()) {
/* (only if use_parallel_compilation == true)
* We ran out of pending materials. Request the compilation of the current batch. */
BatchHandle batch_handle = GPU_material_batch_compile(next_batch);
batches.add(batch_handle, next_batch);
next_batch.clear();
}
else if (!batches.is_empty()) {
/* (only if use_parallel_compilation == true)
* Keep querying the requested batches until all of them are ready. */
Vector<BatchHandle> ready_handles;
for (BatchHandle handle : batches.keys()) {
if (GPU_material_batch_is_ready(handle)) {
ready_handles.append(handle);
}
}
for (BatchHandle handle : ready_handles) {
Vector<GPUMaterial *> batch = batches.pop(handle);
GPU_material_batch_finalize(handle, batch);
for (GPUMaterial *mat : batch) {
* Keep querying the requested materials until all of them are ready. */
async_mats.remove_if([](GPUMaterial *mat) {
if (GPU_material_async_try_finalize(mat)) {
GPU_material_release(mat);
return true;
}
}
return false;
});
}
else {
/* Check for Material Optimization job once there are no more
@@ -172,12 +159,14 @@ static void drw_deferred_shader_compilation_exec(void *custom_data,
/* We have to wait until all the requested batches are ready,
* even if worker_status->stop is true. */
for (BatchHandle handle : batches.keys()) {
Vector<GPUMaterial *> &batch = batches.lookup(handle);
GPU_material_batch_finalize(handle, batch);
for (GPUMaterial *mat : batch) {
GPU_material_release(mat);
}
while (!async_mats.is_empty()) {
async_mats.remove_if([](GPUMaterial *mat) {
if (GPU_material_async_try_finalize(mat)) {
GPU_material_release(mat);
return true;
}
return false;
});
}
GPU_context_active_set(nullptr);

View File

@@ -253,29 +253,9 @@ void GPU_material_compile(GPUMaterial *mat);
void GPU_material_free_single(GPUMaterial *material);
void GPU_material_free(ListBase *gpumaterial);
/**
* Request the creation of multiple `GPUMaterial`s at once, allowing the backend to use
* multithreaded compilation.
* Returns a handle that can be used to poll if all materials have been
* compiled, and to retrieve the compiled result.
* NOTE: This function is asynchronous on OpenGL, but it's blocking on Vulkan and Metal.
* WARNING: The material pointers and their pass->create_info should be valid until
* `GPU_material_batch_finalize` has returned.
*/
BatchHandle GPU_material_batch_compile(blender::Span<GPUMaterial *> mats);
/**
* Returns true if all the materials from the batch have finished their compilation.
*/
bool GPU_material_batch_is_ready(BatchHandle handle);
/**
* Assign the compiled shaders to their respective materials and flag their status.
* The materials list should have the same length and order as in the `GPU_material_batch_compile`
* call.
* If the compilation has not finished yet, this call will block the thread until all the
* shaders are ready.
* WARNING: The handle will be invalidated by this call, you can't process the same batch twice.
*/
void GPU_material_batch_finalize(BatchHandle &handle, blender::Span<GPUMaterial *> mats);
void GPU_material_async_compile(GPUMaterial *mat);
/** Returns true if the material have finished its compilation. */
bool GPU_material_async_try_finalize(GPUMaterial *mat);
void GPU_material_acquire(GPUMaterial *mat);
void GPU_material_release(GPUMaterial *mat);

View File

@@ -109,6 +109,8 @@ struct GPUPass {
bool should_optimize;
/** Whether pass is in the GPUPass cache. */
bool cached;
BatchHandle async_compilation_handle;
};
/* -------------------------------------------------------------------- */
@@ -791,6 +793,7 @@ GPUPass *GPU_generate_pass(GPUMaterial *material,
* Optimized passes cannot be optimized further, even if the heuristic is still not
* favorable. */
pass->should_optimize = (!optimize_graph) && codegen.should_optimize_heuristic();
pass->async_compilation_handle = -1;
codegen.create_info = nullptr;
@@ -894,6 +897,28 @@ bool GPU_pass_finalize_compilation(GPUPass *pass, GPUShader *shader)
return success;
}
void GPU_pass_begin_async_compilation(GPUPass *pass, const char *shname)
{
if (pass->async_compilation_handle == -1) {
GPUShaderCreateInfo *info = GPU_pass_begin_compilation(pass, shname);
BLI_assert(info);
pass->async_compilation_handle = GPU_shader_batch_create_from_infos({info});
}
}
bool GPU_pass_async_compilation_try_finalize(GPUPass *pass)
{
BLI_assert(pass->async_compilation_handle != -1);
if (pass->async_compilation_handle) {
if (GPU_shader_batch_is_ready(pass->async_compilation_handle)) {
GPU_pass_finalize_compilation(
pass, GPU_shader_batch_finalize(pass->async_compilation_handle).first());
}
}
return pass->async_compilation_handle == 0;
}
bool GPU_pass_compile(GPUPass *pass, const char *shname)
{
bool success = true;

View File

@@ -36,6 +36,12 @@ bool GPU_pass_should_optimize(GPUPass *pass);
GPUShaderCreateInfo *GPU_pass_begin_compilation(GPUPass *pass, const char *shname);
bool GPU_pass_finalize_compilation(GPUPass *pass, GPUShader *shader);
void GPU_pass_begin_async_compilation(GPUPass *pass, const char *shname);
/** NOTE: Unlike the non-async version, this one returns true when compilation has finalized,
* regardless if it succeeded or not.
* To check for success, see if `GPU_pass_shader_get() != nullptr`. */
bool GPU_pass_async_compilation_try_finalize(GPUPass *pass);
/* Module */
void gpu_codegen_init();

View File

@@ -1029,45 +1029,26 @@ void GPU_material_compile(GPUMaterial *mat)
gpu_material_finalize(mat, success);
}
BatchHandle GPU_material_batch_compile(blender::Span<GPUMaterial *> mats)
void GPU_material_async_compile(GPUMaterial *mat)
{
blender::Vector<GPUShaderCreateInfo *> infos;
infos.reserve(mats.size());
for (GPUMaterial *mat : mats) {
BLI_assert(ELEM(mat->status, GPU_MAT_QUEUED, GPU_MAT_CREATED));
BLI_assert(mat->pass);
BLI_assert(ELEM(mat->status, GPU_MAT_QUEUED, GPU_MAT_CREATED));
BLI_assert(mat->pass);
#ifndef NDEBUG
const char *name = mat->name;
const char *name = mat->name;
#else
const char *name = __func__;
const char *name = __func__;
#endif
mat->do_batch_compilation = false;
if (GPUShaderCreateInfo *info = GPU_pass_begin_compilation(mat->pass, name)) {
infos.append(info);
mat->do_batch_compilation = true;
}
}
return GPU_shader_batch_create_from_infos(infos);
GPU_pass_begin_async_compilation(mat->pass, name);
}
bool GPU_material_batch_is_ready(BatchHandle handle)
bool GPU_material_async_try_finalize(GPUMaterial *mat)
{
return GPU_shader_batch_is_ready(handle);
}
void GPU_material_batch_finalize(BatchHandle &handle, blender::Span<GPUMaterial *> mats)
{
blender::Vector<GPUShader *> shaders = GPU_shader_batch_finalize(handle);
int i = 0;
for (GPUMaterial *mat : mats) {
bool success = true;
if (mat->do_batch_compilation) {
success = GPU_pass_finalize_compilation(mat->pass, shaders[i++]);
}
gpu_material_finalize(mat, success);
BLI_assert(ELEM(mat->status, GPU_MAT_QUEUED, GPU_MAT_CREATED));
if (GPU_pass_async_compilation_try_finalize(mat->pass)) {
gpu_material_finalize(mat, GPU_pass_shader_get(mat->pass) != nullptr);
return true;
}
return false;
}
void GPU_material_optimize(GPUMaterial *mat)