DNA: Move irradiance grid light cache data to Object level

This is the first step for refactoring the lightcache system.
Each probe instance (as in `Object`) will now store its own baked data.
The data is currently stored in uncompressed readable format.

This introduces two new operators for baking to avoid confusion with
the previous light baking pipeline. These do nothing other than
creating empty caches that will be populated by EEVEE later on.

The DNA storage is made to be able to include multiple caches
in case of baked simulation over time but it isn't yet supported.

I prefer to keep the implementation simple for now as the long term
goals for this feature are uncertain.
There is still a type flag (`LightProbeObjectCache.cache_type`) that
will be used for versioning.

The naming convention of structs is a bit weird but that's all I
found in order to avoid interfering with the old scene light cache
that is still used by (old) EEVEE.

Related task #106449.

Pull Request: https://projects.blender.org/blender/blender/pulls/106808
This commit is contained in:
Clément Foucault 2023-04-17 17:12:19 +02:00
parent b1703bd902
commit a8feb20e1c
8 changed files with 554 additions and 3 deletions

View File

@ -14,10 +14,48 @@ extern "C" {
struct LightProbe;
struct Main;
struct BlendWriter;
struct BlendDataReader;
struct LightProbeObjectCache;
struct LightProbeGridCacheFrame;
struct Object;
void BKE_lightprobe_type_set(struct LightProbe *probe, short lightprobe_type);
void *BKE_lightprobe_add(struct Main *bmain, const char *name);
void BKE_lightprobe_cache_blend_write(struct BlendWriter *writer,
struct LightProbeObjectCache *cache);
void BKE_lightprobe_cache_blend_read(struct BlendDataReader *reader,
struct LightProbeObjectCache *cache);
/**
* Create a single empty irradiance grid cache.
*/
struct LightProbeGridCacheFrame *BKE_lightprobe_grid_cache_frame_create(void);
/**
* Free a single grid cache.
*/
void BKE_lightprobe_grid_cache_frame_free(struct LightProbeGridCacheFrame *cache);
/**
* Create the grid cache list depending on the lightprobe baking settings.
* The list is left empty to be filled by the baking process.
*/
void BKE_lightprobe_cache_create(struct Object *object);
/**
* Free all irradiance grids allocated for the given object.
*/
void BKE_lightprobe_cache_free(struct Object *object);
/**
* Return the number of sample stored inside an irradiance cache.
* This depends on the light cache type.
*/
int64_t BKE_lightprobe_grid_cache_frame_sample_count(const struct LightProbeGridCacheFrame *cache);
#ifdef __cplusplus
}
#endif

View File

