Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/draw')
-rw-r--r--source/blender/draw/CMakeLists.txt6
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_defines.hh4
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_film.cc25
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_film.hh4
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_instance.cc68
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_instance.hh5
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_motion_blur.cc262
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_motion_blur.hh132
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc51
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh3
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_sampling.cc2
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_shader.cc8
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_shader.hh5
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_shader_shared.hh38
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_velocity.cc30
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_velocity.hh7
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_view.cc29
-rw-r--r--source/blender/draw/engines/eevee_next/eevee_view.hh2
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl2
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl116
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl103
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl221
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_lib.glsl48
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl104
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl4
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl38
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh3
-rw-r--r--source/blender/draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh44
-rw-r--r--source/blender/draw/intern/DRW_gpu_wrapper.hh43
-rw-r--r--source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc4
-rw-r--r--source/blender/draw/intern/draw_instance_data.c1
-rw-r--r--source/blender/draw/intern/draw_shader_shared.h2
-rw-r--r--source/blender/draw/intern/draw_texture_pool.h1
-rw-r--r--source/blender/draw/intern/shaders/common_math_geom_lib.glsl24
34 files changed, 1297 insertions, 142 deletions
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 9744f2e3cee..9d3b392d7b3 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -138,6 +138,7 @@ set(SRC
engines/eevee_next/eevee_film.cc
engines/eevee_next/eevee_instance.cc
engines/eevee_next/eevee_material.cc
+ engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
@@ -367,7 +368,12 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl
engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl
engines/eevee_next/shaders/eevee_geom_world_vert.glsl
+ engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl
+ engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl
+ engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
+ engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
+ engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl
engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh
index 1e7979b594e..cb02689f34a 100644
--- a/source/blender/draw/engines/eevee_next/eevee_defines.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh
@@ -45,3 +45,7 @@
#define LIGHTPROBE_FILTER_VIS_GROUP_SIZE 16
#define FILM_GROUP_SIZE 16
+
+#define MOTION_BLUR_GROUP_SIZE 32
+
+#define MOTION_BLUR_DILATE_GROUP_SIZE 512
diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc
index 49f43265aa8..60e5f95d803 100644
--- a/source/blender/draw/engines/eevee_next/eevee_film.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_film.cc
@@ -183,20 +183,17 @@ void Film::init(const int2 &extent, const rcti *output_rect)
* Using the render pass ensure we store the center depth. */
render_passes |= EEVEE_RENDER_PASS_Z;
}
- /* TEST */
- render_passes |= EEVEE_RENDER_PASS_VECTOR;
}
else {
/* Render Case. */
render_passes = eViewLayerEEVEEPassType(inst_.view_layer->eevee.render_passes);
- render_passes |= EEVEE_RENDER_PASS_COMBINED;
-
#define ENABLE_FROM_LEGACY(name_legacy, name_eevee) \
SET_FLAG_FROM_TEST(render_passes, \
(inst_.view_layer->passflag & SCE_PASS_##name_legacy) != 0, \
EEVEE_RENDER_PASS_##name_eevee);
+ ENABLE_FROM_LEGACY(COMBINED, COMBINED)
ENABLE_FROM_LEGACY(Z, Z)
ENABLE_FROM_LEGACY(MIST, MIST)
ENABLE_FROM_LEGACY(NORMAL, NORMAL)
@@ -209,6 +206,7 @@ void Film::init(const int2 &extent, const rcti *output_rect)
ENABLE_FROM_LEGACY(DIFFUSE_DIRECT, DIFFUSE_LIGHT)
ENABLE_FROM_LEGACY(GLOSSY_DIRECT, SPECULAR_LIGHT)
ENABLE_FROM_LEGACY(ENVIRONMENT, ENVIRONMENT)
+ ENABLE_FROM_LEGACY(VECTOR, VECTOR)
#undef ENABLE_FROM_LEGACY
}
@@ -216,6 +214,11 @@ void Film::init(const int2 &extent, const rcti *output_rect)
/* Filter obsolete passes. */
render_passes &= ~(EEVEE_RENDER_PASS_UNUSED_8 | EEVEE_RENDER_PASS_BLOOM);
+ if (scene_eevee.flag & SCE_EEVEE_MOTION_BLUR_ENABLED) {
+ /* Disable motion vector pass if motion blur is enabled. */
+ render_passes &= ~EEVEE_RENDER_PASS_VECTOR;
+ }
+
/* TODO(@fclem): Can't we rely on depsgraph update notification? */
if (assign_if_different(enabled_passes_, render_passes)) {
sampling.reset();
@@ -383,7 +386,7 @@ void Film::sync()
DRW_shgroup_uniform_block_ref(grp, "camera_curr", &(*velocity.camera_steps[STEP_CURRENT]));
DRW_shgroup_uniform_block_ref(grp, "camera_next", &(*velocity.camera_steps[step_next]));
DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &rbuffers.depth_tx);
- DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &rbuffers.combined_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &combined_final_tx_);
DRW_shgroup_uniform_texture_ref(grp, "normal_tx", &rbuffers.normal_tx);
DRW_shgroup_uniform_texture_ref(grp, "vector_tx", &rbuffers.vector_tx);
DRW_shgroup_uniform_texture_ref(grp, "diffuse_light_tx", &rbuffers.diffuse_light_tx);
@@ -458,6 +461,10 @@ float2 Film::pixel_jitter_get() const
eViewLayerEEVEEPassType Film::enabled_passes_get() const
{
+ if (inst_.is_viewport() && data_.use_reprojection) {
+ /* Enable motion vector rendering but not the accumulation buffer. */
+ return enabled_passes_ | EEVEE_RENDER_PASS_VECTOR;
+ }
return enabled_passes_;
}
@@ -538,7 +545,7 @@ void Film::update_sample_table()
}
}
-void Film::accumulate(const DRWView *view)
+void Film::accumulate(const DRWView *view, GPUTexture *combined_final_tx)
{
if (inst_.is_viewport()) {
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
@@ -554,6 +561,8 @@ void Film::accumulate(const DRWView *view)
update_sample_table();
+ combined_final_tx_ = combined_final_tx;
+
/* Need to update the static references as there could have change from a previous swap. */
weight_src_tx_ = weight_tx_.current();
weight_dst_tx_ = weight_tx_.next();
@@ -580,12 +589,14 @@ void Film::display()
BLI_assert(inst_.is_viewport());
/* Acquire dummy render buffers for correct binding. They will not be used. */
- inst_.render_buffers.acquire(int2(1), (void *)this);
+ inst_.render_buffers.acquire(int2(1));
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
GPU_framebuffer_bind(dfbl->default_fb);
GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent));
+ combined_final_tx_ = inst_.render_buffers.combined_tx;
+
/* Need to update the static references as there could have change from a previous swap. */
weight_src_tx_ = weight_tx_.current();
weight_dst_tx_ = weight_tx_.next();
diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh
index 1165b9a4c12..d47e3dfa24b 100644
--- a/source/blender/draw/engines/eevee_next/eevee_film.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_film.hh
@@ -50,6 +50,8 @@ class Film {
/** Static reference as SwapChain does not actually move the objects when swapping. */
GPUTexture *combined_src_tx_ = nullptr;
GPUTexture *combined_dst_tx_ = nullptr;
+ /** Incomming combined buffer with post fx applied (motion blur + depth of field). */
+ GPUTexture *combined_final_tx_ = nullptr;
/** Weight buffers. Double buffered to allow updating it during accumulation. */
SwapChain<Texture, 2> weight_tx_;
/** Static reference as SwapChain does not actually move the objects when swapping. */
@@ -74,7 +76,7 @@ class Film {
void end_sync();
/** Accumulate the newly rendered sample contained in #RenderBuffers and blit to display. */
- void accumulate(const DRWView *view);
+ void accumulate(const DRWView *view, GPUTexture *combined_final_tx);
/** Blit to display. No rendered sample needed. */
void display();
diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc
index 9f8cf6dc6ba..70b5cb7d98f 100644
--- a/source/blender/draw/engines/eevee_next/eevee_instance.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc
@@ -60,6 +60,8 @@ void Instance::init(const int2 &output_res,
sampling.init(scene);
camera.init();
film.init(output_res, output_rect);
+ velocity.init();
+ motion_blur.init();
main_view.init();
}
@@ -92,15 +94,14 @@ void Instance::update_eval_members()
void Instance::begin_sync()
{
materials.begin_sync();
- velocity.begin_sync();
+ velocity.begin_sync(); /* NOTE: Also syncs camera. */
gpencil_engine_enabled = false;
- render_buffers.sync();
+ motion_blur.sync();
pipelines.sync();
main_view.sync();
world.sync();
- camera.sync();
film.sync();
}
@@ -212,22 +213,12 @@ void Instance::render_sample()
sampling.step();
main_view.render();
-}
-
-/** \} */
-/* -------------------------------------------------------------------- */
-/** \name Interface
- * \{ */
+ motion_blur.step();
+}
-void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
+void Instance::render_read_result(RenderLayer *render_layer, const char *view_name)
{
- while (!sampling.finished()) {
- this->render_sample();
- /* TODO(fclem) print progression. */
- }
-
- /* Read Results. */
eViewLayerEEVEEPassType pass_bits = film.enabled_passes_get();
for (auto i : IndexRange(EEVEE_RENDER_PASS_MAX_BIT)) {
eViewLayerEEVEEPassType pass_type = eViewLayerEEVEEPassType(pass_bits & (1 << i));
@@ -240,7 +231,6 @@ void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
if (rp) {
float *result = film.read_pass(pass_type);
if (result) {
- std::cout << "read " << pass_name << std::endl;
BLI_mutex_lock(&render->update_render_passes_mutex);
/* WORKAROUND: We use texture read to avoid using a framebuffer to get the render result.
* However, on some implementation, we need a buffer with a few extra bytes for the read to
@@ -252,6 +242,45 @@ void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
}
}
}
+
+ /* The vector pass is initialized to weird values. Set it to neutral value if not rendered. */
+ if ((pass_bits & EEVEE_RENDER_PASS_VECTOR) == 0) {
+ const char *vector_pass_name = Film::pass_to_render_pass_name(EEVEE_RENDER_PASS_VECTOR);
+ RenderPass *vector_rp = RE_pass_find_by_name(render_layer, vector_pass_name, view_name);
+ if (vector_rp) {
+ memset(vector_rp->rect, 0, sizeof(float) * 4 * vector_rp->rectx * vector_rp->recty);
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Interface
+ * \{ */
+
+void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
+{
+ while (!sampling.finished()) {
+ this->render_sample();
+
+ /* TODO(fclem) print progression. */
+#if 0
+ /* 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->render_read_result(render_layer, view_name);
}
void Instance::draw_viewport(DefaultFramebufferList *dfbl)
@@ -260,7 +289,10 @@ void Instance::draw_viewport(DefaultFramebufferList *dfbl)
render_sample();
velocity.step_swap();
- if (!sampling.finished_viewport()) {
+ /* Do not request redraw during viewport animation to lock the framerate 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() && !DRW_state_is_playback()) {
DRW_viewport_request_redraw();
}
diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.hh b/source/blender/draw/engines/eevee_next/eevee_instance.hh
index 1efda769648..d714111a3c6 100644
--- a/source/blender/draw/engines/eevee_next/eevee_instance.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_instance.hh
@@ -18,6 +18,7 @@
#include "eevee_camera.hh"
#include "eevee_film.hh"
#include "eevee_material.hh"
+#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
@@ -34,6 +35,7 @@ namespace blender::eevee {
*/
class Instance {
friend VelocityModule;
+ friend MotionBlurModule;
public:
ShaderModule &shaders;
@@ -41,6 +43,7 @@ class Instance {
MaterialModule materials;
PipelineModule pipelines;
VelocityModule velocity;
+ MotionBlurModule motion_blur;
Sampling sampling;
Camera camera;
Film film;
@@ -76,6 +79,7 @@ class Instance {
materials(*this),
pipelines(*this),
velocity(*this),
+ motion_blur(*this),
sampling(*this),
camera(*this),
film(*this),
@@ -138,6 +142,7 @@ class Instance {
RenderEngine *engine,
Depsgraph *depsgraph);
void render_sample();
+ void render_read_result(RenderLayer *render_layer, const char *view_name);
void mesh_sync(Object *ob, ObjectHandle &ob_handle);
diff --git a/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc b/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc
new file mode 100644
index 00000000000..660eb9f1e22
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ */
+
+// #include "BLI_map.hh"
+#include "DEG_depsgraph_query.h"
+
+#include "eevee_instance.hh"
+#include "eevee_motion_blur.hh"
+// #include "eevee_sampling.hh"
+// #include "eevee_shader_shared.hh"
+// #include "eevee_velocity.hh"
+
+namespace blender::eevee {
+
+/* -------------------------------------------------------------------- */
+/** \name MotionBlurModule
+ *
+ * \{ */
+
+void MotionBlurModule::init()
+{
+ const Scene *scene = inst_.scene;
+
+ enabled_ = (scene->eevee.flag & SCE_EEVEE_MOTION_BLUR_ENABLED) != 0;
+
+ if (!enabled_) {
+ motion_blur_fx_enabled_ = false;
+ return;
+ }
+
+ /* Take into account the steps needed for fx motion blur. */
+ int steps_count = max_ii(1, scene->eevee.motion_blur_steps) * 2 + 1;
+
+ time_steps_.resize(steps_count);
+
+ initial_frame_ = scene->r.cfra;
+ initial_subframe_ = scene->r.subframe;
+ frame_time_ = initial_frame_ + initial_subframe_;
+ shutter_position_ = scene->eevee.motion_blur_position;
+ shutter_time_ = scene->eevee.motion_blur_shutter;
+
+ data_.depth_scale = scene->eevee.motion_blur_depth_scale;
+ motion_blur_fx_enabled_ = true; /* TODO(fclem): UI option. */
+
+ /* Viewport stops here. We only do Post-FX motion blur. */
+ if (inst_.is_viewport()) {
+ enabled_ = false;
+ return;
+ }
+
+ /* Without this there is the possibility of the curve table not being allocated. */
+ BKE_curvemapping_changed((struct CurveMapping *)&scene->r.mblur_shutter_curve, false);
+
+ Vector<float> cdf(CM_TABLE);
+ Sampling::cdf_from_curvemapping(scene->r.mblur_shutter_curve, cdf);
+ Sampling::cdf_invert(cdf, time_steps_);
+
+ for (float &time : time_steps_) {
+ time = this->shutter_time_to_scene_time(time);
+ }
+
+ step_id_ = 1;
+
+ if (motion_blur_fx_enabled_) {
+ /* A bit weird but we have to sync the first 2 steps here because the step()
+ * function is only called after rendering a sample. */
+ inst_.velocity.step_sync(STEP_PREVIOUS, time_steps_[0]);
+ inst_.velocity.step_sync(STEP_NEXT, time_steps_[2]);
+ }
+ inst_.set_time(time_steps_[1]);
+}
+
+/* Runs after rendering a sample. */
+void MotionBlurModule::step()
+{
+ if (!enabled_) {
+ return;
+ }
+
+ if (inst_.sampling.finished()) {
+ /* Restore original frame number. This is because the render pipeline expects it. */
+ RE_engine_frame_set(inst_.render, initial_frame_, initial_subframe_);
+ }
+ else if (inst_.sampling.do_render_sync()) {
+ /* Time to change motion step. */
+ BLI_assert(time_steps_.size() > step_id_ + 2);
+ step_id_ += 2;
+
+ if (motion_blur_fx_enabled_) {
+ inst_.velocity.step_swap();
+ inst_.velocity.step_sync(eVelocityStep::STEP_NEXT, time_steps_[step_id_ + 1]);
+ }
+ inst_.set_time(time_steps_[step_id_]);
+ }
+}
+
+float MotionBlurModule::shutter_time_to_scene_time(float time)
+{
+ switch (shutter_position_) {
+ case SCE_EEVEE_MB_START:
+ /* No offset. */
+ break;
+ case SCE_EEVEE_MB_CENTER:
+ time -= 0.5f;
+ break;
+ case SCE_EEVEE_MB_END:
+ time -= 1.0;
+ break;
+ default:
+ BLI_assert(!"Invalid motion blur position enum!");
+ break;
+ }
+ time *= shutter_time_;
+ time += frame_time_;
+ return time;
+}
+
+void MotionBlurModule::sync()
+{
+ /* Disable motion blur in viewport when changing camera projection type.
+ * Avoids really high velocities. */
+ if (inst_.velocity.camera_changed_projection()) {
+ motion_blur_fx_enabled_ = false;
+ }
+
+ if (!motion_blur_fx_enabled_) {
+ return;
+ }
+
+ eGPUSamplerState no_filter = GPU_SAMPLER_DEFAULT;
+ RenderBuffers &render_buffers = inst_.render_buffers;
+
+ {
+ /* Create max velocity tiles. */
+ DRW_PASS_CREATE(tiles_flatten_ps_, DRW_STATE_NO_DRAW);
+ eShaderType shader = (inst_.is_viewport()) ? MOTION_BLUR_TILE_FLATTEN_VIEWPORT :
+ MOTION_BLUR_TILE_FLATTEN_RENDER;
+ GPUShader *sh = inst_.shaders.static_shader_get(shader);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, tiles_flatten_ps_);
+ inst_.velocity.bind_resources(grp);
+ DRW_shgroup_uniform_block(grp, "motion_blur_buf", data_);
+ DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &render_buffers.depth_tx);
+ DRW_shgroup_uniform_image_ref(grp, "velocity_img", &render_buffers.vector_tx);
+ DRW_shgroup_uniform_image_ref(grp, "out_tiles_img", &tiles_tx_);
+
+ DRW_shgroup_call_compute_ref(grp, dispatch_flatten_size_);
+ DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
+ }
+ {
+ /* Expand max velocity tiles by spreading them in their neighborhood. */
+ DRW_PASS_CREATE(tiles_dilate_ps_, DRW_STATE_NO_DRAW);
+ GPUShader *sh = inst_.shaders.static_shader_get(MOTION_BLUR_TILE_DILATE);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, tiles_dilate_ps_);
+ DRW_shgroup_storage_block(grp, "tile_indirection_buf", tile_indirection_buf_);
+ DRW_shgroup_uniform_image_ref(grp, "in_tiles_img", &tiles_tx_);
+
+ DRW_shgroup_call_compute_ref(grp, dispatch_dilate_size_);
+ DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE);
+ }
+ {
+ /* Do the motion blur gather algorithm. */
+ DRW_PASS_CREATE(gather_ps_, DRW_STATE_NO_DRAW);
+ GPUShader *sh = inst_.shaders.static_shader_get(MOTION_BLUR_GATHER);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, gather_ps_);
+ inst_.sampling.bind_resources(grp);
+ DRW_shgroup_uniform_block(grp, "motion_blur_buf", data_);
+ DRW_shgroup_storage_block(grp, "tile_indirection_buf", tile_indirection_buf_);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, no_filter);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "velocity_tx", &render_buffers.vector_tx, no_filter);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "in_color_tx", &input_color_tx_, no_filter);
+ DRW_shgroup_uniform_image_ref(grp, "in_tiles_img", &tiles_tx_);
+ DRW_shgroup_uniform_image_ref(grp, "out_color_img", &output_color_tx_);
+
+ DRW_shgroup_call_compute_ref(grp, dispatch_gather_size_);
+ DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH);
+ }
+}
+
+void MotionBlurModule::render(GPUTexture **input_tx, GPUTexture **output_tx)
+{
+ if (!motion_blur_fx_enabled_) {
+ return;
+ }
+
+ const Texture &depth_tx = inst_.render_buffers.depth_tx;
+
+ int2 extent = {depth_tx.width(), depth_tx.height()};
+ int2 tiles_extent = math::divide_ceil(extent, int2(MOTION_BLUR_TILE_SIZE));
+
+ if (inst_.is_viewport()) {
+ float frame_delta = fabsf(inst_.velocity.step_time_delta_get(STEP_PREVIOUS, STEP_CURRENT));
+ /* Avoid highly disturbing blurs, during navigation with high shutter time. */
+ if (frame_delta > 0.0f && !DRW_state_is_navigating()) {
+ /* Rescale motion blur intensity to be shutter time relative and avoid long streak when we
+ * have frame skipping. Always try to stick to what the render frame would look like. */
+ data_.motion_scale = float2(shutter_time_ / frame_delta);
+ }
+ else {
+ /* There is no time change. Motion only comes from viewport navigation and object transform.
+ * Apply motion blur as smoothing and only blur towards last frame. */
+ data_.motion_scale = float2(1.0f, 0.0f);
+
+ if (was_navigating_ != DRW_state_is_navigating()) {
+ /* Special case for navigation events that only last for one frame (for instance mouse
+ * scroll for zooming). For this case we have to wait for the next frame before enabling
+ * the navigation motion blur. */
+ was_navigating_ = DRW_state_is_navigating();
+ return;
+ }
+ }
+ was_navigating_ = DRW_state_is_navigating();
+
+ /* Change texture swizzling to avoid complexity in gather pass shader. */
+ GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgrg");
+ }
+ else {
+ data_.motion_scale = float2(1.0f);
+ }
+ /* Second motion vector is stored inverted. */
+ data_.motion_scale.y = -data_.motion_scale.y;
+ data_.target_size_inv = 1.0f / float2(extent);
+ data_.push_update();
+
+ input_color_tx_ = *input_tx;
+ output_color_tx_ = *output_tx;
+
+ dispatch_flatten_size_ = int3(tiles_extent, 1);
+ dispatch_dilate_size_ = int3(math::divide_ceil(tiles_extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
+ dispatch_gather_size_ = int3(math::divide_ceil(extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
+
+ DRW_stats_group_start("Motion Blur");
+
+ tiles_tx_.acquire(tiles_extent, GPU_RGBA16F);
+
+ GPU_storagebuf_clear_to_zero(tile_indirection_buf_);
+
+ DRW_draw_pass(tiles_flatten_ps_);
+ DRW_draw_pass(tiles_dilate_ps_);
+ DRW_draw_pass(gather_ps_);
+
+ tiles_tx_.release();
+
+ DRW_stats_group_end();
+
+ if (inst_.is_viewport()) {
+ /* Reset swizzle since this texture might be reused in other places. */
+ GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgba");
+ }
+
+ /* Swap buffers so that next effect has the right input. */
+ *input_tx = output_color_tx_;
+ *output_tx = input_color_tx_;
+}
+
+/** \} */
+
+} // namespace blender::eevee \ No newline at end of file
diff --git a/source/blender/draw/engines/eevee_next/eevee_motion_blur.hh b/source/blender/draw/engines/eevee_next/eevee_motion_blur.hh
new file mode 100644
index 00000000000..310e94a702b
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_motion_blur.hh
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * Motion blur is done by accumulating scene samples over shutter time.
+ * Since the number of step is discrete, quite low, and not per pixel randomized,
+ * we couple this with a post processing motion blur.
+ *
+ * The post-fx motion blur is done in two directions, from the previous step and to the next.
+ *
+ * For a scene with 3 motion steps, a flat shutter curve and shutter time of 2 frame
+ * centered on frame we have:
+ *
+ * |--------------------|--------------------|
+ * -1 0 1 Frames
+ *
+ * |-------------|-------------|-------------|
+ * 1 2 3 Motion steps
+ *
+ * |------|------|------|------|------|------|
+ * 0 1 2 4 5 6 7 Time Steps
+ *
+ * |-------------| One motion step blurs this range.
+ * -1 | +1 Objects and geometry steps are recorded here.
+ * 0 Scene is rendered here.
+ *
+ * Since motion step N and N+1 share one time step we reuse it to avoid an extra scene evaluation.
+ *
+ * Note that we have to evaluate -1 and +1 time steps before rendering so eval order is -1, +1, 0.
+ * This is because all GPUBatches from the DRWCache are being free when changing a frame.
+ *
+ * For viewport, we only have the current and previous step data to work with. So we center the
+ * blur on the current frame and extrapolate the motion.
+ *
+ * The Post-FX motion blur is based on:
+ * "A Fast and Stable Feature-Aware Motion Blur Filter"
+ * by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
+ */
+
+#pragma once
+
+#include "BLI_map.hh"
+#include "DEG_depsgraph_query.h"
+
+#include "eevee_sampling.hh"
+#include "eevee_shader_shared.hh"
+#include "eevee_velocity.hh"
+
+namespace blender::eevee {
+
+/* -------------------------------------------------------------------- */
+/** \name MotionBlur
+ *
+ * \{ */
+
+/**
+ * Manages time-steps evaluations and accumulation Motion blur.
+ * Also handles Post process motion blur.
+ */
+class MotionBlurModule {
+ private:
+ Instance &inst_;
+
+ /**
+ * Array containing all steps (in scene time) we need to evaluate (not render).
+ * Only odd steps are rendered. The even ones are evaluated for fx motion blur.
+ */
+ Vector<float> time_steps_;
+
+ /** Copy of input frame and sub-frame to restore after render. */
+ int initial_frame_;
+ float initial_subframe_;
+ /** Time of the frame we are rendering. */
+ float frame_time_;
+ /** Enum controlling when the shutter opens. See SceneEEVEE.motion_blur_position. */
+ int shutter_position_;
+ /** Time in scene frame the shutter is open. Controls the amount of blur. */
+ float shutter_time_;
+
+ /** True if motion blur is enabled as a module. */
+ bool enabled_ = false;
+ /** True if motion blur post-fx is enabled. */
+ float motion_blur_fx_enabled_ = false;
+ /** True if last viewport redraw state was already in navigation state. */
+ bool was_navigating_ = false;
+
+ int step_id_ = 0;
+
+ /** Velocity tiles used to guide and speedup the gather pass. */
+ TextureFromPool tiles_tx_;
+
+ GPUTexture *input_color_tx_ = nullptr;
+ GPUTexture *output_color_tx_ = nullptr;
+
+ DRWPass *tiles_flatten_ps_ = nullptr;
+ DRWPass *tiles_dilate_ps_ = nullptr;
+ DRWPass *gather_ps_ = nullptr;
+
+ MotionBlurTileIndirectionBuf tile_indirection_buf_;
+ MotionBlurDataBuf data_;
+ /** Dispatch size for full-screen passes. */
+ int3 dispatch_flatten_size_ = int3(0);
+ int3 dispatch_dilate_size_ = int3(0);
+ int3 dispatch_gather_size_ = int3(0);
+
+ public:
+ MotionBlurModule(Instance &inst) : inst_(inst){};
+ ~MotionBlurModule(){};
+
+ void init();
+
+ void step();
+
+ void sync();
+
+ bool postfx_enabled() const
+ {
+ return motion_blur_fx_enabled_;
+ }
+
+ void render(GPUTexture **input_tx, GPUTexture **output_tx);
+
+ private:
+ float shutter_time_to_scene_time(float time);
+};
+
+/** \} */
+
+} // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
index c60054496c1..b69fde7b26c 100644
--- a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
@@ -24,25 +24,7 @@
namespace blender::eevee {
-void RenderBuffers::sync()
-{
- depth_tx.sync();
- combined_tx.sync();
-
- normal_tx.sync();
- vector_tx.sync();
- diffuse_light_tx.sync();
- diffuse_color_tx.sync();
- specular_light_tx.sync();
- specular_color_tx.sync();
- volume_light_tx.sync();
- emission_tx.sync();
- environment_tx.sync();
- shadow_tx.sync();
- ambient_occlusion_tx.sync();
-}
-
-void RenderBuffers::acquire(int2 extent, void *owner)
+void RenderBuffers::acquire(int2 extent)
{
auto pass_extent = [&](eViewLayerEEVEEPassType pass_bit) -> int2 {
/* Use dummy texture for disabled passes. Allows correct bindings. */
@@ -53,25 +35,26 @@ void RenderBuffers::acquire(int2 extent, void *owner)
eGPUTextureFormat float_format = GPU_R16F;
/* Depth and combined are always needed. */
- depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8, owner);
- combined_tx.acquire(extent, color_format, owner);
+ depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8);
+ combined_tx.acquire(extent, color_format);
- bool do_vector_render_pass = inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR;
+ bool do_vector_render_pass = (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) ||
+ (inst_.motion_blur.postfx_enabled() && !inst_.is_viewport());
/* Only RG16F when only doing only reprojection or motion blur. */
eGPUTextureFormat vector_format = do_vector_render_pass ? GPU_RGBA16F : GPU_RG16F;
/* TODO(fclem): Make vector pass allocation optional if no TAA or motion blur is needed. */
- vector_tx.acquire(extent, vector_format, owner);
-
- normal_tx.acquire(pass_extent(EEVEE_RENDER_PASS_NORMAL), color_format, owner);
- diffuse_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_LIGHT), color_format, owner);
- diffuse_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_COLOR), color_format, owner);
- specular_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_LIGHT), color_format, owner);
- specular_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_COLOR), color_format, owner);
- volume_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_VOLUME_LIGHT), color_format, owner);
- emission_tx.acquire(pass_extent(EEVEE_RENDER_PASS_EMIT), color_format, owner);
- environment_tx.acquire(pass_extent(EEVEE_RENDER_PASS_ENVIRONMENT), color_format, owner);
- shadow_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SHADOW), float_format, owner);
- ambient_occlusion_tx.acquire(pass_extent(EEVEE_RENDER_PASS_AO), float_format, owner);
+ vector_tx.acquire(extent, vector_format);
+
+ normal_tx.acquire(pass_extent(EEVEE_RENDER_PASS_NORMAL), color_format);
+ diffuse_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_LIGHT), color_format);
+ diffuse_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_COLOR), color_format);
+ specular_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_LIGHT), color_format);
+ specular_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_COLOR), color_format);
+ volume_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_VOLUME_LIGHT), color_format);
+ emission_tx.acquire(pass_extent(EEVEE_RENDER_PASS_EMIT), color_format);
+ environment_tx.acquire(pass_extent(EEVEE_RENDER_PASS_ENVIRONMENT), color_format);
+ shadow_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SHADOW), float_format);
+ ambient_occlusion_tx.acquire(pass_extent(EEVEE_RENDER_PASS_AO), float_format);
const AOVsInfoData &aovs = inst_.film.aovs_info;
aov_color_tx.ensure_2d_array(
diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
index 8c91fed2f0f..787f5604aa4 100644
--- a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
@@ -48,9 +48,8 @@ class RenderBuffers {
public:
RenderBuffers(Instance &inst) : inst_(inst){};
- void sync();
/* Acquires (also ensures) the render buffer before rendering to them. */
- void acquire(int2 extent, void *owner);
+ void acquire(int2 extent);
void release();
};
diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
index 1d320c75f16..76a0e98638b 100644
--- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
@@ -232,7 +232,7 @@ void Sampling::cdf_from_curvemapping(const CurveMapping &curve, Vector<float> &c
BLI_assert(cdf.size() > 1);
cdf[0] = 0.0f;
/* Actual CDF evaluation. */
- for (int u : cdf.index_range()) {
+ for (int u : IndexRange(cdf.size() - 1)) {
float x = (float)(u + 1) / (float)(cdf.size() - 1);
cdf[u + 1] = cdf[u] + BKE_curvemapping_evaluateF(&curve, 0, x);
}
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc
index 7db9692783a..782e73f6dd0 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc
@@ -82,6 +82,14 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_film_frag";
case FILM_COMP:
return "eevee_film_comp";
+ case MOTION_BLUR_GATHER:
+ return "eevee_motion_blur_gather";
+ case MOTION_BLUR_TILE_DILATE:
+ return "eevee_motion_blur_tiles_dilate";
+ case MOTION_BLUR_TILE_FLATTEN_RENDER:
+ return "eevee_motion_blur_tiles_flatten_render";
+ case MOTION_BLUR_TILE_FLATTEN_VIEWPORT:
+ return "eevee_motion_blur_tiles_flatten_viewport";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh
index 280aaab4e1c..8dc61fbae0b 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh
@@ -29,6 +29,11 @@ enum eShaderType {
FILM_FRAG = 0,
FILM_COMP,
+ MOTION_BLUR_GATHER,
+ MOTION_BLUR_TILE_DILATE,
+ MOTION_BLUR_TILE_FLATTEN_RENDER,
+ MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
+
MAX_SHADER_TYPE,
};
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
index 3c10f633740..70de4101bb9 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
@@ -124,7 +124,7 @@ struct CameraData {
float clip_far;
eCameraType type;
- bool initialized;
+ bool1 initialized;
#ifdef __cplusplus
/* Small constructor to allow detecting new buffers. */
@@ -312,6 +312,40 @@ BLI_STATIC_ASSERT_ALIGN(VelocityGeometryIndex, 16)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Motion Blur
+ * \{ */
+
+#define MOTION_BLUR_TILE_SIZE 32
+#define MOTION_BLUR_MAX_TILE 512 /* 16384 / MOTION_BLUR_TILE_SIZE */
+struct MotionBlurData {
+ /** As the name suggests. Used to avoid a division in the sampling. */
+ float2 target_size_inv;
+ /** Viewport motion scaling factor. Make blur relative to frame time not render time. */
+ float2 motion_scale;
+ /** Depth scaling factor. Avoid blurring background behind moving objects. */
+ float depth_scale;
+
+ float _pad0, _pad1, _pad2;
+};
+BLI_STATIC_ASSERT_ALIGN(MotionBlurData, 16)
+
+/* For some reasons some GLSL compilers do not like this struct.
+ * So we declare it as a uint array instead and do indexing ourselves. */
+#ifdef __cplusplus
+struct MotionBlurTileIndirection {
+ /**
+ * Stores indirection to the tile with the highest velocity covering each tile.
+ * This is stored using velocity in the MSB to be able to use atomicMax operations.
+ */
+ uint prev[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
+ uint next[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
+};
+BLI_STATIC_ASSERT_ALIGN(MotionBlurTileIndirection, 16)
+#endif
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Ray-Tracing
* \{ */
@@ -375,6 +409,8 @@ using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;
+using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
+using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
} // namespace blender::eevee
#endif
diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.cc b/source/blender/draw/engines/eevee_next/eevee_velocity.cc
index 048daf1b2db..36734f0c28c 100644
--- a/source/blender/draw/engines/eevee_next/eevee_velocity.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_velocity.cc
@@ -32,13 +32,16 @@ namespace blender::eevee {
void VelocityModule::init()
{
- if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR)) {
- /* No motion blur and the vector pass was requested. Do the step sync here. */
+ if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) != 0) {
+ /* No motion blur and the vector pass was requested. Do the steps sync here. */
const Scene *scene = inst_.scene;
float initial_time = scene->r.cfra + scene->r.subframe;
step_sync(STEP_PREVIOUS, initial_time - 1.0f);
step_sync(STEP_NEXT, initial_time + 1.0f);
+
inst_.set_time(initial_time);
+ step_ = STEP_CURRENT;
+ /* Let the main sync loop handle the current step. */
}
}
@@ -64,10 +67,12 @@ void VelocityModule::step_camera_sync()
{
inst_.camera.sync();
*camera_steps[step_] = inst_.camera.data_get();
+ step_time[step_] = inst_.scene->r.cfra + inst_.scene->r.subframe;
/* Fix undefined camera steps when rendering is starting. */
if ((step_ == STEP_CURRENT) && (camera_steps[STEP_PREVIOUS]->initialized == false)) {
*camera_steps[STEP_PREVIOUS] = *static_cast<CameraData *>(camera_steps[step_]);
camera_steps[STEP_PREVIOUS]->initialized = true;
+ step_time[STEP_PREVIOUS] = step_time[step_];
}
}
@@ -212,6 +217,7 @@ void VelocityModule::step_swap()
SWAP(VelocityObjectBuf *, object_steps[step_a], object_steps[step_b]);
SWAP(VelocityGeometryBuf *, geometry_steps[step_a], geometry_steps[step_b]);
SWAP(CameraDataBuf *, camera_steps[step_a], camera_steps[step_b]);
+ SWAP(float, step_time[step_a], step_time[step_b]);
for (VelocityObjectData &vel : velocity_map.values()) {
vel.obj.ofs[step_a] = vel.obj.ofs[step_b];
@@ -238,10 +244,7 @@ void VelocityModule::step_swap()
void VelocityModule::begin_sync()
{
- if (inst_.is_viewport()) {
- /* Viewport always evaluate current step. */
- step_ = STEP_CURRENT;
- }
+ step_ = STEP_CURRENT;
step_camera_sync();
object_steps_usage[step_] = 0;
}
@@ -360,6 +363,21 @@ bool VelocityModule::camera_has_motion() const
*camera_steps[STEP_NEXT] != *camera_steps[STEP_CURRENT];
}
+bool VelocityModule::camera_changed_projection() const
+{
+ /* Only valid after sync. */
+ if (inst_.is_viewport()) {
+ return camera_steps[STEP_PREVIOUS]->type != camera_steps[STEP_CURRENT]->type;
+ }
+ /* Cannot happen in render mode since we set the type during the init phase. */
+ return false;
+}
+
+float VelocityModule::step_time_delta_get(eVelocityStep start, eVelocityStep end) const
+{
+ return step_time[end] - step_time[start];
+}
+
/** \} */
} // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.hh b/source/blender/draw/engines/eevee_next/eevee_velocity.hh
index 826cd631a96..01b8a5fb8c1 100644
--- a/source/blender/draw/engines/eevee_next/eevee_velocity.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_velocity.hh
@@ -56,6 +56,8 @@ class VelocityModule {
int3 object_steps_usage = int3(0);
/** Buffer of all #VelocityIndex used in this frame. Indexed by draw manager resource id. */
VelocityIndexBuf indirection_buf;
+ /** Frame time at which each steps were evaluated. */
+ float3 step_time;
/**
* Copies of camera data. One for previous and one for next time step.
@@ -78,7 +80,6 @@ class VelocityModule {
}
for (CameraDataBuf *&step_buf : camera_steps) {
step_buf = new CameraDataBuf();
- /* */
}
};
@@ -112,6 +113,10 @@ class VelocityModule {
void bind_resources(DRWShadingGroup *grp);
bool camera_has_motion() const;
+ bool camera_changed_projection() const;
+
+ /* Returns frame time difference between two steps. */
+ float step_time_delta_get(eVelocityStep start, eVelocityStep end) const;
private:
bool object_has_velocity(const Object *ob);
diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc
index 55741bee4df..c7434a662a2 100644
--- a/source/blender/draw/engines/eevee_next/eevee_view.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_view.cc
@@ -79,14 +79,11 @@ void ShadingView::sync()
render_view_ = DRW_view_create_sub(main_view_, viewmat_p, winmat_p);
// dof_.sync(winmat_p, extent_);
- // mb_.sync(extent_);
// rt_buffer_opaque_.sync(extent_);
// rt_buffer_refract_.sync(extent_);
// inst_.hiz_back.view_sync(extent_);
// inst_.hiz_front.view_sync(extent_);
// inst_.gbuffer.view_sync(extent_);
-
- postfx_tx_.sync();
}
void ShadingView::render()
@@ -96,12 +93,8 @@ void ShadingView::render()
}
/* Query temp textures and create frame-buffers. */
- /* HACK: View name should be unique and static.
- * With this, we can reuse the same texture across views. */
- DrawEngineType *owner = (DrawEngineType *)name_;
-
RenderBuffers &rbufs = inst_.render_buffers;
- rbufs.acquire(extent_, owner);
+ rbufs.acquire(extent_);
combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
GPU_ATTACHMENT_TEXTURE(rbufs.combined_tx));
prepass_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
@@ -138,9 +131,9 @@ void ShadingView::render()
// inst_.lights.debug_draw(view_fb_);
// inst_.shadows.debug_draw(view_fb_);
- // GPUTexture *final_radiance_tx = render_post(combined_tx_);
+ GPUTexture *combined_final_tx = render_postfx(rbufs.combined_tx);
- inst_.film.accumulate(sub_view_);
+ inst_.film.accumulate(sub_view_, combined_final_tx);
rbufs.release();
postfx_tx_.release();
@@ -148,23 +141,19 @@ void ShadingView::render()
DRW_stats_group_end();
}
-GPUTexture *ShadingView::render_post(GPUTexture *input_tx)
+GPUTexture *ShadingView::render_postfx(GPUTexture *input_tx)
{
-#if 0
- if (!dof_.postfx_enabled() && !mb_.enabled()) {
+ if (/*!dof_.postfx_enabled() &&*/ !inst_.motion_blur.postfx_enabled()) {
return input_tx;
}
- /* HACK: View name should be unique and static.
- * With this, we can reuse the same texture across views. */
- postfx_tx_.acquire(extent_, GPU_RGBA16F, (void *)name_);
+ postfx_tx_.acquire(extent_, GPU_RGBA16F);
- GPUTexture *velocity_tx = velocity_.view_vectors_get();
GPUTexture *output_tx = postfx_tx_;
/* Swapping is done internally. Actual output is set to the next input. */
- dof_.render(depth_tx_, &input_tx, &output_tx);
- mb_.render(depth_tx_, velocity_tx, &input_tx, &output_tx);
-#endif
+ // dof_.render(depth_tx_, &input_tx, &output_tx);
+ inst_.motion_blur.render(&input_tx, &output_tx);
+
return input_tx;
}
diff --git a/source/blender/draw/engines/eevee_next/eevee_view.hh b/source/blender/draw/engines/eevee_next/eevee_view.hh
index c6faebdd0e5..ac8decc7632 100644
--- a/source/blender/draw/engines/eevee_next/eevee_view.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_view.hh
@@ -78,7 +78,7 @@ class ShadingView {
void render();
- GPUTexture *render_post(GPUTexture *input_tx);
+ GPUTexture *render_postfx(GPUTexture *input_tx);
private:
void update_view();
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
index b286836e8df..135507d956c 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
@@ -636,6 +636,8 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth
vec4 normal = texelFetch(normal_tx, film_sample.texel, 0);
float depth = texelFetch(depth_tx, film_sample.texel, 0).x;
vec4 vector = velocity_resolve(vector_tx, film_sample.texel, depth);
+ /* Transform to pixel space. */
+ vector *= vec4(film_buf.render_extent, -film_buf.render_extent);
film_store_depth(texel_film, depth, out_depth);
film_store_data(texel_film, film_buf.normal_id, normal, out_color);
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl
new file mode 100644
index 00000000000..c59b7d7f4df
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl
@@ -0,0 +1,116 @@
+
+/**
+ * Dilate motion vector tiles until we covered maximum velocity.
+ * Outputs the largest intersecting motion vector in the neighboorhod.
+ *
+ */
+
+#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl)
+
+#define DEBUG_BYPASS_DILATION 0
+
+struct MotionRect {
+ ivec2 bottom_left;
+ ivec2 extent;
+};
+
+MotionRect compute_motion_rect(ivec2 tile, vec2 motion)
+{
+#if DEBUG_BYPASS_DILATION
+ return MotionRect(tile, ivec2(1));
+#endif
+ /* Ceil to number of tile touched.*/
+ ivec2 point1 = tile + ivec2(sign(motion) * ceil(abs(motion) / float(MOTION_BLUR_TILE_SIZE)));
+ ivec2 point2 = tile;
+
+ ivec2 max_point = max(point1, point2);
+ ivec2 min_point = min(point1, point2);
+ /* Clamp to bounds. */
+ max_point = min(max_point, imageSize(in_tiles_img) - 1);
+ min_point = max(min_point, ivec2(0));
+
+ MotionRect rect;
+ rect.bottom_left = min_point;
+ rect.extent = 1 + max_point - min_point;
+ return rect;
+}
+
+struct MotionLine {
+ /** Origin of the line. */
+ vec2 origin;
+ /** Normal to the line direction. */
+ vec2 normal;
+};
+
+MotionLine compute_motion_line(ivec2 tile, vec2 motion)
+{
+ vec2 dir = safe_normalize(motion);
+
+ MotionLine line;
+ line.origin = vec2(tile);
+ /* Rotate 90° Counter-Clockwise. */
+ line.normal = vec2(-dir.y, dir.x);
+ return line;
+}
+
+bool is_inside_motion_line(ivec2 tile, MotionLine motion_line)
+{
+#if DEBUG_BYPASS_DILATION
+ return true;
+#endif
+ /* NOTE: Everything in is tile unit. */
+ float dist = point_line_projection_dist(vec2(tile), motion_line.origin, motion_line.normal);
+ /* In order to be conservative and for simplicity, we use the tiles bounding circles.
+ * Consider that both the tile and the line have bouding radius of M_SQRT1_2. */
+ return abs(dist) < M_SQRT2;
+}
+
+void main()
+{
+ ivec2 src_tile = ivec2(gl_GlobalInvocationID.xy);
+ if (any(greaterThanEqual(src_tile, imageSize(in_tiles_img)))) {
+ return;
+ }
+
+ vec4 max_motion = imageLoad(in_tiles_img, src_tile);
+
+ MotionPayload payload_prv = motion_blur_tile_indirection_pack_payload(max_motion.xy, src_tile);
+ MotionPayload payload_nxt = motion_blur_tile_indirection_pack_payload(max_motion.zw, src_tile);
+ if (true) {
+ /* Rectangular area (in tiles) where the motion vector spreads. */
+ MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.xy);
+ MotionLine motion_line = compute_motion_line(src_tile, max_motion.xy);
+ /* Do a conservative rasterization of the line of the motion vector line. */
+ for (int x = 0; x < motion_rect.extent.x; x++) {
+ for (int y = 0; y < motion_rect.extent.y; y++) {
+ ivec2 tile = motion_rect.bottom_left + ivec2(x, y);
+ if (is_inside_motion_line(tile, motion_line)) {
+ motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_PREV, tile, payload_prv);
+ /* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
+ * the motion next so that weighting in gather pass is better. */
+ motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_NEXT, tile, payload_nxt);
+ }
+ }
+ }
+ }
+
+ if (true) {
+ MotionPayload payload = motion_blur_tile_indirection_pack_payload(max_motion.zw, src_tile);
+ /* Rectangular area (in tiles) where the motion vector spreads. */
+ MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.zw);
+ MotionLine motion_line = compute_motion_line(src_tile, max_motion.zw);
+ /* Do a conservative rasterization of the line of the motion vector line. */
+ for (int x = 0; x < motion_rect.extent.x; x++) {
+ for (int y = 0; y < motion_rect.extent.y; y++) {
+ ivec2 tile = motion_rect.bottom_left + ivec2(x, y);
+ if (is_inside_motion_line(tile, motion_line)) {
+ motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_NEXT, tile, payload_nxt);
+ /* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
+ * the motion next so that weighting in gather pass is better. */
+ motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_PREV, tile, payload_prv);
+ }
+ }
+ }
+ }
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl
new file mode 100644
index 00000000000..cbbeea25d20
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl
@@ -0,0 +1,103 @@
+
+/**
+ * Shaders that down-sample velocity buffer into squared tile of MB_TILE_DIVISOR pixels wide.
+ * Outputs the largest motion vector in the tile area.
+ * Also perform velocity resolve to speedup the convolution pass.
+ *
+ * Based on:
+ * A Fast and Stable Feature-Aware Motion Blur Filter
+ * by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
+ *
+ * Adapted from G3D Innovation Engine implementation.
+ */
+
+#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
+
+shared uint payload_prev;
+shared uint payload_next;
+shared vec2 max_motion_prev;
+shared vec2 max_motion_next;
+
+/* Store velocity magnitude in the MSB and thread id in the LSB. */
+uint pack_payload(vec2 motion, uvec2 thread_id)
+{
+ /* NOTE: We clamp max velocity to 16k pixels. */
+ return (min(uint(ceil(length(motion))), 0xFFFFu) << 16u) | (thread_id.y << 8) | thread_id.x;
+}
+
+/* Return thread index from the payload. */
+uvec2 unpack_payload(uint payload)
+{
+ return uvec2(payload & 0xFFu, (payload >> 8) & 0xFFu);
+}
+
+void main()
+{
+ if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) {
+ payload_prev = 0u;
+ payload_next = 0u;
+ }
+ barrier();
+
+ uint local_payload_prev = 0u;
+ uint local_payload_next = 0u;
+ vec2 local_max_motion_prev;
+ vec2 local_max_motion_next;
+
+ ivec2 texel = min(ivec2(gl_GlobalInvocationID.xy), imageSize(velocity_img) - 1);
+
+ vec2 render_size = vec2(imageSize(velocity_img).xy);
+ vec2 uv = (vec2(texel) + 0.5) / render_size;
+ float depth = texelFetch(depth_tx, texel, 0).r;
+ vec4 motion = velocity_resolve(imageLoad(velocity_img, texel), uv, depth);
+#ifdef FLATTEN_VIEWPORT
+ /* imageLoad does not perform the swizzling like sampler does. Do it manually. */
+ motion = motion.xyxy;
+#endif
+
+ /* Store resolved velocity to speedup the gather pass. Out of bounds writes are ignored.
+ * Unfortunately, we cannot convert to pixel space here since it is also used by TAA and the
+ * motion blur needs to remain optional. */
+ imageStore(velocity_img, ivec2(gl_GlobalInvocationID.xy), velocity_pack(motion));
+ /* Clip velocity to viewport bounds (in NDC space). */
+ vec2 line_clip;
+ line_clip.x = line_unit_square_intersect_dist_safe(uv * 2.0 - 1.0, motion.xy * 2.0);
+ line_clip.y = line_unit_square_intersect_dist_safe(uv * 2.0 - 1.0, -motion.zw * 2.0);
+ motion *= min(line_clip, vec2(1.0)).xxyy;
+ /* Convert to pixel space. Note this is only for velocity tiles. */
+ motion *= render_size.xyxy;
+ /* Rescale to shutter relative motion for viewport. */
+ motion *= motion_blur_buf.motion_scale.xxyy;
+
+ uint sample_payload_prev = pack_payload(motion.xy, gl_LocalInvocationID.xy);
+ if (local_payload_prev < sample_payload_prev) {
+ local_payload_prev = sample_payload_prev;
+ local_max_motion_prev = motion.xy;
+ }
+
+ uint sample_payload_next = pack_payload(motion.zw, gl_LocalInvocationID.xy);
+ if (local_payload_next < sample_payload_next) {
+ local_payload_next = sample_payload_next;
+ local_max_motion_next = motion.zw;
+ }
+
+ /* Compare the local payload with the other threads. */
+ atomicMax(payload_prev, local_payload_prev);
+ atomicMax(payload_next, local_payload_next);
+ barrier();
+
+ /* Need to broadcast the result to another thread in order to issue a unique write. */
+ if (all(equal(unpack_payload(payload_prev), gl_LocalInvocationID.xy))) {
+ max_motion_prev = local_max_motion_prev;
+ }
+ if (all(equal(unpack_payload(payload_next), gl_LocalInvocationID.xy))) {
+ max_motion_next = local_max_motion_next;
+ }
+ barrier();
+
+ if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) {
+ ivec2 tile_co = ivec2(gl_WorkGroupID.xy);
+ imageStore(out_tiles_img, tile_co, vec4(max_motion_prev, max_motion_next));
+ }
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
new file mode 100644
index 00000000000..a7329f77181
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
@@ -0,0 +1,221 @@
+
+/**
+ * Perform two gather blur in the 2 motion blur directions
+ * Based on:
+ * A Fast and Stable Feature-Aware Motion Blur Filter
+ * by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
+ *
+ * With modification from the presentation:
+ * Next Generation Post Processing in Call of Duty Advanced Warfare
+ * by Jorge Jimenez
+ */
+
+#pragma BLENDER_REQUIRE(common_view_lib.glsl)
+#pragma BLENDER_REQUIRE(common_math_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl)
+
+const int gather_sample_count = 8;
+
+/* Converts uv velocity into pixel space. Assumes velocity_tx is the same resolution as the
+ * target post-fx framebuffer. */
+vec4 motion_blur_sample_velocity(sampler2D velocity_tx, vec2 uv)
+{
+ /* We can load velocity without velocity_resolve() since we resovled during the flatten pass. */
+ vec4 velocity = velocity_unpack(texture(velocity_tx, uv));
+ return velocity * vec2(textureSize(velocity_tx, 0)).xyxy * motion_blur_buf.motion_scale.xxyy;
+}
+
+vec2 spread_compare(float center_motion_length, float sample_motion_length, float offset_length)
+{
+ return saturate(vec2(center_motion_length, sample_motion_length) - offset_length + 1.0);
+}
+
+vec2 depth_compare(float center_depth, float sample_depth)
+{
+ vec2 depth_scale = vec2(-motion_blur_buf.depth_scale, motion_blur_buf.depth_scale);
+ return saturate(0.5 + depth_scale * (sample_depth - center_depth));
+}
+
+/* Kill contribution if not going the same direction. */
+float dir_compare(vec2 offset, vec2 sample_motion, float sample_motion_length)
+{
+ if (sample_motion_length < 0.5) {
+ return 1.0;
+ }
+ return (dot(offset, sample_motion) > 0.0) ? 1.0 : 0.0;
+}
+
+/* Return background (x) and foreground (y) weights. */
+vec2 sample_weights(float center_depth,
+ float sample_depth,
+ float center_motion_length,
+ float sample_motion_length,
+ float offset_length)
+{
+ /* Classify foreground/background. */
+ vec2 depth_weight = depth_compare(center_depth, sample_depth);
+ /* Weight if sample is overlapping or under the center pixel. */
+ vec2 spread_weight = spread_compare(center_motion_length, sample_motion_length, offset_length);
+ return depth_weight * spread_weight;
+}
+
+struct Accumulator {
+ vec4 fg;
+ vec4 bg;
+ /** x: Background, y: Foreground, z: dir. */
+ vec3 weight;
+};
+
+void gather_sample(vec2 screen_uv,
+ float center_depth,
+ float center_motion_len,
+ vec2 offset,
+ float offset_len,
+ const bool next,
+ inout Accumulator accum)
+{
+ vec2 sample_uv = screen_uv - offset * motion_blur_buf.target_size_inv;
+ vec4 sample_vectors = motion_blur_sample_velocity(velocity_tx, sample_uv);
+ vec2 sample_motion = (next) ? sample_vectors.zw : sample_vectors.xy;
+ float sample_motion_len = length(sample_motion);
+ float sample_depth = texture(depth_tx, sample_uv).r;
+ vec4 sample_color = textureLod(in_color_tx, sample_uv, 0.0);
+
+ sample_depth = get_view_z_from_depth(sample_depth);
+
+ vec3 weights;
+ weights.xy = sample_weights(
+ center_depth, sample_depth, center_motion_len, sample_motion_len, offset_len);
+ weights.z = dir_compare(offset, sample_motion, sample_motion_len);
+ weights.xy *= weights.z;
+
+ accum.fg += sample_color * weights.y;
+ accum.bg += sample_color * weights.x;
+ accum.weight += weights;
+}
+
+void gather_blur(vec2 screen_uv,
+ vec2 center_motion,
+ float center_depth,
+ vec2 max_motion,
+ float ofs,
+ const bool next,
+ inout Accumulator accum)
+{
+ float center_motion_len = length(center_motion);
+ float max_motion_len = length(max_motion);
+
+ /* Tile boundaries randomization can fetch a tile where there is less motion than this pixel.
+ * Fix this by overriding the max_motion. */
+ if (max_motion_len < center_motion_len) {
+ max_motion_len = center_motion_len;
+ max_motion = center_motion;
+ }
+
+ if (max_motion_len < 0.5) {
+ return;
+ }
+
+ int i;
+ float t, inc = 1.0 / float(gather_sample_count);
+ for (i = 0, t = ofs * inc; i < gather_sample_count; i++, t += inc) {
+ gather_sample(screen_uv,
+ center_depth,
+ center_motion_len,
+ max_motion * t,
+ max_motion_len * t,
+ next,
+ accum);
+ }
+
+ if (center_motion_len < 0.5) {
+ return;
+ }
+
+ for (i = 0, t = ofs * inc; i < gather_sample_count; i++, t += inc) {
+ /* Also sample in center motion direction.
+ * Allow recovering motion where there is conflicting
+ * motion between foreground and background. */
+ gather_sample(screen_uv,
+ center_depth,
+ center_motion_len,
+ center_motion * t,
+ center_motion_len * t,
+ next,
+ accum);
+ }
+}
+
+void main()
+{
+ ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
+ vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(depth_tx, 0).xy);
+
+ if (!in_texture_range(texel, depth_tx)) {
+ return;
+ }
+
+ /* Data of the center pixel of the gather (target). */
+ float center_depth = get_view_z_from_depth(texelFetch(depth_tx, texel, 0).r);
+ vec4 center_motion = motion_blur_sample_velocity(velocity_tx, uv);
+
+ vec4 center_color = textureLod(in_color_tx, uv, 0.0);
+
+ float noise_offset = sampling_rng_1D_get(SAMPLING_TIME);
+ /** TODO(fclem) Blue noise. */
+ vec2 rand = vec2(interlieved_gradient_noise(vec2(gl_GlobalInvocationID.xy), 0, noise_offset),
+ interlieved_gradient_noise(vec2(gl_GlobalInvocationID.xy), 1, noise_offset));
+
+ /* Randomize tile boundary to avoid ugly discontinuities. Randomize 1/4th of the tile.
+ * Note this randomize only in one direction but in practice it's enough. */
+ rand.x = rand.x * 2.0 - 1.0;
+ ivec2 tile = (texel + ivec2(rand.x * float(MOTION_BLUR_TILE_SIZE) * 0.25)) /
+ MOTION_BLUR_TILE_SIZE;
+ tile = clamp(tile, ivec2(0), imageSize(in_tiles_img) - 1);
+ /* NOTE: Tile velocity is already in pixel space and with correct zw sign. */
+ vec4 max_motion;
+ /* Load dilation result from the indirection table. */
+ ivec2 tile_prev;
+ motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_PREV, tile, tile_prev);
+ max_motion.xy = imageLoad(in_tiles_img, tile_prev).xy;
+ ivec2 tile_next;
+ motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_NEXT, tile, tile_next);
+ max_motion.zw = imageLoad(in_tiles_img, tile_next).zw;
+
+ Accumulator accum;
+ accum.weight = vec3(0.0, 0.0, 1.0);
+ accum.bg = vec4(0.0);
+ accum.fg = vec4(0.0);
+ /* First linear gather. time = [T - delta, T] */
+ gather_blur(uv, center_motion.xy, center_depth, max_motion.xy, rand.y, false, accum);
+ /* Second linear gather. time = [T, T + delta] */
+ gather_blur(uv, center_motion.zw, center_depth, max_motion.zw, rand.y, true, accum);
+
+#if 1 /* Own addition. Not present in reference implementation. */
+ /* Avoid division by 0.0. */
+ float w = 1.0 / (50.0 * float(gather_sample_count) * 4.0);
+ accum.bg += center_color * w;
+ accum.weight.x += w;
+ /* NOTE: In Jimenez's presentation, they used center sample.
+ * We use background color as it contains more information for foreground
+ * elements that have not enough weights.
+ * Yield better blur in complex motion. */
+ center_color = accum.bg / accum.weight.x;
+#endif
+ /* Merge background. */
+ accum.fg += accum.bg;
+ accum.weight.y += accum.weight.x;
+ /* Balance accumulation for failed samples.
+ * We replace the missing foreground by the background. */
+ float blend_fac = saturate(1.0 - accum.weight.y / accum.weight.z);
+ vec4 out_color = (accum.fg / accum.weight.z) + center_color * blend_fac;
+
+#if 0 /* For debugging. */
+ out_color.rgb = out_color.ggg;
+ out_color.rg += max_motion.xy;
+#endif
+
+ imageStore(out_color_img, texel, out_color);
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
new file mode 100644
index 00000000000..436fd01795a
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
@@ -0,0 +1,48 @@
+
+
+/* -------------------------------------------------------------------- */
+/** \name Tile indirection packing
+ * \{ */
+
+#define MotionPayload uint
+
+/* Store velocity magnitude in the MSB to be able to use it with atomicMax operations. */
+MotionPayload motion_blur_tile_indirection_pack_payload(vec2 motion, uvec2 payload)
+{
+ /* NOTE: Clamp to 16383 pixel velocity. After that, it is tile position that determine the tile
+ * to dilate over. */
+ uint velocity = min(uint(ceil(length(motion))), 0x3FFFu);
+ /* Designed for 512x512 tiles max. */
+ return (velocity << 18u) | ((payload.x & 0x1FFu) << 9u) | (payload.y & 0x1FFu);
+}
+
+/* Return thread index. */
+ivec2 motion_blur_tile_indirection_pack_payload(uint data)
+{
+ return ivec2((data >> 9u) & 0x1FFu, data & 0x1FFu);
+}
+
+uint motion_blur_tile_indirection_index(uint motion_step, uvec2 tile)
+{
+ uint index = tile.x;
+ index += tile.y * MOTION_BLUR_MAX_TILE;
+ index += motion_step * MOTION_BLUR_MAX_TILE * MOTION_BLUR_MAX_TILE;
+ return index;
+}
+
+#define MOTION_PREV 0u
+#define MOTION_NEXT 1u
+
+#define motion_blur_tile_indirection_store(table_, step_, tile, payload_) \
+ if (true) { \
+ uint index = motion_blur_tile_indirection_index(step_, tile); \
+ atomicMax(table_[index], payload_); \
+ }
+
+#define motion_blur_tile_indirection_load(table_, step_, tile_, result_) \
+ if (true) { \
+ uint index = motion_blur_tile_indirection_index(step_, tile_); \
+ result_ = motion_blur_tile_indirection_pack_payload(table_[index]); \
+ }
+
+/** \} */
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl
new file mode 100644
index 00000000000..0c7bbaa9dc2
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl
@@ -0,0 +1,104 @@
+
+/**
+ * Sampling data accessors and random number generators.
+ * Also contains some sample mapping functions.
+ **/
+
+#pragma BLENDER_REQUIRE(common_math_lib.glsl)
+
+/* -------------------------------------------------------------------- */
+/** \name Sampling data.
+ *
+ * Return a random values from Low Discrepency Sequence in [0..1) range.
+ * This value is uniform (constant) for the whole scene sample.
+ * You might want to couple it with a noise function.
+ * \{ */
+
+#ifdef EEVEE_SAMPLING_DATA
+
+float sampling_rng_1D_get(const eSamplingDimension dimension)
+{
+ return sampling_buf.dimensions[dimension];
+}
+
+vec2 sampling_rng_2D_get(const eSamplingDimension dimension)
+{
+ return vec2(sampling_buf.dimensions[dimension], sampling_buf.dimensions[dimension + 1u]);
+}
+
+vec3 sampling_rng_3D_get(const eSamplingDimension dimension)
+{
+ return vec3(sampling_buf.dimensions[dimension],
+ sampling_buf.dimensions[dimension + 1u],
+ sampling_buf.dimensions[dimension + 2u]);
+}
+
+#endif
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Random Number Generators.
+ * \{ */
+
+/* Interlieved gradient noise by Jorge Jimenez
+ * http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
+ * Seeding found by Epic Game. */
+float interlieved_gradient_noise(vec2 pixel, float seed, float offset)
+{
+ pixel += seed * (vec2(47, 17) * 0.695);
+ return fract(offset + 52.9829189 * fract(0.06711056 * pixel.x + 0.00583715 * pixel.y));
+}
+
+/* From: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html */
+float van_der_corput_radical_inverse(uint bits)
+{
+#if 0 /* Reference */
+ bits = (bits << 16u) | (bits >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+#else
+ bits = bitfieldReverse(bits);
+#endif
+ /* Same as dividing by 0x100000000. */
+ return float(bits) * 2.3283064365386963e-10;
+}
+
+vec2 hammersley_2d(float i, float sample_count)
+{
+ vec2 rand;
+ rand.x = i / sample_count;
+ rand.y = van_der_corput_radical_inverse(uint(i));
+ return rand;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Distribution mapping.
+ *
+ * Functions mapping input random numbers to sampling shapes (i.e: hemisphere).
+ * \{ */
+
+/* Given 2 random number in [0..1] range, return a random unit disk sample. */
+vec2 sample_disk(vec2 noise)
+{
+ float angle = noise.x * M_2PI;
+ return vec2(cos(angle), sin(angle)) * sqrt(noise.y);
+}
+
+/* This transform a 2d random sample (in [0..1] range) to a sample located on a cylinder of the
+ * same range. This is because the sampling functions expect such a random sample which is
+ * normally precomputed. */
+vec3 sample_cylinder(vec2 rand)
+{
+ float theta = rand.x;
+ float phi = (rand.y - 0.5) * M_2PI;
+ float cos_phi = cos(phi);
+ float sin_phi = sqrt(1.0 - sqr(cos_phi)) * sign(phi);
+ return vec3(theta, cos_phi, sin_phi);
+}
+
+/** \} */
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
index 34ea288852a..bd32215ddc2 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
@@ -73,7 +73,7 @@ void main()
nodetree_surface();
- // float noise_offset = sampling_rng_1D_get(sampling_buf, SAMPLING_TRANSPARENCY);
+ // float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY);
float noise_offset = 0.5;
float random_threshold = hashed_alpha_threshold(1.0, noise_offset, g_data.P);
@@ -84,7 +84,7 @@ void main()
#endif
#ifdef MAT_VELOCITY
- out_velocity = velocity_surface(interp.P + motion.prev, interp.P, interp.P - motion.next);
+ out_velocity = velocity_surface(interp.P + motion.prev, interp.P, interp.P + motion.next);
out_velocity = velocity_pack(out_velocity);
#endif
}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl
index c21456b7a5c..c0a5b976810 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl
@@ -2,8 +2,6 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_camera_lib.glsl)
-#ifdef VELOCITY_CAMERA
-
vec4 velocity_pack(vec4 data)
{
return data * 0.01;
@@ -14,6 +12,8 @@ vec4 velocity_unpack(vec4 data)
return data * 100.0;
}
+#ifdef VELOCITY_CAMERA
+
/**
* Given a triple of position, compute the previous and next motion vectors.
* Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy).
@@ -24,7 +24,15 @@ vec4 velocity_surface(vec3 P_prv, vec3 P, vec3 P_nxt)
vec2 prev_uv = project_point(camera_prev.persmat, P_prv).xy;
vec2 curr_uv = project_point(camera_curr.persmat, P).xy;
vec2 next_uv = project_point(camera_next.persmat, P_nxt).xy;
-
+ /* Fix issue with perspective division. */
+ if (any(isnan(prev_uv))) {
+ prev_uv = curr_uv;
+ }
+ if (any(isnan(next_uv))) {
+ next_uv = curr_uv;
+ }
+ /* NOTE: We output both vectors in the same direction so we can reuse the same vector
+ * with rgrg swizzle in viewport. */
vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv);
/* Convert NDC velocity to UV velocity */
motion *= 0.5;
@@ -45,7 +53,8 @@ vec4 velocity_background(vec3 vV)
vec2 prev_uv = project_point(camera_prev.winmat, V).xy;
vec2 curr_uv = project_point(camera_curr.winmat, V).xy;
vec2 next_uv = project_point(camera_next.winmat, V).xy;
-
+ /* NOTE: We output both vectors in the same direction so we can reuse the same vector
+ * with rgrg swizzle in viewport. */
vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv);
/* Convert NDC velocity to UV velocity */
motion *= 0.5;
@@ -53,15 +62,8 @@ vec4 velocity_background(vec3 vV)
return motion;
}
-/**
- * Load and resolve correct velocity as some pixels might still not have correct
- * motion data for performance reasons.
- */
-vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
+vec4 velocity_resolve(vec4 vector, vec2 uv, float depth)
{
- vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(vector_tx, 0).xy);
- vec4 vector = texelFetch(vector_tx, texel, 0);
-
if (vector.x == VELOCITY_INVALID) {
bool is_background = (depth == 1.0);
if (is_background) {
@@ -78,6 +80,18 @@ vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
return velocity_unpack(vector);
}
+/**
+ * Load and resolve correct velocity as some pixels might still not have correct
+ * motion data for performance reasons.
+ * Returns motion vector in render UV space.
+ */
+vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
+{
+ vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(vector_tx, 0).xy);
+ vec4 vector = texelFetch(vector_tx, texel, 0);
+ return velocity_resolve(vector, uv, depth);
+}
+
#endif
#ifdef MAT_VELOCITY
diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
index 2368061402c..db3cfc4a7a2 100644
--- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
+++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
@@ -12,8 +12,9 @@ GPU_SHADER_CREATE_INFO(eevee_shared)
.typedef_source("eevee_shader_shared.hh");
GPU_SHADER_CREATE_INFO(eevee_sampling_data)
+ .define("EEVEE_SAMPLING_DATA")
.additional_info("eevee_shared")
- .uniform_buf(14, "SamplingData", "sampling_buf");
+ .storage_buf(14, Qualifier::READ, "SamplingData", "sampling_buf");
/** \} */
diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
new file mode 100644
index 00000000000..b01d1521c5e
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
@@ -0,0 +1,44 @@
+
+#include "gpu_shader_create_info.hh"
+
+GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten)
+ .local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
+ .additional_info("eevee_shared", "draw_view", "eevee_velocity_camera")
+ .uniform_buf(4, "MotionBlurData", "motion_blur_buf")
+ .sampler(0, ImageType::DEPTH_2D, "depth_tx")
+ .image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_img")
+ .compute_source("eevee_motion_blur_flatten_comp.glsl");
+
+GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_viewport)
+ .do_static_compilation(true)
+ .define("FLATTEN_VIEWPORT")
+ .image(0, GPU_RG16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_img")
+ .additional_info("eevee_motion_blur_tiles_flatten");
+
+GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_render)
+ .do_static_compilation(true)
+ .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_img")
+ .additional_info("eevee_motion_blur_tiles_flatten");
+
+GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_dilate)
+ .do_static_compilation(true)
+ .local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
+ .additional_info("eevee_shared")
+ /* NOTE: See MotionBlurTileIndirection. */
+ .storage_buf(0, Qualifier::READ_WRITE, "uint", "tile_indirection_buf[]")
+ .image(1, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_img")
+ .compute_source("eevee_motion_blur_dilate_comp.glsl");
+
+GPU_SHADER_CREATE_INFO(eevee_motion_blur_gather)
+ .do_static_compilation(true)
+ .local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
+ .additional_info("eevee_shared", "draw_view", "eevee_sampling_data")
+ .uniform_buf(4, "MotionBlurData", "motion_blur_buf")
+ .sampler(0, ImageType::DEPTH_2D, "depth_tx")
+ .sampler(1, ImageType::FLOAT_2D, "velocity_tx")
+ .sampler(2, ImageType::FLOAT_2D, "in_color_tx")
+ /* NOTE: See MotionBlurTileIndirection. */
+ .storage_buf(0, Qualifier::READ, "uint", "tile_indirection_buf[]")
+ .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_img")
+ .image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img")
+ .compute_source("eevee_motion_blur_gather_comp.glsl");
diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh
index 8e61c25be71..954d8c49f4c 100644
--- a/source/blender/draw/intern/DRW_gpu_wrapper.hh
+++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh
@@ -57,6 +57,7 @@
#include "MEM_guardedalloc.h"
+#include "draw_manager.h"
#include "draw_texture_pool.h"
#include "BLI_math_vec_types.hh"
@@ -772,50 +773,26 @@ class Texture : NonCopyable {
};
class TextureFromPool : public Texture, NonMovable {
- private:
- GPUTexture *tx_tmp_saved_ = nullptr;
-
public:
TextureFromPool(const char *name = "gpu::Texture") : Texture(name){};
- /* Always use `release()` after rendering and `sync()` in sync phase. */
- void acquire(int2 extent, eGPUTextureFormat format, void *owner_)
+ /* Always use `release()` after rendering. */
+ void acquire(int2 extent, eGPUTextureFormat format)
{
BLI_assert(this->tx_ == nullptr);
- if (this->tx_ != nullptr) {
- return;
- }
- if (tx_tmp_saved_ != nullptr) {
- if (GPU_texture_width(tx_tmp_saved_) != extent.x ||
- GPU_texture_height(tx_tmp_saved_) != extent.y ||
- GPU_texture_format(tx_tmp_saved_) != format) {
- this->tx_tmp_saved_ = nullptr;
- }
- else {
- this->tx_ = tx_tmp_saved_;
- return;
- }
- }
- DrawEngineType *owner = (DrawEngineType *)owner_;
- this->tx_ = DRW_texture_pool_query_2d(UNPACK2(extent), format, owner);
+
+ this->tx_ = DRW_texture_pool_texture_acquire(
+ DST.vmempool->texture_pool, UNPACK2(extent), format);
}
void release(void)
{
/* Allows multiple release. */
- if (this->tx_ != nullptr) {
- tx_tmp_saved_ = this->tx_;
- this->tx_ = nullptr;
+ if (this->tx_ == nullptr) {
+ return;
}
- }
-
- /**
- * Clears any reference. Workaround for pool texture not being able to release on demand.
- * Needs to be called at during the sync phase.
- */
- void sync(void)
- {
- tx_tmp_saved_ = nullptr;
+ DRW_texture_pool_texture_release(DST.vmempool->texture_pool, this->tx_);
+ this->tx_ = nullptr;
}
/** Remove methods that are forbidden with this type of textures. */
diff --git a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc
index fc09606f9f3..af8e58c78f8 100644
--- a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc
+++ b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc
@@ -538,8 +538,8 @@ MeshRenderData *mesh_render_data_create(Object *object,
/* Seems like the mesh_eval_final do not have the right origin indices.
* Force not mapped in this case. */
- if (use_mapped && do_final && editmesh_eval_final != editmesh_eval_cage) {
- // mr->edit_bmesh = nullptr;
+ if (has_mdata && do_final && editmesh_eval_final != editmesh_eval_cage) {
+ // mr->edit_bmesh = NULL;
mr->extract_type = MR_EXTRACT_MESH;
}
}
diff --git a/source/blender/draw/intern/draw_instance_data.c b/source/blender/draw/intern/draw_instance_data.c
index 0e4e67f3320..88cc71fa0dd 100644
--- a/source/blender/draw/intern/draw_instance_data.c
+++ b/source/blender/draw/intern/draw_instance_data.c
@@ -27,6 +27,7 @@
#include "BKE_duplilist.h"
#include "RNA_access.h"
+#include "RNA_path.h"
#include "BLI_bitmap.h"
#include "BLI_memblock.h"
diff --git a/source/blender/draw/intern/draw_shader_shared.h b/source/blender/draw/intern/draw_shader_shared.h
index e8944442607..266db22a84f 100644
--- a/source/blender/draw/intern/draw_shader_shared.h
+++ b/source/blender/draw/intern/draw_shader_shared.h
@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef GPU_SHADER
+# pragma once
+
# include "GPU_shader.h"
# include "GPU_shader_shared_utils.h"
diff --git a/source/blender/draw/intern/draw_texture_pool.h b/source/blender/draw/intern/draw_texture_pool.h
index 1c30ea88552..7396b55d53a 100644
--- a/source/blender/draw/intern/draw_texture_pool.h
+++ b/source/blender/draw/intern/draw_texture_pool.h
@@ -26,6 +26,7 @@ void DRW_texture_pool_free(DRWTexturePool *pool);
/**
* Try to find a texture corresponding to params into the texture pool.
* If no texture was found, create one and add it to the pool.
+ * DEPRECATED: Use DRW_texture_pool_texture_acquire instead and do it just before rendering.
*/
GPUTexture *DRW_texture_pool_query(
DRWTexturePool *pool, int width, int height, eGPUTextureFormat format, void *user);
diff --git a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl
index ae82277d9a6..cb2da9d35bf 100644
--- a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl
+++ b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl
@@ -10,6 +10,11 @@ float point_plane_projection_dist(vec3 line_origin, vec3 plane_origin, vec3 plan
return dot(plane_normal, plane_origin - line_origin);
}
+float point_line_projection_dist(vec2 point, vec2 line_origin, vec2 line_normal)
+{
+ return dot(line_normal, line_origin - point);
+}
+
float line_plane_intersect_dist(vec3 line_origin,
vec3 line_direction,
vec3 plane_origin,
@@ -104,6 +109,25 @@ float line_unit_box_intersect_dist_safe(vec3 line_origin, vec3 line_direction)
}
/**
+ * Same as line_unit_box_intersect_dist but for 2D case.
+ */
+float line_unit_square_intersect_dist(vec2 line_origin, vec2 line_direction)
+{
+ vec2 first_plane = (vec2(1.0) - line_origin) / line_direction;
+ vec2 second_plane = (vec2(-1.0) - line_origin) / line_direction;
+ vec2 farthest_plane = max(first_plane, second_plane);
+
+ return min_v2(farthest_plane);
+}
+
+float line_unit_square_intersect_dist_safe(vec2 line_origin, vec2 line_direction)
+{
+ vec2 safe_line_direction = max(vec2(1e-8), abs(line_direction)) *
+ select(vec2(1.0), -vec2(1.0), lessThan(line_direction, vec2(0.0)));
+ return line_unit_square_intersect_dist(line_origin, safe_line_direction);
+}
+
+/**
* Returns clipping distance (intersection with the nearest plane) with the given axis-aligned
* bound box along \a line_direction.
* Safe even if \a line_direction is degenerate.