From 0d7d6b00c9eced423861d9ac3e37d348b0cf06c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 31 May 2024 22:55:05 +0200 Subject: [PATCH] EEVEE-Next: LightProbe Bake: Add better feedback when allocation fail - Add a report to explain why allocation fails and display the current limit. - Add heuristic to avoid out of memory issue. - Remove the delay property that did not do anything right now. - Reduce default clip end by half. Fix #121916 Pull Request: https://projects.blender.org/blender/blender/pulls/122572 --- .../draw/engines/eevee_next/eevee_instance.cc | 5 -- .../engines/eevee_next/eevee_lightcache.cc | 18 ++++-- .../engines/eevee_next/eevee_lightcache.hh | 3 + .../eevee_next/eevee_lightprobe_volume.cc | 36 +++++++++--- .../blender/editors/render/render_shading.cc | 56 ++++++++++++------- .../makesdna/DNA_lightprobe_defaults.h | 2 +- 6 files changed, 82 insertions(+), 38 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index e1f2a52f365..e51b9923408 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -683,11 +683,6 @@ void Instance::light_bake_irradiance( volume_probes.bake.irradiance_capture(); } - if (sampling.finished()) { - /* TODO(fclem): Dilation, filter etc... */ - // irradiance_cache.bake.irradiance_finalize(); - } - LightProbeGridCacheFrame *cache_frame; if (sampling.finished()) { cache_frame = volume_probes.bake.read_result_packed(); diff --git a/source/blender/draw/engines/eevee_next/eevee_lightcache.cc b/source/blender/draw/engines/eevee_next/eevee_lightcache.cc index bfdc78aa4b1..95d4217ab2c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightcache.cc +++ b/source/blender/draw/engines/eevee_next/eevee_lightcache.cc @@ -50,6 +50,12 @@ class LightBake { int frame_; /** Milliseconds. Delay the start of the baking to not slowdown interactions (TODO: remove). */ int delay_ms_; + /** + * Reference to the operator report string to print messages to the UI. + * Should be threadsafe to write to as it gets read by the operator code only if the job is + * finished. + */ + std::string &report_; /** * If running in parallel (in a separate thread), use this context. @@ -76,11 +82,13 @@ class LightBake { Scene *scene, Span probes, bool run_as_job, + std::string &report, int frame, int delay_ms = 0) : depsgraph_(DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER)), frame_(frame), delay_ms_(delay_ms), + report_(report), original_probes_(probes) { BLI_assert(BLI_thread_is_main()); @@ -179,8 +187,8 @@ class LightBake { }); if (instance_->info != "") { - /** TODO: Print to the Status Bar UI. */ - printf("%s\n", instance_->info.c_str()); + /* Pipe report to operator. */ + report_ = instance_->info; } if ((G.is_break == true) || (stop != nullptr && *stop == true)) { @@ -292,6 +300,7 @@ wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm, ViewLayer *view_layer, Scene *scene, blender::Vector original_probes, + std::string &report, int delay_ms, int frame) { @@ -311,7 +320,7 @@ wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm, WM_JOB_TYPE_LIGHT_BAKE); LightBake *bake = new LightBake( - bmain, view_layer, scene, std::move(original_probes), true, frame, delay_ms); + bmain, view_layer, scene, std::move(original_probes), true, report, frame, delay_ms); WM_jobs_customdata_set(wm_job, bake, EEVEE_NEXT_lightbake_job_data_free); WM_jobs_timer(wm_job, 0.4, NC_SCENE | NA_EDITED, 0); @@ -330,10 +339,11 @@ void *EEVEE_NEXT_lightbake_job_data_alloc(Main *bmain, ViewLayer *view_layer, Scene *scene, blender::Vector original_probes, + std::string &report, int frame) { LightBake *bake = new LightBake( - bmain, view_layer, scene, std::move(original_probes), false, frame); + bmain, view_layer, scene, std::move(original_probes), false, report, frame); /* TODO(fclem): Can remove this cast once we remove the previous EEVEE light cache. */ return reinterpret_cast(bake); } diff --git a/source/blender/draw/engines/eevee_next/eevee_lightcache.hh b/source/blender/draw/engines/eevee_next/eevee_lightcache.hh index d470fcff245..d11dfc26f05 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightcache.hh +++ b/source/blender/draw/engines/eevee_next/eevee_lightcache.hh @@ -9,6 +9,7 @@ #pragma once #include "BLI_vector.hh" +#include struct wmWindowManager; struct wmWindow; @@ -38,6 +39,7 @@ wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm, ViewLayer *view_layer, Scene *scene, blender::Vector original_probes, + std::string &report, int delay_ms, int frame); @@ -53,6 +55,7 @@ void *EEVEE_NEXT_lightbake_job_data_alloc(Main *bmain, ViewLayer *view_layer, Scene *scene, blender::Vector original_probes, + std::string &report, int frame); /** diff --git a/source/blender/draw/engines/eevee_next/eevee_lightprobe_volume.cc b/source/blender/draw/engines/eevee_next/eevee_lightprobe_volume.cc index 5357a68cc4e..6514f11b161 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightprobe_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_lightprobe_volume.cc @@ -4,6 +4,7 @@ #include "DNA_lightprobe_types.h" +#include "BKE_global.hh" #include "BKE_lightprobe.h" #include "GPU_capabilities.hh" @@ -15,6 +16,8 @@ #include "eevee_lightprobe_volume.hh" +#include + namespace blender::eevee { /* -------------------------------------------------------------------- */ @@ -984,25 +987,44 @@ void IrradianceBake::surfels_create(const Object &probe_object) GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE); capture_info_buf_.read(); if (capture_info_buf_.surfel_len == 0) { - /* No surfel to allocated. */ - do_break_ = true; + /* No surfel to allocate. */ return; } if (capture_info_buf_.surfel_len > surfels_buf_.size()) { + printf("IrradianceBake: Allocating %u surfels.\n", capture_info_buf_.surfel_len); + size_t max_size = GPU_max_storage_buffer_size(); if (GPU_mem_stats_supported()) { int total_mem_kb, free_mem_kb; GPU_mem_stats_get(&total_mem_kb, &free_mem_kb); - max_size = min(max_size, size_t(free_mem_kb) * 1024); + /* Leave at least 128MByte for OS and stuffs. + * Try to avoid crashes because of OUT_OF_MEMORY errors. */ + size_t max_alloc = (size_t(total_mem_kb) - 128 * 1024) * 1024; + /* Cap to 95% of available memory. */ + size_t max_free = size_t((size_t(free_mem_kb) * 1024) * 0.95f); + + max_size = min(max_size, min(max_alloc, max_free)); } size_t required_mem = sizeof(Surfel) * (capture_info_buf_.surfel_len - surfels_buf_.size()); if (required_mem > max_size) { - capture_info_buf_.surfel_len = 0u; - capture_info_buf_.push_update(); - inst_.info += "Error: Not enough memory to bake " + std::string(probe_object.id.name) + - ".\n"; + const bool is_ssbo_bound = (max_size == GPU_max_storage_buffer_size()); + const uint req_mb = required_mem / (1024 * 1024); + const uint max_mb = max_size / (1024 * 1024); + + inst_.info = std::string(is_ssbo_bound ? "Cannot allocate enough" : "Not enough available") + + " video memory to bake \"" + std::string(probe_object.id.name + 2) + "\" (" + + std::to_string(req_mb) + " / " + std::to_string(max_mb) + + " MBytes). " + "Try reducing surfel resolution or capture distance to lower the size of the " + "allocation.\n"; + + if (G.background) { + /* Print something in background mode instead of failing silently. */ + fprintf(stderr, "%s\n", inst_.info.c_str()); + } + do_break_ = true; return; } diff --git a/source/blender/editors/render/render_shading.cc b/source/blender/editors/render/render_shading.cc index 5005901612a..56540c8ea19 100644 --- a/source/blender/editors/render/render_shading.cc +++ b/source/blender/editors/render/render_shading.cc @@ -1590,6 +1590,15 @@ static blender::Vector lightprobe_cache_irradiance_volume_subset_get(b return probes; } +struct BakeOperatorData { + /* Store actual owner of job, so modal operator could check for it, + * the reason of this is that active scene could change when rendering + * several layers from compositor #31800. */ + Scene *scene; + + std::string report; +}; + static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) { wmWindowManager *wm = CTX_wm_manager(C); @@ -1597,7 +1606,6 @@ static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEve ViewLayer *view_layer = CTX_data_view_layer(C); Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); - int delay = RNA_int_get(op->ptr, "delay"); blender::Vector probes = lightprobe_cache_irradiance_volume_subset_get(C, op); @@ -1605,15 +1613,16 @@ static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEve return OPERATOR_CANCELLED; } + BakeOperatorData *data = MEM_new(__func__); + data->scene = scene; + data->report = ""; + wmJob *wm_job = EEVEE_NEXT_lightbake_job_create( - wm, win, bmain, view_layer, scene, probes, scene->r.cfra, delay); + wm, win, bmain, view_layer, scene, probes, data->report, scene->r.cfra, 0); WM_event_add_modal_handler(C, op); - /* Store actual owner of job, so modal operator could check for it, - * the reason of this is that active scene could change when rendering - * several layers from compositor #31800. */ - op->customdata = scene; + op->customdata = static_cast(data); WM_jobs_start(wm, wm_job); @@ -1624,10 +1633,20 @@ static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEve static int lightprobe_cache_bake_modal(bContext *C, wmOperator *op, const wmEvent *event) { - Scene *scene = (Scene *)op->customdata; + BakeOperatorData *data = static_cast(op->customdata); + Scene *scene = data->scene; /* No running bake, remove handler and pass through. */ if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_LIGHT_BAKE)) { + std::string report = data->report; + + MEM_delete(data); + op->customdata = nullptr; + + if (!report.empty()) { + BKE_report(op->reports, RPT_ERROR, report.c_str()); + return OPERATOR_CANCELLED; + } return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; } @@ -1642,7 +1661,7 @@ static int lightprobe_cache_bake_modal(bContext *C, wmOperator *op, const wmEven static void lightprobe_cache_bake_cancel(bContext *C, wmOperator *op) { wmWindowManager *wm = CTX_wm_manager(C); - Scene *scene = (Scene *)op->customdata; + Scene *scene = static_cast(op->customdata)->scene; /* Kill on cancel, because job is using op->reports. */ WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_LIGHT_BAKE); @@ -1659,8 +1678,9 @@ static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op) blender::Vector probes = lightprobe_cache_irradiance_volume_subset_get(C, op); - /* TODO: abort if selected engine is not eevee. */ - void *rj = EEVEE_NEXT_lightbake_job_data_alloc(bmain, view_layer, scene, probes, scene->r.cfra); + std::string report; + void *rj = EEVEE_NEXT_lightbake_job_data_alloc( + bmain, view_layer, scene, probes, report, scene->r.cfra); /* Do the job. */ wmJobWorkerStatus worker_status = {}; EEVEE_NEXT_lightbake_job(rj, &worker_status); @@ -1668,6 +1688,11 @@ static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op) EEVEE_NEXT_lightbake_update(rj); EEVEE_NEXT_lightbake_job_data_free(rj); + if (!report.empty()) { + BKE_report(op->reports, RPT_ERROR, report.c_str()); + return OPERATOR_CANCELLED; + } + return OPERATOR_FINISHED; } @@ -1699,17 +1724,6 @@ void OBJECT_OT_lightprobe_cache_bake(wmOperatorType *ot) ot->cancel = lightprobe_cache_bake_cancel; ot->exec = lightprobe_cache_bake_exec; - ot->prop = RNA_def_int(ot->srna, - "delay", - 0, - 0, - 2000, - "Delay", - "Delay in millisecond before baking starts", - 0, - 2000); - RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE); - ot->prop = RNA_def_enum( ot->srna, "subset", light_cache_subset_items, 0, "Subset", "Subset of probes to update"); RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE); diff --git a/source/blender/makesdna/DNA_lightprobe_defaults.h b/source/blender/makesdna/DNA_lightprobe_defaults.h index 86ef06fef23..614885a1fa4 100644 --- a/source/blender/makesdna/DNA_lightprobe_defaults.h +++ b/source/blender/makesdna/DNA_lightprobe_defaults.h @@ -35,7 +35,7 @@ .distpar = 2.5f, \ .falloff = 0.2f, \ .clipsta = 0.8f, \ - .clipend = 40.0f, \ + .clipend = 20.0f, \ .vis_bias = 1.0f, \ .vis_blur = 0.2f, \ .intensity = 1.0f, \