Files
test2/source/blender/draw/engines/eevee/eevee_instance.cc
Miguel Pozo e6638d6e5e Refactor: GPU: GPUMaterial & GPUPass compilation
Cleanup and simplification of GPUMaterial and GPUPass compilation.
See #133674 for details/goals.

- Remove the `draw_manage_shader` thread.
  Deferred compilation is now handled by the gpu::ShaderCompiler
  through the batch compilation API.
  Batch management is handled by the `GPUPassCache`.
- Simplify `GPUMaterial` status tracking so it just queries the
  `GPUPass` status.
- Split the `GPUPass` and the `GPUCodegen` code.
- Replaced the (broken) `GPU_material_recalc_flag_get` with the new
  `GPU_pass_compilation_timestamp`.
- Add the `GPU_pass_cache_wait_for_all` and
  `GPU_shader_batch_wait_for_all`, and remove the busy waits from
   EEVEE.
- Remove many unused functions, properties, includes...

Pull Request: https://projects.blender.org/blender/blender/pulls/135637
2025-05-22 17:53:22 +02:00

915 lines
28 KiB
C++

/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* An instance contains all structures needed to do a complete render.
*/
#include "BKE_global.hh"
#include "BKE_object.hh"
#include "BLI_rect.h"
#include "BLI_time.h"
#include "BLT_translation.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_ID.h"
#include "DNA_lightprobe_types.h"
#include "DNA_modifier_types.h"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "GPU_context.hh"
#include "GPU_pass.hh"
#include "IMB_imbuf_types.hh"
#include "RE_pipeline.h"
#include "eevee_engine.h"
#include "eevee_instance.hh"
#include "DNA_particle_types.h"
#include "draw_common.hh"
#include "draw_context_private.hh"
#include "draw_view_data.hh"
namespace blender::eevee {
void *Instance::debug_scope_render_sample = nullptr;
void *Instance::debug_scope_irradiance_setup = nullptr;
void *Instance::debug_scope_irradiance_sample = nullptr;
/* -------------------------------------------------------------------- */
/** \name Initialization
*
* Initialization functions need to be called once at the start of a frame.
* Active camera, render extent and enabled render passes are immutable until next init.
* This takes care of resizing output buffers and view in case a parameter changed.
* IMPORTANT: xxx.init() functions are NOT meant to acquire and allocate DRW resources.
* Any attempt to do so will likely produce use after free situations.
* \{ */
void Instance::init()
{
this->draw_ctx = DRW_context_get();
Depsgraph *depsgraph = draw_ctx->depsgraph;
Scene *scene = draw_ctx->scene;
View3D *v3d = draw_ctx->v3d;
ARegion *region = draw_ctx->region;
RegionView3D *rv3d = draw_ctx->rv3d;
DefaultTextureList *dtxl = draw_ctx->viewport_texture_list_get();
int2 size = int2(GPU_texture_width(dtxl->color), GPU_texture_height(dtxl->color));
draw::View &default_view = draw::View::default_get();
Object *camera = nullptr;
/* Get render borders. */
rcti rect;
BLI_rcti_init(&rect, 0, size[0], 0, size[1]);
rcti visible_rect = rect;
if (v3d) {
if (rv3d && (rv3d->persp == RV3D_CAMOB)) {
camera = v3d->camera;
}
if (camera) {
rctf default_border;
BLI_rctf_init(&default_border, 0.0f, 1.0f, 0.0f, 1.0f);
bool is_default_border = BLI_rctf_compare(&scene->r.border, &default_border, 0.0f);
bool use_border = scene->r.mode & R_BORDER;
if (!is_default_border && use_border) {
rctf viewborder;
/* TODO(fclem) Might be better to get it from DRW. */
ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, false, &viewborder);
float viewborder_sizex = BLI_rctf_size_x(&viewborder);
float viewborder_sizey = BLI_rctf_size_y(&viewborder);
rect.xmin = floorf(viewborder.xmin + (scene->r.border.xmin * viewborder_sizex));
rect.ymin = floorf(viewborder.ymin + (scene->r.border.ymin * viewborder_sizey));
rect.xmax = floorf(viewborder.xmin + (scene->r.border.xmax * viewborder_sizex));
rect.ymax = floorf(viewborder.ymin + (scene->r.border.ymax * viewborder_sizey));
/* Clamp it to the viewport area. */
rect.xmin = max(rect.xmin, 0);
rect.ymin = max(rect.ymin, 0);
rect.xmax = min(rect.xmax, size.x);
rect.ymax = min(rect.ymax, size.y);
}
}
else if (v3d->flag2 & V3D_RENDER_BORDER) {
rect.xmin = v3d->render_border.xmin * size[0];
rect.ymin = v3d->render_border.ymin * size[1];
rect.xmax = v3d->render_border.xmax * size[0];
rect.ymax = v3d->render_border.ymax * size[1];
}
if (draw_ctx->is_viewport_image_render()) {
const float2 vp_size = draw_ctx->viewport_size_get();
visible_rect.xmax = vp_size[0];
visible_rect.ymax = vp_size[1];
visible_rect.xmin = visible_rect.ymin = 0;
}
else {
visible_rect = *ED_region_visible_rect(region);
}
}
init(size, &rect, &visible_rect, nullptr, depsgraph, camera, nullptr, &default_view, v3d, rv3d);
}
void Instance::init(const int2 &output_res,
const rcti *output_rect,
const rcti *visible_rect,
RenderEngine *render_,
Depsgraph *depsgraph_,
Object *camera_object_,
const RenderLayer *render_layer_,
View *drw_view_,
const View3D *v3d_,
const RegionView3D *rv3d_)
{
this->draw_ctx = DRW_context_get();
render = render_;
depsgraph = depsgraph_;
camera_orig_object = camera_object_;
render_layer = render_layer_;
drw_view = drw_view_;
v3d = v3d_;
rv3d = rv3d_;
manager = DRW_manager_get();
update_eval_members();
info_ = "";
if (is_viewport()) {
is_image_render = draw_ctx->is_image_render();
is_viewport_image_render = draw_ctx->is_viewport_image_render();
is_viewport_compositor_enabled = draw_ctx->is_viewport_compositor_enabled();
is_playback = draw_ctx->is_playback();
is_navigating = draw_ctx->is_navigating();
is_painting = draw_ctx->is_painting();
is_transforming = draw_ctx->is_transforming();
draw_overlays = v3d && (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0;
/* Note: Do not update the value here as we use it during sync for checking ID updates. */
if (depsgraph_last_update_ != DEG_get_update_count(depsgraph)) {
sampling.reset();
}
if (assign_if_different(debug_mode, (eDebugMode)G.debug_value)) {
sampling.reset();
}
if (output_res != film.display_extent_get()) {
sampling.reset();
}
if (output_rect) {
int2 offset = int2(output_rect->xmin, output_rect->ymin);
int2 extent = int2(BLI_rcti_size_x(output_rect), BLI_rcti_size_y(output_rect));
if (offset != film.get_data().offset || extent != film.get_data().extent) {
sampling.reset();
}
}
if (assign_if_different(overlays_enabled_, v3d && !(v3d->flag2 & V3D_HIDE_OVERLAYS))) {
sampling.reset();
}
if (is_painting) {
sampling.reset();
}
if (is_navigating && scene->eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT) {
sampling.reset();
}
}
else {
is_image_render = true;
}
shaders_are_ready_ = shaders.static_shaders_are_ready(is_image_render);
if (!shaders_are_ready_) {
skip_render_ = true;
return;
}
sampling.init(scene);
camera.init();
film.init(output_res, output_rect);
render_buffers.init();
ambient_occlusion.init();
velocity.init();
raytracing.init();
depth_of_field.init();
shadows.init();
motion_blur.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(visible_rect);
shaders_are_ready_ = shaders.static_shaders_are_ready(is_image_render) &&
shaders.request_specializations(
is_image_render,
render_buffers.data.shadow_id,
shadows.get_data().ray_count,
shadows.get_data().step_count,
DeferredLayer::do_split_direct_indirect_radiance(*this),
DeferredLayer::do_merge_direct_indirect_eval(*this));
skip_render_ = !shaders_are_ready_ || !film.is_valid_render_extent();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
{
this->depsgraph = depsgraph;
this->manager = manager;
camera_orig_object = nullptr;
render = nullptr;
render_layer = nullptr;
drw_view = nullptr;
v3d = nullptr;
rv3d = nullptr;
update_eval_members();
is_light_bake = true;
debug_mode = (eDebugMode)G.debug_value;
info_ = "";
shaders.static_shaders_are_ready(true);
sampling.init(scene);
camera.init();
/* Film isn't used but init to avoid side effects in other module. */
rcti empty_rect{0, 0, 0, 0};
film.init(int2(1), &empty_rect);
render_buffers.init();
velocity.init();
depth_of_field.init();
shadows.init();
main_view.init();
light_probes.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
sphere_probes.init();
volume_probes.init();
volume.init();
lookdev.init(&empty_rect);
shaders.request_specializations(true,
render_buffers.data.shadow_id,
shadows.get_data().ray_count,
shadows.get_data().step_count,
DeferredLayer::do_split_direct_indirect_radiance(*this),
DeferredLayer::do_merge_direct_indirect_eval(*this));
}
void Instance::set_time(float time)
{
BLI_assert(render);
DRW_render_set_time(render, depsgraph, floorf(time), fractf(time));
update_eval_members();
}
void Instance::update_eval_members()
{
scene = DEG_get_evaluated_scene(depsgraph);
view_layer = DEG_get_evaluated_view_layer(depsgraph);
camera_eval_object = (camera_orig_object) ? DEG_get_evaluated(depsgraph, camera_orig_object) :
nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sync
*
* Sync will gather data from the scene that can change over a time step (i.e: motion steps).
* IMPORTANT: xxx.sync() functions area responsible for creating DRW resources as
* well as querying temp texture pool. All DRWPasses should be ready by the end end_sync().
* \{ */
void Instance::begin_sync()
{
if (skip_render_) {
return;
}
/* Needs to be first for sun light parameters. */
world.sync();
materials.begin_sync();
velocity.begin_sync(); /* NOTE: Also syncs camera. */
lights.begin_sync();
shadows.begin_sync();
volume.begin_sync();
pipelines.begin_sync();
cryptomatte.begin_sync();
sphere_probes.begin_sync();
light_probes.begin_sync();
depth_of_field.sync();
raytracing.sync();
motion_blur.sync();
hiz_buffer.sync();
main_view.sync();
film.sync();
ambient_occlusion.sync();
volume_probes.sync();
lookdev.sync();
use_surfaces = (view_layer->layflag & SCE_LAY_SOLID) != 0;
use_curves = (view_layer->layflag & SCE_LAY_STRAND) != 0;
use_volumes = (view_layer->layflag & SCE_LAY_VOLUMES) != 0;
if (is_light_bake) {
/* Do not use render layer visibility during bake.
* NOTE: This is arbitrary and could be changed if needed. */
use_surfaces = use_curves = use_volumes = true;
}
if (is_viewport() && velocity.camera_has_motion()) {
sampling.reset();
}
}
void Instance::object_sync(ObjectRef &ob_ref, Manager & /*manager*/)
{
if (skip_render_) {
return;
}
Object *ob = ob_ref.object;
const bool is_renderable_type = ELEM(ob->type,
OB_CURVES,
OB_GREASE_PENCIL,
OB_MESH,
OB_POINTCLOUD,
OB_VOLUME,
OB_LAMP,
OB_LIGHTPROBE);
const int ob_visibility = DRW_object_visibility_in_active_context(ob);
const bool partsys_is_visible = (ob_visibility & OB_VISIBLE_PARTICLES) != 0 &&
(ob->type == OB_MESH);
const bool object_is_visible = DRW_object_is_renderable(ob) &&
(ob_visibility & OB_VISIBLE_SELF) != 0;
if (!is_renderable_type || (!partsys_is_visible && !object_is_visible)) {
return;
}
ObjectHandle &ob_handle = sync.sync_object(ob_ref);
if (partsys_is_visible && ob != draw_ctx->object_edit) {
auto sync_hair =
[&](ObjectHandle hair_handle, ModifierData &md, ParticleSystem &particle_sys) {
ResourceHandle _res_handle = manager->resource_handle_for_psys(ob_ref,
ob->object_to_world());
sync.sync_curves(ob, hair_handle, ob_ref, _res_handle, &md, &particle_sys);
};
foreach_hair_particle_handle(ob, ob_handle, sync_hair);
}
if (object_is_visible) {
switch (ob->type) {
case OB_LAMP:
lights.sync_light(ob, ob_handle);
break;
case OB_MESH:
if (!sync.sync_sculpt(ob, ob_handle, ob_ref)) {
sync.sync_mesh(ob, ob_handle, ob_ref);
}
break;
case OB_POINTCLOUD:
sync.sync_pointcloud(ob, ob_handle, ob_ref);
break;
case OB_VOLUME:
sync.sync_volume(ob, ob_handle, ob_ref);
break;
case OB_CURVES:
sync.sync_curves(ob, ob_handle, ob_ref);
break;
case OB_LIGHTPROBE:
light_probes.sync_probe(ob, ob_handle);
break;
default:
break;
}
}
}
void Instance::end_sync()
{
if (skip_render_) {
return;
}
velocity.end_sync();
volume.end_sync(); /* Needs to be before shadows. */
shadows.end_sync(); /* Needs to be before lights. */
lights.end_sync();
sampling.end_sync();
subsurface.end_sync();
film.end_sync();
cryptomatte.end_sync();
pipelines.end_sync();
light_probes.end_sync();
sphere_probes.end_sync();
planar_probes.end_sync();
uniform_data.push_update();
depsgraph_last_update_ = DEG_get_update_count(depsgraph);
}
void Instance::render_sync()
{
manager->begin_sync();
begin_sync();
DRW_render_object_iter(
render, depsgraph, [this](blender::draw::ObjectRef &ob_ref, RenderEngine *, Depsgraph *) {
this->object_sync(ob_ref, *this->manager);
});
velocity.geometry_steps_fill();
end_sync();
manager->end_sync();
}
bool Instance::needs_lightprobe_sphere_passes() const
{
return sphere_probes.update_probes_this_sample_;
}
bool Instance::do_lightprobe_sphere_sync() const
{
return (materials.queued_shaders_count == 0) && needs_lightprobe_sphere_passes();
}
bool Instance::needs_planar_probe_passes() const
{
return planar_probes.update_probes_;
}
bool Instance::do_planar_probe_sync() const
{
return (materials.queued_shaders_count == 0) && needs_planar_probe_passes();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rendering
* \{ */
void Instance::render_sample()
{
if (sampling.finished_viewport()) {
DRW_submission_start();
film.display();
lookdev.display();
DRW_submission_end();
return;
}
/* Motion blur may need to do re-sync after a certain number of sample. */
if (!is_viewport() && sampling.do_render_sync()) {
render_sync();
while (materials.queued_shaders_count > 0) {
GPU_pass_cache_wait_for_all();
/** WORKAROUND: Re-sync now that all shaders are compiled. */
/* This may need to happen more than once, since actual materials may require more passes
* (eg. volume ones) than the fallback material used for queued passes. */
/* TODO(@pragma37): There seems to be an issue where multiple `step_object_sync` calls on the
* same step can cause mismatching `has_motion` values between sync. */
render_sync();
}
}
DebugScope debug_scope(debug_scope_render_sample, "EEVEE.render_sample");
{
/* Critical section. Potential GPUShader concurrent usage. */
DRW_submission_start();
sampling.step();
capture_view.render_world();
capture_view.render_probes();
main_view.render();
lookdev_view.render();
DRW_submission_end();
}
motion_blur.step();
}
void Instance::render_read_result(RenderLayer *render_layer, const char *view_name)
{
eViewLayerEEVEEPassType pass_bits = film.enabled_passes_get();
for (auto i : IndexRange(EEVEE_RENDER_PASS_MAX_BIT + 1)) {
eViewLayerEEVEEPassType pass_type = eViewLayerEEVEEPassType(pass_bits & (1 << i));
if (pass_type == 0) {
continue;
}
Vector<std::string> pass_names = Film::pass_to_render_pass_names(pass_type, view_layer);
for (int64_t pass_offset : IndexRange(pass_names.size())) {
RenderPass *rp = RE_pass_find_by_name(
render_layer, pass_names[pass_offset].c_str(), view_name);
if (!rp) {
continue;
}
float *result = film.read_pass(pass_type, pass_offset);
if (result) {
BLI_mutex_lock(&render->update_render_passes_mutex);
/* WORKAROUND: We use texture read to avoid using a frame-buffer to get the render result.
* However, on some implementation, we need a buffer with a few extra bytes for the read to
* happen correctly (see #GLTexture::read()). So we need a custom memory allocation. */
/* Avoid `memcpy()`, replace the pointer directly. */
RE_pass_set_buffer_data(rp, result);
BLI_mutex_unlock(&render->update_render_passes_mutex);
}
}
}
/* AOVs. */
LISTBASE_FOREACH (ViewLayerAOV *, aov, &view_layer->aovs) {
if ((aov->flag & AOV_CONFLICT) != 0) {
continue;
}
RenderPass *rp = RE_pass_find_by_name(render_layer, aov->name, view_name);
if (!rp) {
continue;
}
float *result = film.read_aov(aov);
if (result) {
BLI_mutex_lock(&render->update_render_passes_mutex);
/* WORKAROUND: We use texture read to avoid using a frame-buffer to get the render result.
* However, on some implementation, we need a buffer with a few extra bytes for the read to
* happen correctly (see #GLTexture::read()). So we need a custom memory allocation. */
/* Avoid #memcpy(), replace the pointer directly. */
RE_pass_set_buffer_data(rp, result);
BLI_mutex_unlock(&render->update_render_passes_mutex);
}
}
/* The vector pass is initialized to weird values. Set it to neutral value if not rendered. */
if ((pass_bits & EEVEE_RENDER_PASS_VECTOR) == 0) {
for (const std::string &vector_pass_name :
Film::pass_to_render_pass_names(EEVEE_RENDER_PASS_VECTOR, view_layer))
{
RenderPass *vector_rp = RE_pass_find_by_name(
render_layer, vector_pass_name.c_str(), view_name);
if (vector_rp) {
memset(vector_rp->ibuf->float_buffer.data,
0,
sizeof(float) * 4 * vector_rp->rectx * vector_rp->recty);
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Interface
* \{ */
void Instance::render_frame(RenderEngine *engine, RenderLayer *render_layer, const char *view_name)
{
if (skip_render_) {
if (!info_.empty()) {
RE_engine_set_error_message(engine, info_.c_str());
info_ = "";
}
return;
}
/* TODO: Break on RE_engine_test_break(engine) */
while (!sampling.finished()) {
this->render_sample();
if ((sampling.sample_index() == 1) || ((sampling.sample_index() % 25) == 0) ||
sampling.finished())
{
/* TODO: Use `fmt`. */
std::string re_info = "Rendering " + std::to_string(sampling.sample_index()) + " / " +
std::to_string(sampling.sample_count()) + " samples";
RE_engine_update_stats(engine, nullptr, re_info.c_str());
}
/* Perform render step between samples to allow
* flushing of freed GPUBackend resources. */
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
GPU_flush();
}
GPU_render_step();
#if 0
/* TODO(fclem) print progression. */
RE_engine_update_progress(engine, float(sampling.sample_index()) / float(sampling.sample_count()));
/* TODO(fclem): Does not currently work. But would be better to just display to 2D view like
* cycles does. */
if (G.background == false && first_read) {
/* Allow to preview the first sample. */
/* TODO(fclem): Might want to not do this during animation render to avoid too much stall. */
this->render_read_result(render_layer, view_name);
first_read = false;
DRW_render_context_disable(render->re);
/* Allow the 2D viewport to grab the ticket mutex to display the render. */
DRW_render_context_enable(render->re);
}
#endif
}
this->film.cryptomatte_sort();
this->render_read_result(render_layer, view_name);
if (!info_.empty()) {
RE_engine_set_error_message(
engine, RPT_("Errors during render. See the System Console for more info."));
printf("%s", info_.c_str());
info_ = "";
}
}
void Instance::draw_viewport()
{
if (skip_render_) {
DefaultFramebufferList *dfbl = draw_ctx->viewport_framebuffer_list_get();
GPU_framebuffer_clear_color_depth(dfbl->default_fb, float4(0.0f), 1.0f);
if (!shaders_are_ready_) {
info_append_i18n("Compiling EEVEE engine shaders");
DRW_viewport_request_redraw();
}
return;
}
render_sample();
velocity.step_swap();
if (is_viewport_compositor_enabled) {
this->film.write_viewport_compositor_passes();
}
/* Do not request redraw during viewport animation to lock the frame-rate to the animation
* playback rate. This is in order to preserve motion blur aspect and also to avoid TAA reset
* that can show flickering. */
if (!sampling.finished_viewport() && !is_playback) {
DRW_viewport_request_redraw();
}
if (materials.queued_shaders_count > 0) {
info_append_i18n("Compiling shaders ({} remaining)", materials.queued_shaders_count);
if (!GPU_use_parallel_compilation() &&
GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_ANY, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL))
{
info_append_i18n(
"Increasing Preferences > System > Max Shader Compilation Subprocesses may improve "
"compilation time.");
}
DRW_viewport_request_redraw();
}
else if (materials.queued_optimize_shaders_count > 0) {
info_append_i18n("Optimizing shaders ({} remaining)", materials.queued_optimize_shaders_count);
}
}
void Instance::draw_viewport_image_render()
{
if (skip_render_) {
return;
}
do {
/* Render at least once to blit the finished image. */
this->render_sample();
} while (!sampling.finished_viewport());
velocity.step_swap();
if (is_viewport_compositor_enabled) {
this->film.write_viewport_compositor_passes();
}
}
void Instance::draw(Manager & /*manager*/)
{
if (is_viewport_image_render) {
draw_viewport_image_render();
}
else {
draw_viewport();
}
STRNCPY(info, info_get());
DefaultFramebufferList *dfbl = draw_ctx->viewport_framebuffer_list_get();
GPU_framebuffer_viewport_reset(dfbl->default_fb);
}
void Instance::store_metadata(RenderResult *render_result)
{
if (skip_render_) {
return;
}
cryptomatte.store_metadata(render_result);
}
void Instance::update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view_layer)
{
RE_engine_register_pass(engine, scene, view_layer, RE_PASSNAME_COMBINED, 4, "RGBA", SOCK_RGBA);
#define CHECK_PASS_LEGACY(name, type, channels, chanid) \
if (view_layer->passflag & (SCE_PASS_##name)) { \
RE_engine_register_pass( \
engine, scene, view_layer, RE_PASSNAME_##name, channels, chanid, type); \
} \
((void)0)
#define CHECK_PASS_EEVEE(name, type, channels, chanid) \
if (view_layer->eevee.render_passes & (EEVEE_RENDER_PASS_##name)) { \
RE_engine_register_pass( \
engine, scene, view_layer, RE_PASSNAME_##name, channels, chanid, type); \
} \
((void)0)
CHECK_PASS_LEGACY(Z, SOCK_FLOAT, 1, "Z");
CHECK_PASS_LEGACY(MIST, SOCK_FLOAT, 1, "Z");
CHECK_PASS_LEGACY(NORMAL, SOCK_VECTOR, 3, "XYZ");
CHECK_PASS_LEGACY(POSITION, SOCK_VECTOR, 3, "XYZ");
CHECK_PASS_LEGACY(VECTOR, SOCK_VECTOR, 4, "XYZW");
CHECK_PASS_LEGACY(DIFFUSE_DIRECT, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(DIFFUSE_COLOR, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(GLOSSY_DIRECT, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(GLOSSY_COLOR, SOCK_RGBA, 3, "RGB");
CHECK_PASS_EEVEE(VOLUME_LIGHT, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(EMIT, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(ENVIRONMENT, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(SHADOW, SOCK_RGBA, 3, "RGB");
CHECK_PASS_LEGACY(AO, SOCK_RGBA, 3, "RGB");
CHECK_PASS_EEVEE(TRANSPARENT, SOCK_RGBA, 4, "RGBA");
LISTBASE_FOREACH (ViewLayerAOV *, aov, &view_layer->aovs) {
if ((aov->flag & AOV_CONFLICT) != 0) {
continue;
}
switch (aov->type) {
case AOV_TYPE_COLOR:
RE_engine_register_pass(engine, scene, view_layer, aov->name, 4, "RGBA", SOCK_RGBA);
break;
case AOV_TYPE_VALUE:
RE_engine_register_pass(engine, scene, view_layer, aov->name, 1, "X", SOCK_FLOAT);
break;
default:
break;
}
}
/* NOTE: Name channels lowercase `rgba` so that compression rules check in OpenEXR DWA code uses
* lossless compression. Reportedly this naming is the only one which works good from the
* interoperability point of view. Using `xyzw` naming is not portable. */
auto register_cryptomatte_passes = [&](eViewLayerCryptomatteFlags cryptomatte_layer,
eViewLayerEEVEEPassType eevee_pass) {
if (view_layer->cryptomatte_flag & cryptomatte_layer) {
for (const std::string &pass_name : Film::pass_to_render_pass_names(eevee_pass, view_layer))
{
RE_engine_register_pass(
engine, scene, view_layer, pass_name.c_str(), 4, "rgba", SOCK_RGBA);
}
}
};
register_cryptomatte_passes(VIEW_LAYER_CRYPTOMATTE_OBJECT, EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT);
register_cryptomatte_passes(VIEW_LAYER_CRYPTOMATTE_ASSET, EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET);
register_cryptomatte_passes(VIEW_LAYER_CRYPTOMATTE_MATERIAL,
EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL);
}
void Instance::light_bake_irradiance(
Object &probe,
FunctionRef<void()> context_enable,
FunctionRef<void()> context_disable,
FunctionRef<bool()> stop,
FunctionRef<void(LightProbeGridCacheFrame *, float progress)> result_update)
{
BLI_assert(is_baking());
DRWContext draw_ctx(DRWContext::CUSTOM, depsgraph);
this->draw_ctx = &draw_ctx;
auto custom_pipeline_wrapper = [&](FunctionRef<void()> callback) {
context_enable();
DRW_custom_pipeline_begin(draw_ctx, depsgraph);
callback();
DRW_custom_pipeline_end(draw_ctx);
context_disable();
};
auto context_wrapper = [&](FunctionRef<void()> callback) {
context_enable();
callback();
context_disable();
};
volume_probes.bake.init(probe);
custom_pipeline_wrapper([&]() {
this->render_sync();
while (materials.queued_shaders_count > 0) {
GPU_pass_cache_wait_for_all();
/** WORKAROUND: Re-sync now that all shaders are compiled. */
/* This may need to happen more than once, since actual materials may require more passes
* (eg. volume ones) than the fallback material used for queued passes. */
/* TODO(@pragma37): There seems to be an issue where multiple `step_object_sync` calls on the
* same step can cause mismatching `has_motion` values between sync. */
render_sync();
}
/* Sampling module needs to be initialized to computing lighting. */
sampling.init(probe);
sampling.step();
{
/* Critical section. Potential GPUShader concurrent usage. */
DRW_submission_start();
DebugScope debug_scope(debug_scope_irradiance_setup, "EEVEE.irradiance_setup");
capture_view.render_world();
volume_probes.bake.surfels_create(probe);
if (volume_probes.bake.should_break()) {
DRW_submission_end();
return;
}
volume_probes.bake.surfels_lights_eval();
volume_probes.bake.clusters_build();
volume_probes.bake.irradiance_offset();
DRW_submission_end();
}
});
if (volume_probes.bake.should_break()) {
return;
}
sampling.init(probe);
while (!sampling.finished()) {
context_wrapper([&]() {
DebugScope debug_scope(debug_scope_irradiance_sample, "EEVEE.irradiance_sample");
/* Batch ray cast by pack of 16. Avoids too much overhead of the update function & context
* switch. */
/* TODO(fclem): Could make the number of iteration depend on the computation time. */
for (int i = 0; i < 16 && !sampling.finished(); i++) {
sampling.step();
{
/* Critical section. Potential GPUShader concurrent usage. */
DRW_submission_start();
volume_probes.bake.raylists_build();
volume_probes.bake.propagate_light();
volume_probes.bake.irradiance_capture();
DRW_submission_end();
}
}
LightProbeGridCacheFrame *cache_frame;
if (sampling.finished()) {
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 = volume_probes.bake.read_result_unpacked();
}
float progress = sampling.sample_index() / float(sampling.sample_count());
result_update(cache_frame, progress);
});
if (stop()) {
return;
}
}
}
/** \} */
} // namespace blender::eevee