WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 358 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
59 changed files with 1225 additions and 1364 deletions
Showing only changes of commit 32c472c0af - Show all commits

View File

@ -19,13 +19,13 @@
* which is then stored per instance. Many instances can use the same #InstanceReference.
*/
#include <mutex>
#include <optional>
#include "BLI_array.hh"
#include "BLI_function_ref.hh"
#include "BLI_index_mask_fwd.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_shared_cache.hh"
#include "BLI_vector.hh"
#include "DNA_customdata_types.h"
@ -99,19 +99,16 @@ class Instances {
*/
Vector<InstanceReference> references_;
/** Indices into `references_`. Determines what data is instanced. */
Vector<int> reference_handles_;
/** Transformation of the instances. */
Vector<float4x4> transforms_;
CustomData attributes_;
/* These almost unique ids are generated based on the `id` attribute, which might not contain
* unique ids at all. They are *almost* unique, because under certain very unlikely
* circumstances, they are not unique. Code using these ids should not crash when they are not
* unique but can generally expect them to be unique. */
mutable std::mutex almost_unique_ids_mutex_;
mutable Array<int> almost_unique_ids_;
CustomData attributes_;
mutable SharedCache<Array<int>> almost_unique_ids_cache_;
public:
Instances();
@ -161,7 +158,7 @@ class Instances {
GeometrySet &geometry_set_from_reference(int reference_index);
Span<int> reference_handles() const;
MutableSpan<int> reference_handles();
MutableSpan<int> reference_handles_for_write();
MutableSpan<float4x4> transforms();
Span<float4x4> transforms() const;
@ -189,6 +186,11 @@ class Instances {
bool owns_direct_data() const;
void ensure_owns_direct_data();
void tag_reference_handles_changed()
{
almost_unique_ids_cache_.tag_dirty();
}
};
/* -------------------------------------------------------------------- */

View File

@ -163,6 +163,9 @@ void nodeChainIter(const bNodeTree *ntree,
* Can be called recursively (using another nodeChainIterBackwards) by
* setting the recursion_lvl accordingly.
*
* WARN: No node is guaranteed to be iterated as a to_node,
* since it could have been iterated earlier as a from_node.
*
* \note Needs updated socket links (ntreeUpdateTree).
* \note Recursive
*/

View File

@ -129,6 +129,9 @@ bool allow_procedural_attribute_access(StringRef attribute_name)
if (attribute_name.startswith(".uv")) {
return false;
}
if (attribute_name == ".reference_index") {
return false;
}
if (attribute_name.startswith("." UV_VERTSEL_NAME ".")) {
return false;
}

View File

@ -724,19 +724,25 @@ static std::unique_ptr<Instances> try_load_instances(const DictionaryValue &io_g
return {};
}
const auto *io_handles = io_instances->lookup_dict("handles");
if (!io_handles) {
return {};
}
if (!read_blob_simple_gspan(blob_reader, *io_handles, instances->reference_handles())) {
return {};
}
MutableAttributeAccessor attributes = instances->attributes_for_write();
if (!load_attributes(*io_attributes, attributes, blob_reader, blob_sharing)) {
return {};
}
if (!attributes.contains(".reference_index")) {
/* Try reading the reference index attribute from the old bake format from before it was an
* attribute. */
const auto *io_handles = io_instances->lookup_dict("handles");
if (!io_handles) {
return {};
}
if (!read_blob_simple_gspan(
blob_reader, *io_handles, instances->reference_handles_for_write()))
{
return {};
}
}
return instances;
}
@ -969,9 +975,6 @@ static std::shared_ptr<DictionaryValue> serialize_geometry_set(const GeometrySet
io_instances->append(
"transforms", write_blob_simple_gspan(blob_writer, blob_sharing, instances.transforms()));
io_instances->append(
"handles",
write_blob_simple_gspan(blob_writer, blob_sharing, instances.reference_handles()));
auto io_attributes = serialize_attributes(
instances.attributes(), blob_writer, blob_sharing, {"position"});

View File

@ -168,6 +168,12 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider
}
};
static void tag_component_reference_index_changed(void *owner)
{
Instances &instances = *static_cast<Instances *>(owner);
instances.tag_reference_handles_changed();
}
static ComponentAttributeProviders create_attribute_providers_for_instances()
{
static InstancePositionAttributeProvider position;
@ -200,10 +206,20 @@ static ComponentAttributeProviders create_attribute_providers_for_instances()
instance_custom_data_access,
nullptr);
/** Indices into `Instances::references_`. Determines what data is instanced. */
static BuiltinCustomDataLayerProvider reference_index(".reference_index",
AttrDomain::Instance,
CD_PROP_INT32,
CD_PROP_INT32,
BuiltinAttributeProvider::Creatable,
BuiltinAttributeProvider::NonDeletable,
instance_custom_data_access,
tag_component_reference_index_changed);
static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance,
instance_custom_data_access);
return ComponentAttributeProviders({&position, &id}, {&instance_custom_data});
return ComponentAttributeProviders({&position, &id, &reference_index}, {&instance_custom_data});
}
static AttributeAccessorFunctions get_instances_accessor_functions()

View File

