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
This commit is contained in:
Clément Foucault
2024-05-31 22:55:05 +02:00
committed by Clément Foucault
parent 358ccb8146
commit 0d7d6b00c9
6 changed files with 82 additions and 38 deletions

View File

@@ -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();

View File

@@ -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<Object *> 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<Object *> 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<Object *> 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<void *>(bake);
}

View File

@@ -9,6 +9,7 @@
#pragma once
#include "BLI_vector.hh"
#include <string>
struct wmWindowManager;
struct wmWindow;
@@ -38,6 +39,7 @@ wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm,
ViewLayer *view_layer,
Scene *scene,
blender::Vector<Object *> 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<Object *> original_probes,
std::string &report,
int frame);
/**

View File

@@ -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 <cstdio>
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;
}

View File

@@ -1590,6 +1590,15 @@ static blender::Vector<Object *> 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<Object *> 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<BakeOperatorData>(__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<void *>(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<BakeOperatorData *>(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<BakeOperatorData *>(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<Object *> 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);

View File

@@ -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, \