@ -12,6 +12,8 @@
#include "DNA_lightprobe_types.h"
#include "DNA_object_types.h"
#include "BLI_math_base.h"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "BKE_anim_data.h"
@ -129,3 +131,137 @@ void *BKE_lightprobe_add(Main *bmain, const char *name)
return probe;
}
static void lightprobe_grid_cache_frame_blend_write(BlendWriter *writer,
const LightProbeGridCacheFrame *cache)
{
BLO_write_struct_array(writer, LightProbeGridCacheFrame, cache->block_len, cache->block_infos);
int64_t sample_count = BKE_lightprobe_grid_cache_frame_sample_count(cache);
BLO_write_float3_array(writer, sample_count, (float *)cache->irradiance.L0);
BLO_write_float3_array(writer, sample_count, (float *)cache->irradiance.L1_a);
BLO_write_float3_array(writer, sample_count, (float *)cache->irradiance.L1_b);
BLO_write_float3_array(writer, sample_count, (float *)cache->irradiance.L1_c);
BLO_write_float_array(writer, sample_count, cache->visibility.L0);
BLO_write_float_array(writer, sample_count, cache->visibility.L1_a);
BLO_write_float_array(writer, sample_count, cache->visibility.L1_b);
BLO_write_float_array(writer, sample_count, cache->visibility.L1_c);
BLO_write_struct_array(
writer, LightProbeGridCacheFrame, sample_count, cache->connectivity.bitmask);
}
static void lightprobe_grid_cache_frame_blend_read(BlendDataReader *reader,
LightProbeGridCacheFrame *cache)
{
if (!ELEM(cache->data_layout,
LIGHTPROBE_CACHE_ADAPTIVE_RESOLUTION,
LIGHTPROBE_CACHE_UNIFORM_GRID)) {
/* Do not try to read data from incompatible layout. Clear all pointers. */
memset(cache, 0, sizeof(*cache));
return;
}
BLO_read_data_address(reader, &cache->block_infos);
int64_t sample_count = BKE_lightprobe_grid_cache_frame_sample_count(cache);
/* Baking data is not stored. */
cache->baking.L0 = nullptr;
cache->baking.L1_a = nullptr;
cache->baking.L1_b = nullptr;
cache->baking.L1_c = nullptr;
cache->surfels = nullptr;
cache->surfels_len = 0;
BLO_read_float3_array(reader, sample_count, (float **)&cache->irradiance.L0);
BLO_read_float3_array(reader, sample_count, (float **)&cache->irradiance.L1_a);
BLO_read_float3_array(reader, sample_count, (float **)&cache->irradiance.L1_b);
BLO_read_float3_array(reader, sample_count, (float **)&cache->irradiance.L1_c);
BLO_read_float_array(reader, sample_count, &cache->visibility.L0);
BLO_read_float_array(reader, sample_count, &cache->visibility.L1_a);
BLO_read_float_array(reader, sample_count, &cache->visibility.L1_b);
BLO_read_float_array(reader, sample_count, &cache->visibility.L1_c);
BLO_read_data_address(reader, &cache->connectivity.bitmask);
}
void BKE_lightprobe_cache_blend_write(BlendWriter *writer, LightProbeObjectCache *cache)
{
if (cache->grid_static_cache != nullptr) {
BLO_write_struct(writer, LightProbeGridCacheFrame, cache->grid_static_cache);
lightprobe_grid_cache_frame_blend_write(writer, cache->grid_static_cache);
}
}
void BKE_lightprobe_cache_blend_read(BlendDataReader *reader, LightProbeObjectCache *cache)
{
if (cache->grid_static_cache != nullptr) {
BLO_read_data_address(reader, &cache->grid_static_cache);
lightprobe_grid_cache_frame_blend_read(reader, cache->grid_static_cache);
}
}
template<typename T> static void spherical_harmonic_free(T &data)
{
MEM_SAFE_FREE(data.L0);
MEM_SAFE_FREE(data.L1_a);
MEM_SAFE_FREE(data.L1_b);
MEM_SAFE_FREE(data.L1_c);
}
LightProbeGridCacheFrame *BKE_lightprobe_grid_cache_frame_create()
{
LightProbeGridCacheFrame *cache = static_cast<LightProbeGridCacheFrame *>(
MEM_callocN(sizeof(LightProbeGridCacheFrame), "LightProbeGridCacheFrame"));
return cache;
}
void BKE_lightprobe_grid_cache_frame_free(LightProbeGridCacheFrame *cache)
{
MEM_SAFE_FREE(cache->block_infos);
spherical_harmonic_free(cache->baking);
spherical_harmonic_free(cache->irradiance);
spherical_harmonic_free(cache->visibility);
MEM_SAFE_FREE(cache->connectivity.bitmask);
MEM_SAFE_FREE(cache->surfels);
MEM_SAFE_FREE(cache);
}
void BKE_lightprobe_cache_create(Object *object)
{
BLI_assert(object->lightprobe_cache == nullptr);
object->lightprobe_cache = static_cast<LightProbeObjectCache *>(
MEM_callocN(sizeof(LightProbeObjectCache), "LightProbeObjectCache"));
}
void BKE_lightprobe_cache_free(Object *object)
{
if (object->lightprobe_cache == nullptr) {
return;
}
LightProbeObjectCache *cache = object->lightprobe_cache;
if (cache->shared == false) {
if (cache->grid_static_cache != nullptr) {
BKE_lightprobe_grid_cache_frame_free(cache->grid_static_cache);
}
}
MEM_SAFE_FREE(object->lightprobe_cache);
}
int64_t BKE_lightprobe_grid_cache_frame_sample_count(const LightProbeGridCacheFrame *cache)
{
if (cache->data_layout == LIGHTPROBE_CACHE_ADAPTIVE_RESOLUTION) {
return cache->block_len * cube_i(cache->block_size);
}
/* LIGHTPROBE_CACHE_UNIFORM_GRID */
return cache->size[0] * cache->size[1] * cache->size[2];
}

View File