@ -51,19 +51,17 @@ Instances::Instances()
Instances::Instances(Instances &&other)
: references_(std::move(other.references_)),
reference_handles_(std::move(other.reference_handles_)),
transforms_(std::move(other.transforms_)),
almost_unique_ids_(std::move(other.almost_unique_ids_)),
attributes_(other.attributes_)
attributes_(other.attributes_),
almost_unique_ids_cache_(std::move(other.almost_unique_ids_cache_))
{
CustomData_reset(&other.attributes_);
}
Instances::Instances(const Instances &other)
: references_(other.references_),
reference_handles_(other.reference_handles_),
transforms_(other.transforms_),
almost_unique_ids_(other.almost_unique_ids_)
almost_unique_ids_cache_(other.almost_unique_ids_cache_)
{
CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num());
}
@ -96,7 +94,6 @@ Instances &Instances::operator=(Instances &&other)
void Instances::resize(int capacity)
{
const int old_size = this->instances_num();
reference_handles_.resize(capacity);
transforms_.resize(capacity);
CustomData_realloc(&attributes_, old_size, capacity, CD_SET_DEFAULT);
}
@ -106,19 +103,27 @@ void Instances::add_instance(const int instance_handle, const float4x4 &transfor
BLI_assert(instance_handle >= 0);
BLI_assert(instance_handle < references_.size());
const int old_size = this->instances_num();
reference_handles_.append(instance_handle);
transforms_.append(transform);
CustomData_realloc(&attributes_, old_size, transforms_.size());
this->reference_handles_for_write().last() = instance_handle;
}
Span<int> Instances::reference_handles() const
{
return reference_handles_;
return {static_cast<const int *>(
CustomData_get_layer_named(&attributes_, CD_PROP_INT32, ".reference_index")),
this->instances_num()};
}
MutableSpan<int> Instances::reference_handles()
MutableSpan<int> Instances::reference_handles_for_write()
{
return reference_handles_;
int *data = static_cast<int *>(CustomData_get_layer_named_for_write(
&attributes_, CD_PROP_INT32, ".reference_index", this->instances_num()));
if (!data) {
data = static_cast<int *>(CustomData_add_layer_named(
&attributes_, CD_PROP_INT32, CD_SET_DEFAULT, this->instances_num(), ".reference_index"));
}
return {data, this->instances_num()};
}
MutableSpan<float4x4> Instances::transforms()
@ -178,10 +183,7 @@ void Instances::remove(const IndexMask &mask,
Instances new_instances;
new_instances.references_ = std::move(references_);
new_instances.reference_handles_.resize(new_size);
new_instances.transforms_.resize(new_size);
array_utils::gather(
reference_handles_.as_span(), mask, new_instances.reference_handles_.as_mutable_span());
array_utils::gather(transforms_.as_span(), mask, new_instances.transforms_.as_mutable_span());
gather_attributes(this->attributes(),
@ -212,6 +214,8 @@ void Instances::remove_unused_references()
return;
}
const Span<int> reference_handles = this->reference_handles();
Array<bool> usage_by_handle(tot_references_before, false);
std::mutex mutex;
@ -221,7 +225,7 @@ void Instances::remove_unused_references()
Array<bool> local_usage_by_handle(tot_references_before, false);
for (const int i : range) {
const int handle = reference_handles_[i];
const int handle = reference_handles[i];
BLI_assert(handle >= 0 && handle < tot_references_before);
local_usage_by_handle[handle] = true;
}
@ -266,11 +270,14 @@ void Instances::remove_unused_references()
}
/* Update handles of instances. */
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
for (const int i : range) {
reference_handles_[i] = handle_mapping[reference_handles_[i]];
}
});
{
const MutableSpan<int> reference_handles = this->reference_handles_for_write();
threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) {
for (const int i : range) {
reference_handles[i] = handle_mapping[reference_handles[i]];
}
});
}
}
int Instances::instances_num() const
@ -357,21 +364,20 @@ static Array<int> generate_unique_instance_ids(Span<int> original_ids)
Span<int> Instances::almost_unique_ids() const
{
std::lock_guard lock(almost_unique_ids_mutex_);
bke::AttributeReader<int> instance_ids_attribute = this->attributes().lookup<int>("id");
if (instance_ids_attribute) {
Span<int> instance_ids = instance_ids_attribute.varray.get_internal_span();
if (almost_unique_ids_.size() != instance_ids.size()) {
almost_unique_ids_ = generate_unique_instance_ids(instance_ids);
almost_unique_ids_cache_.ensure([&](Array<int> &r_data) {
bke::AttributeReader<int> instance_ids_attribute = this->attributes().lookup<int>("id");
if (instance_ids_attribute) {
Span<int> instance_ids = instance_ids_attribute.varray.get_internal_span();
if (r_data.size() != instance_ids.size()) {
r_data = generate_unique_instance_ids(instance_ids);
}
}
}
else {
almost_unique_ids_.reinitialize(this->instances_num());
for (const int i : almost_unique_ids_.index_range()) {
almost_unique_ids_[i] = i;
else {
r_data.reinitialize(this->instances_num());
array_utils::fill_index_range(r_data.as_mutable_span());
}
}
return almost_unique_ids_;
});
return almost_unique_ids_cache_.data();
}
} // namespace blender::bke

View File

@ -60,6 +60,8 @@ bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL();
# include <iosfwd>
# include <string>
# include "BLI_string_ref.hh"
/** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */
std::ostream &operator<<(std::ostream &stream, bUUID uuid);
@ -79,7 +81,7 @@ class bUUID : public ::bUUID {
bUUID(std::initializer_list<uint32_t> field_values);
/** Initialize by parsing the string; undefined behavior when the string is invalid. */
explicit bUUID(const std::string &string_formatted_uuid);
explicit bUUID(const StringRefNull string_formatted_uuid);
uint64_t hash() const;
}; // namespace blender

View File

@ -153,7 +153,7 @@ bUUID::bUUID(const std::initializer_list<uint32_t> field_values)
std::copy(field_iter, field_values.end(), this->node);
}
bUUID::bUUID(const std::string &string_formatted_uuid)
bUUID::bUUID(const StringRefNull string_formatted_uuid)
{
const bool parsed_ok = BLI_uuid_parse_string(this, string_formatted_uuid.c_str());
if (!parsed_ok) {

View File

@ -110,7 +110,7 @@ void Camera::sync()
if (inst_.is_baking()) {
/* Any view so that shadows and light culling works during irradiance bake. */
draw::View &view = inst_.irradiance_cache.bake.view_z_;
draw::View &view = inst_.volume_probes.bake.view_z_;
data.viewmat = view.viewmat();
data.viewinv = view.viewinv();
data.winmat = view.winmat();

View File

@ -30,16 +30,24 @@
#define CULLING_TILE_GROUP_SIZE 256
/* Reflection Probes. */
#define REFLECTION_PROBES_MAX 128
#define REFLECTION_PROBE_GROUP_SIZE 16
#define REFLECTION_PROBE_SELECT_GROUP_SIZE 64
#define SPHERE_PROBE_GROUP_SIZE 16
#define SPHERE_PROBE_SELECT_GROUP_SIZE 64
/* Number of additional pixels on the border of an octahedral map to reserve for fixing seams.
* Border size requires depends on the max number of mipmap levels. */
#define REFLECTION_PROBE_MIPMAP_LEVELS 5
#define REFLECTION_PROBE_SH_GROUP_SIZE 512
#define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64
#define SPHERE_PROBE_MIPMAP_LEVELS 5
#define SPHERE_PROBE_SH_GROUP_SIZE 512
#define SPHERE_PROBE_SH_SAMPLES_PER_GROUP 64
/**
* Limited by the UBO size limit (16384 bytes / sizeof(SphereProbeData)).
*/
#define SPHERE_PROBE_MAX 128
#define PLANAR_PROBES_MAX 16
/**
* Limited by the performance impact it can cause.
* Limited by the max layer count supported by a hardware (256).
* Limited by the UBO size limit (16384 bytes / sizeof(PlanarProbeData)).
*/
#define PLANAR_PROBE_MAX 16
/**
* IMPORTANT: Some data packing are tweaked for these values.
@ -170,8 +178,8 @@
/* Only during surface shading (forward and deferred eval). */
#define SHADOW_TILEMAPS_TEX_SLOT 4
#define SHADOW_ATLAS_TEX_SLOT 5
#define IRRADIANCE_ATLAS_TEX_SLOT 6
#define REFLECTION_PROBE_TEX_SLOT 7
#define VOLUME_PROBE_TEX_SLOT 6
#define SPHERE_PROBE_TEX_SLOT 7
#define VOLUME_SCATTERING_TEX_SLOT 8
#define VOLUME_TRANSMITTANCE_TEX_SLOT 9
/* Currently only used by ray-tracing, but might become used by forward too. */
@ -202,7 +210,7 @@
#define UNIFORM_BUF_SLOT 1
/* Only during surface shading (forward and deferred eval). */
#define IRRADIANCE_GRID_BUF_SLOT 2
#define REFLECTION_PROBE_BUF_SLOT 3
#define SPHERE_PROBE_BUF_SLOT 3
#define PLANAR_PROBE_BUF_SLOT 4
/* Only during pre-pass. */
#define VELOCITY_CAMERA_PREV_BUF 2

View File

@ -18,8 +18,8 @@ namespace blender::eevee {
void HiZBuffer::sync()
{
int2 render_extent = inst_.film.render_extent_get();
int2 probe_extent = int2(inst_.sphere_probes.probe_render_extent());
/* Padding to avoid complexity during down-sampling and screen tracing. */
int2 probe_extent = int2(inst_.reflection_probes.probe_render_extent());
int2 hiz_extent = math::ceil_to_multiple(math::max(render_extent, probe_extent),
int2(1u << (HIZ_MIP_COUNT - 1)));
int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE));

View File

@ -90,10 +90,11 @@ void Instance::init(const int2 &output_res,
shadows.init();
motion_blur.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(visible_rect);
}
@ -124,10 +125,11 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
depth_of_field.init();
shadows.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(&empty_rect);
}
@ -173,8 +175,7 @@ void Instance::begin_sync()
volume.begin_sync();
pipelines.begin_sync();
cryptomatte.begin_sync();
reflection_probes.begin_sync();
planar_probes.begin_sync();
sphere_probes.begin_sync();
light_probes.begin_sync();
gpencil_engine_enabled = false;
@ -188,7 +189,7 @@ void Instance::begin_sync()
film.sync();
render_buffers.sync();
ambient_occlusion.sync();
irradiance_cache.sync();
volume_probes.sync();
lookdev.sync();
use_surfaces = (view_layer->layflag & SCE_LAY_SOLID) != 0;
@ -267,7 +268,7 @@ void Instance::object_sync(Object *ob)
sync.sync_gpencil(ob, ob_handle, res_handle);
break;
case OB_LIGHTPROBE:
sync.sync_light_probe(ob, ob_handle);
light_probes.sync_probe(ob, ob_handle);
break;
default:
break;
@ -303,7 +304,7 @@ void Instance::end_sync()
cryptomatte.end_sync();
pipelines.end_sync();
light_probes.end_sync();
reflection_probes.end_sync();
sphere_probes.end_sync();
planar_probes.end_sync();
uniform_data.push_update();
@ -344,7 +345,7 @@ void Instance::render_sync()
bool Instance::do_reflection_probe_sync() const
{
if (!reflection_probes.update_probes_this_sample_) {
if (!sphere_probes.update_probes_this_sample_) {
return false;
}
if (materials.queued_shaders_count > 0) {
@ -478,12 +479,6 @@ void Instance::render_read_result(RenderLayer *render_layer, const char *view_na
void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
{
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when there
* are other light probes in the scene. */
if (DEG_id_type_any_exists(this->depsgraph, ID_LP)) {
reflection_probes.update_probes_next_sample_ = true;
planar_probes.update_probes_ = true;
}
while (!sampling.finished()) {
this->render_sample();
@ -635,7 +630,7 @@ void Instance::light_bake_irradiance(
context_disable();
};
irradiance_cache.bake.init(probe);
volume_probes.bake.init(probe);
custom_pipeline_wrapper([&]() {
manager->begin_sync();
@ -646,19 +641,19 @@ void Instance::light_bake_irradiance(
capture_view.render_world();
irradiance_cache.bake.surfels_create(probe);
volume_probes.bake.surfels_create(probe);
if (irradiance_cache.bake.should_break()) {
if (volume_probes.bake.should_break()) {
return;
}
irradiance_cache.bake.surfels_lights_eval();
volume_probes.bake.surfels_lights_eval();
irradiance_cache.bake.clusters_build();
irradiance_cache.bake.irradiance_offset();
volume_probes.bake.clusters_build();
volume_probes.bake.irradiance_offset();
});
if (irradiance_cache.bake.should_break()) {
if (volume_probes.bake.should_break()) {
return;
}
@ -673,9 +668,9 @@ void Instance::light_bake_irradiance(
for (int i = 0; i < 16 && !sampling.finished(); i++) {
sampling.step();
irradiance_cache.bake.raylists_build();
irradiance_cache.bake.propagate_light();
irradiance_cache.bake.irradiance_capture();
volume_probes.bake.raylists_build();
volume_probes.bake.propagate_light();
volume_probes.bake.irradiance_capture();
}
if (sampling.finished()) {
@ -685,11 +680,11 @@ void Instance::light_bake_irradiance(
LightProbeGridCacheFrame *cache_frame;
if (sampling.finished()) {
cache_frame = irradiance_cache.bake.read_result_packed();
cache_frame = volume_probes.bake.read_result_packed();
}
else {
/* TODO(fclem): Only do this read-back if needed. But it might be tricky to know when. */
cache_frame = irradiance_cache.bake.read_result_unpacked();
cache_frame = volume_probes.bake.read_result_unpacked();
}
float progress = sampling.sample_index() / float(sampling.sample_count());

View File

@ -86,8 +86,6 @@ class Instance {
LightModule lights;
AmbientOcclusion ambient_occlusion;
RayTraceModule raytracing;
ReflectionProbeModule reflection_probes;
PlanarProbeModule planar_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -103,8 +101,10 @@ class Instance {
World world;
LookdevView lookdev_view;
LookdevModule lookdev;
SphereProbeModule sphere_probes;
PlanarProbeModule planar_probes;
VolumeProbeModule volume_probes;
LightProbeModule light_probes;
IrradianceCache irradiance_cache;
VolumeModule volume;
/** Input data. */
@ -149,8 +149,6 @@ class Instance {
lights(*this),
ambient_occlusion(*this, uniform_data.data.ao),
raytracing(*this, uniform_data.data.raytrace),
reflection_probes(*this),
planar_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),
@ -165,8 +163,10 @@ class Instance {
world(*this),
lookdev_view(*this),
lookdev(*this),
sphere_probes(*this),
planar_probes(*this),
volume_probes(*this),
light_probes(*this),
irradiance_cache(*this),
volume(*this, uniform_data.data.volumes){};
~Instance(){};

View File

@ -21,7 +21,7 @@ namespace blender::eevee {
/** \name Interface
* \{ */
void IrradianceCache::init()
void VolumeProbeModule::init()
{
display_grids_enabled_ = DRW_state_draw_support();
@ -48,7 +48,7 @@ void IrradianceCache::init()
if (do_full_update_) {
/* Delete all references to existing bricks. */
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
grid.bricks.clear();
}
brick_pool_.clear();
@ -71,7 +71,7 @@ void IrradianceCache::init()
irradiance_atlas_tx_.clear(float4(0.0f));
}
inst_.reflection_probes.do_world_update_irradiance_set(true);
inst_.sphere_probes.tag_world_irradiance_for_update();
}
if (irradiance_atlas_tx_.is_valid() == false) {
@ -79,14 +79,14 @@ void IrradianceCache::init()
}
}
void IrradianceCache::sync()
void VolumeProbeModule::sync()
{
if (inst_.is_baking()) {
bake.sync();
}
}
Vector<IrradianceBrickPacked> IrradianceCache::bricks_alloc(int brick_len)
Vector<IrradianceBrickPacked> VolumeProbeModule::bricks_alloc(int brick_len)
{
if (brick_pool_.size() < brick_len) {
/* Fail allocation. Not enough brick in the atlas. */
@ -101,20 +101,20 @@ Vector<IrradianceBrickPacked> IrradianceCache::bricks_alloc(int brick_len)
return allocated;
}
void IrradianceCache::bricks_free(Vector<IrradianceBrickPacked> &bricks)
void VolumeProbeModule::bricks_free(Vector<IrradianceBrickPacked> &bricks)
{
brick_pool_.extend(bricks.as_span());
bricks.clear();
}
void IrradianceCache::set_view(View & /*view*/)
void VolumeProbeModule::set_view(View & /*view*/)
{
Vector<IrradianceGrid *> grid_loaded;
Vector<VolumeProbe *> grid_loaded;
bool any_update = false;
/* First allocate the needed bricks and populate the brick buffer. */
bricks_infos_buf_.clear();
for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
LightProbeGridCacheFrame *cache = grid.cache ? grid.cache->grid_static_cache : nullptr;
if (cache == nullptr) {
continue;
@ -182,7 +182,7 @@ void IrradianceCache::set_view(View & /*view*/)
* before tagging update. But this is a bit too complex and update is quite cheap. So we update
* everything if there is any update on any grid. */
if (any_update) {
for (IrradianceGrid *grid : grid_loaded) {
for (VolumeProbe *grid : grid_loaded) {
grid->do_update = true;
}
}
@ -190,44 +190,43 @@ void IrradianceCache::set_view(View & /*view*/)
/* Then create brick & grid infos UBOs content. */
{
/* Stable sorting of grids. */
std::sort(grid_loaded.begin(),
grid_loaded.end(),
[](const IrradianceGrid *a, const IrradianceGrid *b) {
float volume_a = math::determinant(float3x3(a->object_to_world));
float volume_b = math::determinant(float3x3(b->object_to_world));
if (volume_a != volume_b) {
/* Smallest first. */
return volume_a < volume_b;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
float3 _a = a->object_to_world.location();
float3 _b = b->object_to_world.location();
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
std::sort(
grid_loaded.begin(), grid_loaded.end(), [](const VolumeProbe *a, const VolumeProbe *b) {
float volume_a = math::determinant(float3x3(a->object_to_world));
float volume_b = math::determinant(float3x3(b->object_to_world));
if (volume_a != volume_b) {
/* Smallest first. */
return volume_a < volume_b;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
float3 _a = a->object_to_world.location();
float3 _b = b->object_to_world.location();
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
/* Insert grids in UBO in sorted order. */
int grids_len = 0;
for (IrradianceGrid *grid : grid_loaded) {
for (VolumeProbe *grid : grid_loaded) {
grid->grid_index = grids_len;
grids_infos_buf_[grids_len++] = *grid;
}
/* Insert world grid last. */
IrradianceGridData grid;
VolumeProbeData grid;
grid.world_to_grid_transposed = float3x4::identity();
grid.grid_size = int3(1);
grid.brick_offset = bricks_infos_buf_.size();
@ -253,7 +252,7 @@ void IrradianceCache::set_view(View & /*view*/)
for (auto it = grid_loaded.rbegin(); it != grid_loaded.rend(); ++it) {
grid_start_index--;
IrradianceGrid *grid = *it;
VolumeProbe *grid = *it;
if (!grid->do_update) {
continue;
}
@ -391,7 +390,7 @@ void IrradianceCache::set_view(View & /*view*/)
do_update_world_ = false;
}
void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!inst_.is_baking()) {
debug_pass_draw(view, view_fb);
@ -399,7 +398,7 @@ void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb)
}
}
void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
switch (inst_.debug_mode) {
case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL:
@ -425,7 +424,7 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (const VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
if (grid.cache == nullptr) {
continue;
}
@ -527,13 +526,13 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb)
}
}
void IrradianceCache::display_pass_draw(View &view, GPUFrameBuffer *view_fb)
void VolumeProbeModule::display_pass_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!display_grids_enabled_) {
return;
}
for (const IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) {
for (const VolumeProbe &grid : inst_.light_probes.volume_map_.values()) {
if (!grid.viewport_display || grid.viewport_display_size == 0.0f || !grid.cache ||
!grid.cache->grid_static_cache)
{
@ -701,7 +700,7 @@ void IrradianceBake::sync()
sub.shader_set(inst_.shaders.static_shader_get(SURFEL_RAY));
sub.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
sub.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
sub.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.sphere_probes);
sub.push_constant("radiance_src", &radiance_src_);
sub.push_constant("radiance_dst", &radiance_dst_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
@ -714,7 +713,7 @@ void IrradianceBake::sync()
pass.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_RAY));
pass.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_);
pass.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_ssbo("list_start_buf", &list_start_buf_);
pass.bind_ssbo("list_info_buf", &list_info_buf_);
pass.push_constant("radiance_src", &radiance_src_);
@ -825,10 +824,9 @@ void IrradianceBake::surfels_create(const Object &probe_object)
capture_info_buf_.capture_indirect = capture_indirect_;
capture_info_buf_.capture_emission = capture_emission_;
ReflectionProbeModule &reflections = inst_.reflection_probes;
ReflectionProbeAtlasCoordinate atlas_coord = reflections.world_atlas_coord_get();
ReflectionProbeCoordinate coord = atlas_coord.as_sampling_coord(reflections.atlas_extent());
capture_info_buf_.world_atlas_coord = coord;
LightProbeModule &light_probes = inst_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
capture_info_buf_.world_atlas_coord = world_data.atlas_coord;
dispatch_per_grid_sample_ = math::divide_ceil(grid_resolution, int3(IRRADIANCE_GRID_GROUP_SIZE));
capture_info_buf_.irradiance_grid_size = grid_resolution;

View File

@ -24,7 +24,7 @@ class Instance;
class CapturePipeline;
class ShadowModule;
class Camera;
class ReflectionProbeModule;
class SphereProbeModule;
/**
* Baking related pass and data. Not used at runtime.
@ -186,7 +186,7 @@ class IrradianceBake {
* Runtime container of diffuse indirect lighting.
* Also have debug and baking components.
*/
class IrradianceCache {
class VolumeProbeModule {
public:
IrradianceBake bake;
@ -202,27 +202,27 @@ class IrradianceCache {
/** Reserved atlas brick for world irradiance. */
int world_brick_index_ = 0;
/** Data structure used to index irradiance cache pages inside the atlas. */
IrradianceGridDataBuf grids_infos_buf_ = {"grids_infos_buf_"};
VolumeProbeDataBuf grids_infos_buf_ = {"grids_infos_buf_"};
IrradianceBrickBuf bricks_infos_buf_ = {"bricks_infos_buf_"};
/** Pool of atlas regions to allocate to different grids. */
Vector<IrradianceBrickPacked> brick_pool_;
/** Stream data into the irradiance atlas texture. */
PassSimple grid_upload_ps_ = {"IrradianceCache.Upload"};
PassSimple grid_upload_ps_ = {"VolumeProbeModule.Upload"};
/** If true, will trigger the reupload of all grid data instead of just streaming new ones. */
bool do_full_update_ = true;
/** Display debug data. */
PassSimple debug_ps_ = {"IrradianceCache.Debug"};
PassSimple debug_ps_ = {"VolumeProbeModule.Debug"};
/** Debug surfel elements copied from the light cache. */
draw::StorageArrayBuffer<Surfel> debug_surfels_buf_;
/** Display grid cache data. */
bool display_grids_enabled_ = false;
PassSimple display_grids_ps_ = {"IrradianceCache.Display Grids"};
PassSimple display_grids_ps_ = {"VolumeProbeModule.Display Grids"};
public:
IrradianceCache(Instance &inst) : bake(inst), inst_(inst){};
~IrradianceCache(){};
VolumeProbeModule(Instance &inst) : bake(inst), inst_(inst){};
~VolumeProbeModule(){};
void init();
void sync();
@ -236,14 +236,14 @@ class IrradianceCache {
{
pass.bind_ubo(IRRADIANCE_GRID_BUF_SLOT, &grids_infos_buf_);
pass.bind_ssbo(IRRADIANCE_BRICK_BUF_SLOT, &bricks_infos_buf_);
pass.bind_texture(IRRADIANCE_ATLAS_TEX_SLOT, &irradiance_atlas_tx_);
pass.bind_texture(VOLUME_PROBE_TEX_SLOT, &irradiance_atlas_tx_);
}
private:
void debug_pass_draw(View &view, GPUFrameBuffer *view_fb);
void display_pass_draw(View &view, GPUFrameBuffer *view_fb);
friend class ReflectionProbeModule;
friend class SphereProbeModule;
};
} // namespace blender::eevee

View File

@ -6,7 +6,8 @@
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#include "DNA_lightprobe_types.h"
@ -17,17 +18,64 @@
#include "draw_debug.hh"
#include <iostream>
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Light-Probe Module
* \{ */
LightProbeModule::LightProbeModule(Instance &inst) : inst_(inst)
{
/* Initialize the world probe. */
world_sphere_.clipping_distances = float2(1.0f, 10.0f);
world_sphere_.world_to_probe_transposed = float3x4::identity();
world_sphere_.influence_shape = SHAPE_ELIPSOID;
world_sphere_.parallax_shape = SHAPE_ELIPSOID;
/* Full influence. */
world_sphere_.influence_scale = 0.0f;
world_sphere_.influence_bias = 1.0f;
world_sphere_.parallax_distance = 1e10f;
/* In any case, the world must always be up to valid and used for render. */
world_sphere_.use_for_render = true;
}
static eLightProbeResolution resolution_to_probe_resolution_enum(int resolution)
{
switch (resolution) {
case 64:
return LIGHT_PROBE_RESOLUTION_64;
case 128:
return LIGHT_PROBE_RESOLUTION_128;
case 256:
return LIGHT_PROBE_RESOLUTION_256;
case 512:
return LIGHT_PROBE_RESOLUTION_512;
case 1024:
return LIGHT_PROBE_RESOLUTION_1024;
default:
/* Default to maximum resolution because the old max was 4K for Legacy-EEVEE. */
case 2048:
return LIGHT_PROBE_RESOLUTION_2048;
}
}
void LightProbeModule::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
sphere_object_resolution_ = resolution_to_probe_resolution_enum(sce_eevee.gi_cubemap_resolution);
}
void LightProbeModule::begin_sync()
{
auto_bake_enabled_ = inst_.is_viewport() &&
(inst_.scene->eevee.flag & SCE_EEVEE_GI_AUTOBAKE) != 0;
}
void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
void LightProbeModule::sync_volume(const Object *ob, ObjectHandle &handle)
{
IrradianceGrid &grid = grid_map_.lookup_or_add_default(handle.object_key);
VolumeProbe &grid = volume_map_.lookup_or_add_default(handle.object_key);
grid.used = true;
if (handle.recalc != 0 || grid.initialized == false) {
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
@ -53,17 +101,78 @@ void LightProbeModule::sync_grid(const Object *ob, ObjectHandle &handle)
grid.viewport_display_size = lightprobe->data_display_size;
/* Force reupload. */
inst_.irradiance_cache.bricks_free(grid.bricks);
inst_.volume_probes.bricks_free(grid.bricks);
}
}
void LightProbeModule::sync_cube(ObjectHandle &handle)
void LightProbeModule::sync_sphere(const Object *ob, ObjectHandle &handle)
{
ReflectionCube &cube = cube_map_.lookup_or_add_default(handle.object_key);
SphereProbe &cube = sphere_map_.lookup_or_add_default(handle.object_key);
cube.used = true;
if (handle.recalc != 0 || cube.initialized == false) {
const ::LightProbe &light_probe = *(::LightProbe *)ob->data;
cube.initialized = true;
cube_update_ = true;
cube.updated = true;
cube.do_render = true;
SphereProbeModule &probe_module = inst_.sphere_probes;
eLightProbeResolution probe_resolution = sphere_object_resolution_;
int subdivision_lvl = probe_module.subdivision_level_get(probe_resolution);
if (cube.atlas_coord.subdivision_lvl != subdivision_lvl) {
cube.atlas_coord.free();
cube.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &cube_data = *static_cast<SphereProbeData *>(&cube);
/* Update gpu data sampling coordinates. */
cube_data.atlas_coord = cube.atlas_coord.as_sampling_coord(probe_module.max_resolution_);
/* Coordinates have changed. Area might contain random data. Do not use for rendering. */
cube.use_for_render = false;
}
bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
float influence_distance = light_probe.distinf;
float influence_falloff = light_probe.falloff;
float parallax_distance = light_probe.distpar;
parallax_distance = use_custom_parallax ? max_ff(parallax_distance, influence_distance) :
influence_distance;
auto to_eevee_shape = [](int bl_shape_type) {
return (bl_shape_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : SHAPE_ELIPSOID;
};
cube.influence_shape = to_eevee_shape(light_probe.attenuation_type);
cube.parallax_shape = to_eevee_shape(light_probe.parallax_type);
float4x4 object_to_world = math::scale(float4x4(ob->object_to_world),
float3(influence_distance));
cube.location = object_to_world.location();
cube.volume = math::abs(math::determinant(object_to_world));
cube.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
cube.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
cube.influence_bias = cube.influence_scale;
cube.parallax_distance = parallax_distance / influence_distance;
cube.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
cube.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
cube.viewport_display_size = light_probe.data_display_size;
}
}
void LightProbeModule::sync_planar(const Object *ob, ObjectHandle &handle)
{
PlanarProbe &plane = planar_map_.lookup_or_add_default(handle.object_key);
plane.used = true;
if (handle.recalc != 0 || plane.initialized == false) {
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
plane.initialized = true;
plane.updated = true;
plane.plane_to_world = float4x4(ob->object_to_world);
plane.plane_to_world.z_axis() = math::normalize(plane.plane_to_world.z_axis()) *
light_probe->distinf;
plane.world_to_plane = math::invert(plane.plane_to_world);
plane.clipping_offset = light_probe->clipsta;
plane.viewport_display = (light_probe->flag & LIGHTPROBE_FLAG_SHOW_DATA) != 0;
}
}
@ -72,84 +181,185 @@ void LightProbeModule::sync_probe(const Object *ob, ObjectHandle &handle)
const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
switch (lightprobe->type) {
case LIGHTPROBE_TYPE_SPHERE:
sync_cube(handle);
sync_sphere(ob, handle);
return;
case LIGHTPROBE_TYPE_PLANE:
/* TODO(fclem): Remove support? Add support? */
sync_planar(ob, handle);
return;
case LIGHTPROBE_TYPE_VOLUME:
sync_grid(ob, handle);
sync_volume(ob, handle);
return;
}
BLI_assert_unreachable();
}
void LightProbeModule::end_sync()
void LightProbeModule::sync_world(const ::World *world, bool has_update)
{
{
/* Check for deleted or updated grid. */
grid_update_ = false;
auto it_end = grid_map_.items().end();
for (auto it = grid_map_.items().begin(); it != it_end; ++it) {
IrradianceGrid &grid = (*it).value;
if (grid.updated) {
grid.updated = false;
grid_update_ = true;
}
if (!grid.used) {
inst_.irradiance_cache.bricks_free(grid.bricks);
grid_map_.remove(it);
grid_update_ = true;
continue;
}
/* Untag for next sync. */
grid.used = false;
}
}
{
/* Check for deleted or updated cube. */
cube_update_ = false;
auto it_end = cube_map_.items().end();
for (auto it = cube_map_.items().begin(); it != it_end; ++it) {
ReflectionCube &cube = (*it).value;
if (cube.updated) {
cube.updated = false;
cube_update_ = true;
}
if (!cube.used) {
cube_map_.remove(it);
cube_update_ = true;
continue;
}
/* Untag for next sync. */
cube.used = false;
}
const eLightProbeResolution probe_resolution = static_cast<eLightProbeResolution>(
world->probe_resolution);
SphereProbeModule &sph_module = inst_.sphere_probes;
int subdivision_lvl = sph_module.subdivision_level_get(probe_resolution);
if (subdivision_lvl != world_sphere_.atlas_coord.subdivision_lvl) {
world_sphere_.atlas_coord.free();
world_sphere_.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&world_sphere_);
world_data.atlas_coord = world_sphere_.atlas_coord.as_sampling_coord(
sph_module.max_resolution_);
has_update = true;
}
#if 0 /* TODO make this work with new per object light cache. */
/* If light-cache auto-update is enable we tag the relevant part
* of the cache to update and fire up a baking job. */
if (auto_bake_enabled_ && (grid_update_ || cube_update_)) {
Scene *original_scene = DEG_get_input_scene(inst_.depsgraph);
LightCache *light_cache = original_scene->eevee.light_cache_data;
if (light_cache != nullptr) {
if (grid_update_) {
light_cache->flag |= LIGHTCACHE_UPDATE_GRID;
}
/* TODO(fclem): Reflection Cube-map should capture albedo + normal and be
* relit at runtime. So no dependency like in the old system. */
if (cube_update_) {
light_cache->flag |= LIGHTCACHE_UPDATE_CUBE;
}
/* Tag the lightcache to auto update. */
light_cache->flag |= LIGHTCACHE_UPDATE_AUTO;
/* Use a notifier to trigger the operator after drawing. */
/* TODO(fclem): Avoid usage of global DRW. */
WM_event_add_notifier(DRW_context_state_get()->evil_C, NC_LIGHTPROBE, original_scene);
}
if (has_update) {
world_sphere_.do_render = true;
sph_module.tag_world_irradiance_for_update();
}
#endif
}
void LightProbeModule::end_sync()
{
/* Check for deleted or updated grid. */
volume_update_ = false;
volume_map_.remove_if([&](const Map<ObjectKey, VolumeProbe>::MutableItem &item) {
VolumeProbe &grid = item.value;
bool remove_grid = !grid.used;
if (grid.updated || remove_grid) {
volume_update_ = true;
}
grid.updated = false;
grid.used = false;
return remove_grid;
});
/* Check for deleted or updated cube. */
sphere_update_ = false;
sphere_map_.remove_if([&](const Map<ObjectKey, SphereProbe>::MutableItem &item) {
SphereProbe &cube = item.value;
bool remove_cube = !cube.used;
if (cube.updated || remove_cube) {
sphere_update_ = true;
}
cube.updated = false;
cube.used = false;
return remove_cube;
});
/* Check for deleted or updated plane. */
planar_update_ = false;
planar_map_.remove_if([&](const Map<ObjectKey, PlanarProbe>::MutableItem &item) {
PlanarProbe &plane = item.value;
bool remove_plane = !plane.used;
if (plane.updated || remove_plane) {
planar_update_ = true;
}
plane.updated = false;
plane.used = false;
return remove_plane;
});
}
SphereProbeAtlasCoord LightProbeModule::find_empty_atlas_region(int subdivision_level) const
{
int layer_count = sphere_layer_count();
SphereProbeAtlasCoord::LocationFinder location_finder(layer_count, subdivision_level);
location_finder.mark_space_used(world_sphere_.atlas_coord);
for (const SphereProbe &probe : sphere_map_.values()) {
location_finder.mark_space_used(probe.atlas_coord);
}
return location_finder.first_free_spot();
}
int LightProbeModule::sphere_layer_count() const
{
int max_layer = world_sphere_.atlas_coord.atlas_layer;
for (const SphereProbe &probe : sphere_map_.values()) {
max_layer = max_ii(max_layer, probe.atlas_coord.atlas_layer);
}
int layer_count = max_layer + 1;
return layer_count;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
SphereProbeAtlasCoord::LocationFinder::LocationFinder(int allocated_layer_count,
int subdivision_level)
{
subdivision_level_ = subdivision_level;
areas_per_dimension_ = 1 << subdivision_level_;
areas_per_layer_ = square_i(areas_per_dimension_);
/* Always add an additional layer to make sure that there is always a free area.
* If this area is chosen the atlas will grow. */
int area_len = (allocated_layer_count + 1) * areas_per_layer_;
areas_occupancy_.resize(area_len, false);
}
void SphereProbeAtlasCoord::LocationFinder::mark_space_used(const SphereProbeAtlasCoord &coord)
{
if (coord.atlas_layer == -1) {
/* Coordinate not allocated yet. */
return;
}
/* The input probe data can be stored in a different subdivision level and should tag all areas
* of the target subdivision level. Shift right if subdivision is higher, left if lower. */
const int shift_right = max_ii(coord.subdivision_lvl - subdivision_level_, 0);
const int shift_left = max_ii(subdivision_level_ - coord.subdivision_lvl, 0);
const int2 pos_in_location_finder = (coord.area_location() >> shift_right) << shift_left;
/* Tag all areas this probe overlaps. */
const int layer_offset = coord.atlas_layer * areas_per_layer_;
const int areas_overlapped_per_dim = 1 << shift_left;
for (const int y : IndexRange(areas_overlapped_per_dim)) {
for (const int x : IndexRange(areas_overlapped_per_dim)) {
const int2 pos = pos_in_location_finder + int2(x, y);
const int area_index = pos.x + pos.y * areas_per_dimension_;
areas_occupancy_[area_index + layer_offset].set();
}
}
}
SphereProbeAtlasCoord SphereProbeAtlasCoord::LocationFinder::first_free_spot() const
{
SphereProbeAtlasCoord result;
result.subdivision_lvl = subdivision_level_;
for (int index : areas_occupancy_.index_range()) {
if (!areas_occupancy_[index]) {
result.atlas_layer = index / areas_per_layer_;
result.area_index = index % areas_per_layer_;
return result;
}
}
/* There should always be a free area. See constructor. */
BLI_assert_unreachable();
return result;
}
void SphereProbeAtlasCoord::LocationFinder::print_debug() const
{
std::ostream &os = std::cout;
int layer = 0, row = 0, column = 0;
os << "subdivision " << subdivision_level_ << "\n";
for (bool spot_taken : areas_occupancy_) {
if (row == 0 && column == 0) {
os << "layer " << layer << "\n";
}
os << (spot_taken ? 'X' : '-');
column++;
if (column == areas_per_dimension_) {
os << "\n";
column = 0;
row++;
}
if (row == areas_per_dimension_) {
row = 0;
layer++;
}
}
}
/** \} */
} // namespace blender::eevee

View File

@ -6,11 +6,13 @@
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `IrradianceCache` and `ReflectionProbes`.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#pragma once
#include "BLI_bit_vector.hh"
#include "BLI_map.hh"
#include "eevee_sync.hh"
@ -18,15 +20,130 @@
namespace blender::eevee {
class Instance;
class IrradianceCache;
class VolumeProbeModule;
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
struct SphereProbeAtlasCoord {
/** On which layer of the texture array is this reflection probe stored. */
int atlas_layer = -1;
/** Gives the extent of this probe relative to the atlas size. */
int subdivision_lvl = -1;
/** Area index within the layer with the according subdivision level. */
int area_index = -1;
/** Release the current atlas space held by this probe. */
void free()
{
atlas_layer = -1;
}
/* Return the area extent in pixel. */
int area_extent(int atlas_extent) const
{
return atlas_extent >> subdivision_lvl;
}
/* Coordinate of the area in [0..area_count_per_dimension[ range. */
int2 area_location() const
{
const int area_count_per_dimension = 1 << subdivision_lvl;
return int2(area_index % area_count_per_dimension, area_index / area_count_per_dimension);
}
/* Coordinate of the bottom left corner of the area in [0..atlas_extent[ range. */
int2 area_offset(int atlas_extent) const
{
return area_location() * area_extent(atlas_extent);
}
SphereProbeUvArea as_sampling_coord(int atlas_extent) const
{
/**
* We want to cover the last mip exactly at the pixel center to reduce padding texels and
* interpolation artifacts.
* This is a diagram of a 2px^2 map with `c` being the texels corners and `x` the pixels
* centers.
*
* c-------c-------c
* | | |
* | x | x | <
* | | | |
* c-------c-------c | sampling area
* | | | |
* | x | x | <
* | | |
* c-------c-------c
* ^-------^
* sampling area
*/
/* Max level only need half a pixel of padding around the sampling area. */
const int mip_max_lvl_padding = 1;
const int mip_min_lvl_padding = mip_max_lvl_padding << SPHERE_PROBE_MIPMAP_LEVELS;
/* Extent and offset in mip 0 texels. */
const int sampling_area_extent = area_extent(atlas_extent) - mip_min_lvl_padding;
const int2 sampling_area_offset = area_offset(atlas_extent) + mip_min_lvl_padding / 2;
/* Convert to atlas UVs. */
SphereProbeUvArea coord;
coord.scale = sampling_area_extent / float(atlas_extent);
coord.offset = float2(sampling_area_offset) / float(atlas_extent);
coord.layer = atlas_layer;
return coord;
}
SphereProbePixelArea as_write_coord(int atlas_extent, int mip_lvl) const
{
SphereProbePixelArea coord;
coord.extent = atlas_extent >> (subdivision_lvl + mip_lvl);
coord.offset = (area_location() * coord.extent) >> mip_lvl;
coord.layer = atlas_layer;
return coord;
}
/**
* Utility class to find a location in the probe atlas that can be used to store a new probe in
* a specified subdivision level.
*
* The allocation space is subdivided in target subdivision level and is multi layered.
* A layer has `(2 ^ subdivision_lvl) ^ 2` areas.
*
* All allocated probe areas are then process and the candidate areas containing allocated probes
* are marked as occupied. The location finder then return the first available area.
*/
class LocationFinder {
BitVector<> areas_occupancy_;
int subdivision_level_;
/* Area count for the given subdivision level. */
int areas_per_dimension_;
int areas_per_layer_;
public:
LocationFinder(int allocated_layer_count, int subdivision_level);
/* Mark space to be occupied by the given probe_data. */
void mark_space_used(const SphereProbeAtlasCoord &coord);
SphereProbeAtlasCoord first_free_spot() const;
void print_debug() const;
};
};
/** \} */
struct LightProbe {
bool used = false;
bool initialized = false;
/* NOTE: Might be not needed if depsgraph updates work as intended. */
bool updated = false;
/** Display debug visuals in the viewport. */
bool viewport_display = false;
float viewport_display_size = 0.0f;
};
struct IrradianceGrid : public LightProbe, IrradianceGridData {
struct VolumeProbe : public LightProbe, VolumeProbeData {
/** Copy of the transform matrix. */
float4x4 object_to_world;
/** Precomputed inverse transform with normalized axes. No position. Used for rotating SH. */
@ -51,41 +168,104 @@ struct IrradianceGrid : public LightProbe, IrradianceGridData {
float dilation_threshold;
float dilation_radius;
float intensity;
/** Display irradiance samples in the viewport. */
bool viewport_display;
float viewport_display_size;
};
struct ReflectionCube : public LightProbe {};
struct SphereProbe : public LightProbe, SphereProbeData {
/** Used to sort the probes by priority. */
float volume;
/** True if the area in the atlas needs to be updated. */
bool do_render = true;
/** False if the area in the atlas contains undefined data. */
bool use_for_render = false;
/** Far and near clipping distances for rendering. */
float2 clipping_distances;
/** Atlas region this probe is rendered at (or will be rendered at). */
SphereProbeAtlasCoord atlas_coord;
};
struct PlanarProbe : public LightProbe, PlanarProbeData {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
public:
/**
* Update the PlanarProbeData part of the struct.
* `view` is the view we want to render this probe with.
*/
void set_view(const draw::View &view, int layer_id);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
* \note Only works after `set_view` was called.
*/
float4 reflection_clip_plane_get()
{
return float4(-normal, math::dot(normal, plane_to_world.location()) - clipping_offset);
}
private:
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get()
{
return plane_to_world * math::from_scale<float4x4>(float3(1, 1, -1)) * world_to_plane;
}
};
class LightProbeModule {
friend class IrradianceCache;
friend class IrradianceBake;
friend class VolumeProbeModule;
friend class PlanarProbeModule;
friend class SphereProbeModule;
private:
Instance &inst_;
/** Light Probe map to detect deletion and store associated data. */
Map<ObjectKey, IrradianceGrid> grid_map_;
Map<ObjectKey, ReflectionCube> cube_map_;
/** True if a grid update was detected. It will trigger a bake if auto bake is enabled. */
bool grid_update_;
/** True if a grid update was detected. It will trigger a bake if auto bake is enabled. */
bool cube_update_;
Map<ObjectKey, VolumeProbe> volume_map_;
Map<ObjectKey, SphereProbe> sphere_map_;
Map<ObjectKey, PlanarProbe> planar_map_;
/* World probe is stored separately. */
SphereProbe world_sphere_;
/** True if a light-probe update was detected. */
bool volume_update_;
bool sphere_update_;
bool planar_update_;
/** True if the auto bake feature is enabled & available in this context. */
bool auto_bake_enabled_;
eLightProbeResolution sphere_object_resolution_ = LIGHT_PROBE_RESOLUTION_64;
public:
LightProbeModule(Instance &inst) : inst_(inst){};
LightProbeModule(Instance &inst);
~LightProbeModule(){};
void init();
void begin_sync();
void sync_cube(ObjectHandle &handle);
void sync_grid(const Object *ob, ObjectHandle &handle);
void sync_probe(const Object *ob, ObjectHandle &handle);
void sync_world(const ::World *world, bool has_update);
void end_sync();
private:
void sync_sphere(const Object *ob, ObjectHandle &handle);
void sync_volume(const Object *ob, ObjectHandle &handle);
void sync_planar(const Object *ob, ObjectHandle &handle);
/** Get the number of atlas layers needed to store light probe spheres. */
int sphere_layer_count() const;
/** Returns coordinates of an area in the atlas for a probe with the given subdivision level. */
SphereProbeAtlasCoord find_empty_atlas_region(int subdivision_level) const;
};
} // namespace blender::eevee

View File

@ -235,8 +235,8 @@ void LookdevModule::sync_pass(PassSimple &pass,
pass.bind_image("aov_value_img", dummy_aov_value_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.volume.result);
pass.bind_resources(inst_.cryptomatte);

View File

@ -295,8 +295,8 @@ void ForwardPipeline::sync()
opaque_ps_.bind_resources(inst_.volume.result);
opaque_ps_.bind_resources(inst_.sampling);
opaque_ps_.bind_resources(inst_.hiz_buffer.front);
opaque_ps_.bind_resources(inst_.irradiance_cache);
opaque_ps_.bind_resources(inst_.reflection_probes);
opaque_ps_.bind_resources(inst_.volume_probes);
opaque_ps_.bind_resources(inst_.sphere_probes);
}
opaque_single_sided_ps_ = &opaque_ps_.sub("SingleSided");
@ -323,8 +323,8 @@ void ForwardPipeline::sync()
sub.bind_resources(inst_.volume.result);
sub.bind_resources(inst_.sampling);
sub.bind_resources(inst_.hiz_buffer.front);
sub.bind_resources(inst_.irradiance_cache);
sub.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.volume_probes);
sub.bind_resources(inst_.sphere_probes);
}
}
@ -410,7 +410,7 @@ void ForwardPipeline::render(View &view, Framebuffer &prepass_fb, Framebuffer &c
inst_.hiz_buffer.set_dirty();
inst_.shadows.set_view(view, inst_.render_buffers.depth_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
if (has_opaque_) {
combined_fb.bind();
@ -463,8 +463,8 @@ void DeferredLayerBase::gbuffer_pass_sync(Instance &inst)
* Non-NPR shaders will override these resource bindings. */
gbuffer_ps_.bind_resources(inst.lights);
gbuffer_ps_.bind_resources(inst.shadows);
gbuffer_ps_.bind_resources(inst.reflection_probes);
gbuffer_ps_.bind_resources(inst.irradiance_cache);
gbuffer_ps_.bind_resources(inst.sphere_probes);
gbuffer_ps_.bind_resources(inst.volume_probes);
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL;
@ -591,8 +591,8 @@ void DeferredLayer::end_sync()
sub.bind_resources(inst_.shadows);
sub.bind_resources(inst_.sampling);
sub.bind_resources(inst_.hiz_buffer.front);
sub.bind_resources(inst_.reflection_probes);
sub.bind_resources(inst_.irradiance_cache);
sub.bind_resources(inst_.sphere_probes);
sub.bind_resources(inst_.volume_probes);
sub.state_stencil(0xFFu, i + 1, 0xFFu);
sub.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@ -719,7 +719,7 @@ void DeferredLayer::render(View &main_view,
/* Update for lighting pass or AO node. */
inst_.hiz_buffer.update();
inst_.irradiance_cache.set_view(render_view);
inst_.volume_probes.set_view(render_view);
inst_.shadows.set_view(render_view, inst_.render_buffers.depth_tx);
if (/* FIXME(fclem): Vulkan doesn't implement load / store config yet. */
@ -1206,7 +1206,7 @@ void DeferredProbeLayer::end_sync()
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.volume_probes);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@ -1254,7 +1254,7 @@ void DeferredProbeLayer::render(View &view,
inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx);
inst_.lights.set_view(view, extent);
inst_.shadows.set_view(view, inst_.render_buffers.depth_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
/* Update for lighting pass. */
inst_.hiz_buffer.update();
@ -1347,8 +1347,8 @@ void PlanarProbePipeline::begin_sync()
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.volume_probes);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
@ -1408,7 +1408,7 @@ void PlanarProbePipeline::render(View &view,
inst_.hiz_buffer.set_source(&depth_layer_tx, 0);
inst_.lights.set_view(view, extent);
inst_.shadows.set_view(view, depth_layer_tx);
inst_.irradiance_cache.set_view(view);
inst_.volume_probes.set_view(view);
/* Update for lighting pass. */
inst_.hiz_buffer.update();
@ -1448,10 +1448,10 @@ void CapturePipeline::sync()
/* Surfel output is done using a SSBO, so no need for a fragment shader output color or depth. */
/* WORKAROUND: Avoid rasterizer discard, but the shaders actually use no fragment output. */
surface_ps_.state_set(DRW_STATE_WRITE_STENCIL);
surface_ps_.framebuffer_set(&inst_.irradiance_cache.bake.empty_raster_fb_);
surface_ps_.framebuffer_set(&inst_.volume_probes.bake.empty_raster_fb_);
surface_ps_.bind_ssbo(SURFEL_BUF_SLOT, &inst_.irradiance_cache.bake.surfels_buf_);
surface_ps_.bind_ssbo(CAPTURE_BUF_SLOT, &inst_.irradiance_cache.bake.capture_info_buf_);
surface_ps_.bind_ssbo(SURFEL_BUF_SLOT, &inst_.volume_probes.bake.surfels_buf_);
surface_ps_.bind_ssbo(CAPTURE_BUF_SLOT, &inst_.volume_probes.bake.capture_info_buf_);
surface_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
/* TODO(fclem): Remove. Bind to get the camera data,

View File

@ -13,18 +13,6 @@ using namespace blender::math;
/** \name Planar Probe
* \{ */
void PlanarProbe::sync(const float4x4 &world_to_object,
float clipping_offset,
float influence_distance,
bool viewport_display)
{
this->plane_to_world = float4x4(world_to_object);
this->plane_to_world.z_axis() = normalize(this->plane_to_world.z_axis()) * influence_distance;
this->world_to_plane = invert(this->plane_to_world);
this->clipping_offset = clipping_offset;
this->viewport_display = viewport_display;
}
void PlanarProbe::set_view(const draw::View &view, int layer_id)
{
this->viewmat = view.viewmat() * reflection_matrix_get();
@ -40,16 +28,6 @@ void PlanarProbe::set_view(const draw::View &view, int layer_id)
this->layer_id = layer_id;
}
float4x4 PlanarProbe::reflection_matrix_get()
{
return plane_to_world * from_scale<float4x4>(float3(1, 1, -1)) * world_to_plane;
}
float4 PlanarProbe::reflection_clip_plane_get()
{
return float4(-normal, dot(normal, plane_to_world.location()) - clipping_offset);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -58,47 +36,33 @@ float4 PlanarProbe::reflection_clip_plane_get()
void PlanarProbeModule::init()
{
update_probes_ = !probes_.is_empty();
/* This triggers the compilation of clipped shader only if we can detect lightprobe planes. */
if (inst_.is_viewport()) {
/* This check needs to happen upfront before sync, so we use the previous sync result. */
update_probes_ = !inst_.light_probes.planar_map_.is_empty();
}
else {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_ = DEG_id_type_any_exists(inst_.depsgraph, ID_LP);
}
do_display_draw_ = false;
}
void PlanarProbeModule::begin_sync()
{
for (PlanarProbe &probe : probes_.values()) {
probe.is_probe_used = false;
}
}
void PlanarProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
{
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
if (light_probe->type != LIGHTPROBE_TYPE_PLANE) {
return;
}
PlanarProbe &probe = find_or_insert(ob_handle);
probe.sync(float4x4(ob->object_to_world),
light_probe->clipsta,
light_probe->distinf,
light_probe->flag & LIGHTPROBE_FLAG_SHOW_DATA);
probe.is_probe_used = true;
}
void PlanarProbeModule::end_sync()
{
probes_.remove_if([](const PlanarProbes::Item &item) { return !item.value.is_probe_used; });
/* When first planar probes are enabled it can happen that the first sample is off. */
if (!update_probes_ && !probes_.is_empty()) {
if (!update_probes_ && !inst_.light_probes.planar_map_.is_empty()) {
DRW_viewport_request_redraw();
}
}
void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_extent)
{
GBuffer &gbuf = instance_.gbuffer;
GBuffer &gbuf = inst_.gbuffer;
const int64_t num_probes = probes_.size();
const int64_t num_probes = inst_.light_probes.planar_map_.size();
/* TODO resolution percentage. */
int2 extent = main_view_extent;
@ -119,12 +83,12 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
int resource_index = 0;
int display_index = 0;
for (PlanarProbe &probe : probes_.values()) {
if (resource_index == PLANAR_PROBES_MAX) {
for (PlanarProbe &probe : inst_.light_probes.planar_map_.values()) {
if (resource_index == PLANAR_PROBE_MAX) {
break;
}
PlanarProbeResources &res = resources_[resource_index];
PlanarResources &res = resources_[resource_index];
/* TODO Cull out of view planars. */
@ -137,8 +101,8 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
world_clip_buf_.push_update();
gbuf.acquire(extent,
instance_.pipelines.deferred.closure_layer_count(),
instance_.pipelines.deferred.normal_layer_count());
inst_.pipelines.deferred.closure_layer_count(),
inst_.pipelines.deferred.normal_layer_count());
res.combined_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(radiance_tx_, resource_index));
@ -150,7 +114,7 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(1), 0));
instance_.pipelines.planar.render(
inst_.pipelines.planar.render(
res.view, depth_tx_.layer_view(resource_index), res.gbuffer_fb, res.combined_fb, extent);
if (do_display_draw_ && probe.viewport_display) {
@ -162,7 +126,7 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
gbuf.release();
if (resource_index < PLANAR_PROBES_MAX) {
if (resource_index < PLANAR_PROBE_MAX) {
/* Tag the end of the array. */
probe_planar_buf_[resource_index].layer_id = -1;
}
@ -175,12 +139,6 @@ void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_ext
}
}
PlanarProbe &PlanarProbeModule::find_or_insert(ObjectHandle &ob_handle)
{
PlanarProbe &planar_probe = probes_.lookup_or_add_default(ob_handle.object_key.hash());
return planar_probe;
}
void PlanarProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!do_display_draw_) {
@ -191,12 +149,12 @@ void PlanarProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_PLANAR));
viewport_display_ps_.shader_set(inst_.shaders.static_shader_get(DISPLAY_PROBE_PLANAR));
bind_resources(viewport_display_ps_);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
instance_.manager->submit(viewport_display_ps_, view);
inst_.manager->submit(viewport_display_ps_, view);
}
/** \} */

View File

@ -22,91 +22,43 @@ class Instance;
class HiZBuffer;
struct ObjectHandle;
/* -------------------------------------------------------------------- */
/** \name Planar Probe
* \{ */
struct PlanarProbe : ProbePlanarData {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
/* Pruning flag. */
bool is_probe_used = false;
/** Display a debug plane in the viewport. */
bool viewport_display = false;
public:
void sync(const float4x4 &world_to_object,
float clipping_offset,
float influence_distance,
bool viewport_display);
/**
* Update the ProbePlanarData part of the struct.
* `view` is the view we want to render this probe with.
*/
void set_view(const draw::View &view, int layer_id);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
* \note Only works after `set_view` was called.
*/
float4 reflection_clip_plane_get();
private:
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get();
};
struct PlanarProbeResources : NonCopyable {
Framebuffer combined_fb = {"planar.combined_fb"};
Framebuffer gbuffer_fb = {"planar.gbuffer_fb"};
draw::View view = {"planar.view"};
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
class PlanarProbeModule {
using PlanarProbes = Map<uint64_t, PlanarProbe>;
friend class Instance;
friend class HiZBuffer;
friend class PlanarProbePipeline;
private:
Instance &instance_;
Instance &inst_;
PlanarProbes probes_;
std::array<PlanarProbeResources, PLANAR_PROBES_MAX> resources_;
struct PlanarResources : NonCopyable {
Framebuffer combined_fb = {"planar.combined_fb"};
Framebuffer gbuffer_fb = {"planar.gbuffer_fb"};
draw::View view = {"planar.view"};
};
std::array<PlanarResources, PLANAR_PROBE_MAX> resources_;
Texture radiance_tx_ = {"planar.radiance_tx"};
Texture depth_tx_ = {"planar.depth_tx"};
ClipPlaneBuf world_clip_buf_ = {"world_clip_buf"};
ProbePlanarDataBuf probe_planar_buf_ = {"probe_planar_buf"};
PlanarProbeDataBuf probe_planar_buf_ = {"probe_planar_buf"};
bool update_probes_ = false;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
ProbePlanarDisplayDataBuf display_data_buf_;
PlanarProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"PlanarProbeModule.Viewport Display"};
public:
PlanarProbeModule(Instance &instance) : instance_(instance) {}
PlanarProbeModule(Instance &instance) : inst_(instance) {}
void init();
void begin_sync();
void sync_object(Object *ob, ObjectHandle &ob_handle);
void end_sync();
void set_view(const draw::View &main_view, int2 main_view_extent);
@ -126,13 +78,6 @@ class PlanarProbeModule {
{
return update_probes_;
}
private:
PlanarProbe &find_or_insert(ObjectHandle &ob_handle);
friend class Instance;
friend class HiZBuffer;
friend class PlanarProbePipeline;
};
/** \} */

View File

@ -106,8 +106,8 @@ void RayTraceModule::sync()
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.planar_probes);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
/* TODO(@fclem): Use another dispatch with only tiles that touches planar captures. */
pass.dispatch(raytrace_tracing_dispatch_buf_);
@ -134,8 +134,8 @@ void RayTraceModule::sync()
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@ -150,8 +150,8 @@ void RayTraceModule::sync()
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.sampling);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@ -269,8 +269,8 @@ void RayTraceModule::sync()
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.bind_resources(inst_.irradiance_cache);
pass.bind_resources(inst_.reflection_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.dispatch(horizon_denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}

View File

@ -2,512 +2,214 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_bit_vector.hh"
#include "eevee_instance.hh"
#include "eevee_reflection_probes.hh"
#include <iostream>
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name ProbeLocationFinder
* \{ */
/**
* Utility class to find a location in the probes_tx_ that can be used to store a new probe in
* a specified subdivision level.
*/
class ProbeLocationFinder {
BitVector<> taken_spots_;
int probes_per_dimension_;
int probes_per_layer_;
int subdivision_level_;
public:
ProbeLocationFinder(int num_layers, int subdivision_level)
{
subdivision_level_ = subdivision_level;
probes_per_dimension_ = 1 << subdivision_level_;
probes_per_layer_ = probes_per_dimension_ * probes_per_dimension_;
int num_spots = num_layers * probes_per_layer_;
taken_spots_.resize(num_spots, false);
}
void print_debug() const
{
std::ostream &os = std::cout;
int layer = 0;
int row = 0;
int column = 0;
os << "subdivision " << subdivision_level_ << "\n";
for (bool spot_taken : taken_spots_) {
if (row == 0 && column == 0) {
os << "layer " << layer << "\n";
}
os << (spot_taken ? '1' : '0');
column++;
if (column == probes_per_dimension_) {
os << "\n";
column = 0;
row++;
}
if (row == probes_per_dimension_) {
row = 0;
layer++;
}
}
}
/**
* Mark space to be occupied by the given probe_data.
*
* The input probe data can be stored in a different subdivision level and should be converted to
* the subdivision level what we are looking for.
*/
void mark_space_used(const ReflectionProbeAtlasCoordinate &coord)
{
const int shift_right = max_ii(coord.layer_subdivision - subdivision_level_, 0);
const int shift_left = max_ii(subdivision_level_ - coord.layer_subdivision, 0);
const int spots_per_dimension = 1 << shift_left;
const int probes_per_dimension_in_probe_data = 1 << coord.layer_subdivision;
const int2 pos_in_probe_data = int2(coord.area_index % probes_per_dimension_in_probe_data,
coord.area_index / probes_per_dimension_in_probe_data);
const int2 pos_in_location_finder = int2((pos_in_probe_data.x >> shift_right) << shift_left,
(pos_in_probe_data.y >> shift_right) << shift_left);
const int layer_offset = coord.layer * probes_per_layer_;
for (const int y : IndexRange(spots_per_dimension)) {
for (const int x : IndexRange(spots_per_dimension)) {
const int2 pos = pos_in_location_finder + int2(x, y);
const int area_index = pos.x + pos.y * probes_per_dimension_;
taken_spots_[area_index + layer_offset].set();
}
}
}
/**
* Get the first free spot.
*
* .x contains the layer the first free spot was detected.
* .y contains the area_index to use.
*
* Asserts when no free spot is found. ProbeLocationFinder should always be initialized with an
* additional layer to make sure that there is always a free spot.
*/
ReflectionProbeAtlasCoordinate first_free_spot() const
{
ReflectionProbeAtlasCoordinate result;
result.layer_subdivision = subdivision_level_;
for (int index : taken_spots_.index_range()) {
if (!taken_spots_[index]) {
result.layer = index / probes_per_layer_;
result.area_index = index % probes_per_layer_;
return result;
}
}
BLI_assert_unreachable();
return result;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
eLightProbeResolution ReflectionProbeModule::reflection_probe_resolution() const
{
switch (instance_.scene->eevee.gi_cubemap_resolution) {
case 64:
return LIGHT_PROBE_RESOLUTION_64;
case 128:
return LIGHT_PROBE_RESOLUTION_128;
case 256:
return LIGHT_PROBE_RESOLUTION_256;
case 512:
return LIGHT_PROBE_RESOLUTION_512;
case 1024:
return LIGHT_PROBE_RESOLUTION_1024;
default:
return LIGHT_PROBE_RESOLUTION_2048;
}
return LIGHT_PROBE_RESOLUTION_2048;
}
int ReflectionProbeModule::probe_render_extent() const
int SphereProbeModule::probe_render_extent() const
{
return instance_.scene->eevee.gi_cubemap_resolution / 2;
}
void ReflectionProbeModule::init()
void SphereProbeModule::init()
{
if (!is_initialized) {
is_initialized = true;
/* Initialize the world probe. */
ReflectionProbe world_probe = {};
world_probe.type = ReflectionProbe::Type::WORLD;
world_probe.is_probe_used = true;
world_probe.do_render = true;
world_probe.clipping_distances = float2(1.0f, 10.0f);
world_probe.world_to_probe_transposed = float3x4::identity();
world_probe.influence_shape = SHAPE_ELIPSOID;
world_probe.parallax_shape = SHAPE_ELIPSOID;
/* Full influence. */
world_probe.influence_scale = 0.0f;
world_probe.influence_bias = 1.0f;
world_probe.parallax_distance = 1e10f;
probes_.add(world_object_key_, world_probe);
probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
1,
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr,
REFLECTION_PROBE_MIPMAP_LEVELS);
GPU_texture_mipmap_mode(probes_tx_, true, true);
probes_tx_.clear(float4(0.0f));
if (!instance_.is_viewport()) {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_next_sample_ = DEG_id_type_any_exists(instance_.depsgraph, ID_LP);
}
do_display_draw_ = false;
}
void SphereProbeModule::begin_sync()
{
update_probes_this_sample_ = update_probes_next_sample_;
LightProbeModule &light_probes = instance_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
{
const RaytraceEEVEE &options = instance_.scene->eevee.ray_tracing_options;
float probe_brightness_clamp = (options.sample_clamp > 0.0) ? options.sample_clamp : 1e20;
PassSimple &pass = remap_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP));
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_REMAP));
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_texture("atlas_tx", &probes_tx_);
pass.bind_image("atlas_img", &probes_tx_);
pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_sampling_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
pass.push_constant("mip_level", &probe_mip_level_);
pass.push_constant("probe_brightness_clamp", probe_brightness_clamp);
pass.dispatch(&dispatch_probe_pack_);
}
{
PassSimple &pass = update_irradiance_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_UPDATE_IRRADIANCE));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_sampling_coord_));
pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_);
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_UPDATE_IRRADIANCE));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
pass.bind_image("irradiance_atlas_img", &instance_.volume_probes.irradiance_atlas_tx_);
pass.bind_texture("reflection_probes_tx", &probes_tx_);
pass.dispatch(int2(1, 1));
}
do_display_draw_ = false;
}
void ReflectionProbeModule::begin_sync()
{
for (ReflectionProbe &reflection_probe : probes_.values()) {
if (reflection_probe.type == ReflectionProbe::Type::PROBE) {
reflection_probe.is_probe_used = false;
}
}
update_probes_this_sample_ = false;
if (update_probes_next_sample_) {
update_probes_this_sample_ = true;
}
{
PassSimple &pass = select_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_SELECT));
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SELECT));
pass.push_constant("reflection_probe_count", &reflection_probe_count_);
pass.bind_ssbo("reflection_probe_buf", &data_buf_);
instance_.irradiance_cache.bind_resources(pass);
instance_.volume_probes.bind_resources(pass);
instance_.sampling.bind_resources(pass);
pass.dispatch(&dispatch_probe_select_);
pass.barrier(GPU_BARRIER_UNIFORM);
}
}
int ReflectionProbeModule::needed_layers_get() const
bool SphereProbeModule::ensure_atlas()
{
int max_layer = 0;
for (const ReflectionProbe &probe : probes_.values()) {
max_layer = max_ii(max_layer, probe.atlas_coord.layer);
/* Make sure the atlas is always initialized even if there is nothing to render to it to fullfil
* the resource bindings. */
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ;
if (probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
instance_.light_probes.sphere_layer_count(),
usage,
nullptr,
SPHERE_PROBE_MIPMAP_LEVELS))
{
/* TODO(fclem): Clearing means that we need to render all probes again.
* If existing data exists, copy it using `CopyImageSubData`. */
probes_tx_.clear(float4(0.0f));
GPU_texture_mipmap_mode(probes_tx_, true, true);
/* Avoid undefined pixel data. Update all mips. */
GPU_texture_update_mipmap_chain(probes_tx_);
return true;
}
return max_layer + 1;
return false;
}
static int layer_subdivision_for(const int max_resolution,
const eLightProbeResolution probe_resolution)
void SphereProbeModule::end_sync()
{
int i_probe_resolution = int(probe_resolution);
return max_ii(int(log2(max_resolution)) - i_probe_resolution, 0);
}
void ReflectionProbeModule::sync_world(::World *world)
{
ReflectionProbe &probe = probes_.lookup(world_object_key_);
eLightProbeResolution resolution = static_cast<eLightProbeResolution>(world->probe_resolution);
int layer_subdivision = layer_subdivision_for(max_resolution_, resolution);
if (layer_subdivision != probe.atlas_coord.layer_subdivision) {
probe.atlas_coord = find_empty_atlas_region(layer_subdivision);
do_world_update_set(true);
}
world_sampling_coord_ = probe.atlas_coord.as_sampling_coord(atlas_extent());
}
void ReflectionProbeModule::sync_world_lookdev()
{
ReflectionProbe &probe = probes_.lookup(world_object_key_);
const eLightProbeResolution resolution = reflection_probe_resolution();
int layer_subdivision = layer_subdivision_for(max_resolution_, resolution);
if (layer_subdivision != probe.atlas_coord.layer_subdivision) {
probe.atlas_coord = find_empty_atlas_region(layer_subdivision);
}
world_sampling_coord_ = probe.atlas_coord.as_sampling_coord(atlas_extent());
do_world_update_set(true);
}
void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
{
const ::LightProbe &light_probe = *(::LightProbe *)ob->data;
if (light_probe.type != LIGHTPROBE_TYPE_SPHERE) {
return;
}
ReflectionProbe &probe = probes_.lookup_or_add_cb(ob_handle.object_key.hash(), [&]() {
ReflectionProbe probe = {};
probe.do_render = true;
probe.type = ReflectionProbe::Type::PROBE;
return probe;
});
probe.do_render |= (ob_handle.recalc != 0);
probe.is_probe_used = true;
const bool probe_sync_active = instance_.do_reflection_probe_sync();
if (!probe_sync_active && probe.do_render) {
update_probes_next_sample_ = true;
}
/* Only update data when rerendering the probes to reduce flickering. */
if (!probe_sync_active) {
return;
}
probe.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
int subdivision = layer_subdivision_for(max_resolution_, reflection_probe_resolution());
if (probe.atlas_coord.layer_subdivision != subdivision) {
probe.atlas_coord = find_empty_atlas_region(subdivision);
}
bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
float parallax_distance = use_custom_parallax ?
max_ff(light_probe.distpar, light_probe.distinf) :
light_probe.distinf;
float influence_distance = light_probe.distinf;
float influence_falloff = light_probe.falloff;
probe.influence_shape = (light_probe.attenuation_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID :
SHAPE_ELIPSOID;
probe.parallax_shape = (light_probe.parallax_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID :
SHAPE_ELIPSOID;
float4x4 object_to_world = math::scale(float4x4(ob->object_to_world),
float3(influence_distance));
probe.location = object_to_world.location();
probe.volume = math::abs(math::determinant(object_to_world));
probe.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
probe.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
probe.influence_bias = probe.influence_scale;
probe.parallax_distance = parallax_distance / influence_distance;
probe.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
probe.viewport_display_size = light_probe.data_display_size;
}
ReflectionProbeAtlasCoordinate ReflectionProbeModule::find_empty_atlas_region(
int subdivision_level) const
{
ProbeLocationFinder location_finder(needed_layers_get() + 1, subdivision_level);
for (const ReflectionProbe &probe : probes_.values()) {
if (probe.atlas_coord.layer != -1) {
location_finder.mark_space_used(probe.atlas_coord);
}
}
return location_finder.first_free_spot();
}
void ReflectionProbeModule::end_sync()
{
const bool probes_removed = remove_unused_probes();
const bool world_updated = do_world_update_get();
const bool only_world = has_only_world_probe();
const int number_layers_needed = needed_layers_get();
const int current_layers = probes_tx_.depth();
const bool resize_layers = current_layers < number_layers_needed;
const bool rerender_all_probes = resize_layers || world_updated;
if (rerender_all_probes) {
for (ReflectionProbe &probe : probes_.values()) {
const bool world_updated = instance_.light_probes.world_sphere_.do_render;
const bool atlas_resized = ensure_atlas();
/* Detect if we need to render probe objects. */
update_probes_next_sample_ = false;
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (atlas_resized || world_updated) {
/* Last minute tagging. */
probe.do_render = true;
}
}
const bool do_update = instance_.do_reflection_probe_sync() || (only_world && world_updated);
if (!do_update) {
/* World has changed this sample, but probe update isn't initialized this sample. */
if (world_updated && !only_world) {
if (probe.do_render) {
/* Tag the next redraw to warm up the probe pipeline.
* Keep doing this until there is no update.
* This avoids stuttering when moving a lightprobe. */
update_probes_next_sample_ = true;
}
if (update_probes_next_sample_ && !update_probes_this_sample_) {
DRW_viewport_request_redraw();
}
if (!update_probes_next_sample_ && probes_removed) {
data_buf_.push_update();
}
return;
}
if (resize_layers) {
probes_tx_.ensure_2d_array(GPU_RGBA16F,
int2(max_resolution_),
number_layers_needed,
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr,
9999);
GPU_texture_mipmap_mode(probes_tx_, true, true);
probes_tx_.clear(float4(0.0f));
/* If we cannot render probes this redraw make sure we request another redraw. */
if (update_probes_next_sample_ && (instance_.do_reflection_probe_sync() == false)) {
DRW_viewport_request_redraw();
}
}
/* Check reset probe updating as we will rendering probes. */
if (update_probes_this_sample_ || only_world) {
update_probes_next_sample_ = false;
void SphereProbeModule::ensure_cubemap_render_target(int resolution)
{
if (cubemap_tx_.ensure_cube(
GPU_RGBA16F, resolution, GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ))
{
GPU_texture_mipmap_mode(cubemap_tx_, false, true);
}
data_buf_.push_update();
/* TODO(fclem): dealocate it. */
}
bool ReflectionProbeModule::remove_unused_probes()
SphereProbeModule::UpdateInfo SphereProbeModule::update_info_from_probe(const SphereProbe &probe)
{
const int64_t removed_count = probes_.remove_if(
[](const ReflectionProbes::Item &item) { return !item.value.is_probe_used; });
return removed_count > 0;
}
bool ReflectionProbeModule::do_world_update_get() const
{
const ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
return world_probe.do_render;
}
void ReflectionProbeModule::do_world_update_set(bool value)
{
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
world_probe.do_render = value;
do_world_update_irradiance_set(value);
}
void ReflectionProbeModule::do_world_update_irradiance_set(bool value)
{
ReflectionProbe &world_probe = probes_.lookup(world_object_key_);
world_probe.do_world_irradiance_update = value;
}
bool ReflectionProbeModule::has_only_world_probe() const
{
return probes_.size() == 1;
}
std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
const ReflectionProbe::Type probe_type)
{
const bool do_probe_sync = instance_.do_reflection_probe_sync();
const bool only_world = has_only_world_probe();
const int max_shift = int(log2(max_resolution_));
for (ReflectionProbe &probe : probes_.values()) {
if (!probe.do_render && !probe.do_world_irradiance_update) {
continue;
}
if (probe.type != probe_type) {
continue;
}
/* Do not update this probe during this sample. */
if (probe.type == ReflectionProbe::Type::WORLD && !only_world && !do_probe_sync) {
continue;
}
if (probe.type == ReflectionProbe::Type::PROBE && !do_probe_sync) {
continue;
}
ReflectionProbeUpdateInfo info = {};
info.probe_type = probe.type;
info.atlas_coord = probe.atlas_coord;
info.resolution = 1 << (max_shift - probe.atlas_coord.layer_subdivision - 1);
info.clipping_distances = probe.clipping_distances;
info.probe_pos = probe.location;
info.do_render = probe.do_render;
info.do_world_irradiance_update = probe.do_world_irradiance_update;
SphereProbeModule::UpdateInfo info = {};
info.atlas_coord = probe.atlas_coord;
info.resolution = 1 << (max_shift - probe.atlas_coord.subdivision_lvl - 1);
info.clipping_distances = probe.clipping_distances;
info.probe_pos = probe.location;
info.do_render = probe.do_render;
info.do_world_irradiance_update = false;
return info;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::world_update_info_pop()
{
SphereProbe &world_probe = instance_.light_probes.world_sphere_;
if (!world_probe.do_render && !do_world_irradiance_update) {
return std::nullopt;
}
SphereProbeModule::UpdateInfo info = update_info_from_probe(world_probe);
info.do_world_irradiance_update = do_world_irradiance_update;
world_probe.do_render = false;
do_world_irradiance_update = false;
ensure_cubemap_render_target(info.resolution);
return info;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::probe_update_info_pop()
{
if (!instance_.do_reflection_probe_sync()) {
/* Do not update probes during this sample as we did not sync the draw::Passes. */
return std::nullopt;
}
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (!probe.do_render) {
continue;
}
SphereProbeModule::UpdateInfo info = update_info_from_probe(probe);
probe.do_render = false;
probe.do_world_irradiance_update = false;
if (cubemap_tx_.ensure_cube(GPU_RGBA16F,
info.resolution,
GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ))
{
GPU_texture_mipmap_mode(cubemap_tx_, false, true);
}
probe.use_for_render = true;
ensure_cubemap_render_target(info.resolution);
return info;
}
return std::nullopt;
}
void ReflectionProbeModule::remap_to_octahedral_projection(
const ReflectionProbeAtlasCoordinate &atlas_coord)
void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord)
{
int resolution = max_resolution_ >> atlas_coord.layer_subdivision;
int resolution = max_resolution_ >> atlas_coord.subdivision_lvl;
/* Update shader parameters that change per dispatch. */
probe_sampling_coord_ = atlas_coord.as_sampling_coord(atlas_extent());
probe_write_coord_ = atlas_coord.as_write_coord(atlas_extent(), 0);
probe_mip_level_ = atlas_coord.layer_subdivision;
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, REFLECTION_PROBE_GROUP_SIZE)), 1);
probe_sampling_coord_ = atlas_coord.as_sampling_coord(max_resolution_);
probe_write_coord_ = atlas_coord.as_write_coord(max_resolution_, 0);
probe_mip_level_ = atlas_coord.subdivision_lvl;
dispatch_probe_pack_ = int3(int2(ceil_division(resolution, SPHERE_PROBE_GROUP_SIZE)), 1);
instance_.manager->submit(remap_ps_);
}
void ReflectionProbeModule::update_world_irradiance()
void SphereProbeModule::update_world_irradiance()
{
instance_.manager->submit(update_irradiance_ps_);
}
void ReflectionProbeModule::update_probes_texture_mipmaps()
void SphereProbeModule::update_probes_texture_mipmaps()
{
GPU_texture_update_mipmap_chain(probes_tx_);
}
void ReflectionProbeModule::set_view(View & /*view*/)
void SphereProbeModule::set_view(View & /*view*/)
{
Vector<ReflectionProbe *> probe_active;
for (auto &probe : probes_.values()) {
Vector<SphereProbe *> probe_active;
for (auto &probe : instance_.light_probes.sphere_map_.values()) {
/* Last slot is reserved for the world probe. */
if (reflection_probe_count_ >= REFLECTION_PROBES_MAX - 1) {
if (reflection_probe_count_ >= SPHERE_PROBE_MAX - 1) {
break;
}
probe.prepare_for_upload(atlas_extent());
/* World is always considered active and added last. */
if (probe.type == ReflectionProbe::Type::WORLD) {
if (!probe.use_for_render) {
continue;
}
/* TODO(fclem): Culling. */
@ -515,32 +217,29 @@ void ReflectionProbeModule::set_view(View & /*view*/)
}
/* Stable sorting of probes. */
std::sort(probe_active.begin(),
probe_active.end(),
[](const ReflectionProbe *a, const ReflectionProbe *b) {
if (a->volume != b->volume) {
/* Smallest first. */
return a->volume < b->volume;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
float3 _a = a->location;
float3 _b = b->location;
if (_a.x != _b.x) {
return _a.x < _b.x;
}
else if (_a.y != _b.y) {
return _a.y < _b.y;
}
else if (_a.z != _b.z) {
return _a.z < _b.z;
}
else {
/* Fallback to memory address, since there's no good alternative. */
return a < b;
}
});
std::sort(
probe_active.begin(), probe_active.end(), [](const SphereProbe *a, const SphereProbe *b) {
if (a->volume != b->volume) {
/* Smallest first. */
return a->volume < b->volume;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
float3 _a = a->location;
float3 _b = b->location;
if (_a.x != _b.x) {
return _a.x < _b.x;
}
if (_a.y != _b.y) {
return _a.y < _b.y;
}
if (_a.z != _b.z) {
return _a.z < _b.z;
}
/* Fallback to memory address, since there's no good alternative. */
return a < b;
});
/* Push all sorted data to the UBO. */
int probe_id = 0;
@ -548,9 +247,9 @@ void ReflectionProbeModule::set_view(View & /*view*/)
data_buf_[probe_id++] = *probe;
}
/* Add world probe at the end. */
data_buf_[probe_id++] = probes_.lookup(world_object_key_);
data_buf_[probe_id++] = instance_.light_probes.world_sphere_;
/* Tag the end of the array. */
if (probe_id < REFLECTION_PROBES_MAX) {
if (probe_id < SPHERE_PROBE_MAX) {
data_buf_[probe_id].atlas_coord.layer = -1;
}
data_buf_.push_update();
@ -574,16 +273,11 @@ void ReflectionProbeModule::set_view(View & /*view*/)
/* Add one for world probe. */
reflection_probe_count_ = probe_active.size() + 1;
dispatch_probe_select_.x = divide_ceil_u(reflection_probe_count_,
REFLECTION_PROBE_SELECT_GROUP_SIZE);
SPHERE_PROBE_SELECT_GROUP_SIZE);
instance_.manager->submit(select_ps_);
}
ReflectionProbeAtlasCoordinate ReflectionProbeModule::world_atlas_coord_get() const
{
return probes_.lookup(world_object_key_).atlas_coord;
}
void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
void SphereProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
{
if (!do_display_draw_) {
return;
@ -594,7 +288,7 @@ void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_REFLECTION));
bind_resources(viewport_display_ps_);
viewport_display_ps_.bind_resources(*this);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
@ -603,37 +297,4 @@ void ReflectionProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging
*
* \{ */
void ReflectionProbeModule::debug_print() const
{
std::ostream &os = std::cout;
for (const ReflectionProbe &probe : probes_.values()) {
switch (probe.type) {
case ReflectionProbe::Type::WORLD: {
os << "WORLD";
os << " do_render: " << probe.do_render;
os << "\n";
break;
}
case ReflectionProbe::Type::PROBE: {
os << "PROBE";
os << " do_render: " << probe.do_render;
os << " is_used: " << probe.is_probe_used;
os << "\n";
break;
}
}
os << " - layer: " << probe.atlas_coord.layer;
os << " subdivision: " << probe.atlas_coord.layer_subdivision;
os << " area: " << probe.atlas_coord.area_index;
os << "\n";
}
}
/** \} */
} // namespace blender::eevee

View File

@ -8,6 +8,7 @@
#pragma once
#include "eevee_lightprobe.hh"
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
@ -22,144 +23,17 @@ class Instance;
struct ObjectHandle;
struct WorldHandle;
class CaptureView;
struct ReflectionProbeUpdateInfo;
/* -------------------------------------------------------------------- */
/** \name Reflection Probe
* \{ */
struct ReflectionProbeAtlasCoordinate {
/** On which layer of the texture array is this reflection probe stored. */
int layer = -1;
/**
* Subdivision of the layer. 0 = no subdivision and resolution would be
* ReflectionProbeModule::MAX_RESOLUTION.
*/
int layer_subdivision = -1;
/**
* Which area of the subdivided layer is the reflection probe located.
*
* A layer has (2^layer_subdivision)^2 areas.
*/
int area_index = -1;
/* Return the area extent in pixel. */
int area_extent(int atlas_extent) const
{
return atlas_extent >> layer_subdivision;
}
/* Coordinate of the area in [0..area_count_per_dimension[ range. */
int2 area_location() const
{
const int area_count_per_dimension = 1 << layer_subdivision;
return int2(area_index % area_count_per_dimension, area_index / area_count_per_dimension);
}
/* Coordinate of the bottom left corner of the area in [0..atlas_extent[ range. */
int2 area_offset(int atlas_extent) const
{
return area_location() * area_extent(atlas_extent);
}
ReflectionProbeCoordinate as_sampling_coord(int atlas_extent) const
{
/**
* We want to cover the last mip exactly at the pixel center to reduce padding texels and
* interpolation artifacts.
* This is a diagram of a 2px^2 map with `c` being the texels corners and `x` the pixels
* centers.
*
* c-------c-------c
* | | |
* | x | x | <
* | | | |
* c-------c-------c | sampling area
* | | | |
* | x | x | <
* | | |
* c-------c-------c
* ^-------^
* sampling area
*/
/* First level only need half a pixel of padding around the sampling area. */
const int mip_max_lvl_padding = 1;
const int mip_min_lvl_padding = mip_max_lvl_padding << REFLECTION_PROBE_MIPMAP_LEVELS;
/* Extent and offset in mip 0 texels. */
const int sampling_area_extent = area_extent(atlas_extent) - mip_min_lvl_padding;
const int2 sampling_area_offset = area_offset(atlas_extent) + mip_min_lvl_padding / 2;
/* Convert to atlas UVs. */
ReflectionProbeCoordinate coord;
coord.scale = sampling_area_extent / float(atlas_extent);
coord.offset = float2(sampling_area_offset) / float(atlas_extent);
coord.layer = layer;
return coord;
}
ReflectionProbeWriteCoordinate as_write_coord(int atlas_extent, int mip_lvl) const
{
ReflectionProbeWriteCoordinate coord;
coord.extent = atlas_extent >> (layer_subdivision + mip_lvl);
coord.offset = (area_location() * coord.extent) >> mip_lvl;
coord.layer = layer;
return coord;
}
};
struct ReflectionProbe : ReflectionProbeData {
public:
enum class Type {
WORLD,
PROBE,
} type;
/* Used to sort the probes by priority. */
float volume;
/* Should the area in the probes_tx_ be updated? */
bool do_render = false;
bool do_world_irradiance_update = false;
/**
* Probes that aren't used during a draw can be cleared.
*
* Only valid when type == Type::Probe.
*/
bool is_probe_used = false;
/**
* Far and near clipping distances for rendering
*/
float2 clipping_distances;
/** Display debug spheres in the viewport. */
bool viewport_display;
float viewport_display_size;
ReflectionProbeAtlasCoordinate atlas_coord;
void prepare_for_upload(int atlas_extent)
{
/* Compute LOD factor. */
const int probe_resolution = atlas_coord.area_extent(atlas_extent);
const float bias = 0.0;
const float lod_factor = bias + 0.5 * log2f(square_i(probe_resolution));
this->lod_factor = lod_factor;
/* Compute sampling offset and scale. */
static_cast<ReflectionProbeData *>(this)->atlas_coord = atlas_coord.as_sampling_coord(
atlas_extent);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
class ReflectionProbeModule {
using ReflectionProbes = Map<uint64_t, ReflectionProbe>;
class SphereProbeModule {
friend LightProbeModule;
/* Capture View requires access to the cube-maps texture for frame-buffer configuration. */
friend class CaptureView;
/* Instance requires access to #update_probes_this_sample_ */
friend class Instance;
private:
/**
@ -169,17 +43,15 @@ class ReflectionProbeModule {
*/
static constexpr int max_resolution_ = 2048;
static constexpr uint64_t world_object_key_ = 0;
bool is_initialized = false;
Instance &instance_;
ReflectionProbeDataBuf data_buf_;
ReflectionProbes probes_;
SphereProbeDataBuf data_buf_;
/** Probes texture stored in octahedral mapping. */
Texture probes_tx_ = {"Probes"};
/** Copy the rendered cube-map to the atlas texture. */
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
/** Extract irradiance information from the world. */
PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"};
PassSimple select_ps_ = {"Probe.Select"};
@ -197,52 +69,48 @@ class ReflectionProbeModule {
/** Mip level being sampled for remapping. */
int probe_mip_level_ = 0;
/** Updated Probe coordinates in the atlas. */
ReflectionProbeCoordinate probe_sampling_coord_;
ReflectionProbeWriteCoordinate probe_write_coord_;
SphereProbeUvArea probe_sampling_coord_;
SphereProbePixelArea probe_write_coord_;
/** World coordinates in the atlas. */
ReflectionProbeCoordinate world_sampling_coord_;
SphereProbeUvArea world_sampling_coord_;
/** Number of the probe to process in the select phase. */
int reflection_probe_count_ = 0;
/**
* True if the next redraw will trigger a light-probe sphere update.
* As syncing the draw passes for rendering has a significant overhead,
* we only trigger this sync path if we detect updates. But we only know
* this after `end_sync` which is too late to sync objects for light-probe
* rendering. So we tag the next redraw (or sample) to do the sync.
*/
bool update_probes_next_sample_ = false;
/** True if the this redraw will trigger a light-probe sphere update. */
bool update_probes_this_sample_ = false;
/** Compute world irradiance coefficient and store them into the volume probe atlas. */
bool do_world_irradiance_update = true;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
ReflectionProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"ReflectionProbeModule.Viewport Display"};
SphereProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"ProbeSphereModule.Viewport Display"};
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
SphereProbeModule(Instance &instance) : instance_(instance){};
void init();
void begin_sync();
void sync_world(::World *world);
void sync_world_lookdev();
void sync_object(Object *ob, ObjectHandle &ob_handle);
void end_sync();
void viewport_draw(View &view, GPUFrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(REFLECTION_PROBE_TEX_SLOT, &probes_tx_);
pass.bind_ubo(REFLECTION_PROBE_BUF_SLOT, &data_buf_);
pass.bind_texture(SPHERE_PROBE_TEX_SLOT, &probes_tx_);
pass.bind_ubo(SPHERE_PROBE_BUF_SLOT, &data_buf_);
}
bool do_world_update_get() const;
void do_world_update_set(bool value);
void do_world_update_irradiance_set(bool value);
void set_view(View &view);
void debug_print() const;
int atlas_extent() const
{
return probes_tx_.width();
}
/**
* Get the resolution of a single cube-map side when rendering probes.
*
@ -250,59 +118,57 @@ class ReflectionProbeModule {
*/
int probe_render_extent() const;
ReflectionProbeAtlasCoordinate world_atlas_coord_get() const;
void tag_world_irradiance_for_update()
{
do_world_irradiance_update = true;
}
private:
/** Get the number of layers that is needed to store probes. */
int needed_layers_get() const;
bool remove_unused_probes();
/* Return the subdivision level for the requested probe resolution.
* Result is safely clamped to max resolution. */
int subdivision_level_get(const eLightProbeResolution probe_resolution)
{
return max_ii(int(log2(max_resolution_)) - int(probe_resolution), 0);
}
/**
* Create a reflection probe data element that points to an empty spot in the cubemap that can
* hold a texture with the given subdivision_level.
* Ensure atlas texture is the right size.
* Returns true if the texture has been cleared and all probes needs to be rendered again.
*/
ReflectionProbeAtlasCoordinate find_empty_atlas_region(int subdivision_level) const;
bool ensure_atlas();
/**
* Ensure the cube-map target texture for rendering the probe is allocated.
*/
void ensure_cubemap_render_target(int resolution);
struct UpdateInfo {
float3 probe_pos;
/** Resolution of the cube-map to be rendered. */
int resolution;
float2 clipping_distances;
SphereProbeAtlasCoord atlas_coord;
bool do_render;
bool do_world_irradiance_update;
};
UpdateInfo update_info_from_probe(const SphereProbe &probe);
/**
* Pop the next reflection probe that requires to be updated.
*/
std::optional<ReflectionProbeUpdateInfo> update_info_pop(ReflectionProbe::Type probe_type);
std::optional<UpdateInfo> world_update_info_pop();
std::optional<UpdateInfo> probe_update_info_pop();
void remap_to_octahedral_projection(const ReflectionProbeAtlasCoordinate &atlas_coord);
/**
* Internal processing passes.
*/
void remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord);
void update_probes_texture_mipmaps();
void update_world_irradiance();
bool has_only_world_probe() const;
eLightProbeResolution reflection_probe_resolution() const;
/* Capture View requires access to the cube-maps texture for frame-buffer configuration. */
friend class CaptureView;
/* Instance requires access to #update_probes_this_sample_ */
friend class Instance;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Update Info
* \{ */
struct ReflectionProbeUpdateInfo {
float3 probe_pos;
ReflectionProbe::Type probe_type;
/**
* Resolution of the cubemap to be rendered.
*/
int resolution;
float2 clipping_distances;
ReflectionProbeAtlasCoordinate atlas_coord;
bool do_render;
bool do_world_irradiance_update;
};
/** \} */

View File

@ -214,11 +214,11 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_lightprobe_irradiance_ray";
case LIGHTPROBE_IRRADIANCE_LOAD:
return "eevee_lightprobe_irradiance_load";
case REFLECTION_PROBE_REMAP:
case SPHERE_PROBE_REMAP:
return "eevee_reflection_probe_remap";
case REFLECTION_PROBE_UPDATE_IRRADIANCE:
case SPHERE_PROBE_UPDATE_IRRADIANCE:
return "eevee_reflection_probe_update_irradiance";
case REFLECTION_PROBE_SELECT:
case SPHERE_PROBE_SELECT:
return "eevee_reflection_probe_select";
case SHADOW_CLIPMAP_CLEAR:
return "eevee_shadow_clipmap_clear";

View File

@ -104,9 +104,9 @@ enum eShaderType {
RAY_TRACE_PLANAR,
RAY_TRACE_SCREEN,
REFLECTION_PROBE_REMAP,
REFLECTION_PROBE_UPDATE_IRRADIANCE,
REFLECTION_PROBE_SELECT,
SPHERE_PROBE_REMAP,
SPHERE_PROBE_UPDATE_IRRADIANCE,
SPHERE_PROBE_SELECT,
SHADOW_CLIPMAP_CLEAR,
SHADOW_DEBUG,

View File

@ -1043,7 +1043,8 @@ enum LightProbeShape : uint32_t {
SHAPE_CUBOID = 1u,
};
struct ReflectionProbeCoordinate {
/* Sampling coordinates using UV space. */
struct SphereProbeUvArea {
/* Offset in UV space to the start of the sampling space of the octahedron map. */
float2 offset;
/* Scaling of the squared UV space of the octahedron map. */
@ -1051,9 +1052,10 @@ struct ReflectionProbeCoordinate {
/* Layer of the atlas where the octahedron map is stored. */
float layer;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeCoordinate, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeUvArea, 16)
struct ReflectionProbeWriteCoordinate {
/* Pixel read/write coordinates using pixel space. */
struct SphereProbePixelArea {
/* Offset in pixel space to the start of the writing space of the octahedron map.
* Note that the writing space is not the same as the sampling space as we have borders. */
int2 offset;
@ -1062,29 +1064,23 @@ struct ReflectionProbeWriteCoordinate {
/* Layer of the atlas where the octahedron map is stored. */
int layer;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeWriteCoordinate, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbePixelArea, 16)
/** Mapping data to locate a reflection probe in texture. */
struct ReflectionProbeData {
struct SphereProbeData {
/** Transform to probe local position with non-uniform scaling. */
float3x4 world_to_probe_transposed;
packed_float3 location;
float _pad2;
/** Shape of the parallax projection. */
float parallax_distance;
LightProbeShape parallax_shape;
LightProbeShape influence_shape;
float parallax_distance;
/** Influence factor based on the distance to the parallax shape. */
float influence_scale;
float influence_bias;
/** LOD factor for mipmap selection. */
float lod_factor;
float _pad0;
float _pad1;
ReflectionProbeCoordinate atlas_coord;
SphereProbeUvArea atlas_coord;
/**
* Irradiance at the probe location encoded as spherical harmonics.
@ -1092,21 +1088,21 @@ struct ReflectionProbeData {
*/
ReflectionProbeLowFreqLight low_freq_light;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeData, 16)
/** Viewport Display Pass. */
struct ReflectionProbeDisplayData {
struct SphereProbeDisplayData {
int probe_index;
float display_size;
float _pad0;
float _pad1;
};
BLI_STATIC_ASSERT_ALIGN(ReflectionProbeDisplayData, 16)
BLI_STATIC_ASSERT_ALIGN(SphereProbeDisplayData, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Irradiance Cache
/** \name Volume Probe Cache
* \{ */
struct SurfelRadiance {
@ -1197,7 +1193,7 @@ struct CaptureInfoData {
bool1 capture_emission;
int _pad0;
/* World light probe atlas coordinate. */
ReflectionProbeCoordinate world_atlas_coord;
SphereProbeUvArea world_atlas_coord;
};
BLI_STATIC_ASSERT_ALIGN(CaptureInfoData, 16)
@ -1211,7 +1207,7 @@ struct SurfelListInfoData {
};
BLI_STATIC_ASSERT_ALIGN(SurfelListInfoData, 16)
struct IrradianceGridData {
struct VolumeProbeData {
/** World to non-normalized local grid space [0..size-1]. Stored transposed for compactness. */
float3x4 world_to_grid_transposed;
/** Number of bricks for this grid. */
@ -1224,7 +1220,7 @@ struct IrradianceGridData {
float facing_bias;
int _pad1;
};
BLI_STATIC_ASSERT_ALIGN(IrradianceGridData, 16)
BLI_STATIC_ASSERT_ALIGN(VolumeProbeData, 16)
struct IrradianceBrick {
/* Offset in pixel to the start of the data inside the atlas texture. */
@ -1420,7 +1416,7 @@ static inline float3 burley_eval(float3 d, float r)
/** \name Light-probe Planar Data
* \{ */
struct ProbePlanarData {
struct PlanarProbeData {
/** Matrices used to render the planar capture. */
float4x4 viewmat;
float4x4 winmat;
@ -1431,7 +1427,7 @@ struct ProbePlanarData {
/** Layer in the planar capture textures used by this probe. */
int layer_id;
};
BLI_STATIC_ASSERT_ALIGN(ProbePlanarData, 16)
BLI_STATIC_ASSERT_ALIGN(PlanarProbeData, 16)
struct ClipPlaneData {
/** World space clip plane equation. Used to render planar light-probes. */
@ -1440,14 +1436,14 @@ struct ClipPlaneData {
BLI_STATIC_ASSERT_ALIGN(ClipPlaneData, 16)
/** Viewport Display Pass. */
struct ProbePlanarDisplayData {
struct PlanarProbeDisplayData {
float4x4 plane_to_world;
int probe_index;
float _pad0;
float _pad1;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(ProbePlanarDisplayData, 16)
BLI_STATIC_ASSERT_ALIGN(PlanarProbeDisplayData, 16)
/** \} */
@ -1577,7 +1573,7 @@ using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, tru
using DrawIndirectBuf = draw::StorageBuffer<DrawCommand, true>;
using DispatchIndirectBuf = draw::StorageBuffer<DispatchCommand>;
using UniformDataBuf = draw::UniformBuffer<UniformData>;
using IrradianceGridDataBuf = draw::UniformArrayBuffer<IrradianceGridData, IRRADIANCE_GRID_MAX>;
using VolumeProbeDataBuf = draw::UniformArrayBuffer<VolumeProbeData, IRRADIANCE_GRID_MAX>;
using IrradianceBrickBuf = draw::StorageVectorBuffer<IrradianceBrickPacked, 16>;
using LightCullingDataBuf = draw::StorageBuffer<LightCullingData>;
using LightCullingKeyBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
@ -1589,11 +1585,10 @@ using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
using RayTraceTileBuf = draw::StorageArrayBuffer<uint, 1024, true>;
using SubsurfaceTileBuf = RayTraceTileBuf;
using ReflectionProbeDataBuf =
draw::UniformArrayBuffer<ReflectionProbeData, REFLECTION_PROBES_MAX>;
using ReflectionProbeDisplayDataBuf = draw::StorageArrayBuffer<ReflectionProbeDisplayData>;
using ProbePlanarDataBuf = draw::UniformArrayBuffer<ProbePlanarData, PLANAR_PROBES_MAX>;
using ProbePlanarDisplayDataBuf = draw::StorageArrayBuffer<ProbePlanarDisplayData>;
using SphereProbeDataBuf = draw::UniformArrayBuffer<SphereProbeData, SPHERE_PROBE_MAX>;
using SphereProbeDisplayDataBuf = draw::StorageArrayBuffer<SphereProbeDisplayData>;
using PlanarProbeDataBuf = draw::UniformArrayBuffer<PlanarProbeData, PLANAR_PROBE_MAX>;
using PlanarProbeDisplayDataBuf = draw::StorageArrayBuffer<PlanarProbeDisplayData>;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using ShadowStatisticsBuf = draw::StorageBuffer<ShadowStatistics>;
using ShadowPagesInfoDataBuf = draw::StorageBuffer<ShadowPagesInfoData>;

View File

@ -831,9 +831,9 @@ void ShadowModule::begin_sync()
pass.init();
if (inst_.is_baking()) {
SurfelBuf &surfels_buf = inst_.irradiance_cache.bake.surfels_buf_;
CaptureInfoBuf &capture_info_buf = inst_.irradiance_cache.bake.capture_info_buf_;
float surfel_coverage_area = inst_.irradiance_cache.bake.surfel_density_;
SurfelBuf &surfels_buf = inst_.volume_probes.bake.surfels_buf_;
CaptureInfoBuf &capture_info_buf = inst_.volume_probes.bake.capture_info_buf_;
float surfel_coverage_area = inst_.volume_probes.bake.surfel_density_;
/* Directional shadows. */
float texel_size = ShadowDirectional::tile_size_get(0) / float(SHADOW_PAGE_RES);
@ -850,7 +850,7 @@ void ShadowModule::begin_sync()
sub.push_constant("directional_level", directional_level);
sub.push_constant("tilemap_projection_ratio", projection_ratio);
sub.bind_resources(inst_.lights);
sub.dispatch(&inst_.irradiance_cache.bake.dispatch_per_surfel_);
sub.dispatch(&inst_.volume_probes.bake.dispatch_per_surfel_);
/* Skip opaque and transparent tagging for light baking. */
return;

View File

@ -571,19 +571,6 @@ void SyncModule::sync_curves(Object *ob,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Light Probes
* \{ */
void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle)
{
inst_.light_probes.sync_probe(ob, ob_handle);
inst_.reflection_probes.sync_object(ob, ob_handle);
inst_.planar_probes.sync_object(ob, ob_handle);
}
/** \} */
void foreach_hair_particle_handle(Object *ob, ObjectHandle ob_handle, HairHandleCallback callback)
{
int sub_key = 1;

View File

@ -186,7 +186,6 @@ class SyncModule {
const ObjectRef &ob_ref,
ModifierData *modifier_data = nullptr,
ParticleSystem *particle_sys = nullptr);
void sync_light_probe(Object *ob, ObjectHandle &ob_handle);
};
using HairHandleCallback = FunctionRef<void(ObjectHandle, ModifierData &, ParticleSystem &)>;

View File

@ -120,7 +120,7 @@ void ShadingView::render()
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_, extent_);
inst_.reflection_probes.set_view(render_view_);
inst_.sphere_probes.set_view(render_view_);
inst_.pipelines.background.render(render_view_);
@ -151,8 +151,8 @@ void ShadingView::render()
inst_.lights.debug_draw(render_view_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_, combined_fb_);
inst_.shadows.debug_draw(render_view_, combined_fb_);
inst_.irradiance_cache.viewport_draw(render_view_, combined_fb_);
inst_.reflection_probes.viewport_draw(render_view_, combined_fb_);
inst_.volume_probes.viewport_draw(render_view_, combined_fb_);
inst_.sphere_probes.viewport_draw(render_view_, combined_fb_);
inst_.planar_probes.viewport_draw(render_view_, combined_fb_);
inst_.ambient_occlusion.render_pass(render_view_);
@ -237,8 +237,7 @@ void ShadingView::update_view()
void CaptureView::render_world()
{
const std::optional<ReflectionProbeUpdateInfo> update_info =
inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::WORLD);
const auto update_info = inst_.sphere_probes.world_update_info_pop();
if (!update_info.has_value()) {
return;
}
@ -257,19 +256,18 @@ void CaptureView::render_world()
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
combined_fb_.ensure(
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
combined_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
GPU_framebuffer_bind(combined_fb_);
inst_.pipelines.world.render(view);
}
inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.reflection_probes.update_probes_texture_mipmaps();
inst_.sphere_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.sphere_probes.update_probes_texture_mipmaps();
}
if (update_info->do_world_irradiance_update) {
inst_.reflection_probes.update_world_irradiance();
inst_.sphere_probes.update_world_irradiance();
}
GPU_debug_group_end();
@ -280,9 +278,7 @@ void CaptureView::render_probes()
Framebuffer prepass_fb;
View view = {"Capture.View"};
bool do_update_mipmap_chain = false;
while (const std::optional<ReflectionProbeUpdateInfo> update_info =
inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::PROBE))
{
while (const auto update_info = inst_.sphere_probes.probe_update_info_pop()) {
GPU_debug_group_begin("Probe.Capture");
do_update_mipmap_chain = true;
@ -308,17 +304,15 @@ void CaptureView::render_probes()
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
combined_fb_.ensure(
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face));
combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
gbuffer_fb_.ensure(
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face),
GPU_ATTACHMENT_TEXTURE(inst_.gbuffer.header_tx),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(1), 0));
gbuffer_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face),
GPU_ATTACHMENT_TEXTURE(inst_.gbuffer.header_tx),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(1), 0));
GPU_framebuffer_bind(combined_fb_);
GPU_framebuffer_clear_color_depth(combined_fb_, float4(0.0f, 0.0f, 0.0f, 1.0f), 1.0f);
@ -328,12 +322,12 @@ void CaptureView::render_probes()
inst_.render_buffers.release();
inst_.gbuffer.release();
GPU_debug_group_end();
inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord);
inst_.sphere_probes.remap_to_octahedral_projection(update_info->atlas_coord);
}
if (do_update_mipmap_chain) {
/* TODO: only update the regions that have been updated. */
inst_.reflection_probes.update_probes_texture_mipmaps();
inst_.sphere_probes.update_probes_texture_mipmaps();
}
}

View File

@ -192,8 +192,8 @@ void VolumeModule::end_sync()
scatter_ps_.shader_set(
inst_.shaders.static_shader_get(use_lights_ ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER));
inst_.lights.bind_resources(scatter_ps_);
inst_.reflection_probes.bind_resources(scatter_ps_);
inst_.irradiance_cache.bind_resources(scatter_ps_);
inst_.sphere_probes.bind_resources(scatter_ps_);
inst_.volume_probes.bind_resources(scatter_ps_);
inst_.shadows.bind_resources(scatter_ps_);
inst_.sampling.bind_resources(scatter_ps_);
scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_);

View File

@ -119,17 +119,14 @@ void World::sync()
}
}
inst_.reflection_probes.sync_world(bl_world);
if (has_update) {
inst_.reflection_probes.do_world_update_set(true);
}
/* We have to manually test here because we have overrides. */
::World *orig_world = (::World *)DEG_get_original_id(&bl_world->id);
if (assign_if_different(prev_original_world, orig_world)) {
inst_.reflection_probes.do_world_update_set(true);
has_update = true;
}
inst_.light_probes.sync_world(bl_world, has_update);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_DEFERRED);
inst_.manager->register_layer_attributes(gpumat);

View File

@ -11,7 +11,7 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#ifdef REFLECTION_PROBE
#ifdef SPHERE_PROBE
struct LightProbeSample {
SphericalHarmonicL1 volume_irradiance;
@ -34,7 +34,7 @@ LightProbeSample lightprobe_load(vec3 P, vec3 Ng, vec3 V)
}
/* Return the best parallax corrected ray direction from the probe center. */
vec3 lightprobe_sphere_parallax(ReflectionProbeData probe, vec3 P, vec3 L)
vec3 lightprobe_sphere_parallax(SphereProbeData probe, vec3 P, vec3 L)
{
bool is_world = (probe.influence_scale == 0.0);
if (is_world) {
@ -63,7 +63,7 @@ vec3 lightprobe_sphere_parallax(ReflectionProbeData probe, vec3 P, vec3 L)
vec3 lightprobe_spherical_sample_normalized_with_parallax(
int probe_index, vec3 P, vec3 L, float lod, SphericalHarmonicL1 P_sh)
{
ReflectionProbeData probe = reflection_probe_buf[probe_index];
SphereProbeData probe = reflection_probe_buf[probe_index];
ReflectionProbeLowFreqLight shading_sh = reflection_probes_extract_low_freq(P_sh);
vec3 normalization_factor = reflection_probes_normalization_eval(
L, shading_sh, probe.low_freq_light);
@ -93,7 +93,7 @@ float lightprobe_roughness_to_cube_sh_mix_fac(float roughness)
float lightprobe_roughness_to_lod(float roughness)
{
/* Temporary. Do something better. */
return sqrt(roughness) * REFLECTION_PROBE_MIPMAP_LEVELS;
return sqrt(roughness) * SPHERE_PROBE_MIPMAP_LEVELS;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V)
@ -150,4 +150,4 @@ vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V
return mix(radiance_cube, radiance_sh, fac);
}
#endif /* REFLECTION_PROBE */
#endif /* SPHERE_PROBE */

View File

@ -67,7 +67,7 @@ void irradiance_capture_world(vec3 L, inout SphericalHarmonicL1 sh)
float visibility = 0.0;
if (capture_info_buf.capture_world_direct) {
ReflectionProbeCoordinate atlas_coord = capture_info_buf.world_atlas_coord;
SphereProbeUvArea atlas_coord = capture_info_buf.world_atlas_coord;
radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb;
/* Clamped brightness. */

View File

@ -19,7 +19,7 @@ vec3 lightprobe_irradiance_grid_sample_position(mat4 grid_local_to_world_mat,
* Return true if sample position is valid.
* \a r_lP is the local position in grid units [0..grid_size).
*/
bool lightprobe_irradiance_grid_local_coord(IrradianceGridData grid_data, vec3 P, out vec3 r_lP)
bool lightprobe_irradiance_grid_local_coord(VolumeProbeData grid_data, vec3 P, out vec3 r_lP)
{
/* Position in cell units. */
/* NOTE: The vector-matrix multiplication swapped on purpose to cancel the matrix transpose. */
@ -29,7 +29,7 @@ bool lightprobe_irradiance_grid_local_coord(IrradianceGridData grid_data, vec3 P
return all(equal(lP, r_lP));
}
int lightprobe_irradiance_grid_brick_index_get(IrradianceGridData grid_data, ivec3 brick_coord)
int lightprobe_irradiance_grid_brick_index_get(VolumeProbeData grid_data, ivec3 brick_coord)
{
int3 grid_size_in_bricks = divide_ceil(grid_data.grid_size,
int3(IRRADIANCE_GRID_BRICK_SIZE - 1));
@ -46,7 +46,7 @@ ivec3 lightprobe_irradiance_grid_cell_corner(int cell_corner_id)
return (ivec3(cell_corner_id) >> ivec3(0, 1, 2)) & 1;
}
float lightprobe_planar_score(ProbePlanarData planar, vec3 P, vec3 V, vec3 L)
float lightprobe_planar_score(PlanarProbeData planar, vec3 P, vec3 V, vec3 L)
{
vec3 lP = vec4(P, 1.0) * planar.world_to_object_transposed;
if (any(greaterThan(abs(lP), vec3(1.0)))) {
@ -68,9 +68,9 @@ int lightprobe_planar_select(vec3 P, vec3 V, vec3 L)
float best_score = saturate(dot(L, -V));
int best_index = -1;
for (int index = 0; index < PLANAR_PROBES_MAX; index++) {
for (int index = 0; index < PLANAR_PROBE_MAX; index++) {
if (probe_planar_buf[index].layer_id == -1) {
/* ProbePlanarData doesn't contain any gap, exit at first item that is invalid. */
/* PlanarProbeData doesn't contain any gap, exit at first item that is invalid. */
break;
}
float score = lightprobe_planar_score(probe_planar_buf[index], P, V, L);

View File

@ -30,7 +30,7 @@ ivec3 lightprobe_irradiance_grid_brick_coord(vec3 lP)
/**
* Return the local coordinated of the shading point inside the brick in unnormalized coordinate.
*/
vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
vec3 lightprobe_irradiance_grid_brick_local_coord(VolumeProbeData grid_data,
vec3 lP,
ivec3 brick_coord)
{
@ -44,7 +44,7 @@ vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
/**
* Return the biased local brick local coordinated.
*/
vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data,
vec3 lightprobe_irradiance_grid_bias_sample_coord(VolumeProbeData grid_data,
uvec2 brick_atlas_coord,
vec3 brick_lP,
vec3 lNg)
@ -161,7 +161,7 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(
}
}
IrradianceGridData grid_data = grids_infos_buf[index];
VolumeProbeData grid_data = grids_infos_buf[index];
/* TODO(fclem): Make sure this is working as expected. */
mat3x3 world_to_grid_transposed = mat3x3(grid_data.world_to_grid_transposed);

View File

@ -58,7 +58,7 @@ void main()
return;
}
ProbePlanarData planar = probe_planar_buf[planar_id];
PlanarProbeData planar = probe_planar_buf[planar_id];
/* Tag the ray data so that screen trace will not try to evaluate it and override the result. */
imageStore(ray_data_img, texel, vec4(ray_data.xyz, -ray_data.w));

View File

@ -164,7 +164,7 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data,
ScreenTraceHitData raytrace_planar(RayTraceData rt_data,
depth2DArray planar_depth_tx,
ProbePlanarData planar,
PlanarProbeData planar,
float stride_rand,
Ray ray)
{

View File

@ -8,12 +8,12 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#ifdef REFLECTION_PROBE
#ifdef SPHERE_PROBE
int reflection_probes_select(vec3 P, float random_probe)
{
for (int index = 0; index < REFLECTION_PROBES_MAX; index++) {
ReflectionProbeData probe_data = reflection_probe_buf[index];
/* ReflectionProbeData doesn't contain any gap, exit at first item that is invalid. */
for (int index = 0; index < SPHERE_PROBE_MAX; index++) {
SphereProbeData probe_data = reflection_probe_buf[index];
/* SphereProbeData doesn't contain any gap, exit at first item that is invalid. */
if (probe_data.atlas_coord.layer == -1) {
/* We hit the end of the array. Return last valid index. */
return index - 1;
@ -29,6 +29,6 @@ int reflection_probes_select(vec3 P, float random_probe)
}
}
/* This should never happen (world probe is always last). */
return REFLECTION_PROBES_MAX - 1;
return SPHERE_PROBE_MAX - 1;
}
#endif /* REFLECTION_PROBE */
#endif /* SPHERE_PROBE */

View File

@ -6,8 +6,8 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#ifdef REFLECTION_PROBE
vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeCoordinate atlas_coord)
#ifdef SPHERE_PROBE
vec4 reflection_probes_sample(vec3 L, float lod, SphereProbeUvArea atlas_coord)
{
vec2 octahedral_uv = octahedral_uv_from_direction(L) * atlas_coord.scale + atlas_coord.offset;
return textureLod(reflection_probes_tx, vec3(octahedral_uv, atlas_coord.layer), lod);

View File

@ -7,18 +7,18 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
ReflectionProbeCoordinate reinterpret_as_atlas_coord(ivec4 packed_coord)
SphereProbeUvArea reinterpret_as_atlas_coord(ivec4 packed_coord)
{
ReflectionProbeCoordinate unpacked;
SphereProbeUvArea unpacked;
unpacked.offset = intBitsToFloat(packed_coord.xy);
unpacked.scale = intBitsToFloat(packed_coord.z);
unpacked.layer = intBitsToFloat(packed_coord.w);
return unpacked;
}
ReflectionProbeWriteCoordinate reinterpret_as_write_coord(ivec4 packed_coord)
SphereProbePixelArea reinterpret_as_write_coord(ivec4 packed_coord)
{
ReflectionProbeWriteCoordinate unpacked;
SphereProbePixelArea unpacked;
unpacked.offset = packed_coord.xy;
unpacked.extent = packed_coord.z;
unpacked.layer = packed_coord.w;
@ -40,9 +40,9 @@ vec2 mirror_repeat_uv(vec2 uv)
void main()
{
ReflectionProbeCoordinate world_coord = reinterpret_as_atlas_coord(world_coord_packed);
ReflectionProbeCoordinate sample_coord = reinterpret_as_atlas_coord(probe_coord_packed);
ReflectionProbeWriteCoordinate write_coord = reinterpret_as_write_coord(write_coord_packed);
SphereProbeUvArea world_coord = reinterpret_as_atlas_coord(world_coord_packed);
SphereProbeUvArea sample_coord = reinterpret_as_atlas_coord(probe_coord_packed);
SphereProbePixelArea write_coord = reinterpret_as_write_coord(write_coord_packed);
/* Texel in probe. */
ivec2 local_texel = ivec2(gl_GlobalInvocationID.xy);

View File

@ -9,9 +9,9 @@
#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
ReflectionProbeCoordinate reinterpret_as_atlas_coord(ivec4 packed_coord)
SphereProbeUvArea reinterpret_as_atlas_coord(ivec4 packed_coord)
{
ReflectionProbeCoordinate unpacked;
SphereProbeUvArea unpacked;
unpacked.offset = intBitsToFloat(packed_coord.xy);
unpacked.scale = intBitsToFloat(packed_coord.z);
unpacked.layer = intBitsToFloat(packed_coord.w);
@ -42,15 +42,14 @@ void main()
cooef.L1.M0 = vec4(0.0);
cooef.L1.Mp1 = vec4(0.0);
ReflectionProbeCoordinate atlas_coord = reinterpret_as_atlas_coord(world_coord_packed);
SphereProbeUvArea atlas_coord = reinterpret_as_atlas_coord(world_coord_packed);
float layer_mipmap = 5;
/* Perform multiple sample. */
uint store_index = gl_LocalInvocationID.x;
float total_samples = float(gl_WorkGroupSize.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP);
float total_samples = float(gl_WorkGroupSize.x * SPHERE_PROBE_SH_SAMPLES_PER_GROUP);
float sample_weight = 4.0 * M_PI / total_samples;
float sample_offset = float(gl_LocalInvocationID.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP);
for (int sample_index = 0; sample_index < REFLECTION_PROBE_SH_SAMPLES_PER_GROUP; sample_index++)
{
float sample_offset = float(gl_LocalInvocationID.x * SPHERE_PROBE_SH_SAMPLES_PER_GROUP);
for (int sample_index = 0; sample_index < SPHERE_PROBE_SH_SAMPLES_PER_GROUP; sample_index++) {
vec2 rand = fract(hammersley_2d(sample_index + sample_offset, total_samples));
vec3 direction = sample_sphere(rand);
vec4 light = reflection_probes_sample(direction, layer_mipmap, atlas_coord);

View File

@ -88,7 +88,7 @@ void radiance_transfer_world(inout Surfel receiver, vec3 L)
float visibility = 0.0;
if (capture_info_buf.capture_world_indirect) {
ReflectionProbeCoordinate atlas_coord = capture_info_buf.world_atlas_coord;
SphereProbeUvArea atlas_coord = capture_info_buf.world_atlas_coord;
radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb;
}

View File

@ -144,7 +144,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_planar_eval)
.early_fragment_test(true)
/* Inputs. */
.fragment_out(0, Type::VEC4, "out_radiance")
.define("REFLECTION_PROBE")
.define("SPHERE_PROBE")
.define("SHADOW_SUBSURFACE")
.define("LIGHT_CLOSURE_EVAL_COUNT", "2")
.additional_info("eevee_shared",

View File

@ -182,7 +182,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
.push_constant(Type::FLOAT, "dilation_threshold")
.push_constant(Type::FLOAT, "dilation_radius")
.push_constant(Type::FLOAT, "grid_intensity_factor")
.uniform_buf(0, "IrradianceGridData", "grids_infos_buf[IRRADIANCE_GRID_MAX]")
.uniform_buf(0, "VolumeProbeData", "grids_infos_buf[IRRADIANCE_GRID_MAX]")
.storage_buf(0, Qualifier::READ, "uint", "bricks_infos_buf[]")
.sampler(0, ImageType::FLOAT_3D, "irradiance_a_tx")
.sampler(1, ImageType::FLOAT_3D, "irradiance_b_tx")
@ -200,20 +200,20 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load)
GPU_SHADER_CREATE_INFO(eevee_volume_probe_data)
.uniform_buf(IRRADIANCE_GRID_BUF_SLOT,
"IrradianceGridData",
"VolumeProbeData",
"grids_infos_buf[IRRADIANCE_GRID_MAX]")
/* NOTE: Use uint instead of IrradianceBrickPacked because Metal needs to know the exact type.
*/
.storage_buf(IRRADIANCE_BRICK_BUF_SLOT, Qualifier::READ, "uint", "bricks_infos_buf[]")
.sampler(IRRADIANCE_ATLAS_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx")
.sampler(VOLUME_PROBE_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx")
.define("IRRADIANCE_GRID_SAMPLING");
GPU_SHADER_CREATE_INFO(eevee_lightprobe_data)
.additional_info("eevee_reflection_probe_data", "eevee_volume_probe_data");
GPU_SHADER_CREATE_INFO(eevee_lightprobe_planar_data)
.define("REFLECTION_PROBE")
.uniform_buf(PLANAR_PROBE_BUF_SLOT, "ProbePlanarData", "probe_planar_buf[PLANAR_PROBES_MAX]")
.define("SPHERE_PROBE")
.uniform_buf(PLANAR_PROBE_BUF_SLOT, "PlanarProbeData", "probe_planar_buf[PLANAR_PROBE_MAX]")
.sampler(PLANAR_PROBE_RADIANCE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "planar_radiance_tx")
.sampler(PLANAR_PROBE_DEPTH_TEX_SLOT, ImageType::DEPTH_2D_ARRAY, "planar_depth_tx");

View File

@ -10,15 +10,15 @@
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.define("REFLECTION_PROBE")
.uniform_buf(REFLECTION_PROBE_BUF_SLOT,
"ReflectionProbeData",
"reflection_probe_buf[REFLECTION_PROBES_MAX]")
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx");
.define("SPHERE_PROBE")
.uniform_buf(SPHERE_PROBE_BUF_SLOT,
"SphereProbeData",
"reflection_probe_buf[SPHERE_PROBE_MAX]")
.sampler(SPHERE_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx");
/* Sample cubemap and remap into an octahedral texture. */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
.local_group_size(REFLECTION_PROBE_GROUP_SIZE, REFLECTION_PROBE_GROUP_SIZE)
.local_group_size(SPHERE_PROBE_GROUP_SIZE, SPHERE_PROBE_GROUP_SIZE)
.push_constant(Type::IVEC4, "probe_coord_packed")
.push_constant(Type::IVEC4, "write_coord_packed")
.push_constant(Type::IVEC4, "world_coord_packed")
@ -34,8 +34,8 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap)
/* Extract spherical harmonics band L0 + L1 from octahedral mapped reflection probe and update the
* world brick of the irradiance cache. */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance)
.local_group_size(REFLECTION_PROBE_SH_GROUP_SIZE, 1)
.define("REFLECTION_PROBE")
.local_group_size(SPHERE_PROBE_SH_GROUP_SIZE, 1)
.define("SPHERE_PROBE")
.push_constant(Type::IVEC4, "world_coord_packed")
.sampler(0, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx")
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img")
@ -44,11 +44,11 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance)
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select)
.local_group_size(REFLECTION_PROBE_SELECT_GROUP_SIZE)
.local_group_size(SPHERE_PROBE_SELECT_GROUP_SIZE)
.storage_buf(0,
Qualifier::READ_WRITE,
"ReflectionProbeData",
"reflection_probe_buf[REFLECTION_PROBES_MAX]")
"SphereProbeData",
"reflection_probe_buf[SPHERE_PROBE_MAX]")
.push_constant(Type::INT, "reflection_probe_count")
.additional_info("eevee_shared", "eevee_sampling_data", "eevee_volume_probe_data")
.compute_source("eevee_reflection_probe_select_comp.glsl")
@ -60,7 +60,7 @@ GPU_SHADER_INTERFACE_INFO(eevee_display_probe_reflection_iface, "")
GPU_SHADER_CREATE_INFO(eevee_display_probe_reflection)
.additional_info("eevee_shared", "draw_view", "eevee_reflection_probe_data")
.storage_buf(0, Qualifier::READ, "ReflectionProbeDisplayData", "display_data_buf[]")
.storage_buf(0, Qualifier::READ, "SphereProbeDisplayData", "display_data_buf[]")
.vertex_source("eevee_display_probe_reflection_vert.glsl")
.vertex_out(eevee_display_probe_reflection_iface)
.fragment_source("eevee_display_probe_reflection_frag.glsl")
@ -71,7 +71,7 @@ GPU_SHADER_INTERFACE_INFO(eevee_display_probe_planar_iface, "").flat(Type::INT,
GPU_SHADER_CREATE_INFO(eevee_display_probe_planar)
.additional_info("eevee_shared", "draw_view", "eevee_lightprobe_planar_data")
.storage_buf(0, Qualifier::READ, "ProbePlanarDisplayData", "display_data_buf[]")
.storage_buf(0, Qualifier::READ, "PlanarProbeDisplayData", "display_data_buf[]")
.vertex_source("eevee_display_probe_planar_vert.glsl")
.vertex_out(eevee_display_probe_planar_iface)
.fragment_source("eevee_display_probe_planar_frag.glsl")

View File

@ -647,13 +647,14 @@ static void drw_call_calc_orco(const Object *ob, float (*r_orcofacs)[4])
const Volume &volume = *reinterpret_cast<const Volume *>(ob_data);
const std::optional<blender::Bounds<blender::float3>> bounds = BKE_volume_min_max(&volume);
if (bounds) {
mid_v3_v3v3(static_buf.texspace_location, bounds->max, bounds->min);
sub_v3_v3v3(static_buf.texspace_size, bounds->max, bounds->min);
texspace_location = static_buf.texspace_location;
texspace_size = static_buf.texspace_size;
mid_v3_v3v3(texspace_location, bounds->max, bounds->min);
sub_v3_v3v3(texspace_size, bounds->max, bounds->min);
texspace_size[0] = std::max(texspace_size[0], 0.001f);
texspace_size[1] = std::max(texspace_size[1], 0.001f);
texspace_size[2] = std::max(texspace_size[2], 0.001f);
}
static_buf.texspace_size[0] = std::max(static_buf.texspace_size[0], 0.001f);
static_buf.texspace_size[1] = std::max(static_buf.texspace_size[1], 0.001f);
static_buf.texspace_size[2] = std::max(static_buf.texspace_size[2], 0.001f);
texspace_location = static_buf.texspace_location;
break;
}
case ID_ME:

View File

@ -144,7 +144,7 @@ struct AssetEntryReader {
*/
DictionaryValue::Lookup lookup;
StringRefNull get_name_with_idcode() const
StringRef get_name_with_idcode() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_NAME)->as_string_value()->value();
}
@ -154,13 +154,13 @@ struct AssetEntryReader {
ID_Type get_idcode() const
{
const StringRefNull name_with_idcode = get_name_with_idcode();
return GS(name_with_idcode.c_str());
const StringRef name_with_idcode = get_name_with_idcode();
return GS(name_with_idcode.data());
}
StringRef get_name() const
{
const StringRefNull name_with_idcode = get_name_with_idcode();
const StringRef name_with_idcode = get_name_with_idcode();
return name_with_idcode.substr(2);
}
@ -169,7 +169,7 @@ struct AssetEntryReader {
return lookup.contains(ATTRIBUTE_ENTRIES_DESCRIPTION);
}
StringRefNull get_description() const
StringRef get_description() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_DESCRIPTION)->as_string_value()->value();
}
@ -179,7 +179,7 @@ struct AssetEntryReader {
return lookup.contains(ATTRIBUTE_ENTRIES_AUTHOR);
}
StringRefNull get_author() const
StringRef get_author() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_AUTHOR)->as_string_value()->value();
}
@ -189,7 +189,7 @@ struct AssetEntryReader {
return lookup.contains(ATTRIBUTE_ENTRIES_COPYRIGHT);
}
StringRefNull get_copyright() const
StringRef get_copyright() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_COPYRIGHT)->as_string_value()->value();
}
@ -199,19 +199,19 @@ struct AssetEntryReader {
return lookup.contains(ATTRIBUTE_ENTRIES_LICENSE);
}
StringRefNull get_license() const
StringRef get_license() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_LICENSE)->as_string_value()->value();
}
StringRefNull get_catalog_name() const
StringRef get_catalog_name() const
{
return lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_NAME)->as_string_value()->value();
}
CatalogID get_catalog_id() const
{
const std::string &catalog_id =
const StringRefNull catalog_id =
lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_ID)->as_string_value()->value();
CatalogID catalog_uuid(catalog_id);
return catalog_uuid;
@ -400,36 +400,24 @@ static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry,
indexer_entry.datablock_info.free_asset_data = true;
if (entry.has_description()) {
const StringRefNull description = entry.get_description();
const size_t c_str_size = description.size() + 1;
char *description_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
memcpy(description_c_str, description.c_str(), c_str_size);
asset_data->description = description_c_str;
const StringRef description = entry.get_description();
asset_data->description = BLI_strdupn(description.data(), description.size());
}
if (entry.has_author()) {
const StringRefNull author = entry.get_author();
const size_t c_str_size = author.size() + 1;
char *author_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
memcpy(author_c_str, author.c_str(), c_str_size);
asset_data->author = author_c_str;
const StringRef author = entry.get_author();
asset_data->author = BLI_strdupn(author.data(), author.size());
}
if (entry.has_copyright()) {
const StringRefNull copyright = entry.get_copyright();
const size_t c_str_size = copyright.size() + 1;
char *copyright_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
memcpy(copyright_c_str, copyright.c_str(), c_str_size);
asset_data->copyright = copyright_c_str;
const StringRef copyright = entry.get_copyright();
asset_data->copyright = BLI_strdupn(copyright.data(), copyright.size());
}
if (entry.has_license()) {
const StringRefNull license = entry.get_license();
const size_t c_str_size = license.size() + 1;
char *license_c_str = static_cast<char *>(MEM_mallocN(c_str_size, __func__));
memcpy(license_c_str, license.c_str(), c_str_size);
asset_data->license = license_c_str;
const StringRef license = entry.get_license();
asset_data->license = BLI_strdupn(license.data(), license.size());
}
const StringRefNull catalog_name = entry.get_catalog_name();
STRNCPY(asset_data->catalog_simple_name, catalog_name.c_str());
const StringRef catalog_name = entry.get_catalog_name();
catalog_name.copy(asset_data->catalog_simple_name);
asset_data->catalog_id = entry.get_catalog_id();

View File

@ -110,7 +110,7 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
dst_instances->resize(offsets.total_size());
MutableSpan<float4x4> all_transforms = dst_instances->transforms();
MutableSpan<int> all_handles = dst_instances->reference_handles();
MutableSpan<int> all_handles = dst_instances->reference_handles_for_write();
for (const int i : src_components.index_range()) {
const auto &src_component = static_cast<const bke::InstancesComponent &>(*src_components[i]);
@ -131,7 +131,7 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
result.replace_instances(dst_instances.release());
auto &dst_component = result.get_component_for_write<bke::InstancesComponent>();
join_attributes(src_components, dst_component, {"position"});
join_attributes(src_components, dst_component, {"position", ".reference_index"});
}
static void join_volumes(const Span<const GeometryComponent *> /*src_components*/,

View File

@ -253,7 +253,7 @@ void debug_randomize_instance_order(bke::Instances *instances)
new_transforms[new_i] = old_transforms[old_i];
}
instances->reference_handles().copy_from(new_reference_handles);
instances->reference_handles_for_write().copy_from(new_reference_handles);
instances->transforms().copy_from(new_transforms);
}

View File

@ -210,10 +210,6 @@ static void reorder_instaces_exec(const bke::Instances &src_instances,
old_by_new_map,
dst_instances.attributes_for_write());
const Span<int> old_reference_handles = src_instances.reference_handles();
MutableSpan<int> new_reference_handles = dst_instances.reference_handles();
array_utils::gather(old_reference_handles, old_by_new_map, new_reference_handles);
const Span<float4x4> old_transforms = src_instances.transforms();
MutableSpan<float4x4> new_transforms = dst_instances.transforms();
array_utils::gather(old_transforms, old_by_new_map, new_transforms);

View File

@ -956,13 +956,13 @@ static void duplicate_instances(GeometrySet &geometry_set,
const int new_handle = dst_instances->add_reference(reference);
const float4x4 transform = src_instances.transforms()[i_selection];
dst_instances->transforms().slice(range).fill(transform);
dst_instances->reference_handles().slice(range).fill(new_handle);
dst_instances->reference_handles_for_write().slice(range).fill(new_handle);
}
bke::gather_attributes_to_groups(src_instances.attributes(),
AttrDomain::Instance,
propagation_info,
{"id"},
{"id", ".reference_index"},
duplicates,
selection,
dst_instances->attributes_for_write());

View File

@ -85,7 +85,8 @@ static void add_instances_from_component(
const int select_len = selection.index_range().size();
dst_component.resize(start_len + select_len);
MutableSpan<int> dst_handles = dst_component.reference_handles().slice(start_len, select_len);
MutableSpan<int> dst_handles = dst_component.reference_handles_for_write().slice(start_len,
select_len);
MutableSpan<float4x4> dst_transforms = dst_component.transforms().slice(start_len, select_len);
const VArraySpan positions = *src_attributes.lookup<float3>("position");
@ -213,6 +214,7 @@ static void node_geo_exec(GeoNodeExecParams params)
propagation_info,
attributes_to_propagate);
attributes_to_propagate.remove("position");
attributes_to_propagate.remove(".reference_index");
for (const GeometryComponent::Type type : types) {
if (geometry_set.has(type)) {

View File

@ -264,8 +264,6 @@ static void split_instance_groups(const InstancesComponent &component,
}
array_utils::gather(src_instances.transforms(), mask, group_instances->transforms());
array_utils::gather(
src_instances.reference_handles(), mask, group_instances->reference_handles());
bke::gather_attributes(src_instances.attributes(),
AttrDomain::Instance,
propagation_info,
@ -343,7 +341,7 @@ static void node_geo_exec(GeoNodeExecParams params)
}
dst_instances->transforms().fill(float4x4::identity());
array_utils::fill_index_range(dst_instances->reference_handles());
array_utils::fill_index_range(dst_instances->reference_handles_for_write());
for (auto item : geometry_by_group_id.items()) {
std::unique_ptr<GeometrySet> &group_geometry = item.value;

View File

@ -319,7 +319,7 @@ static void add_instances_from_handles(bke::Instances &instances,
const TextLayout &layout)
{
instances.resize(layout.positions.size());
MutableSpan<int> handles = instances.reference_handles();
MutableSpan<int> handles = instances.reference_handles_for_write();
MutableSpan<float4x4> transforms = instances.transforms();
threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {

View File

@ -21,6 +21,7 @@
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
@ -580,69 +581,87 @@ static bool ntree_branch_count_and_tag_nodes(bNode *fromnode, bNode *tonode, voi
return true;
}
/* Create a copy of a branch starting from a given node.
* callback is executed once for every copied node.
* Returns input node copy. */
static bNode *ntree_shader_copy_branch(bNodeTree *ntree,
bNode *start_node,
bool (*node_filter)(const bNode *node),
void (*callback)(bNode *node, int user_data),
int user_data)
/* Create a copy of a branch starting from a given node. */
static void ntree_shader_copy_branch(bNodeTree *ntree,
bNode *start_node,
bool (*node_filter)(const bNode *node))
{
auto gather_branch_nodes = [](bNode *fromnode, bNode * /*tonode*/, void *userdata) {
blender::Set<bNode *> *set = static_cast<blender::Set<bNode *> *>(userdata);
set->add(fromnode);
return true;
};
blender::Set<bNode *> branch_nodes = {start_node};
blender::bke::nodeChainIterBackwards(ntree, start_node, gather_branch_nodes, &branch_nodes, 0);
/* Initialize `runtime->tmp_flag`. */
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
node->runtime->tmp_flag = -1;
}
/* Count and tag all nodes inside the displacement branch of the tree. */
start_node->runtime->tmp_flag = 0;
branchIterData iter_data;
iter_data.node_filter = node_filter;
iter_data.node_count = 1;
iter_data.node_count = 0;
blender::bke::nodeChainIterBackwards(
ntree, start_node, ntree_branch_count_and_tag_nodes, &iter_data, 1);
/* Make a full copy of the branch */
/* Copies of the non-filtered nodes on the branch. */
Array<bNode *> nodes_copy(iter_data.node_count);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->runtime->tmp_flag >= 0) {
int id = node->runtime->tmp_flag;
/* Avoid creating unique names in the new tree, since it is very slow. The names on the new
* nodes will be invalid. But identifiers must be created for the `bNodeTree::all_nodes()`
* vector, though they won't match the original. */
/* Avoid creating unique names in the new tree, since it is very slow.
* The names on the new nodes will be invalid. */
nodes_copy[id] = blender::bke::node_copy(
ntree, *node, LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_MAIN, false);
/* But identifiers must be created for the `bNodeTree::all_nodes()` vector,
* so they won't match the original. */
nodeUniqueID(ntree, nodes_copy[id]);
nodes_copy[id]->runtime->tmp_flag = -2; /* Copy */
nodes_copy[id]->runtime->original = node->runtime->original;
bNode *copy = nodes_copy[id];
copy->runtime->tmp_flag = -2; /* Copy */
copy->runtime->original = node->runtime->original;
/* Make sure to clear all sockets links as they are invalid. */
LISTBASE_FOREACH (bNodeSocket *, sock, &nodes_copy[id]->inputs) {
LISTBASE_FOREACH (bNodeSocket *, sock, &copy->inputs) {
sock->link = nullptr;
}
LISTBASE_FOREACH (bNodeSocket *, sock, &nodes_copy[id]->outputs) {
LISTBASE_FOREACH (bNodeSocket *, sock, &copy->outputs) {
sock->link = nullptr;
}
}
}
/* Recreate links between copied nodes AND incoming links to the copied nodes. */
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
if (link->tonode->runtime->tmp_flag >= 0) {
bool from_node_copied = link->fromnode->runtime->tmp_flag >= 0;
bNode *fromnode = from_node_copied ? nodes_copy[link->fromnode->runtime->tmp_flag] :
link->fromnode;
bNode *tonode = nodes_copy[link->tonode->runtime->tmp_flag];
bNodeSocket *fromsock = ntree_shader_node_find_output(fromnode, link->fromsock->identifier);
bNodeSocket *tosock = ntree_shader_node_find_input(tonode, link->tosock->identifier);
nodeAddLink(ntree, fromnode, fromsock, tonode, tosock);
/* Unlink the original nodes from this branch and link the copies. */
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
bool from_copy = link->fromnode->runtime->tmp_flag >= 0;
bool to_copy = link->tonode->runtime->tmp_flag >= 0;
if (from_copy && to_copy) {
bNode *from_node = nodes_copy[link->fromnode->runtime->tmp_flag];
bNode *to_node = nodes_copy[link->tonode->runtime->tmp_flag];
nodeAddLink(ntree,
from_node,
ntree_shader_node_find_output(from_node, link->fromsock->identifier),
to_node,
ntree_shader_node_find_input(to_node, link->tosock->identifier));
}
else if (to_copy) {
bNode *to_node = nodes_copy[link->tonode->runtime->tmp_flag];
nodeAddLink(ntree,
link->fromnode,
link->fromsock,
to_node,
ntree_shader_node_find_input(to_node, link->tosock->identifier));
}
else if (from_copy && branch_nodes.contains(link->tonode)) {
bNode *from_node = nodes_copy[link->fromnode->runtime->tmp_flag];
nodeAddLink(ntree,
from_node,
ntree_shader_node_find_output(from_node, link->fromsock->identifier),
link->tonode,
link->tosock);
nodeRemLink(ntree, link);
}
}
/* Per node callback. */
if (callback) {
for (int i = 0; i < iter_data.node_count; i++) {
callback(nodes_copy[i], user_data);
}
}
bNode *start_node_copy = nodes_copy[start_node->runtime->tmp_flag];
return start_node_copy;
}
/* Generate emission node to convert regular data to closure sockets.
@ -985,47 +1004,51 @@ static bool closure_node_filter(const bNode *node)
}
}
static bool shader_to_rgba_node_gather(bNode * /*fromnode*/, bNode *tonode, void *userdata)
{
Vector<bNode *> &shader_to_rgba_nodes = *(Vector<bNode *> *)userdata;
if (tonode->runtime->tmp_flag == -1 && tonode->type == SH_NODE_SHADERTORGB) {
tonode->runtime->tmp_flag = 0;
shader_to_rgba_nodes.append(tonode);
}
return true;
}
/* Shader to rgba needs their associated closure duplicated and the weight tree generated for. */
static void ntree_shader_shader_to_rgba_branch(bNodeTree *ntree, bNode *output_node)
{
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
node->runtime->tmp_flag = -1;
}
/* First gather the shader_to_rgba nodes linked to the output. This is separate to avoid
* conflicting usage of the `node->runtime->tmp_flag`. */
Vector<bNode *> shader_to_rgba_nodes;
blender::bke::nodeChainIterBackwards(
ntree, output_node, shader_to_rgba_node_gather, &shader_to_rgba_nodes, 0);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == SH_NODE_SHADERTORGB) {
shader_to_rgba_nodes.append(node);
}
}
for (bNode *shader_to_rgba : shader_to_rgba_nodes) {
bNodeSocket *closure_input = ntree_shader_node_input_get(shader_to_rgba, 0);
if (closure_input->link == nullptr) {
continue;
}
bNode *start_node = closure_input->link->fromnode;
bNode *start_node_copy = ntree_shader_copy_branch(
ntree, start_node, closure_node_filter, nullptr, 0);
/* Replace node copy link. This assumes that every node possibly connected to the closure input
* has only one output. */
bNodeSocket *closure_output = ntree_shader_node_output_get(start_node_copy, 0);
nodeRemLink(ntree, closure_input->link);
nodeAddLink(ntree, start_node_copy, closure_output, shader_to_rgba, closure_input);
ntree_shader_copy_branch(ntree, shader_to_rgba, closure_node_filter);
BKE_ntree_update_main_tree(G.main, ntree, nullptr);
ntree_shader_weight_tree_invert(ntree, shader_to_rgba);
}
}
static void iter_shader_to_rgba_depth_count(bNode *node,
int16_t &max_depth,
int16_t depth_level = 0)
{
if (node->type == SH_NODE_SHADERTORGB) {
depth_level++;
max_depth = std::max(max_depth, depth_level);
}
node->runtime->tmp_flag = std::max(node->runtime->tmp_flag, depth_level);
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
bNodeLink *link = sock->link;
if (link == nullptr) {
continue;
}
if ((link->flag & NODE_LINK_VALID) == 0) {
/* Skip links marked as cyclic. */
continue;
}
iter_shader_to_rgba_depth_count(link->fromnode, max_depth, depth_level);
}
}
static void shader_node_disconnect_input(bNodeTree *ntree, bNode *node, int index)
{
bNodeLink *link = ntree_shader_node_input_get(node, index)->link;
@ -1180,10 +1203,24 @@ void ntreeGPUMaterialNodes(bNodeTree *localtree, GPUMaterial *mat)
}
exec = ntreeShaderBeginExecTree(localtree);
ntreeExecGPUNodes(exec, mat, output);
/* Execute nodes ordered by the number of ShaderToRGB nodes found in their path,
* so all closures can be properly evaluated. */
int16_t max_depth = 0;
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
node->runtime->tmp_flag = -1;
}
iter_shader_to_rgba_depth_count(output, max_depth);
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
if (node->type == SH_NODE_OUTPUT_AOV) {
ntreeExecGPUNodes(exec, mat, node);
iter_shader_to_rgba_depth_count(node, max_depth);
}
}
for (int depth = max_depth; depth >= 0; depth--) {
ntreeExecGPUNodes(exec, mat, output, &depth);
LISTBASE_FOREACH (bNode *, node, &localtree->nodes) {
if (node->type == SH_NODE_OUTPUT_AOV) {
ntreeExecGPUNodes(exec, mat, node, &depth);
}
}
}
ntreeShaderEndExecTree(exec);

View File

@ -305,7 +305,7 @@ bNode *nodeGetActivePaintCanvas(bNodeTree *ntree)
}
} // namespace blender::bke
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node)
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node, int *depth_level)
{
bNodeExec *nodeexec;
bNode *node;
@ -321,6 +321,10 @@ void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node
for (n = 0, nodeexec = exec->nodeexec; n < exec->totnodes; n++, nodeexec++) {
node = nodeexec->node;
if (depth_level && node->runtime->tmp_flag != *depth_level) {
continue;
}
do_it = false;
/* for groups, only execute outputs for edited group */
if (node->typeinfo->nclass == NODE_CLASS_OUTPUT) {
@ -334,6 +338,7 @@ void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node
}
if (do_it) {
BLI_assert(!depth_level || node->runtime->tmp_flag >= 0);
if (node->typeinfo->gpu_fn) {
node_get_stack(node, stack, nsin, nsout);
gpu_stack_from_data_list(gpuin, &node->inputs, nsin);

View File

@ -68,7 +68,14 @@ bNodeTreeExec *ntreeShaderBeginExecTree_internal(bNodeExecContext *context,
bNodeInstanceKey parent_key);
void ntreeShaderEndExecTree_internal(bNodeTreeExec *exec);
void ntreeExecGPUNodes(bNodeTreeExec *exec, GPUMaterial *mat, bNode *output_node);
/* If depth_level is not null, only nodes where `node->runtime->tmp_flag == depth_level` will be
* executed. This allows finer control over node execution order without modifying the tree
* topology. */
void ntreeExecGPUNodes(bNodeTreeExec *exec,
GPUMaterial *mat,
bNode *output_node,
int *depth_level = nullptr);
void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data);
bool node_socket_not_zero(const GPUNodeStack &socket);