@ -267,6 +267,18 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in
if (ob_src->lightgroup) {
ob_dst->lightgroup = (LightgroupMembership *)MEM_dupallocN(ob_src->lightgroup);
}
if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) != 0) {
if (ob_src->lightprobe_cache) {
/* Reference the original object data. */
ob_dst->lightprobe_cache = (LightProbeObjectCache *)MEM_dupallocN(ob_src->lightprobe_cache);
ob_dst->lightprobe_cache->shared = true;
}
}
else {
/* Do not copy lightprobe's cache. */
ob_dst->lightprobe_cache = nullptr;
}
}
static void object_free_data(ID *id)
@ -319,6 +331,8 @@ static void object_free_data(ID *id)
BKE_previewimg_free(&ob->preview);
MEM_SAFE_FREE(ob->lightgroup);
BKE_lightprobe_cache_free(ob);
}
static void library_foreach_modifiersForeachIDLink(void *user_data,
@ -597,6 +611,11 @@ static void object_blend_write(BlendWriter *writer, ID *id, const void *id_addre
if (ob->lightgroup) {
BLO_write_struct(writer, LightgroupMembership, ob->lightgroup);
}
if (ob->lightprobe_cache) {
BLO_write_struct(writer, LightProbeObjectCache, ob->lightprobe_cache);
BKE_lightprobe_cache_blend_write(writer, ob->lightprobe_cache);
}
}
/* XXX deprecated - old animation system */
@ -815,6 +834,11 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
BKE_previewimg_blend_read(reader, ob->preview);
BLO_read_data_address(reader, &ob->lightgroup);
BLO_read_data_address(reader, &ob->lightprobe_cache);
if (ob->lightprobe_cache) {
BKE_lightprobe_cache_blend_read(reader, ob->lightprobe_cache);
}
}
/* XXX deprecated - old animation system */

View File

@ -41,6 +41,9 @@ void SCENE_OT_view_layer_remove_unused_lightgroups(struct wmOperatorType *ot);
void SCENE_OT_light_cache_bake(struct wmOperatorType *ot);
void SCENE_OT_light_cache_free(struct wmOperatorType *ot);
void OBJECT_OT_lightprobe_cache_bake(struct wmOperatorType *ot);
void OBJECT_OT_lightprobe_cache_free(struct wmOperatorType *ot);
void SCENE_OT_render_view_add(struct wmOperatorType *ot);
void SCENE_OT_render_view_remove(struct wmOperatorType *ot);

View File

@ -28,6 +28,9 @@ void ED_operatortypes_render()
WM_operatortype_append(OBJECT_OT_material_slot_move);
WM_operatortype_append(OBJECT_OT_material_slot_remove_unused);
WM_operatortype_append(OBJECT_OT_lightprobe_cache_bake);
WM_operatortype_append(OBJECT_OT_lightprobe_cache_free);
WM_operatortype_append(MATERIAL_OT_new);
WM_operatortype_append(TEXTURE_OT_new);
WM_operatortype_append(WORLD_OT_new);

View File

@ -37,6 +37,7 @@
#include "BKE_image.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lightprobe.h"
#include "BKE_linestyle.h"
#include "BKE_main.h"
#include "BKE_material.h"
@ -53,6 +54,7 @@
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#ifdef WITH_FREESTYLE
# include "BKE_freestyle.h"
@ -1332,6 +1334,8 @@ enum {
LIGHTCACHE_SUBSET_ALL = 0,
LIGHTCACHE_SUBSET_DIRTY,
LIGHTCACHE_SUBSET_CUBE,
LIGHTCACHE_SUBSET_SELECTED,
LIGHTCACHE_SUBSET_ACTIVE,
};
static void light_cache_bake_tag_cache(Scene *scene, wmOperator *op)
@ -1348,6 +1352,9 @@ static void light_cache_bake_tag_cache(Scene *scene, wmOperator *op)
case LIGHTCACHE_SUBSET_DIRTY:
/* Leave tag untouched. */
break;
default:
BLI_assert_unreachable();
break;
}
}
}
@ -1492,6 +1499,169 @@ void SCENE_OT_light_cache_bake(wmOperatorType *ot)
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
}
/* NOTE: New version destined to replace the old lightcache bake operator. */
static void lightprobe_cache_bake_start(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
auto is_irradiance_volume = [](Object *ob) -> bool {
return ob->type == OB_LIGHTPROBE &&
static_cast<LightProbe *>(ob->data)->type == LIGHTPROBE_TYPE_GRID;
};
auto irradiance_volume_setup = [](Object *ob) {
BKE_lightprobe_cache_free(ob);
BKE_lightprobe_cache_create(ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
};
int subset = RNA_enum_get(op->ptr, "subset");
switch (subset) {
case LIGHTCACHE_SUBSET_ALL: {
FOREACH_OBJECT_BEGIN (scene, view_layer, ob) {
if (is_irradiance_volume(ob)) {
irradiance_volume_setup(ob);
}
}
FOREACH_OBJECT_END;
break;
}
case LIGHTCACHE_SUBSET_DIRTY: {
FOREACH_OBJECT_BEGIN (scene, view_layer, ob) {
if (is_irradiance_volume(ob) && ob->lightprobe_cache && ob->lightprobe_cache->dirty) {
irradiance_volume_setup(ob);
}
}
FOREACH_OBJECT_END;
break;
}
case LIGHTCACHE_SUBSET_SELECTED: {
uint objects_len = 0;
ObjectsInViewLayerParams parameters;
parameters.filter_fn = nullptr;
parameters.no_dup_data = true;
Object **objects = BKE_view_layer_array_selected_objects_params(
view_layer, nullptr, &objects_len, &parameters);
for (Object *ob : blender::MutableSpan<Object *>(objects, objects_len)) {
if (is_irradiance_volume(ob)) {
irradiance_volume_setup(ob);
}
}
MEM_freeN(objects);
break;
}
case LIGHTCACHE_SUBSET_ACTIVE: {
Object *active_ob = CTX_data_active_object(C);
if (is_irradiance_volume(active_ob)) {
irradiance_volume_setup(active_ob);
}
break;
}
default:
BLI_assert_unreachable();
break;
}
}
static int lightprobe_cache_bake_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Scene *scene = CTX_data_scene(C);
lightprobe_cache_bake_start(C, op);
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;
WM_cursor_wait(false);
return OPERATOR_RUNNING_MODAL;
}
static int lightprobe_cache_bake_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = (Scene *)op->customdata;
/* No running bake, remove handler and pass through. */
if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_LIGHT_BAKE)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
/* Running bake. */
switch (event->type) {
case EVT_ESCKEY:
return OPERATOR_RUNNING_MODAL;
}
return OPERATOR_PASS_THROUGH;
}
static void lightprobe_cache_bake_cancel(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
Scene *scene = (Scene *)op->customdata;
/* Kill on cancel, because job is using op->reports. */
WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_LIGHT_BAKE);
}
/* Executes blocking bake. */
static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op)
{
lightprobe_cache_bake_start(C, op);
return OPERATOR_FINISHED;
}
void OBJECT_OT_lightprobe_cache_bake(wmOperatorType *ot)
{
static const EnumPropertyItem light_cache_subset_items[] = {
{LIGHTCACHE_SUBSET_ALL, "ALL", 0, "All Light Probes", "Bake all light probes"},
{LIGHTCACHE_SUBSET_DIRTY,
"DIRTY",
0,
"Dirty Only",
"Only bake light probes that are marked as dirty"},
{LIGHTCACHE_SUBSET_SELECTED,
"SELECTED",
0,
"Selected Only",
"Only bake selected light probes"},
{LIGHTCACHE_SUBSET_ACTIVE, "ACTIVE", 0, "Active Only", "Only bake the active light probe"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Bake Light Cache";
ot->idname = "OBJECT_OT_lightprobe_cache_bake";
ot->description = "Bake the active view layer lighting";
/* api callbacks */
ot->invoke = lightprobe_cache_bake_invoke;
ot->modal = lightprobe_cache_bake_modal;
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);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -1541,6 +1711,49 @@ void SCENE_OT_light_cache_free(wmOperatorType *ot)
ot->poll = light_cache_free_poll;
}
/* NOTE: New version destined to replace the old lightcache bake operator. */
static bool lightprobe_cache_free_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
return object && object->lightprobe_cache != nullptr;
}
static int lightprobe_cache_free_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Object *object = CTX_data_active_object(C);
/* Kill potential bake job first (see #57011). */
wmWindowManager *wm = CTX_wm_manager(C);
WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_LIGHT_BAKE);
if (object->lightprobe_cache == nullptr) {
return OPERATOR_CANCELLED;
}
BKE_lightprobe_cache_free(object);
DEG_id_tag_update(&object->id, ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, scene);
return OPERATOR_FINISHED;
}
void OBJECT_OT_lightprobe_cache_free(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Light Cache";
ot->idname = "OBJECT_OT_lightprobe_cache_free";
ot->description = "Delete cached indirect lighting";
/* api callbacks */
ot->exec = lightprobe_cache_free_exec;
ot->poll = lightprobe_cache_free_poll;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -61,9 +61,6 @@ typedef struct LightProbe {
struct Image *image;
/** Object visibility group, inclusive or exclusive. */
struct Collection *visibility_grp;
/* Runtime display data */
float distfalloff, distgridinf;
} LightProbe;
/* Probe->type */
@ -201,6 +198,137 @@ enum {
LIGHTCACHETEX_UINT = (1 << 2),
};
/* -------------------------------------------------------------------- */
/** \name Irradiance grid data storage
*
* Each spherical harmonic band is stored separately. This allow loading only a specific band.
* The layout of each array is set by the #LightProbeGridType.
* Any unavailable data is be set to nullptr.
* \{ */
/**
* Irradiance data (RGB) stored along visibility (A).
* This is the format used during baking and is used for visualizing the baking process.
*/
typedef struct LightProbeBakingData {
float (*L0)[4];
float (*L1_a)[4];
float (*L1_b)[4];
float (*L1_c)[4];
} LightProbeBakingData;
/**
* Irradiance stored as RGB triple using scene linear color space.
*/
typedef struct LightProbeIrradianceData {
float (*L0)[3];
float (*L1_a)[3];
float (*L1_b)[3];
float (*L1_c)[3];
} LightProbeIrradianceData;
/**
* Normalized visibility of distant light. Used for compositing grids together.
*/
typedef struct LightProbeVisibilityData {
float *L0;
float *L1_a;
float *L1_b;
float *L1_c;
} LightProbeVisibilityData;
/**
* Used to avoid light leaks. Validate visibility between each grid sample.
*/
typedef struct LightProbeConnectivityData {
/** Stores a bitmask of valid connections within a cell. */
uint8_t *bitmask;
} LightProbeConnectivityData;
/**
* Defines one block of data inside the grid cache data arrays.
* The block size if the same for all the blocks.
*/
typedef struct LightProbeBlockData {
/* Offset inside the level-of-detail this block starts. */
int offset[3];
/* Level-of-detail this block is from. */
int level;
} LightProbeBlockData;
/** \} */
/* -------------------------------------------------------------------- */
/** \name LightProbeGridCacheFrame
*
* \{ */
/**
* A frame worth of baked lighting data.
*/
typedef struct LightProbeGridCacheFrame {
/** Number of samples in the highest level of detail. */
int size[3];
/** Spatial layout type of the data stored inside the data arrays. */
int data_layout;
/** Sparse or adaptive layout only: number of blocks inside data arrays. */
int block_len;
/** Sparse or adaptive layout only: size of a block in samples. All 3 dimensions are equal. */
int block_size;
/** Sparse or adaptive layout only: specify the blocks positions. */
LightProbeBlockData *block_infos;
/** In-progress baked data. Not stored in file. */
LightProbeBakingData baking;
/** Baked data. */
LightProbeIrradianceData irradiance;
LightProbeVisibilityData visibility;
LightProbeConnectivityData connectivity;
char _pad[4];
/** Number of debug surfels. */
int surfels_len;
/** Debug surfels used to visualize the baking process. Not stored in file. */
void *surfels;
} LightProbeGridCacheFrame;
/** #LightProbeGridCacheFrame.data_layout (int) */
enum {
/** Simple uniform grid. Raw output from GPU. Used during the baking process. */
LIGHTPROBE_CACHE_UNIFORM_GRID = 0,
/** Fills the space with different level of resolution. More efficient storage. */
LIGHTPROBE_CACHE_ADAPTIVE_RESOLUTION = 1,
};
/**
* Per object container of baked data.
* Should be called #LightProbeCache but name is already taken.
*/
typedef struct LightProbeObjectCache {
/** Allow correct versioning / different types of data for the same layout. */
int cache_type;
/** True if this cache references the original object's cache. */
char shared;
/** True if the cache has been tagged for automatic baking. */
char dirty;
char _pad0[2];
struct LightProbeGridCacheFrame *grid_static_cache;
} LightProbeObjectCache;
/** #LightProbeObjectCache.type (int) */
enum {
/** Light cache was just created and is not yet baked. Keep as 0 for default value. */
LIGHTPROBE_CACHE_TYPE_NONE = 0,
/** Light cache is baked for one specific frame and capture all indirect lighting. */
LIGHTPROBE_CACHE_TYPE_STATIC = 1,
};
/** \} */
#ifdef __cplusplus
}
#endif

View File

@ -34,6 +34,7 @@ struct FluidsimSettings;
struct GeometrySet;
struct Ipo;
struct LightgroupMembership;
struct LightProbeGridCacheFrame;
struct Material;
struct Mesh;
struct Object;
@ -449,6 +450,11 @@ typedef struct Object {
/** Lightgroup membership information. */
struct LightgroupMembership *lightgroup;
/** Irradiance caches baked for this object (light-probes only). */
struct LightProbeObjectCache *lightprobe_cache;
void *_pad9;
/** Runtime evaluation data (keep last). */
Object_Runtime runtime;
} Object;