diff options
Diffstat (limited to 'source/blender/draw')
129 files changed, 11450 insertions, 823 deletions
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 30352d3d19c..0a62263ce3b 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -23,11 +23,11 @@ set(INC ../nodes ../render ../render/intern + ../compositor/realtime_compositor ../windowmanager ../../../intern/atomic ../../../intern/clog - ../../../intern/glew-mx ../../../intern/guardedalloc ../../../intern/opensubdiv @@ -77,13 +77,13 @@ set(SRC intern/draw_cache_impl_mesh.cc intern/draw_cache_impl_metaball.c intern/draw_cache_impl_particles.c - intern/draw_cache_impl_pointcloud.c + intern/draw_cache_impl_pointcloud.cc intern/draw_cache_impl_subdivision.cc intern/draw_cache_impl_volume.c intern/draw_color_management.cc intern/draw_common.c intern/draw_curves.cc - intern/draw_debug.c + intern/draw_debug.cc intern/draw_fluid.c intern/draw_hair.cc intern/draw_instance_data.c @@ -103,6 +103,7 @@ set(SRC intern/smaa_textures.c engines/basic/basic_engine.c engines/basic/basic_shader.c + engines/compositor/compositor_engine.cc engines/image/image_engine.cc engines/image/image_shader.cc engines/eevee/eevee_bloom.c @@ -134,10 +135,14 @@ set(SRC engines/eevee/eevee_temporal_sampling.c engines/eevee/eevee_volumes.c engines/eevee_next/eevee_camera.cc + engines/eevee_next/eevee_depth_of_field.cc engines/eevee_next/eevee_engine.cc engines/eevee_next/eevee_film.cc engines/eevee_next/eevee_instance.cc + engines/eevee_next/eevee_light.cc engines/eevee_next/eevee_material.cc + engines/eevee_next/eevee_hizbuffer.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 @@ -212,6 +217,7 @@ set(SRC intern/draw_common_shader_shared.h intern/draw_curves_private.h intern/draw_debug.h + intern/draw_debug.hh intern/draw_hair_private.h intern/draw_instance_data.h intern/draw_manager.h @@ -228,6 +234,7 @@ set(SRC intern/smaa_textures.h engines/basic/basic_engine.h engines/basic/basic_private.h + engines/compositor/compositor_engine.h engines/eevee/eevee_engine.h engines/eevee/eevee_lightcache.h engines/eevee/eevee_lut.h @@ -259,6 +266,7 @@ set(SRC set(LIB bf_blenkernel bf_blenlib + bf_realtime_compositor bf_windowmanager ) @@ -361,6 +369,22 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_attributes_lib.glsl engines/eevee_next/shaders/eevee_camera_lib.glsl + engines/eevee_next/shaders/eevee_colorspace_lib.glsl + engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl + engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_filter_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_gather_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_hole_fill_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl + engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl + engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl + engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_tiles_dilate_comp.glsl + engines/eevee_next/shaders/eevee_depth_of_field_tiles_flatten_comp.glsl engines/eevee_next/shaders/eevee_film_comp.glsl engines/eevee_next/shaders/eevee_film_frag.glsl engines/eevee_next/shaders/eevee_film_lib.glsl @@ -368,7 +392,23 @@ 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_hiz_debug_frag.glsl + engines/eevee_next/shaders/eevee_hiz_update_comp.glsl + engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl + engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl + engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl + engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl + engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl + engines/eevee_next/shaders/eevee_light_eval_lib.glsl + engines/eevee_next/shaders/eevee_light_iter_lib.glsl + engines/eevee_next/shaders/eevee_light_lib.glsl + engines/eevee_next/shaders/eevee_ltc_lib.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 @@ -409,22 +449,25 @@ set(GLSL_SRC engines/workbench/workbench_shader_shared.h + intern/shaders/common_aabb_lib.glsl intern/shaders/common_attribute_lib.glsl intern/shaders/common_colormanagement_lib.glsl + intern/shaders/common_debug_draw_lib.glsl + intern/shaders/common_debug_print_lib.glsl + intern/shaders/common_debug_shape_lib.glsl + intern/shaders/common_fullscreen_vert.glsl + intern/shaders/common_fxaa_lib.glsl intern/shaders/common_globals_lib.glsl intern/shaders/common_gpencil_lib.glsl - intern/shaders/common_pointcloud_lib.glsl intern/shaders/common_hair_lib.glsl - intern/shaders/common_hair_refine_vert.glsl intern/shaders/common_hair_refine_comp.glsl - intern/shaders/common_math_lib.glsl + intern/shaders/common_hair_refine_vert.glsl + intern/shaders/common_intersect_lib.glsl intern/shaders/common_math_geom_lib.glsl - intern/shaders/common_view_clipping_lib.glsl - intern/shaders/common_view_lib.glsl - intern/shaders/common_fxaa_lib.glsl + intern/shaders/common_math_lib.glsl + intern/shaders/common_pointcloud_lib.glsl + intern/shaders/common_shape_lib.glsl intern/shaders/common_smaa_lib.glsl - intern/shaders/common_fullscreen_vert.glsl - intern/shaders/common_subdiv_custom_data_interp_comp.glsl intern/shaders/common_subdiv_ibo_lines_comp.glsl intern/shaders/common_subdiv_ibo_tris_comp.glsl @@ -437,6 +480,13 @@ set(GLSL_SRC intern/shaders/common_subdiv_vbo_edituv_strech_area_comp.glsl intern/shaders/common_subdiv_vbo_lnor_comp.glsl intern/shaders/common_subdiv_vbo_sculpt_data_comp.glsl + intern/shaders/common_view_clipping_lib.glsl + intern/shaders/common_view_lib.glsl + intern/shaders/draw_debug_draw_display_frag.glsl + intern/shaders/draw_debug_draw_display_vert.glsl + intern/shaders/draw_debug_info.hh + intern/shaders/draw_debug_print_display_frag.glsl + intern/shaders/draw_debug_print_display_vert.glsl intern/draw_common_shader_shared.h intern/draw_shader_shared.h diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc new file mode 100644 index 00000000000..f36a59a4ce6 --- /dev/null +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_listbase.h" +#include "BLI_math_vec_types.hh" +#include "BLI_string_ref.hh" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_ID_enums.h" +#include "DNA_scene_types.h" + +#include "DEG_depsgraph_query.h" + +#include "DRW_render.h" + +#include "IMB_colormanagement.h" + +#include "COM_context.hh" +#include "COM_evaluator.hh" +#include "COM_texture_pool.hh" + +#include "GPU_texture.h" + +namespace blender::draw::compositor { + +class TexturePool : public realtime_compositor::TexturePool { + public: + GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) override + { + DrawEngineType *owner = (DrawEngineType *)this; + return DRW_texture_pool_query_2d(size.x, size.y, format, owner); + } +}; + +class Context : public realtime_compositor::Context { + private: + /* A pointer to the info message of the compositor engine. This is a char array of size + * GPU_INFO_SIZE. The message is cleared prior to updating or evaluating the compositor. */ + char *info_message_; + + public: + Context(realtime_compositor::TexturePool &texture_pool, char *info_message) + : realtime_compositor::Context(texture_pool), info_message_(info_message) + { + } + + const Scene *get_scene() const override + { + return DRW_context_state_get()->scene; + } + + int2 get_output_size() override + { + return int2(float2(DRW_viewport_size_get())); + } + + GPUTexture *get_output_texture() override + { + return DRW_viewport_texture_list_get()->color; + } + + GPUTexture *get_input_texture(int UNUSED(view_layer), eScenePassType UNUSED(pass_type)) override + { + return get_output_texture(); + } + + StringRef get_view_name() override + { + const SceneRenderView *view = static_cast<SceneRenderView *>( + BLI_findlink(&get_scene()->r.views, DRW_context_state_get()->v3d->multiview_eye)); + return view->name; + } + + void set_info_message(StringRef message) const override + { + message.copy(info_message_, GPU_INFO_SIZE); + } +}; + +class Engine { + private: + TexturePool texture_pool_; + Context context_; + realtime_compositor::Evaluator evaluator_; + /* Stores the viewport size at the time the last compositor evaluation happened. See the + * update_viewport_size method for more information. */ + int2 last_viewport_size_; + + public: + Engine(char *info_message) + : context_(texture_pool_, info_message), + evaluator_(context_, node_tree()), + last_viewport_size_(context_.get_output_size()) + { + } + + /* Update the viewport size and evaluate the compositor. */ + void draw() + { + update_viewport_size(); + evaluator_.evaluate(); + } + + /* If the size of the viewport changed from the last time the compositor was evaluated, update + * the viewport size and reset the evaluator. That's because the evaluator compiles the node tree + * in a manner that is specifically optimized for the size of the viewport. This should be called + * before evaluating the compositor. */ + void update_viewport_size() + { + if (last_viewport_size_ == context_.get_output_size()) { + return; + } + + last_viewport_size_ = context_.get_output_size(); + + evaluator_.reset(); + } + + /* If the compositor node tree changed, reset the evaluator. */ + void update(const Depsgraph *depsgraph) + { + if (DEG_id_type_updated(depsgraph, ID_NT)) { + evaluator_.reset(); + } + } + + /* Get a reference to the compositor node tree. */ + static bNodeTree &node_tree() + { + return *DRW_context_state_get()->scene->nodetree; + } +}; + +} // namespace blender::draw::compositor + +using namespace blender::draw::compositor; + +struct COMPOSITOR_Data { + DrawEngineType *engine_type; + DRWViewportEmptyList *fbl; + DRWViewportEmptyList *txl; + DRWViewportEmptyList *psl; + DRWViewportEmptyList *stl; + Engine *instance_data; + char info[GPU_INFO_SIZE]; +}; + +static void compositor_engine_init(void *data) +{ + COMPOSITOR_Data *compositor_data = static_cast<COMPOSITOR_Data *>(data); + + if (!compositor_data->instance_data) { + compositor_data->instance_data = new Engine(compositor_data->info); + } +} + +static void compositor_engine_free(void *instance_data) +{ + Engine *engine = static_cast<Engine *>(instance_data); + delete engine; +} + +static void compositor_engine_draw(void *data) +{ + const COMPOSITOR_Data *compositor_data = static_cast<COMPOSITOR_Data *>(data); + compositor_data->instance_data->draw(); +} + +static void compositor_engine_update(void *data) +{ + COMPOSITOR_Data *compositor_data = static_cast<COMPOSITOR_Data *>(data); + + /* Clear any info message that was set in a previous update. */ + compositor_data->info[0] = '\0'; + + if (compositor_data->instance_data) { + compositor_data->instance_data->update(DRW_context_state_get()->depsgraph); + } +} + +extern "C" { + +static const DrawEngineDataSize compositor_data_size = DRW_VIEWPORT_DATA_SIZE(COMPOSITOR_Data); + +DrawEngineType draw_engine_compositor_type = { + nullptr, /* next */ + nullptr, /* prev */ + N_("Compositor"), /* idname */ + &compositor_data_size, /* vedata_size */ + &compositor_engine_init, /* engine_init */ + nullptr, /* engine_free */ + &compositor_engine_free, /* instance_free */ + nullptr, /* cache_init */ + nullptr, /* cache_populate */ + nullptr, /* cache_finish */ + &compositor_engine_draw, /* draw_scene */ + &compositor_engine_update, /* view_update */ + nullptr, /* id_update */ + nullptr, /* render_to_image */ + nullptr, /* store_metadata */ +}; +} diff --git a/source/blender/draw/engines/compositor/compositor_engine.h b/source/blender/draw/engines/compositor/compositor_engine.h new file mode 100644 index 00000000000..5de0de8a0b3 --- /dev/null +++ b/source/blender/draw/engines/compositor/compositor_engine.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern DrawEngineType draw_engine_compositor_type; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/engines/eevee/eevee_shadows_cascade.c b/source/blender/draw/engines/eevee/eevee_shadows_cascade.c index 536242f67d8..a3ab4cdb830 100644 --- a/source/blender/draw/engines/eevee/eevee_shadows_cascade.c +++ b/source/blender/draw/engines/eevee/eevee_shadows_cascade.c @@ -357,7 +357,7 @@ static void eevee_shadow_cascade_setup(EEVEE_LightsInfo *linfo, mul_m4_m4m4(csm_data->shadowmat[c], texcomat, viewprojmat); #ifdef DEBUG_CSM - DRW_debug_m4_as_bbox(viewprojmat, dbg_col, true); + DRW_debug_m4_as_bbox(viewprojmat, true, dbg_col); #endif } diff --git a/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl index 688ae4915e1..7dec30a96b1 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl @@ -124,7 +124,7 @@ void dof_slight_focus_gather(float radius, out vec4 out_color, out float out_wei dof_gather_accumulate_resolve(total_sample_count, bg_accum, bg_col, bg_weight, unused_occlusion); dof_gather_accumulate_resolve(total_sample_count, fg_accum, fg_col, fg_weight, unused_occlusion); - /* Fix weighting issues on perfectly focus > slight focus transitionning areas. */ + /* Fix weighting issues on perfectly focus > slight focus transitioning areas. */ if (abs(center_data.coc) < 0.5) { bg_col = center_data.color; bg_weight = 1.0; diff --git a/source/blender/draw/engines/eevee/shaders/effect_dof_scatter_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_dof_scatter_frag.glsl index 06dcbeaed66..7230758a93f 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_dof_scatter_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_dof_scatter_frag.glsl @@ -67,7 +67,7 @@ void main(void) /* Occlude the sprite with geometry from the same field * using a VSM like chebychev test (slide 85). */ float mean = occlusion_data.x; - float variance = occlusion_data.x; + float variance = occlusion_data.y; shapes *= variance * safe_rcp(variance + sqr(max(cocs * correction_fac - mean, 0.0))); } diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.hh b/source/blender/draw/engines/eevee_next/eevee_camera.hh index 8bf64199246..49f9b14e11b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.hh +++ b/source/blender/draw/engines/eevee_next/eevee_camera.hh @@ -82,7 +82,6 @@ class Camera { private: Instance &inst_; - /** Double buffered to detect changes and have history for re-projection. */ CameraDataBuf data_; public: @@ -112,6 +111,10 @@ class Camera { { return data_.type == CAMERA_ORTHO; } + bool is_perspective() const + { + return data_.type == CAMERA_PERSP; + } const float3 &position() const { return *reinterpret_cast<const float3 *>(data_.viewinv[3]); diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index 1e7979b594e..67643471639 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -11,12 +11,18 @@ #pragma once -/** - * Number of items in a culling batch. Needs to be Power of 2. Must be <= to 65536. - * Current limiting factor is the sorting phase which is single pass and only sort within a - * thread-group which maximum size is 1024. - */ -#define CULLING_BATCH_SIZE 1024 +/* Hierarchical Z down-sampling. */ +#define HIZ_MIP_COUNT 8 +/* NOTE: The shader is written to update 5 mipmaps using LDS. */ +#define HIZ_GROUP_SIZE 32 + +/* Avoid too much overhead caused by resizing the light buffers too many time. */ +#define LIGHT_CHUNK 256 + +#define CULLING_SELECT_GROUP_SIZE 256 +#define CULLING_SORT_GROUP_SIZE 256 +#define CULLING_ZBIN_GROUP_SIZE 1024 +#define CULLING_TILE_GROUP_SIZE 1024 /** * IMPORTANT: Some data packing are tweaked for these values. @@ -34,14 +40,31 @@ #define SHADOW_MAX_PAGE 4096 #define SHADOW_PAGE_PER_ROW 64 -#define HIZ_MIP_COUNT 6u -/* Group size is 2x smaller because we simply copy the level 0. */ -#define HIZ_GROUP_SIZE 1u << (HIZ_MIP_COUNT - 2u) - +/* Ray-tracing. */ #define RAYTRACE_GROUP_SIZE 16 #define RAYTRACE_MAX_TILES (16384 / RAYTRACE_GROUP_SIZE) * (16384 / RAYTRACE_GROUP_SIZE) /* Minimum visibility size. */ #define LIGHTPROBE_FILTER_VIS_GROUP_SIZE 16 +/* Film. */ #define FILM_GROUP_SIZE 16 + +/* Motion Blur. */ +#define MOTION_BLUR_GROUP_SIZE 32 +#define MOTION_BLUR_DILATE_GROUP_SIZE 512 + +/* Depth Of Field. */ +#define DOF_TILES_SIZE 8 +#define DOF_TILES_FLATTEN_GROUP_SIZE DOF_TILES_SIZE +#define DOF_TILES_DILATE_GROUP_SIZE 8 +#define DOF_BOKEH_LUT_SIZE 32 +#define DOF_MAX_SLIGHT_FOCUS_RADIUS 5 +#define DOF_SLIGHT_FOCUS_SAMPLE_MAX 16 +#define DOF_MIP_COUNT 4 +#define DOF_REDUCE_GROUP_SIZE (1 << (DOF_MIP_COUNT - 1)) +#define DOF_DEFAULT_GROUP_SIZE 32 +#define DOF_STABILIZE_GROUP_SIZE 16 +#define DOF_FILTER_GROUP_SIZE 8 +#define DOF_GATHER_GROUP_SIZE DOF_TILES_SIZE +#define DOF_RESOLVE_GROUP_SIZE (DOF_TILES_SIZE * 2) diff --git a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc new file mode 100644 index 00000000000..afabeb8b729 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc @@ -0,0 +1,768 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. + */ + +/** \file + * \ingroup eevee + * + * Depth of field post process effect. + * + * There are 2 methods to achieve this effect. + * - The first uses projection matrix offsetting and sample accumulation to give + * reference quality depth of field. But this needs many samples to hide the + * under-sampling. + * - The second one is a post-processing based one. It follows the + * implementation described in the presentation + * "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. + * There are some difference with our actual implementation that prioritize quality. + */ + +#include "DRW_render.h" + +#include "BKE_camera.h" +#include "DNA_camera_types.h" + +#include "GPU_platform.h" +#include "GPU_texture.h" +#include "GPU_uniform_buffer.h" + +#include "eevee_camera.hh" +#include "eevee_instance.hh" +#include "eevee_sampling.hh" +#include "eevee_shader.hh" +#include "eevee_shader_shared.hh" + +#include "eevee_depth_of_field.hh" + +namespace blender::eevee { + +/* -------------------------------------------------------------------- */ +/** \name Depth of field + * \{ */ + +void DepthOfField::init() +{ + const SceneEEVEE &sce_eevee = inst_.scene->eevee; + const Object *camera_object_eval = inst_.camera_eval_object; + const ::Camera *camera = (camera_object_eval) ? + reinterpret_cast<const ::Camera *>(camera_object_eval->data) : + nullptr; + if (camera == nullptr) { + /* Set to invalid value for update detection */ + data_.scatter_color_threshold = -1.0f; + return; + } + /* Reminder: These are parameters not interpolated by motion blur. */ + int update = 0; + int sce_flag = sce_eevee.flag; + update += assign_if_different(do_jitter_, (sce_flag & SCE_EEVEE_DOF_JITTER) != 0); + update += assign_if_different(user_overblur_, sce_eevee.bokeh_overblur / 100.0f); + update += assign_if_different(fx_max_coc_, sce_eevee.bokeh_max_size); + update += assign_if_different(data_.scatter_color_threshold, sce_eevee.bokeh_threshold); + update += assign_if_different(data_.scatter_neighbor_max_color, sce_eevee.bokeh_neighbor_max); + update += assign_if_different(data_.bokeh_blades, float(camera->dof.aperture_blades)); + if (update > 0) { + inst_.sampling.reset(); + } +} + +void DepthOfField::sync() +{ + const Camera &camera = inst_.camera; + const Object *camera_object_eval = inst_.camera_eval_object; + const ::Camera *camera_data = (camera_object_eval) ? + reinterpret_cast<const ::Camera *>(camera_object_eval->data) : + nullptr; + + int update = 0; + + if (camera_data == nullptr || (camera_data->dof.flag & CAM_DOF_ENABLED) == 0) { + update += assign_if_different(jitter_radius_, 0.0f); + update += assign_if_different(fx_radius_, 0.0f); + if (update > 0) { + inst_.sampling.reset(); + } + return; + } + + float2 anisotropic_scale = {clamp_f(1.0f / camera_data->dof.aperture_ratio, 1e-5f, 1.0f), + clamp_f(camera_data->dof.aperture_ratio, 1e-5f, 1.0f)}; + update += assign_if_different(data_.bokeh_anisotropic_scale, anisotropic_scale); + update += assign_if_different(data_.bokeh_rotation, camera_data->dof.aperture_rotation); + update += assign_if_different(focus_distance_, + BKE_camera_object_dof_distance(camera_object_eval)); + data_.bokeh_anisotropic_scale_inv = 1.0f / data_.bokeh_anisotropic_scale; + + float fstop = max_ff(camera_data->dof.aperture_fstop, 1e-5f); + + if (update) { + inst_.sampling.reset(); + } + + float aperture = 1.0f / (2.0f * fstop); + if (camera.is_perspective()) { + aperture *= camera_data->lens * 1e-3f; + } + + if (camera.is_orthographic()) { + /* FIXME: Why is this needed? Some kind of implicit unit conversion? */ + aperture *= 0.04f; + /* Really strange behavior from Cycles but replicating. */ + focus_distance_ += camera.data_get().clip_near; + } + + if (camera.is_panoramic()) { + /* FIXME: Eyeballed. */ + aperture *= 0.185f; + } + + if (camera_data->dof.aperture_ratio < 1.0) { + /* If ratio is scaling the bokeh outwards, we scale the aperture so that + * the gather kernel size will encompass the maximum axis. */ + aperture /= max_ff(camera_data->dof.aperture_ratio, 1e-5f); + } + + float jitter_radius, fx_radius; + + /* Balance blur radius between fx dof and jitter dof. */ + if (do_jitter_ && (inst_.sampling.dof_ring_count_get() > 0) && !camera.is_panoramic() && + !inst_.is_viewport()) { + /* Compute a minimal overblur radius to fill the gaps between the samples. + * This is just the simplified form of dividing the area of the bokeh by + * the number of samples. */ + float minimal_overblur = 1.0f / sqrtf(inst_.sampling.dof_sample_count_get()); + + fx_radius = (minimal_overblur + user_overblur_) * aperture; + /* Avoid dilating the shape. Over-blur only soften. */ + jitter_radius = max_ff(0.0f, aperture - fx_radius); + } + else { + jitter_radius = 0.0f; + fx_radius = aperture; + } + + /* Disable post fx if result wouldn't be noticeable. */ + if (fx_max_coc_ <= 0.5f) { + fx_radius = 0.0f; + } + + update += assign_if_different(jitter_radius_, jitter_radius); + update += assign_if_different(fx_radius_, fx_radius); + if (update > 0) { + inst_.sampling.reset(); + } + + if (fx_radius_ == 0.0f) { + return; + } + + /* TODO(fclem): Once we render into multiple view, we will need to use the maximum resolution. */ + int2 max_render_res = inst_.film.render_extent_get(); + int2 half_res = math::divide_ceil(max_render_res, int2(2)); + int2 reduce_size = math::ceil_to_multiple(half_res, int2(DOF_REDUCE_GROUP_SIZE)); + + data_.gather_uv_fac = 1.0f / float2(reduce_size); + + /* Now that we know the maximum render resolution of every view, using depth of field, allocate + * the reduced buffers. Color needs to be signed format here. See note in shader for + * explanation. Do not use texture pool because of needs mipmaps. */ + reduced_color_tx_.ensure_2d(GPU_RGBA16F, reduce_size, nullptr, DOF_MIP_COUNT); + reduced_coc_tx_.ensure_2d(GPU_R16F, reduce_size, nullptr, DOF_MIP_COUNT); + reduced_color_tx_.ensure_mip_views(); + reduced_coc_tx_.ensure_mip_views(); + + /* Resize the scatter list to contain enough entry to cover half the screen with sprites (which + * is unlikely due to local contrast test). */ + data_.scatter_max_rect = (reduced_color_tx_.pixel_count() / 4) / 2; + scatter_fg_list_buf_.resize(data_.scatter_max_rect); + scatter_bg_list_buf_.resize(data_.scatter_max_rect); + + bokeh_lut_pass_sync(); + setup_pass_sync(); + stabilize_pass_sync(); + downsample_pass_sync(); + reduce_pass_sync(); + tiles_flatten_pass_sync(); + tiles_dilate_pass_sync(); + gather_pass_sync(); + filter_pass_sync(); + scatter_pass_sync(); + hole_fill_pass_sync(); + resolve_pass_sync(); +} + +void DepthOfField::jitter_apply(float4x4 &winmat, float4x4 &viewmat) +{ + if (jitter_radius_ == 0.0f) { + return; + } + + float radius, theta; + inst_.sampling.dof_disk_sample_get(&radius, &theta); + + if (data_.bokeh_blades >= 3.0f) { + theta = circle_to_polygon_angle(data_.bokeh_blades, theta); + radius *= circle_to_polygon_radius(data_.bokeh_blades, theta); + } + radius *= jitter_radius_; + theta += data_.bokeh_rotation; + + /* Sample in View Space. */ + float2 sample = float2(radius * cosf(theta), radius * sinf(theta)); + sample *= data_.bokeh_anisotropic_scale; + /* Convert to NDC Space. */ + float3 jitter = float3(UNPACK2(sample), -focus_distance_); + float3 center = float3(0.0f, 0.0f, -focus_distance_); + mul_project_m4_v3(winmat.ptr(), jitter); + mul_project_m4_v3(winmat.ptr(), center); + + const bool is_ortho = (winmat[2][3] != -1.0f); + if (is_ortho) { + sample *= focus_distance_; + } + /* Translate origin. */ + sub_v2_v2(viewmat[3], sample); + /* Skew winmat Z axis. */ + add_v2_v2(winmat[2], center - jitter); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Passes setup. + * \{ */ + +void DepthOfField::bokeh_lut_pass_sync() +{ + const bool has_anisotropy = data_.bokeh_anisotropic_scale != float2(1.0f); + if (!has_anisotropy && (data_.bokeh_blades == 0.0)) { + /* No need for LUTs in these cases. */ + bokeh_lut_ps_ = nullptr; + return; + } + + /* Precompute bokeh texture. */ + bokeh_lut_ps_ = DRW_pass_create("Dof.bokeh_lut_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_BOKEH_LUT); + DRWShadingGroup *grp = DRW_shgroup_create(sh, bokeh_lut_ps_); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_image_ref(grp, "out_gather_lut_img", &bokeh_gather_lut_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_scatter_lut_img", &bokeh_scatter_lut_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_resolve_lut_img", &bokeh_resolve_lut_tx_); + DRW_shgroup_call_compute(grp, 1, 1, 1); +} + +void DepthOfField::setup_pass_sync() +{ + RenderBuffers &render_buffers = inst_.render_buffers; + + setup_ps_ = DRW_pass_create("Dof.setup_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_SETUP); + DRWShadingGroup *grp = DRW_shgroup_create(sh, setup_ps_); + DRW_shgroup_uniform_texture_ref_ex(grp, "color_tx", &input_color_tx_, no_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, no_filter); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &setup_color_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_coc_img", &setup_coc_tx_); + DRW_shgroup_call_compute_ref(grp, dispatch_setup_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); +} + +void DepthOfField::stabilize_pass_sync() +{ + RenderBuffers &render_buffers = inst_.render_buffers; + VelocityModule &velocity = inst_.velocity; + + stabilize_ps_ = DRW_pass_create("Dof.stabilize_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_STABILIZE); + DRWShadingGroup *grp = DRW_shgroup_create(sh, stabilize_ps_); + DRW_shgroup_uniform_block_ref(grp, "camera_prev", &(*velocity.camera_steps[STEP_PREVIOUS])); + DRW_shgroup_uniform_block_ref(grp, "camera_curr", &(*velocity.camera_steps[STEP_CURRENT])); + /* This is only for temporal stability. The next step is not needed. */ + DRW_shgroup_uniform_block_ref(grp, "camera_next", &(*velocity.camera_steps[STEP_PREVIOUS])); + DRW_shgroup_uniform_texture_ref_ex(grp, "coc_tx", &setup_coc_tx_, no_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "color_tx", &setup_color_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_history_tx", &stabilize_input_, with_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, no_filter); + DRW_shgroup_uniform_bool(grp, "use_history", &stabilize_valid_history_, 1); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_image(grp, "out_coc_img", reduced_coc_tx_.mip_view(0)); + DRW_shgroup_uniform_image(grp, "out_color_img", reduced_color_tx_.mip_view(0)); + DRW_shgroup_uniform_image_ref(grp, "out_history_img", &stabilize_output_tx_); + DRW_shgroup_call_compute_ref(grp, dispatch_stabilize_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); +} + +void DepthOfField::downsample_pass_sync() +{ + downsample_ps_ = DRW_pass_create("Dof.downsample_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_DOWNSAMPLE); + DRWShadingGroup *grp = DRW_shgroup_create(sh, downsample_ps_); + DRW_shgroup_uniform_texture_ex(grp, "color_tx", reduced_color_tx_.mip_view(0), no_filter); + DRW_shgroup_uniform_texture_ex(grp, "coc_tx", reduced_coc_tx_.mip_view(0), no_filter); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &downsample_tx_); + DRW_shgroup_call_compute_ref(grp, dispatch_downsample_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); +} + +void DepthOfField::reduce_pass_sync() +{ + reduce_ps_ = DRW_pass_create("Dof.reduce_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_REDUCE); + DRWShadingGroup *grp = DRW_shgroup_create(sh, reduce_ps_); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_texture_ref_ex(grp, "downsample_tx", &downsample_tx_, no_filter); + DRW_shgroup_storage_block(grp, "scatter_fg_list_buf", scatter_fg_list_buf_); + DRW_shgroup_storage_block(grp, "scatter_bg_list_buf", scatter_bg_list_buf_); + DRW_shgroup_storage_block(grp, "scatter_fg_indirect_buf", scatter_fg_indirect_buf_); + DRW_shgroup_storage_block(grp, "scatter_bg_indirect_buf", scatter_bg_indirect_buf_); + DRW_shgroup_uniform_image(grp, "inout_color_lod0_img", reduced_color_tx_.mip_view(0)); + DRW_shgroup_uniform_image(grp, "out_color_lod1_img", reduced_color_tx_.mip_view(1)); + DRW_shgroup_uniform_image(grp, "out_color_lod2_img", reduced_color_tx_.mip_view(2)); + DRW_shgroup_uniform_image(grp, "out_color_lod3_img", reduced_color_tx_.mip_view(3)); + DRW_shgroup_uniform_image(grp, "in_coc_lod0_img", reduced_coc_tx_.mip_view(0)); + DRW_shgroup_uniform_image(grp, "out_coc_lod1_img", reduced_coc_tx_.mip_view(1)); + DRW_shgroup_uniform_image(grp, "out_coc_lod2_img", reduced_coc_tx_.mip_view(2)); + DRW_shgroup_uniform_image(grp, "out_coc_lod3_img", reduced_coc_tx_.mip_view(3)); + DRW_shgroup_call_compute_ref(grp, dispatch_reduce_size_); + /* NOTE: Command buffer barrier is done automatically by the GPU backend. */ + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_STORAGE); +} + +void DepthOfField::tiles_flatten_pass_sync() +{ + tiles_flatten_ps_ = DRW_pass_create("Dof.tiles_flatten_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_TILES_FLATTEN); + DRWShadingGroup *grp = DRW_shgroup_create(sh, tiles_flatten_ps_); + /* NOTE(fclem): We should use the reduced_coc_tx_ as it is stable, but we need the slight focus + * flag from the setup pass. A better way would be to do the brute-force in focus gather without + * this. */ + DRW_shgroup_uniform_texture_ref_ex(grp, "coc_tx", &setup_coc_tx_, no_filter); + DRW_shgroup_uniform_image_ref(grp, "out_tiles_fg_img", &tiles_fg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "out_tiles_bg_img", &tiles_bg_tx_.current()); + DRW_shgroup_call_compute_ref(grp, dispatch_tiles_flatten_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS); +} + +void DepthOfField::tiles_dilate_pass_sync() +{ + tiles_dilate_minmax_ps_ = DRW_pass_create("Dof.tiles_dilate_minmax_ps_", DRW_STATE_NO_DRAW); + tiles_dilate_minabs_ps_ = DRW_pass_create("Dof.tiles_dilate_minabs_ps_", DRW_STATE_NO_DRAW); + for (int pass = 0; pass < 2; pass++) { + DRWPass *drw_pass = (pass == 0) ? tiles_dilate_minmax_ps_ : tiles_dilate_minabs_ps_; + GPUShader *sh = inst_.shaders.static_shader_get((pass == 0) ? DOF_TILES_DILATE_MINMAX : + DOF_TILES_DILATE_MINABS); + DRWShadingGroup *grp = DRW_shgroup_create(sh, drw_pass); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_fg_img", &tiles_fg_tx_.previous()); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_bg_img", &tiles_bg_tx_.previous()); + DRW_shgroup_uniform_image_ref(grp, "out_tiles_fg_img", &tiles_fg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "out_tiles_bg_img", &tiles_bg_tx_.current()); + DRW_shgroup_uniform_int(grp, "ring_count", &tiles_dilate_ring_count_, 1); + DRW_shgroup_uniform_int(grp, "ring_width_multiplier", &tiles_dilate_ring_width_mul_, 1); + DRW_shgroup_call_compute_ref(grp, dispatch_tiles_dilate_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS); + } +} + +void DepthOfField::gather_pass_sync() +{ + gather_fg_ps_ = DRW_pass_create("Dof.gather_fg_ps_", DRW_STATE_NO_DRAW); + gather_bg_ps_ = DRW_pass_create("Dof.gather_bg_ps_", DRW_STATE_NO_DRAW); + for (int pass = 0; pass < 2; pass++) { + SwapChain<TextureFromPool, 2> &color_chain = (pass == 0) ? color_fg_tx_ : color_bg_tx_; + SwapChain<TextureFromPool, 2> &weight_chain = (pass == 0) ? weight_fg_tx_ : weight_bg_tx_; + bool use_lut = bokeh_lut_ps_ != nullptr; + eShaderType sh_type = (pass == 0) ? + (use_lut ? DOF_GATHER_FOREGROUND_LUT : DOF_GATHER_FOREGROUND) : + (use_lut ? DOF_GATHER_BACKGROUND_LUT : DOF_GATHER_BACKGROUND); + GPUShader *sh = inst_.shaders.static_shader_get(sh_type); + DRWShadingGroup *grp = DRW_shgroup_create(sh, (pass == 0) ? gather_fg_ps_ : gather_bg_ps_); + inst_.sampling.bind_resources(grp); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_texture_ex(grp, "color_bilinear_tx", reduced_color_tx_, gather_bilinear); + DRW_shgroup_uniform_texture_ex(grp, "color_tx", reduced_color_tx_, gather_nearest); + DRW_shgroup_uniform_texture_ex(grp, "coc_tx", reduced_coc_tx_, gather_nearest); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_fg_img", &tiles_fg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_bg_img", &tiles_bg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &color_chain.current()); + DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_chain.current()); + DRW_shgroup_uniform_image_ref(grp, "out_occlusion_img", &occlusion_tx_); + DRW_shgroup_uniform_texture_ref(grp, "bokeh_lut_tx", &bokeh_gather_lut_tx_); + DRW_shgroup_call_compute_ref(grp, dispatch_gather_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); + } +} + +void DepthOfField::filter_pass_sync() +{ + filter_fg_ps_ = DRW_pass_create("Dof.filter_fg_ps_", DRW_STATE_NO_DRAW); + filter_bg_ps_ = DRW_pass_create("Dof.filter_bg_ps_", DRW_STATE_NO_DRAW); + for (int pass = 0; pass < 2; pass++) { + SwapChain<TextureFromPool, 2> &color_chain = (pass == 0) ? color_fg_tx_ : color_bg_tx_; + SwapChain<TextureFromPool, 2> &weight_chain = (pass == 0) ? weight_fg_tx_ : weight_bg_tx_; + GPUShader *sh = inst_.shaders.static_shader_get(DOF_FILTER); + DRWShadingGroup *grp = DRW_shgroup_create(sh, (pass == 0) ? filter_fg_ps_ : filter_bg_ps_); + DRW_shgroup_uniform_texture_ref(grp, "color_tx", &color_chain.previous()); + DRW_shgroup_uniform_texture_ref(grp, "weight_tx", &weight_chain.previous()); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &color_chain.current()); + DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_chain.current()); + DRW_shgroup_call_compute_ref(grp, dispatch_filter_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); + } +} + +void DepthOfField::scatter_pass_sync() +{ + DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL; + scatter_fg_ps_ = DRW_pass_create("Dof.scatter_fg_ps_", state); + scatter_bg_ps_ = DRW_pass_create("Dof.scatter_bg_ps_", state); + for (int pass = 0; pass < 2; pass++) { + GPUStorageBuf *scatter_buf = (pass == 0) ? scatter_fg_indirect_buf_ : scatter_bg_indirect_buf_; + GPUStorageBuf *rect_list_buf = (pass == 0) ? scatter_fg_list_buf_ : scatter_bg_list_buf_; + + GPUShader *sh = inst_.shaders.static_shader_get(DOF_SCATTER); + DRWShadingGroup *grp = DRW_shgroup_create(sh, (pass == 0) ? scatter_fg_ps_ : scatter_bg_ps_); + DRW_shgroup_uniform_bool_copy(grp, "use_bokeh_lut", bokeh_lut_ps_ != nullptr); + DRW_shgroup_storage_block(grp, "scatter_list_buf", rect_list_buf); + DRW_shgroup_uniform_texture_ref(grp, "bokeh_lut_tx", &bokeh_scatter_lut_tx_); + DRW_shgroup_uniform_texture_ref(grp, "occlusion_tx", &occlusion_tx_); + DRW_shgroup_call_procedural_indirect(grp, GPU_PRIM_TRI_STRIP, nullptr, scatter_buf); + if (pass == 0) { + /* Avoid background gather pass writing to the occlusion_tx mid pass. */ + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS); + } + } +} + +void DepthOfField::hole_fill_pass_sync() +{ + hole_fill_ps_ = DRW_pass_create("Dof.hole_fill_ps_", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(DOF_GATHER_HOLE_FILL); + DRWShadingGroup *grp = DRW_shgroup_create(sh, hole_fill_ps_); + inst_.sampling.bind_resources(grp); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_texture_ex(grp, "color_bilinear_tx", reduced_color_tx_, gather_bilinear); + DRW_shgroup_uniform_texture_ex(grp, "color_tx", reduced_color_tx_, gather_nearest); + DRW_shgroup_uniform_texture_ex(grp, "coc_tx", reduced_coc_tx_, gather_nearest); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_fg_img", &tiles_fg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_bg_img", &tiles_bg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &hole_fill_color_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &hole_fill_weight_tx_); + DRW_shgroup_call_compute_ref(grp, dispatch_gather_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); +} + +void DepthOfField::resolve_pass_sync() +{ + eGPUSamplerState with_filter = GPU_SAMPLER_FILTER; + RenderBuffers &render_buffers = inst_.render_buffers; + + resolve_ps_ = DRW_pass_create("Dof.resolve_ps_", DRW_STATE_NO_DRAW); + bool use_lut = bokeh_lut_ps_ != nullptr; + eShaderType sh_type = use_lut ? DOF_RESOLVE_LUT : DOF_RESOLVE; + GPUShader *sh = inst_.shaders.static_shader_get(sh_type); + DRWShadingGroup *grp = DRW_shgroup_create(sh, resolve_ps_); + inst_.sampling.bind_resources(grp); + DRW_shgroup_uniform_block(grp, "dof_buf", data_); + DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, no_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "color_tx", &input_color_tx_, no_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "stable_color_tx", &resolve_stable_color_tx_, no_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "color_bg_tx", &color_bg_tx_.current(), with_filter); + DRW_shgroup_uniform_texture_ref_ex(grp, "color_fg_tx", &color_fg_tx_.current(), with_filter); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_fg_img", &tiles_fg_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "in_tiles_bg_img", &tiles_bg_tx_.current()); + DRW_shgroup_uniform_texture_ref(grp, "weight_bg_tx", &weight_bg_tx_.current()); + DRW_shgroup_uniform_texture_ref(grp, "weight_fg_tx", &weight_fg_tx_.current()); + DRW_shgroup_uniform_texture_ref(grp, "color_hole_fill_tx", &hole_fill_color_tx_); + DRW_shgroup_uniform_texture_ref(grp, "weight_hole_fill_tx", &hole_fill_weight_tx_); + DRW_shgroup_uniform_texture_ref(grp, "bokeh_lut_tx", &bokeh_resolve_lut_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_color_img", &output_color_tx_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); + DRW_shgroup_call_compute_ref(grp, dispatch_resolve_size_); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Post-FX Rendering. + * \{ */ + +/* Similar to Film::update_sample_table() but with constant filter radius and constant sample + * count. */ +void DepthOfField::update_sample_table() +{ + float2 subpixel_offset = inst_.film.pixel_jitter_get(); + /* Since the film jitter is in full-screen res, divide by 2 to get the jitter in half res. */ + subpixel_offset *= 0.5; + + /* Same offsets as in dof_spatial_filtering(). */ + const std::array<int2, 4> plus_offsets = {int2(-1, 0), int2(0, -1), int2(1, 0), int2(0, 1)}; + + const float radius = 1.5f; + int i = 0; + for (int2 offset : plus_offsets) { + float2 pixel_ofs = float2(offset) - subpixel_offset; + data_.filter_samples_weight[i++] = film_filter_weight(radius, math::length_squared(pixel_ofs)); + } + data_.filter_center_weight = film_filter_weight(radius, math::length_squared(subpixel_offset)); +} + +void DepthOfField::render(GPUTexture **input_tx, + GPUTexture **output_tx, + DepthOfFieldBuffer &dof_buffer) +{ + if (fx_radius_ == 0.0f) { + return; + } + + input_color_tx_ = *input_tx; + output_color_tx_ = *output_tx; + extent_ = {GPU_texture_width(input_color_tx_), GPU_texture_height(input_color_tx_)}; + + { + const CameraData &cam_data = inst_.camera.data_get(); + data_.camera_type = cam_data.type; + /* OPTI(fclem) Could be optimized. */ + float3 jitter = float3(fx_radius_, 0.0f, -focus_distance_); + float3 center = float3(0.0f, 0.0f, -focus_distance_); + mul_project_m4_v3(cam_data.winmat.ptr(), jitter); + mul_project_m4_v3(cam_data.winmat.ptr(), center); + /* Simplify CoC calculation to a simple MADD. */ + if (inst_.camera.is_orthographic()) { + data_.coc_mul = (center[0] - jitter[0]) * 0.5f * extent_[0]; + data_.coc_bias = focus_distance_ * data_.coc_mul; + } + else { + data_.coc_bias = -(center[0] - jitter[0]) * 0.5f * extent_[0]; + data_.coc_mul = focus_distance_ * data_.coc_bias; + } + + float min_fg_coc = coc_radius_from_camera_depth(data_, -cam_data.clip_near); + float max_bg_coc = coc_radius_from_camera_depth(data_, -cam_data.clip_far); + if (data_.camera_type != CAMERA_ORTHO) { + /* Background is at infinity so maximum CoC is the limit of coc_radius_from_camera_depth + * at -inf. We only do this for perspective camera since orthographic coc limit is inf. */ + max_bg_coc = data_.coc_bias; + } + /* Clamp with user defined max. */ + data_.coc_abs_max = min_ff(max_ff(fabsf(min_fg_coc), fabsf(max_bg_coc)), fx_max_coc_); + /* TODO(fclem): Make this dependent of the quality of the gather pass. */ + data_.scatter_coc_threshold = 4.0f; + + update_sample_table(); + + data_.push_update(); + } + + int2 half_res = math::divide_ceil(extent_, int2(2)); + int2 quarter_res = math::divide_ceil(extent_, int2(4)); + int2 tile_res = math::divide_ceil(half_res, int2(DOF_TILES_SIZE)); + + dispatch_setup_size_ = int3(math::divide_ceil(half_res, int2(DOF_DEFAULT_GROUP_SIZE)), 1); + dispatch_stabilize_size_ = int3(math::divide_ceil(half_res, int2(DOF_STABILIZE_GROUP_SIZE)), 1); + dispatch_downsample_size_ = int3(math::divide_ceil(quarter_res, int2(DOF_DEFAULT_GROUP_SIZE)), + 1); + dispatch_reduce_size_ = int3(math::divide_ceil(half_res, int2(DOF_REDUCE_GROUP_SIZE)), 1); + dispatch_tiles_flatten_size_ = int3(math::divide_ceil(half_res, int2(DOF_TILES_SIZE)), 1); + dispatch_tiles_dilate_size_ = int3( + math::divide_ceil(tile_res, int2(DOF_TILES_DILATE_GROUP_SIZE)), 1); + dispatch_gather_size_ = int3(math::divide_ceil(half_res, int2(DOF_GATHER_GROUP_SIZE)), 1); + dispatch_filter_size_ = int3(math::divide_ceil(half_res, int2(DOF_FILTER_GROUP_SIZE)), 1); + dispatch_resolve_size_ = int3(math::divide_ceil(extent_, int2(DOF_RESOLVE_GROUP_SIZE)), 1); + + if (GPU_type_matches_ex(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) { + /* On Mesa, there is a sync bug which can make a portion of the main pass (usually one shader) + * leave blocks of un-initialized memory. Doing a flush seems to alleviate the issue. */ + GPU_flush(); + } + + DRW_stats_group_start("Depth of Field"); + + { + DRW_stats_group_start("Setup"); + { + bokeh_gather_lut_tx_.acquire(int2(DOF_BOKEH_LUT_SIZE), GPU_RG16F); + bokeh_scatter_lut_tx_.acquire(int2(DOF_BOKEH_LUT_SIZE), GPU_R16F); + bokeh_resolve_lut_tx_.acquire(int2(DOF_MAX_SLIGHT_FOCUS_RADIUS * 2 + 1), GPU_R16F); + + DRW_draw_pass(bokeh_lut_ps_); + } + { + setup_color_tx_.acquire(half_res, GPU_RGBA16F); + setup_coc_tx_.acquire(half_res, GPU_R16F); + + DRW_draw_pass(setup_ps_); + } + { + stabilize_output_tx_.acquire(half_res, GPU_RGBA16F); + stabilize_valid_history_ = !dof_buffer.stabilize_history_tx_.ensure_2d(GPU_RGBA16F, + half_res); + + if (stabilize_valid_history_ == false) { + /* Avoid uninitialized memory that can contain NaNs. */ + dof_buffer.stabilize_history_tx_.clear(float4(0.0f)); + } + + stabilize_input_ = dof_buffer.stabilize_history_tx_; + /* Outputs to reduced_*_tx_ mip 0. */ + DRW_draw_pass(stabilize_ps_); + + /* WATCH(fclem): Swap Texture an TextureFromPool internal GPUTexture in order to reuse + * the one that we just consumed. */ + TextureFromPool::swap(stabilize_output_tx_, dof_buffer.stabilize_history_tx_); + + /* Used by stabilize pass. */ + stabilize_output_tx_.release(); + setup_color_tx_.release(); + } + { + DRW_stats_group_start("Tile Prepare"); + + /* WARNING: If format changes, make sure dof_tile_* GLSL constants are properly encoded. */ + tiles_fg_tx_.previous().acquire(tile_res, GPU_R11F_G11F_B10F); + tiles_bg_tx_.previous().acquire(tile_res, GPU_R11F_G11F_B10F); + tiles_fg_tx_.current().acquire(tile_res, GPU_R11F_G11F_B10F); + tiles_bg_tx_.current().acquire(tile_res, GPU_R11F_G11F_B10F); + + DRW_draw_pass(tiles_flatten_ps_); + + /* Used by tile_flatten and stabilize_ps pass. */ + setup_coc_tx_.release(); + + /* Error introduced by gather center jittering. */ + const float error_multiplier = 1.0f + 1.0f / (DOF_GATHER_RING_COUNT + 0.5f); + int dilation_end_radius = ceilf((fx_max_coc_ * error_multiplier) / (DOF_TILES_SIZE * 2)); + + /* Run dilation twice. One for minmax and one for minabs. */ + for (int pass = 0; pass < 2; pass++) { + /* This algorithm produce the exact dilation radius by dividing it in multiple passes. */ + int dilation_radius = 0; + while (dilation_radius < dilation_end_radius) { + int remainder = dilation_end_radius - dilation_radius; + /* Do not step over any unvisited tile. */ + int max_multiplier = dilation_radius + 1; + + int ring_count = min_ii(DOF_DILATE_RING_COUNT, ceilf(remainder / (float)max_multiplier)); + int multiplier = min_ii(max_multiplier, floorf(remainder / (float)ring_count)); + + dilation_radius += ring_count * multiplier; + + tiles_dilate_ring_count_ = ring_count; + tiles_dilate_ring_width_mul_ = multiplier; + + tiles_fg_tx_.swap(); + tiles_bg_tx_.swap(); + + DRW_draw_pass((pass == 0) ? tiles_dilate_minmax_ps_ : tiles_dilate_minabs_ps_); + } + } + + tiles_fg_tx_.previous().release(); + tiles_bg_tx_.previous().release(); + + DRW_stats_group_end(); + } + + downsample_tx_.acquire(quarter_res, GPU_RGBA16F); + + DRW_draw_pass(downsample_ps_); + + scatter_fg_indirect_buf_.clear_to_zero(); + scatter_bg_indirect_buf_.clear_to_zero(); + + DRW_draw_pass(reduce_ps_); + + /* Used by reduce pass. */ + downsample_tx_.release(); + + DRW_stats_group_end(); + } + + for (int is_background = 0; is_background < 2; is_background++) { + DRW_stats_group_start(is_background ? "Background Convolution" : "Foreground Convolution"); + + SwapChain<TextureFromPool, 2> &color_tx = is_background ? color_bg_tx_ : color_fg_tx_; + SwapChain<TextureFromPool, 2> &weight_tx = is_background ? weight_bg_tx_ : weight_fg_tx_; + Framebuffer &scatter_fb = is_background ? scatter_bg_fb_ : scatter_fg_fb_; + DRWPass *gather_ps = is_background ? gather_bg_ps_ : gather_fg_ps_; + DRWPass *filter_ps = is_background ? filter_bg_ps_ : filter_fg_ps_; + DRWPass *scatter_ps = is_background ? scatter_bg_ps_ : scatter_fg_ps_; + + color_tx.current().acquire(half_res, GPU_RGBA16F); + weight_tx.current().acquire(half_res, GPU_R16F); + occlusion_tx_.acquire(half_res, GPU_RG16F); + + DRW_draw_pass(gather_ps); + + { + /* Filtering pass. */ + color_tx.swap(); + weight_tx.swap(); + + color_tx.current().acquire(half_res, GPU_RGBA16F); + weight_tx.current().acquire(half_res, GPU_R16F); + + DRW_draw_pass(filter_ps); + + color_tx.previous().release(); + weight_tx.previous().release(); + } + + GPU_memory_barrier(GPU_BARRIER_FRAMEBUFFER); + + scatter_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(color_tx.current())); + + GPU_framebuffer_bind(scatter_fb); + DRW_draw_pass(scatter_ps); + + /* Used by scatter pass. */ + occlusion_tx_.release(); + + DRW_stats_group_end(); + } + { + DRW_stats_group_start("Hole Fill"); + + bokeh_gather_lut_tx_.release(); + bokeh_scatter_lut_tx_.release(); + + hole_fill_color_tx_.acquire(half_res, GPU_RGBA16F); + hole_fill_weight_tx_.acquire(half_res, GPU_R16F); + + DRW_draw_pass(hole_fill_ps_); + + /* NOTE: We do not filter the hole-fill pass as effect is likely to not be noticeable. */ + + DRW_stats_group_end(); + } + { + DRW_stats_group_start("Resolve"); + + resolve_stable_color_tx_ = dof_buffer.stabilize_history_tx_; + + DRW_draw_pass(resolve_ps_); + + color_bg_tx_.current().release(); + color_fg_tx_.current().release(); + weight_bg_tx_.current().release(); + weight_fg_tx_.current().release(); + tiles_fg_tx_.current().release(); + tiles_bg_tx_.current().release(); + hole_fill_color_tx_.release(); + hole_fill_weight_tx_.release(); + bokeh_resolve_lut_tx_.release(); + + DRW_stats_group_end(); + } + + DRW_stats_group_end(); + + /* Swap buffers so that next effect has the right input. */ + SWAP(GPUTexture *, *input_tx, *output_tx); +} + +/** \} */ + +} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh new file mode 100644 index 00000000000..8c291b241bd --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. + */ + +/** \file + * \ingroup eevee + * + * Depth of field post process effect. + * + * There are 2 methods to achieve this effect. + * - The first uses projection matrix offsetting and sample accumulation to give + * reference quality depth of field. But this needs many samples to hide the + * under-sampling. + * - The second one is a post-processing based one. It follows the + * implementation described in the presentation + * "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. + * There are some difference with our actual implementation that prioritize quality. + */ + +#pragma once + +#include "eevee_shader_shared.hh" + +namespace blender::eevee { + +class Instance; + +/* -------------------------------------------------------------------- */ +/** \name Depth of field + * \{ */ + +struct DepthOfFieldBuffer { + /** + * Per view history texture for stabilize pass. + * Swapped with stabilize_output_tx_ in order to reuse the previous history during DoF + * processing. + * Note this should be private as its inner working only concerns the Depth Of Field + * implementation. The view itself should not touch it. + */ + Texture stabilize_history_tx_ = {"dof_taa"}; +}; + +class DepthOfField { + private: + class Instance &inst_; + + /** Samplers */ + static constexpr eGPUSamplerState gather_bilinear = GPU_SAMPLER_MIPMAP | GPU_SAMPLER_FILTER; + static constexpr eGPUSamplerState gather_nearest = GPU_SAMPLER_MIPMAP; + + /** Input/Output texture references. */ + GPUTexture *input_color_tx_ = nullptr; + GPUTexture *output_color_tx_ = nullptr; + + /** Bokeh LUT precompute pass. */ + TextureFromPool bokeh_gather_lut_tx_ = {"dof_bokeh_gather_lut"}; + TextureFromPool bokeh_resolve_lut_tx_ = {"dof_bokeh_resolve_lut"}; + TextureFromPool bokeh_scatter_lut_tx_ = {"dof_bokeh_scatter_lut"}; + DRWPass *bokeh_lut_ps_ = nullptr; + + /** Outputs half-resolution color and Circle Of Confusion. */ + TextureFromPool setup_coc_tx_ = {"dof_setup_coc"}; + TextureFromPool setup_color_tx_ = {"dof_setup_color"}; + int3 dispatch_setup_size_ = int3(-1); + DRWPass *setup_ps_ = nullptr; + + /** Allocated because we need mip chain. Which isn't supported by TextureFromPool. */ + Texture reduced_coc_tx_ = {"dof_reduced_coc"}; + Texture reduced_color_tx_ = {"dof_reduced_color"}; + + /** Stabilization (flicker attenuation) of Color and CoC output of the setup pass. */ + TextureFromPool stabilize_output_tx_ = {"dof_taa"}; + GPUTexture *stabilize_input_ = nullptr; + bool1 stabilize_valid_history_ = false; + int3 dispatch_stabilize_size_ = int3(-1); + DRWPass *stabilize_ps_ = nullptr; + + /** 1/4th res color buffer used to speedup the local contrast test in the first reduce pass. */ + TextureFromPool downsample_tx_ = {"dof_downsample"}; + int3 dispatch_downsample_size_ = int3(-1); + DRWPass *downsample_ps_ = nullptr; + + /** Create mip-mapped color & COC textures for gather passes as well as scatter rect list. */ + DepthOfFieldScatterListBuf scatter_fg_list_buf_; + DepthOfFieldScatterListBuf scatter_bg_list_buf_; + DrawIndirectBuf scatter_fg_indirect_buf_; + DrawIndirectBuf scatter_bg_indirect_buf_; + int3 dispatch_reduce_size_ = int3(-1); + DRWPass *reduce_ps_ = nullptr; + + /** Outputs min & max COC in each 8x8 half res pixel tiles (so 1/16th of full resolution). */ + SwapChain<TextureFromPool, 2> tiles_fg_tx_; + SwapChain<TextureFromPool, 2> tiles_bg_tx_; + int3 dispatch_tiles_flatten_size_ = int3(-1); + DRWPass *tiles_flatten_ps_ = nullptr; + + /** Dilates the min & max CoCs to cover maximum COC values. */ + int tiles_dilate_ring_count_ = -1; + int tiles_dilate_ring_width_mul_ = -1; + int3 dispatch_tiles_dilate_size_ = int3(-1); + DRWPass *tiles_dilate_minmax_ps_ = nullptr; + DRWPass *tiles_dilate_minabs_ps_ = nullptr; + + /** Gather convolution for low intensity pixels and low contrast areas. */ + SwapChain<TextureFromPool, 2> color_bg_tx_; + SwapChain<TextureFromPool, 2> color_fg_tx_; + SwapChain<TextureFromPool, 2> weight_bg_tx_; + SwapChain<TextureFromPool, 2> weight_fg_tx_; + TextureFromPool occlusion_tx_ = {"dof_occlusion"}; + int3 dispatch_gather_size_ = int3(-1); + DRWPass *gather_fg_ps_ = nullptr; + DRWPass *gather_bg_ps_ = nullptr; + + /** Hole-fill convolution: Gather pass meant to fill areas of foreground dis-occlusion. */ + TextureFromPool hole_fill_color_tx_ = {"dof_color_hole_fill"}; + TextureFromPool hole_fill_weight_tx_ = {"dof_weight_hole_fill"}; + DRWPass *hole_fill_ps_ = nullptr; + + /** Small Filter pass to reduce noise out of gather passes. */ + int3 dispatch_filter_size_ = int3(-1); + DRWPass *filter_fg_ps_ = nullptr; + DRWPass *filter_bg_ps_ = nullptr; + + /** Scatter convolution: A quad is emitted for every 4 bright enough half pixels. */ + Framebuffer scatter_fg_fb_ = {"dof_scatter_fg"}; + Framebuffer scatter_bg_fb_ = {"dof_scatter_bg"}; + DRWPass *scatter_fg_ps_ = nullptr; + DRWPass *scatter_bg_ps_ = nullptr; + + /** Recombine the results and also perform a slight out of focus gather. */ + GPUTexture *resolve_stable_color_tx_ = nullptr; + int3 dispatch_resolve_size_ = int3(-1); + DRWPass *resolve_ps_ = nullptr; + + DepthOfFieldDataBuf data_; + + /** Scene settings that are immutable. */ + float user_overblur_; + float fx_max_coc_; + /** Use jittered depth of field where we randomize camera location. */ + bool do_jitter_; + + /** Circle of Confusion radius for FX DoF passes. Is in view X direction in [0..1] range. */ + float fx_radius_; + /** Circle of Confusion radius for jittered DoF. Is in view X direction in [0..1] range. */ + float jitter_radius_; + /** Focus distance in view space. */ + float focus_distance_; + /** Extent of the input buffer. */ + int2 extent_; + + public: + DepthOfField(Instance &inst) : inst_(inst){}; + ~DepthOfField(){}; + + void init(); + + void sync(); + + /** + * Apply Depth Of Field jittering to the view and projection matrices.. + */ + void jitter_apply(float4x4 &winmat, float4x4 &viewmat); + + /** + * Will swap input and output texture if rendering happens. The actual output of this function + * is in input_tx. + */ + void render(GPUTexture **input_tx, GPUTexture **output_tx, DepthOfFieldBuffer &dof_buffer); + + bool postfx_enabled() const + { + return fx_radius_ > 0.0f; + } + + private: + void bokeh_lut_pass_sync(); + void setup_pass_sync(); + void stabilize_pass_sync(); + void downsample_pass_sync(); + void reduce_pass_sync(); + void tiles_flatten_pass_sync(); + void tiles_dilate_pass_sync(); + void gather_pass_sync(); + void filter_pass_sync(); + void scatter_pass_sync(); + void hole_fill_pass_sync(); + void resolve_pass_sync(); + + void update_sample_table(); +}; + +/** \} */ + +} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index 49f43265aa8..ae41bd204d0 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,12 +386,11 @@ 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); + DRW_shgroup_uniform_texture_ref(grp, "light_tx", &rbuffers.light_tx); DRW_shgroup_uniform_texture_ref(grp, "diffuse_color_tx", &rbuffers.diffuse_color_tx); - DRW_shgroup_uniform_texture_ref(grp, "specular_light_tx", &rbuffers.specular_light_tx); DRW_shgroup_uniform_texture_ref(grp, "specular_color_tx", &rbuffers.specular_color_tx); DRW_shgroup_uniform_texture_ref(grp, "volume_light_tx", &rbuffers.volume_light_tx); DRW_shgroup_uniform_texture_ref(grp, "emission_tx", &rbuffers.emission_tx); @@ -400,10 +402,10 @@ void Film::sync() /* NOTE(@fclem): 16 is the max number of sampled texture in many implementations. * If we need more, we need to pack more of the similar passes in the same textures as arrays or * use image binding instead. */ - DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_src_tx_); - DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_dst_tx_); - DRW_shgroup_uniform_texture_ref_ex(grp, "in_combined_tx", &combined_src_tx_, filter); - DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_dst_tx_); + DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_tx_.current()); + DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_tx_.next()); + DRW_shgroup_uniform_texture_ref_ex(grp, "in_combined_tx", &combined_tx_.current(), filter); + DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_tx_.next()); DRW_shgroup_uniform_image_ref(grp, "depth_img", &depth_tx_); DRW_shgroup_uniform_image_ref(grp, "color_accum_img", &color_accum_tx_); DRW_shgroup_uniform_image_ref(grp, "value_accum_img", &value_accum_tx_); @@ -458,6 +460,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_; } @@ -476,7 +482,7 @@ void Film::update_sample_table() data_.samples_weight_total = 1.0f; data_.samples_len = 1; } - /* NOTE: Threshold determined by hand until we don't hit the assert bellow. */ + /* NOTE: Threshold determined by hand until we don't hit the assert below. */ else if (data_.filter_radius < 2.20f) { /* Small filter Size. */ int closest_index = 0; @@ -538,7 +544,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,11 +560,7 @@ void Film::accumulate(const DRWView *view) update_sample_table(); - /* 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(); - combined_src_tx_ = combined_tx_.current(); - combined_dst_tx_ = combined_tx_.next(); + combined_final_tx_ = combined_final_tx; data_.display_only = false; data_.push_update(); @@ -580,17 +582,13 @@ 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)); - /* 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(); - combined_src_tx_ = combined_tx_.current(); - combined_dst_tx_ = combined_tx_.next(); + combined_final_tx_ = inst_.render_buffers.combined_tx; data_.display_only = true; data_.push_update(); diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 1165b9a4c12..3e368782d31 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -40,6 +40,9 @@ class Film { private: Instance &inst_; + /** Incoming combined buffer with post FX applied (motion blur + depth of field). */ + GPUTexture *combined_final_tx_ = nullptr; + /** Main accumulation textures containing every render-pass except depth and combined. */ Texture color_accum_tx_; Texture value_accum_tx_; @@ -47,14 +50,8 @@ class Film { Texture depth_tx_; /** Combined "Color" buffer. Double buffered to allow re-projection. */ SwapChain<Texture, 2> combined_tx_; - /** Static reference as SwapChain does not actually move the objects when swapping. */ - GPUTexture *combined_src_tx_ = nullptr; - GPUTexture *combined_dst_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. */ - GPUTexture *weight_src_tx_ = nullptr; - GPUTexture *weight_dst_tx_ = nullptr; /** User setting to disable reprojection. Useful for debugging or have a more precise render. */ bool force_disable_reprojection_ = false; @@ -74,7 +71,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(); @@ -82,6 +79,7 @@ class Film { float *read_pass(eViewLayerEEVEEPassType pass_type); float *read_aov(ViewLayerAOV *aov); + /** Returns shading views internal resolution. */ int2 render_extent_get() const { return data_.render_extent; diff --git a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc new file mode 100644 index 00000000000..e2022d74093 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. + */ + +#include "BKE_global.h" + +#include "eevee_instance.hh" + +#include "eevee_hizbuffer.hh" + +namespace blender::eevee { + +/* -------------------------------------------------------------------- */ +/** \name Hierarchical-Z buffer + * + * \{ */ + +void HiZBuffer::sync() +{ + RenderBuffers &render_buffers = inst_.render_buffers; + + int2 render_extent = inst_.film.render_extent_get(); + /* Padding to avoid complexity during down-sampling and screen tracing. */ + int2 hiz_extent = math::ceil_to_multiple(render_extent, int2(1u << (HIZ_MIP_COUNT - 1))); + int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE)); + + hiz_tx_.ensure_2d(GPU_R32F, hiz_extent, nullptr, HIZ_MIP_COUNT); + hiz_tx_.ensure_mip_views(); + GPU_texture_mipmap_mode(hiz_tx_, true, false); + + data_.uv_scale = float2(render_extent) / float2(hiz_extent); + data_.push_update(); + + { + hiz_update_ps_ = DRW_pass_create("HizUpdate", DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(HIZ_UPDATE); + DRWShadingGroup *grp = DRW_shgroup_create(sh, hiz_update_ps_); + DRW_shgroup_storage_block(grp, "finished_tile_counter", atomic_tile_counter_); + DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, with_filter); + DRW_shgroup_uniform_image(grp, "out_mip_0", hiz_tx_.mip_view(0)); + DRW_shgroup_uniform_image(grp, "out_mip_1", hiz_tx_.mip_view(1)); + DRW_shgroup_uniform_image(grp, "out_mip_2", hiz_tx_.mip_view(2)); + DRW_shgroup_uniform_image(grp, "out_mip_3", hiz_tx_.mip_view(3)); + DRW_shgroup_uniform_image(grp, "out_mip_4", hiz_tx_.mip_view(4)); + DRW_shgroup_uniform_image(grp, "out_mip_5", hiz_tx_.mip_view(5)); + DRW_shgroup_uniform_image(grp, "out_mip_6", hiz_tx_.mip_view(6)); + DRW_shgroup_uniform_image(grp, "out_mip_7", hiz_tx_.mip_view(7)); + /* TODO(@fclem): There might be occasions where we might not want to + * copy mip 0 for performance reasons if there is no need for it. */ + DRW_shgroup_uniform_bool_copy(grp, "update_mip_0", true); + DRW_shgroup_call_compute(grp, UNPACK2(dispatch_size), 1); + DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH); + } + + if (inst_.debug_mode == eDebugMode::DEBUG_HIZ_VALIDATION) { + DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM; + debug_draw_ps_ = DRW_pass_create("HizUpdate.Debug", state); + GPUShader *sh = inst_.shaders.static_shader_get(HIZ_DEBUG); + DRWShadingGroup *grp = DRW_shgroup_create(sh, debug_draw_ps_); + this->bind_resources(grp); + DRW_shgroup_call_procedural_triangles(grp, nullptr, 1); + } + else { + debug_draw_ps_ = nullptr; + } +} + +void HiZBuffer::update() +{ + if (!is_dirty_) { + return; + } + + /* Bind another framebuffer in order to avoid triggering the feedback loop check. + * This is safe because we only use compute shaders in this section of the code. + * Ideally the check should be smarter. */ + GPUFrameBuffer *fb = GPU_framebuffer_active_get(); + if (G.debug & G_DEBUG_GPU) { + GPU_framebuffer_restore(); + } + + DRW_draw_pass(hiz_update_ps_); + + if (G.debug & G_DEBUG_GPU) { + GPU_framebuffer_bind(fb); + } +} + +void HiZBuffer::debug_draw(GPUFrameBuffer *view_fb) +{ + if (debug_draw_ps_ == nullptr) { + return; + } + inst_.info = "Debug Mode: HiZ Validation"; + inst_.hiz_buffer.update(); + GPU_framebuffer_bind(view_fb); + DRW_draw_pass(debug_draw_ps_); +} + +/** \} */ + +} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh new file mode 100644 index 00000000000..039f7e4f16d --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. + */ + +/** \file + * \ingroup eevee + * + * The Hierarchical-Z buffer is texture containing a copy of the depth buffer with mipmaps. + * Each mip contains the maximum depth of each 4 pixels on the upper level. + * The size of the texture is padded to avoid messing with the mipmap pixels alignments. + */ + +#pragma once + +#include "DRW_render.h" + +#include "eevee_shader_shared.hh" + +namespace blender::eevee { + +class Instance; + +/* -------------------------------------------------------------------- */ +/** \name Hierarchical-Z buffer + * \{ */ + +class HiZBuffer { + private: + Instance &inst_; + + /** The texture containing the hiz mip chain. */ + Texture hiz_tx_ = {"hiz_tx_"}; + /** + * Atomic counter counting the number of tile that have finished down-sampling. + * The last one will process the last few mip level. + */ + draw::StorageBuffer<uint4, true> atomic_tile_counter_ = {"atomic_tile_counter"}; + /** Single pass recursive downsample. */ + DRWPass *hiz_update_ps_ = nullptr; + /** Debug pass. */ + DRWPass *debug_draw_ps_ = nullptr; + /** Dirty flag to check if the update is necessary. */ + bool is_dirty_ = true; + + HiZDataBuf data_; + + public: + HiZBuffer(Instance &inst) : inst_(inst) + { + atomic_tile_counter_.clear_to_zero(); + }; + + void sync(); + + /** + * Tag the buffer for update if needed. + */ + void set_dirty() + { + is_dirty_ = true; + } + + /** + * Update the content of the HiZ buffer with the depth render target. + * Noop if the buffer has not been tagged as dirty. + * Should be called before each passes that needs to read the hiz buffer. + */ + void update(); + + void debug_draw(GPUFrameBuffer *view_fb); + + void bind_resources(DRWShadingGroup *grp) + { + DRW_shgroup_uniform_texture_ref(grp, "hiz_tx", &hiz_tx_); + DRW_shgroup_uniform_block_ref(grp, "hiz_buf", &data_); + } +}; + +/** \} */ + +} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 9f8cf6dc6ba..6665b3a7c9b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -53,6 +53,10 @@ void Instance::init(const int2 &output_res, v3d = v3d_; rv3d = rv3d_; + if (assign_if_different(debug_mode, (eDebugMode)G.debug_value)) { + sampling.reset(); + } + info = ""; update_eval_members(); @@ -60,6 +64,9 @@ void Instance::init(const int2 &output_res, sampling.init(scene); camera.init(); film.init(output_res, output_rect); + velocity.init(); + depth_of_field.init(); + motion_blur.init(); main_view.init(); } @@ -92,21 +99,23 @@ void Instance::update_eval_members() void Instance::begin_sync() { materials.begin_sync(); - velocity.begin_sync(); + velocity.begin_sync(); /* NOTE: Also syncs camera. */ + lights.begin_sync(); gpencil_engine_enabled = false; - render_buffers.sync(); + depth_of_field.sync(); + motion_blur.sync(); + hiz_buffer.sync(); pipelines.sync(); main_view.sync(); world.sync(); - camera.sync(); film.sync(); } void Instance::object_sync(Object *ob) { - const bool is_renderable_type = ELEM(ob->type, OB_CURVES, OB_GPENCIL, OB_MESH); + const bool is_renderable_type = ELEM(ob->type, OB_CURVES, OB_GPENCIL, OB_MESH, OB_LAMP); 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); @@ -130,6 +139,7 @@ void Instance::object_sync(Object *ob) if (object_is_visible) { switch (ob->type) { case OB_LAMP: + lights.sync_light(ob, ob_handle); break; case OB_MESH: case OB_CURVES_LEGACY: @@ -169,6 +179,7 @@ void Instance::object_sync_render(void *instance_, void Instance::end_sync() { velocity.end_sync(); + lights.end_sync(); sampling.end_sync(); film.end_sync(); } @@ -212,22 +223,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 +241,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 +252,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 +299,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..cc3d1c32fde 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.hh +++ b/source/blender/draw/engines/eevee_next/eevee_instance.hh @@ -16,8 +16,12 @@ #include "DRW_render.h" #include "eevee_camera.hh" +#include "eevee_depth_of_field.hh" #include "eevee_film.hh" +#include "eevee_hizbuffer.hh" +#include "eevee_light.hh" #include "eevee_material.hh" +#include "eevee_motion_blur.hh" #include "eevee_pipeline.hh" #include "eevee_renderbuffers.hh" #include "eevee_sampling.hh" @@ -34,13 +38,18 @@ namespace blender::eevee { */ class Instance { friend VelocityModule; + friend MotionBlurModule; public: ShaderModule &shaders; SyncModule sync; MaterialModule materials; PipelineModule pipelines; + LightModule lights; VelocityModule velocity; + MotionBlurModule motion_blur; + DepthOfField depth_of_field; + HiZBuffer hiz_buffer; Sampling sampling; Camera camera; Film film; @@ -66,8 +75,10 @@ class Instance { /** True if the grease pencil engine might be running. */ bool gpencil_engine_enabled; - /* Info string displayed at the top of the render / viewport. */ + /** Info string displayed at the top of the render / viewport. */ std::string info = ""; + /** Debug mode from debug value. */ + eDebugMode debug_mode = eDebugMode::DEBUG_NONE; public: Instance() @@ -75,7 +86,11 @@ class Instance { sync(*this), materials(*this), pipelines(*this), + lights(*this), velocity(*this), + motion_blur(*this), + depth_of_field(*this), + hiz_buffer(*this), sampling(*this), camera(*this), film(*this), @@ -138,6 +153,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_light.cc b/source/blender/draw/engines/eevee_next/eevee_light.cc new file mode 100644 index 00000000000..5392816124b --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_light.cc @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. + */ + +/** \file + * \ingroup eevee + * + * The light module manages light data buffers and light culling system. + */ + +#include "draw_debug.hh" + +#include "eevee_instance.hh" + +#include "eevee_light.hh" + +namespace blender::eevee { + +/* -------------------------------------------------------------------- */ +/** \name LightData + * \{ */ + +static eLightType to_light_type(short blender_light_type, short blender_area_type) +{ + switch (blender_light_type) { + default: + case LA_LOCAL: + return LIGHT_POINT; + case LA_SUN: + return LIGHT_SUN; + case LA_SPOT: + return LIGHT_SPOT; + case LA_AREA: + return ELEM(blender_area_type, LA_AREA_DISK, LA_AREA_ELLIPSE) ? LIGHT_ELLIPSE : LIGHT_RECT; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Light Object + * \{ */ + +void Light::sync(/* ShadowModule &shadows , */ const Object *ob, float threshold) +{ + const ::Light *la = (const ::Light *)ob->data; + float scale[3]; + + float max_power = max_fff(la->r, la->g, la->b) * fabsf(la->energy / 100.0f); + float surface_max_power = max_ff(la->diff_fac, la->spec_fac) * max_power; + float volume_max_power = la->volume_fac * max_power; + + float influence_radius_surface = attenuation_radius_get(la, threshold, surface_max_power); + float influence_radius_volume = attenuation_radius_get(la, threshold, volume_max_power); + + this->influence_radius_max = max_ff(influence_radius_surface, influence_radius_volume); + this->influence_radius_invsqr_surface = 1.0f / square_f(max_ff(influence_radius_surface, 1e-8f)); + this->influence_radius_invsqr_volume = 1.0f / square_f(max_ff(influence_radius_volume, 1e-8f)); + + this->color = float3(&la->r) * la->energy; + normalize_m4_m4_ex(this->object_mat.ptr(), ob->obmat, scale); + /* Make sure we have consistent handedness (in case of negatively scaled Z axis). */ + float3 cross = math::cross(float3(this->_right), float3(this->_up)); + if (math::dot(cross, float3(this->_back)) < 0.0f) { + negate_v3(this->_up); + } + + shape_parameters_set(la, scale); + + float shape_power = shape_power_get(la); + float point_power = point_power_get(la); + this->diffuse_power = la->diff_fac * shape_power; + this->transmit_power = la->diff_fac * point_power; + this->specular_power = la->spec_fac * shape_power; + this->volume_power = la->volume_fac * point_power; + + eLightType new_type = to_light_type(la->type, la->area_shape); + if (this->type != new_type) { + /* shadow_discard_safe(shadows); */ + this->type = new_type; + } + +#if 0 + if (la->mode & LA_SHADOW) { + if (la->type == LA_SUN) { + if (this->shadow_id == LIGHT_NO_SHADOW) { + this->shadow_id = shadows.directionals.alloc(); + } + + ShadowDirectional &shadow = shadows.directionals[this->shadow_id]; + shadow.sync(this->object_mat, la->bias * 0.05f, 1.0f); + } + else { + float cone_aperture = DEG2RAD(360.0); + if (la->type == LA_SPOT) { + cone_aperture = min_ff(DEG2RAD(179.9), la->spotsize); + } + else if (la->type == LA_AREA) { + cone_aperture = DEG2RAD(179.9); + } + + if (this->shadow_id == LIGHT_NO_SHADOW) { + this->shadow_id = shadows.punctuals.alloc(); + } + + ShadowPunctual &shadow = shadows.punctuals[this->shadow_id]; + shadow.sync(this->type, + this->object_mat, + cone_aperture, + la->clipsta, + this->influence_radius_max, + la->bias * 0.05f); + } + } + else { + shadow_discard_safe(shadows); + } +#endif + + this->initialized = true; +} + +#if 0 +void Light::shadow_discard_safe(ShadowModule &shadows) +{ + if (shadow_id != LIGHT_NO_SHADOW) { + if (this->type != LIGHT_SUN) { + shadows.punctuals.free(shadow_id); + } + else { + shadows.directionals.free(shadow_id); + } + shadow_id = LIGHT_NO_SHADOW; + } +} +#endif + +/* Returns attenuation radius inverted & squared for easy bound checking inside the shader. */ +float Light::attenuation_radius_get(const ::Light *la, float light_threshold, float light_power) +{ + if (la->type == LA_SUN) { + return (light_power > 1e-5f) ? 1e16f : 0.0f; + } + + if (la->mode & LA_CUSTOM_ATTENUATION) { + return la->att_dist; + } + /* Compute the distance (using the inverse square law) + * at which the light power reaches the light_threshold. */ + /* TODO take area light scale into account. */ + return sqrtf(light_power / light_threshold); +} + +void Light::shape_parameters_set(const ::Light *la, const float scale[3]) +{ + if (la->type == LA_AREA) { + float area_size_y = (ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE)) ? la->area_sizey : + la->area_size; + _area_size_x = max_ff(0.003f, la->area_size * scale[0] * 0.5f); + _area_size_y = max_ff(0.003f, area_size_y * scale[1] * 0.5f); + /* For volume point lighting. */ + radius_squared = max_ff(0.001f, hypotf(_area_size_x, _area_size_y) * 0.5f); + radius_squared = square_f(radius_squared); + } + else { + if (la->type == LA_SPOT) { + /* Spot size & blend */ + spot_size_inv[0] = scale[2] / scale[0]; + spot_size_inv[1] = scale[2] / scale[1]; + float spot_size = cosf(la->spotsize * 0.5f); + float spot_blend = (1.0f - spot_size) * la->spotblend; + _spot_mul = 1.0f / max_ff(1e-8f, spot_blend); + _spot_bias = -spot_size * _spot_mul; + spot_tan = tanf(min_ff(la->spotsize * 0.5f, M_PI_2 - 0.0001f)); + } + + if (la->type == LA_SUN) { + _area_size_x = tanf(min_ff(la->sun_angle, DEG2RADF(179.9f)) / 2.0f); + } + else { + _area_size_x = la->area_size; + } + _area_size_y = _area_size_x = max_ff(0.001f, _area_size_x); + radius_squared = square_f(_area_size_x); + } +} + +float Light::shape_power_get(const ::Light *la) +{ + /* Make illumination power constant */ + switch (la->type) { + case LA_AREA: { + float area = _area_size_x * _area_size_y; + float power = 1.0f / (area * 4.0f * float(M_PI)); + /* FIXME : Empirical, Fit cycles power */ + power *= 0.8f; + if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) { + /* Scale power to account for the lower area of the ellipse compared to the surrounding + * rectangle. */ + power *= 4.0f / M_PI; + } + return power; + } + case LA_SPOT: + case LA_LOCAL: { + return 1.0f / (4.0f * square_f(_radius) * float(M_PI * M_PI)); + } + default: + case LA_SUN: { + float power = 1.0f / (square_f(_radius) * float(M_PI)); + /* Make illumination power closer to cycles for bigger radii. Cycles uses a cos^3 term that + * we cannot reproduce so we account for that by scaling the light power. This function is + * the result of a rough manual fitting. */ + /* Simplification of: power *= 1 + r²/2 */ + power += 1.0f / (2.0f * M_PI); + + return power; + } + } +} + +float Light::point_power_get(const ::Light *la) +{ + /* Volume light is evaluated as point lights. Remove the shape power. */ + switch (la->type) { + case LA_AREA: { + /* Match cycles. Empirical fit... must correspond to some constant. */ + float power = 0.0792f * M_PI; + + /* This corrects for area light most representative point trick. The fit was found by + * reducing the average error compared to cycles. */ + float area = _area_size_x * _area_size_y; + float tmp = M_PI_2 / (M_PI_2 + sqrtf(area)); + /* Lerp between 1.0 and the limit (1 / pi). */ + power *= tmp + (1.0f - tmp) * M_1_PI; + + return power; + } + case LA_SPOT: + case LA_LOCAL: { + /* Match cycles. Empirical fit... must correspond to some constant. */ + return 0.0792f; + } + default: + case LA_SUN: { + return 1.0f; + } + } +} + +void Light::debug_draw() +{ +#ifdef DEBUG + drw_debug_sphere(_position, influence_radius_max, float4(0.8f, 0.3f, 0.0f, 1.0f)); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name LightModule + * \{ */ + +void LightModule::begin_sync() +{ + use_scene_lights_ = inst_.use_scene_lights(); + + /* In begin_sync so it can be animated. */ + if (assign_if_different(light_threshold_, max_ff(1e-16f, inst_.scene->eevee.light_threshold))) { + inst_.sampling.reset(); + } + + sun_lights_len_ = 0; + local_lights_len_ = 0; +} + +void LightModule::sync_light(const Object *ob, ObjectHandle &handle) +{ + if (use_scene_lights_ == false) { + return; + } + Light &light = light_map_.lookup_or_add_default(handle.object_key); + light.used = true; + if (handle.recalc != 0 || !light.initialized) { + light.sync(/* inst_.shadows, */ ob, light_threshold_); + } + sun_lights_len_ += int(light.type == LIGHT_SUN); + local_lights_len_ += int(light.type != LIGHT_SUN); +} + +void LightModule::end_sync() +{ + // ShadowModule &shadows = inst_.shadows; + + /* NOTE: We resize this buffer before removing deleted lights. */ + int lights_allocated = ceil_to_multiple_u(max_ii(light_map_.size(), 1), LIGHT_CHUNK); + light_buf_.resize(lights_allocated); + + /* Track light deletion. */ + Vector<ObjectKey, 0> deleted_keys; + /* Indices inside GPU data array. */ + int sun_lights_idx = 0; + int local_lights_idx = sun_lights_len_; + + /* Fill GPU data with scene data. */ + for (auto item : light_map_.items()) { + Light &light = item.value; + + if (!light.used) { + /* Deleted light. */ + deleted_keys.append(item.key); + // light.shadow_discard_safe(shadows); + continue; + } + + int dst_idx = (light.type == LIGHT_SUN) ? sun_lights_idx++ : local_lights_idx++; + /* Put all light data into global data SSBO. */ + light_buf_[dst_idx] = light; + +#if 0 + if (light.shadow_id != LIGHT_NO_SHADOW) { + if (light.type == LIGHT_SUN) { + light_buf_[dst_idx].shadow_data = shadows.directionals[light.shadow_id]; + } + else { + light_buf_[dst_idx].shadow_data = shadows.punctuals[light.shadow_id]; + } + } +#endif + /* Untag for next sync. */ + light.used = false; + } + /* This scene data buffer is then immutable after this point. */ + light_buf_.push_update(); + + for (auto key : deleted_keys) { + light_map_.remove(key); + } + + /* Update sampling on deletion or un-hidding (use_scene_lights). */ + if (assign_if_different(light_map_size_, light_map_.size())) { + inst_.sampling.reset(); + } + + /* If exceeding the limit, just trim off the excess to avoid glitchy rendering. */ + if (sun_lights_len_ + local_lights_len_ > CULLING_MAX_ITEM) { + sun_lights_len_ = min_ii(sun_lights_len_, CULLING_MAX_ITEM); + local_lights_len_ = min_ii(local_lights_len_, CULLING_MAX_ITEM - sun_lights_len_); + inst_.info = "Error: Too many lights in the scene."; + } + lights_len_ = sun_lights_len_ + local_lights_len_; + + /* Resize to the actual number of lights after pruning. */ + lights_allocated = ceil_to_multiple_u(max_ii(lights_len_, 1), LIGHT_CHUNK); + culling_key_buf_.resize(lights_allocated); + culling_zdist_buf_.resize(lights_allocated); + culling_light_buf_.resize(lights_allocated); + + { + /* Compute tile size and total word count. */ + uint word_per_tile = divide_ceil_u(max_ii(lights_len_, 1), 32); + int2 render_extent = inst_.film.render_extent_get(); + int2 tiles_extent; + /* Default to 32 as this is likely to be the maximum + * tile size used by hardware or compute shading. */ + uint tile_size = 16; + do { + tile_size *= 2; + tiles_extent = math::divide_ceil(render_extent, int2(tile_size)); + uint tile_count = tiles_extent.x * tiles_extent.y; + if (tile_count > max_tile_count_threshold) { + continue; + } + total_word_count_ = tile_count * word_per_tile; + + } while (total_word_count_ > max_word_count_threshold); + /* Keep aligned with storage buffer requirements. */ + total_word_count_ = ceil_to_multiple_u(total_word_count_, 32); + + culling_data_buf_.tile_word_len = word_per_tile; + culling_data_buf_.tile_size = tile_size; + culling_data_buf_.tile_x_len = tiles_extent.x; + culling_data_buf_.tile_y_len = tiles_extent.y; + culling_data_buf_.items_count = lights_len_; + culling_data_buf_.local_lights_len = local_lights_len_; + culling_data_buf_.sun_lights_len = sun_lights_len_; + } + culling_tile_buf_.resize(total_word_count_); + + culling_pass_sync(); + debug_pass_sync(); +} + +void LightModule::culling_pass_sync() +{ + uint safe_lights_len = max_ii(lights_len_, 1); + uint culling_select_dispatch_size = divide_ceil_u(safe_lights_len, CULLING_SELECT_GROUP_SIZE); + uint culling_sort_dispatch_size = divide_ceil_u(safe_lights_len, CULLING_SORT_GROUP_SIZE); + uint culling_tile_dispatch_size = divide_ceil_u(total_word_count_, CULLING_TILE_GROUP_SIZE); + + /* NOTE: We reference the buffers that may be resized or updated later. */ + { + DRW_PASS_CREATE(culling_select_ps_, DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(LIGHT_CULLING_SELECT); + DRWShadingGroup *grp = DRW_shgroup_create(sh, culling_select_ps_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block(grp, "in_light_buf", light_buf_); + DRW_shgroup_storage_block(grp, "out_light_buf", culling_light_buf_); + DRW_shgroup_storage_block(grp, "out_zdist_buf", culling_zdist_buf_); + DRW_shgroup_storage_block(grp, "out_key_buf", culling_key_buf_); + DRW_shgroup_call_compute(grp, culling_select_dispatch_size, 1, 1); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE); + } + { + DRW_PASS_CREATE(culling_sort_ps_, DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(LIGHT_CULLING_SORT); + DRWShadingGroup *grp = DRW_shgroup_create(sh, culling_sort_ps_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block(grp, "in_light_buf", light_buf_); + DRW_shgroup_storage_block(grp, "out_light_buf", culling_light_buf_); + DRW_shgroup_storage_block(grp, "in_zdist_buf", culling_zdist_buf_); + DRW_shgroup_storage_block(grp, "in_key_buf", culling_key_buf_); + DRW_shgroup_call_compute(grp, culling_sort_dispatch_size, 1, 1); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE); + } + { + DRW_PASS_CREATE(culling_zbin_ps_, DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(LIGHT_CULLING_ZBIN); + DRWShadingGroup *grp = DRW_shgroup_create(sh, culling_zbin_ps_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block(grp, "light_buf", culling_light_buf_); + DRW_shgroup_storage_block(grp, "out_zbin_buf", culling_zbin_buf_); + DRW_shgroup_call_compute(grp, 1, 1, 1); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE); + } + { + DRW_PASS_CREATE(culling_tile_ps_, DRW_STATE_NO_DRAW); + GPUShader *sh = inst_.shaders.static_shader_get(LIGHT_CULLING_TILE); + DRWShadingGroup *grp = DRW_shgroup_create(sh, culling_tile_ps_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block(grp, "light_buf", culling_light_buf_); + DRW_shgroup_storage_block(grp, "out_light_tile_buf", culling_tile_buf_); + DRW_shgroup_call_compute(grp, culling_tile_dispatch_size, 1, 1); + DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE); + } +} + +void LightModule::debug_pass_sync() +{ + if (inst_.debug_mode != eDebugMode::DEBUG_LIGHT_CULLING) { + debug_draw_ps_ = nullptr; + return; + } + + DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM; + debug_draw_ps_ = DRW_pass_create("LightCulling.Debug", state); + GPUShader *sh = inst_.shaders.static_shader_get(LIGHT_CULLING_DEBUG); + DRWShadingGroup *grp = DRW_shgroup_create(sh, debug_draw_ps_); + inst_.hiz_buffer.bind_resources(grp); + DRW_shgroup_storage_block_ref(grp, "light_buf", &culling_light_buf_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block_ref(grp, "light_zbin_buf", &culling_zbin_buf_); + DRW_shgroup_storage_block_ref(grp, "light_tile_buf", &culling_tile_buf_); + DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &inst_.render_buffers.depth_tx); + DRW_shgroup_call_procedural_triangles(grp, nullptr, 1); +} + +void LightModule::set_view(const DRWView *view, const int2 extent) +{ + float far_z = DRW_view_far_distance_get(view); + float near_z = DRW_view_near_distance_get(view); + + culling_data_buf_.zbin_scale = -CULLING_ZBIN_COUNT / fabsf(far_z - near_z); + culling_data_buf_.zbin_bias = -near_z * culling_data_buf_.zbin_scale; + culling_data_buf_.tile_to_uv_fac = (culling_data_buf_.tile_size / float2(extent)); + culling_data_buf_.visible_count = 0; + culling_data_buf_.push_update(); + + DRW_stats_group_start("Light Culling"); + + DRW_view_set_active(view); + DRW_draw_pass(culling_select_ps_); + DRW_draw_pass(culling_sort_ps_); + DRW_draw_pass(culling_zbin_ps_); + DRW_draw_pass(culling_tile_ps_); + + DRW_stats_group_end(); +} + +void LightModule::debug_draw(GPUFrameBuffer *view_fb) +{ + if (debug_draw_ps_ == nullptr) { + return; + } + inst_.info = "Debug Mode: Light Culling Validation"; + inst_.hiz_buffer.update(); + GPU_framebuffer_bind(view_fb); + DRW_draw_pass(debug_draw_ps_); +} + +/** \} */ + +} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_light.hh b/source/blender/draw/engines/eevee_next/eevee_light.hh new file mode 100644 index 00000000000..aad798ccec2 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/eevee_light.hh @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. + */ + +/** \file + * \ingroup eevee + * + * The light module manages light data buffers and light culling system. + * + * The culling follows the principles of Tiled Culling + Z binning from: + * "Improved Culling for Tiled and Clustered Rendering" + * by Michal Drobot + * http://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf + * + * The culling is separated in 4 compute phases: + * - View Culling (select pass): Create a z distance and a index buffer of visible lights. + * - Light sorting: Outputs visible lights sorted by Z distance. + * - Z binning: Compute the Z bins min/max light indices. + * - Tile intersection: Fine grained 2D culling of each lights outputting a bitmap per tile. + */ + +#pragma once + +#include "BLI_bitmap.h" +#include "BLI_vector.hh" +#include "DNA_light_types.h" + +#include "eevee_camera.hh" +#include "eevee_sampling.hh" +#include "eevee_shader.hh" +#include "eevee_shader_shared.hh" +#include "eevee_sync.hh" + +namespace blender::eevee { + +class Instance; + +/* -------------------------------------------------------------------- */ +/** \name Light Object + * \{ */ + +struct Light : public LightData { + public: + bool initialized = false; + bool used = false; + + public: + Light() + { + shadow_id = LIGHT_NO_SHADOW; + } + + void sync(/* ShadowModule &shadows, */ const Object *ob, float threshold); + + // void shadow_discard_safe(ShadowModule &shadows); + + void debug_draw(); + + private: + float attenuation_radius_get(const ::Light *la, float light_threshold, float light_power); + void shape_parameters_set(const ::Light *la, const float scale[3]); + float shape_power_get(const ::Light *la); + float point_power_get(const ::Light *la); +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name LightModule + * \{ */ + +/** + * The light module manages light data buffers and light culling system. + */ +class LightModule { + // friend ShadowModule; + + private: + /* Keep tile count reasonable for memory usage and 2D culling performance. */ + static constexpr uint max_memory_threshold = 32 * 1024 * 1024; /* 32 MiB */ + static constexpr uint max_word_count_threshold = max_memory_threshold / sizeof(uint); + static constexpr uint max_tile_count_threshold = 8192; + + Instance &inst_; + + /** Map of light objects data. Converted to flat array each frame. */ + Map<ObjectKey, Light> light_map_; + /** Flat array sent to GPU, populated from light_map_. Source buffer for light culling. */ + LightDataBuf light_buf_ = {"Lights_no_cull"}; + /** Recorded size of light_map_ (after pruning) to detect deletion. */ + int64_t light_map_size_ = 0; + /** Luminous intensity to consider the light boundary at. Used for culling. */ + float light_threshold_ = 0.01f; + /** If false, will prevent all scene light from being synced. */ + bool use_scene_lights_ = false; + /** Number of sun lights synced during the last sync. Used as offset. */ + int sun_lights_len_ = 0; + int local_lights_len_ = 0; + /** Sun plus local lights count for convenience. */ + int lights_len_ = 0; + + /** + * Light Culling + */ + + /** LightData buffer used for rendering. Filled by the culling pass. */ + LightDataBuf culling_light_buf_ = {"Lights_culled"}; + /** Culling infos. */ + LightCullingDataBuf culling_data_buf_ = {"LightCull_data"}; + /** Z-distance matching the key for each visible lights. Used for sorting. */ + LightCullingZdistBuf culling_zdist_buf_ = {"LightCull_zdist"}; + /** Key buffer containing only visible lights indices. Used for sorting. */ + LightCullingKeyBuf culling_key_buf_ = {"LightCull_key"}; + /** Zbins containing min and max light index for each Z bin. */ + LightCullingZbinBuf culling_zbin_buf_ = {"LightCull_zbin"}; + /** Bitmap of lights touching each tiles. */ + LightCullingTileBuf culling_tile_buf_ = {"LightCull_tile"}; + /** Culling compute passes. */ + DRWPass *culling_select_ps_ = nullptr; + DRWPass *culling_sort_ps_ = nullptr; + DRWPass *culling_zbin_ps_ = nullptr; + DRWPass *culling_tile_ps_ = nullptr; + /** Total number of words the tile buffer needs to contain for the render resolution. */ + uint total_word_count_ = 0; + + /** Debug Culling visualization. */ + DRWPass *debug_draw_ps_ = nullptr; + /* GPUTexture *input_depth_tx_ = nullptr; */ + + public: + LightModule(Instance &inst) : inst_(inst){}; + ~LightModule(){}; + + void begin_sync(); + void sync_light(const Object *ob, ObjectHandle &handle); + void end_sync(); + + /** + * Update acceleration structure for the given view. + */ + void set_view(const DRWView *view, const int2 extent); + + void debug_draw(GPUFrameBuffer *view_fb); + + void bind_resources(DRWShadingGroup *grp) + { + DRW_shgroup_storage_block_ref(grp, "light_buf", &culling_light_buf_); + DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_); + DRW_shgroup_storage_block_ref(grp, "light_zbin_buf", &culling_zbin_buf_); + DRW_shgroup_storage_block_ref(grp, "light_tile_buf", &culling_tile_buf_); +#if 0 + DRW_shgroup_uniform_texture(grp, "shadow_atlas_tx", inst_.shadows.atlas_tx_get()); + DRW_shgroup_uniform_texture(grp, "shadow_tilemaps_tx", inst_.shadows.tilemap_tx_get()); +#endif + } + + private: + void culling_pass_sync(); + void debug_pass_sync(); +}; + +/** \} */ + +} // namespace blender::eevee 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..d9545e2e972 --- /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 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_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 214fe9c7153..d9ac39f4fb9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -43,9 +43,8 @@ void WorldPipeline::sync(GPUMaterial *gpumat) DRW_shgroup_storage_block_ref(grp, "aov_buf", &inst_.film.aovs_info); /* RenderPasses. Cleared by background (even if bad practice). */ DRW_shgroup_uniform_image_ref(grp, "rp_normal_img", &rbufs.normal_tx); - DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_light_img", &rbufs.diffuse_light_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_light_img", &rbufs.light_tx); DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_color_img", &rbufs.diffuse_color_tx); - DRW_shgroup_uniform_image_ref(grp, "rp_specular_light_img", &rbufs.specular_light_tx); DRW_shgroup_uniform_image_ref(grp, "rp_specular_color_img", &rbufs.specular_color_tx); DRW_shgroup_uniform_image_ref(grp, "rp_emission_img", &rbufs.emission_tx); /* To allow opaque pass rendering over it. */ @@ -101,12 +100,14 @@ DRWShadingGroup *ForwardPipeline::material_opaque_add(::Material *blender_mat, G { RenderBuffers &rbufs = inst_.render_buffers; DRWPass *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? opaque_culled_ps_ : opaque_ps_; - // LightModule &lights = inst_.lights; + LightModule &lights = inst_.lights; + Sampling &sampling = inst_.sampling; // LightProbeModule &lightprobes = inst_.lightprobes; // RaytracingModule &raytracing = inst_.raytracing; // eGPUSamplerState no_interp = GPU_SAMPLER_DEFAULT; DRWShadingGroup *grp = DRW_shgroup_material_create(gpumat, pass); - // lights.shgroup_resources(grp); + lights.bind_resources(grp); + sampling.bind_resources(grp); // DRW_shgroup_uniform_block(grp, "sampling_buf", inst_.sampling.ubo_get()); // DRW_shgroup_uniform_block(grp, "grids_buf", lightprobes.grid_ubo_get()); // DRW_shgroup_uniform_block(grp, "cubes_buf", lightprobes.cube_ubo_get()); @@ -120,9 +121,8 @@ DRWShadingGroup *ForwardPipeline::material_opaque_add(::Material *blender_mat, G DRW_shgroup_storage_block_ref(grp, "aov_buf", &inst_.film.aovs_info); /* RenderPasses. */ DRW_shgroup_uniform_image_ref(grp, "rp_normal_img", &rbufs.normal_tx); - DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_light_img", &rbufs.diffuse_light_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_light_img", &rbufs.light_tx); DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_color_img", &rbufs.diffuse_color_tx); - DRW_shgroup_uniform_image_ref(grp, "rp_specular_light_img", &rbufs.specular_light_tx); DRW_shgroup_uniform_image_ref(grp, "rp_specular_color_img", &rbufs.specular_color_tx); DRW_shgroup_uniform_image_ref(grp, "rp_emission_img", &rbufs.emission_tx); @@ -162,19 +162,22 @@ DRWShadingGroup *ForwardPipeline::prepass_opaque_add(::Material *blender_mat, DRWShadingGroup *ForwardPipeline::material_transparent_add(::Material *blender_mat, GPUMaterial *gpumat) { - // LightModule &lights = inst_.lights; + RenderBuffers &rbufs = inst_.render_buffers; + LightModule &lights = inst_.lights; + Sampling &sampling = inst_.sampling; // LightProbeModule &lightprobes = inst_.lightprobes; // RaytracingModule &raytracing = inst_.raytracing; // eGPUSamplerState no_interp = GPU_SAMPLER_DEFAULT; DRWShadingGroup *grp = DRW_shgroup_material_create(gpumat, transparent_ps_); - // lights.shgroup_resources(grp); + lights.bind_resources(grp); + sampling.bind_resources(grp); // DRW_shgroup_uniform_block(grp, "sampling_buf", inst_.sampling.ubo_get()); // DRW_shgroup_uniform_block(grp, "grids_buf", lightprobes.grid_ubo_get()); // DRW_shgroup_uniform_block(grp, "cubes_buf", lightprobes.cube_ubo_get()); // DRW_shgroup_uniform_block(grp, "probes_buf", lightprobes.info_ubo_get()); // DRW_shgroup_uniform_texture_ref(grp, "lightprobe_grid_tx", lightprobes.grid_tx_ref_get()); // DRW_shgroup_uniform_texture_ref(grp, "lightprobe_cube_tx", lightprobes.cube_tx_ref_get()); - // DRW_shgroup_uniform_texture(grp, "utility_tx", inst_.pipelines.utility_tx); + DRW_shgroup_uniform_texture(grp, "utility_tx", inst_.pipelines.utility_tx); /* TODO(fclem): Make this only needed if material uses it ... somehow. */ // if (true) { // DRW_shgroup_uniform_texture_ref( @@ -191,6 +194,21 @@ DRWShadingGroup *ForwardPipeline::material_transparent_add(::Material *blender_m // DRW_shgroup_uniform_block(grp, "hiz_buf", inst_.hiz.ubo_get()); // DRW_shgroup_uniform_texture_ref(grp, "hiz_tx", inst_.hiz_front.texture_ref_get()); // } + { + /* TODO(fclem): This is not needed. This is only to please the OpenGL debug Layer. + * If we are to introduce transparency render-passes support, it would be through a separate + * pass. */ + /* AOVs. */ + DRW_shgroup_uniform_image_ref(grp, "aov_color_img", &rbufs.aov_color_tx); + DRW_shgroup_uniform_image_ref(grp, "aov_value_img", &rbufs.aov_value_tx); + DRW_shgroup_storage_block_ref(grp, "aov_buf", &inst_.film.aovs_info); + /* RenderPasses. */ + DRW_shgroup_uniform_image_ref(grp, "rp_normal_img", &rbufs.normal_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_light_img", &rbufs.light_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_color_img", &rbufs.diffuse_color_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_specular_color_img", &rbufs.specular_color_tx); + DRW_shgroup_uniform_image_ref(grp, "rp_emission_img", &rbufs.emission_tx); + } DRWState state_disable = DRW_STATE_WRITE_DEPTH; DRWState state_enable = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM; @@ -224,22 +242,22 @@ DRWShadingGroup *ForwardPipeline::prepass_transparent_add(::Material *blender_ma void ForwardPipeline::render(const DRWView *view, Framebuffer &prepass_fb, Framebuffer &combined_fb, - GPUTexture *depth_tx, GPUTexture *UNUSED(combined_tx)) { - UNUSED_VARS(view, depth_tx, prepass_fb, combined_fb); - // HiZBuffer &hiz = inst_.hiz_front; + UNUSED_VARS(view); DRW_stats_group_start("ForwardOpaque"); GPU_framebuffer_bind(prepass_fb); DRW_draw_pass(prepass_ps_); - // hiz.set_dirty(); + if (!DRW_pass_is_empty(prepass_ps_)) { + inst_.hiz_buffer.set_dirty(); + } // if (inst_.raytracing.enabled()) { // rt_buffer.radiance_copy(combined_tx); - // hiz.update(depth_tx); + // inst_.hiz_buffer.update(); // } // inst_.shadows.set_view(view, depth_tx); diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 3bdc718767b..ed6986b9b61 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -91,7 +91,6 @@ class ForwardPipeline { void render(const DRWView *view, Framebuffer &prepass_fb, Framebuffer &combined_fb, - GPUTexture *depth_tx, GPUTexture *combined_tx); }; diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc index c60054496c1..dd7da0d8f76 100644 --- a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc +++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc @@ -24,54 +24,48 @@ namespace blender::eevee { -void RenderBuffers::sync() +void RenderBuffers::acquire(int2 extent) { - 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(); -} + const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get(); -void RenderBuffers::acquire(int2 extent, void *owner) -{ auto pass_extent = [&](eViewLayerEEVEEPassType pass_bit) -> int2 { /* Use dummy texture for disabled passes. Allows correct bindings. */ - return (inst_.film.enabled_passes_get() & pass_bit) ? extent : int2(1); + return (enabled_passes & pass_bit) ? extent : int2(1); }; eGPUTextureFormat color_format = GPU_RGBA16F; 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); - - bool do_vector_render_pass = inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR; + depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8); + combined_tx.acquire(extent, color_format); + + bool do_vector_render_pass = (enabled_passes & EEVEE_RENDER_PASS_VECTOR) || + (inst_.motion_blur.postfx_enabled() && !inst_.is_viewport()); + uint32_t max_light_color_layer = max_ii(enabled_passes & EEVEE_RENDER_PASS_DIFFUSE_LIGHT ? + RENDER_PASS_LAYER_DIFFUSE_LIGHT : + -1, + enabled_passes & EEVEE_RENDER_PASS_SPECULAR_LIGHT ? + RENDER_PASS_LAYER_SPECULAR_LIGHT : + -1) + + 1; /* 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_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_COLOR), 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); + + light_tx.ensure_2d_array(color_format, + max_light_color_layer > 0 ? extent : int2(1), + max_ii(1, max_light_color_layer)); const AOVsInfoData &aovs = inst_.film.aovs_info; aov_color_tx.ensure_2d_array( @@ -87,9 +81,7 @@ void RenderBuffers::release() normal_tx.release(); vector_tx.release(); - diffuse_light_tx.release(); diffuse_color_tx.release(); - specular_light_tx.release(); specular_color_tx.release(); volume_light_tx.release(); emission_tx.release(); diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh index 8c91fed2f0f..0b761d618cc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh +++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh @@ -28,9 +28,7 @@ class RenderBuffers { // TextureFromPool mist_tx; /* Derived from depth_tx during accumulation. */ TextureFromPool normal_tx; TextureFromPool vector_tx; - TextureFromPool diffuse_light_tx; TextureFromPool diffuse_color_tx; - TextureFromPool specular_light_tx; TextureFromPool specular_color_tx; TextureFromPool volume_light_tx; TextureFromPool emission_tx; @@ -39,6 +37,7 @@ class RenderBuffers { TextureFromPool ambient_occlusion_tx; // TextureFromPool cryptomatte_tx; /* TODO */ /* TODO(fclem): Use texture from pool once they support texture array. */ + Texture light_tx; Texture aov_color_tx; Texture aov_value_tx; @@ -48,9 +47,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_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh index c604ecef40b..be87ee74886 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh @@ -27,11 +27,11 @@ class Sampling { Instance &inst_; /* Number of samples in the first ring of jittered depth of field. */ - constexpr static uint64_t dof_web_density_ = 6; + static constexpr uint64_t dof_web_density_ = 6; /* High number of sample for viewport infinite rendering. */ - constexpr static uint64_t infinite_sample_count_ = 0xFFFFFFu; + static constexpr uint64_t infinite_sample_count_ = 0xFFFFFFu; /* During interactive rendering, loop over the first few samples. */ - constexpr static uint64_t interactive_sample_max_ = 8; + static constexpr uint64_t interactive_sample_max_ = 8; /** 0 based current sample. Might not increase sequentially in viewport. */ uint64_t sample_ = 0; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 7db9692783a..0e49b195ea2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -82,6 +82,62 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_film_frag"; case FILM_COMP: return "eevee_film_comp"; + case HIZ_DEBUG: + return "eevee_hiz_debug"; + case HIZ_UPDATE: + return "eevee_hiz_update"; + 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"; + case DOF_BOKEH_LUT: + return "eevee_depth_of_field_bokeh_lut"; + case DOF_DOWNSAMPLE: + return "eevee_depth_of_field_downsample"; + case DOF_FILTER: + return "eevee_depth_of_field_filter"; + case DOF_GATHER_FOREGROUND_LUT: + return "eevee_depth_of_field_gather_foreground_lut"; + case DOF_GATHER_FOREGROUND: + return "eevee_depth_of_field_gather_foreground_no_lut"; + case DOF_GATHER_BACKGROUND_LUT: + return "eevee_depth_of_field_gather_background_lut"; + case DOF_GATHER_BACKGROUND: + return "eevee_depth_of_field_gather_background_no_lut"; + case DOF_GATHER_HOLE_FILL: + return "eevee_depth_of_field_hole_fill"; + case DOF_REDUCE: + return "eevee_depth_of_field_reduce"; + case DOF_RESOLVE: + return "eevee_depth_of_field_resolve_no_lut"; + case DOF_RESOLVE_LUT: + return "eevee_depth_of_field_resolve_lut"; + case DOF_SETUP: + return "eevee_depth_of_field_setup"; + case DOF_SCATTER: + return "eevee_depth_of_field_scatter"; + case DOF_STABILIZE: + return "eevee_depth_of_field_stabilize"; + case DOF_TILES_DILATE_MINABS: + return "eevee_depth_of_field_tiles_dilate_minabs"; + case DOF_TILES_DILATE_MINMAX: + return "eevee_depth_of_field_tiles_dilate_minmax"; + case DOF_TILES_FLATTEN: + return "eevee_depth_of_field_tiles_flatten"; + case LIGHT_CULLING_DEBUG: + return "eevee_light_culling_debug"; + case LIGHT_CULLING_SELECT: + return "eevee_light_culling_select"; + case LIGHT_CULLING_SORT: + return "eevee_light_culling_sort"; + case LIGHT_CULLING_TILE: + return "eevee_light_culling_tile"; + case LIGHT_CULLING_ZBIN: + return "eevee_light_culling_zbin"; /* 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..9ef42c84373 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -29,6 +29,38 @@ enum eShaderType { FILM_FRAG = 0, FILM_COMP, + DOF_BOKEH_LUT, + DOF_DOWNSAMPLE, + DOF_FILTER, + DOF_GATHER_BACKGROUND_LUT, + DOF_GATHER_BACKGROUND, + DOF_GATHER_FOREGROUND_LUT, + DOF_GATHER_FOREGROUND, + DOF_GATHER_HOLE_FILL, + DOF_REDUCE, + DOF_RESOLVE_LUT, + DOF_RESOLVE, + DOF_SCATTER, + DOF_SETUP, + DOF_STABILIZE, + DOF_TILES_DILATE_MINABS, + DOF_TILES_DILATE_MINMAX, + DOF_TILES_FLATTEN, + + HIZ_UPDATE, + HIZ_DEBUG, + + LIGHT_CULLING_DEBUG, + LIGHT_CULLING_SELECT, + LIGHT_CULLING_SORT, + LIGHT_CULLING_TILE, + LIGHT_CULLING_ZBIN, + + 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..a0829bc49aa 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -23,11 +23,64 @@ using draw::SwapChain; using draw::Texture; using draw::TextureFromPool; +constexpr eGPUSamplerState no_filter = GPU_SAMPLER_DEFAULT; +constexpr eGPUSamplerState with_filter = GPU_SAMPLER_FILTER; + #endif #define UBO_MIN_MAX_SUPPORTED_SIZE 1 << 14 /* -------------------------------------------------------------------- */ +/** \name Debug Mode + * \{ */ + +/** These are just to make more sense of G.debug_value's values. Reserved range is 1-30. */ +enum eDebugMode : uint32_t { + DEBUG_NONE = 0u, + /** + * Gradient showing light evaluation hot-spots. + */ + DEBUG_LIGHT_CULLING = 1u, + /** + * Show incorrectly downsample tiles in red. + */ + DEBUG_HIZ_VALIDATION = 2u, + /** + * Tile-maps to screen. Is also present in other modes. + * - Black pixels, no pages allocated. + * - Green pixels, pages cached. + * - Red pixels, pages allocated. + */ + DEBUG_SHADOW_TILEMAPS = 10u, + /** + * Random color per pages. Validates page density allocation and sampling. + */ + DEBUG_SHADOW_PAGES = 11u, + /** + * Outputs random color per tile-map (or tile-map level). Validates tile-maps coverage. + * Black means not covered by any tile-maps LOD of the shadow. + */ + DEBUG_SHADOW_LOD = 12u, + /** + * Outputs white pixels for pages allocated and black pixels for unused pages. + * This needs DEBUG_SHADOW_PAGE_ALLOCATION_ENABLED defined in order to work. + */ + DEBUG_SHADOW_PAGE_ALLOCATION = 13u, + /** + * Outputs the tile-map atlas. Default tile-map is too big for the usual screen resolution. + * Try lowering SHADOW_TILEMAP_PER_ROW and SHADOW_MAX_TILEMAP before using this option. + */ + DEBUG_SHADOW_TILE_ALLOCATION = 14u, + /** + * Visualize linear depth stored in the atlas regions of the active light. + * This way, one can check if the rendering, the copying and the shadow sampling functions works. + */ + DEBUG_SHADOW_SHADOW_DEPTH = 15u +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Sampling * \{ */ @@ -124,7 +177,7 @@ struct CameraData { float clip_far; eCameraType type; - bool initialized; + bool1 initialized; #ifdef __cplusplus /* Small constructor to allow detecting new buffers. */ @@ -239,6 +292,17 @@ static inline float film_filter_weight(float filter_radius, float sample_distanc /** \} */ /* -------------------------------------------------------------------- */ +/** \name Render passes + * \{ */ + +enum eRenderPassLayerIndex : uint32_t { + RENDER_PASS_LAYER_DIFFUSE_LIGHT = 0u, + RENDER_PASS_LAYER_SPECULAR_LIGHT = 1u, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Arbitrary Output Variables * \{ */ @@ -312,6 +376,272 @@ 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 Depth of field + * \{ */ + +/* 5% error threshold. */ +#define DOF_FAST_GATHER_COC_ERROR 0.05 +#define DOF_GATHER_RING_COUNT 5 +#define DOF_DILATE_RING_COUNT 3 + +struct DepthOfFieldData { + /** Size of the render targets for gather & scatter passes. */ + int2 extent; + /** Size of a pixel in uv space (1.0 / extent). */ + float2 texel_size; + /** Scale factor for anisotropic bokeh. */ + float2 bokeh_anisotropic_scale; + float2 bokeh_anisotropic_scale_inv; + /* Correction factor to align main target pixels with the filtered mipmap chain texture. */ + float2 gather_uv_fac; + /** Scatter parameters. */ + float scatter_coc_threshold; + float scatter_color_threshold; + float scatter_neighbor_max_color; + int scatter_sprite_per_row; + /** Number of side the bokeh shape has. */ + float bokeh_blades; + /** Rotation of the bokeh shape. */ + float bokeh_rotation; + /** Multiplier and bias to apply to linear depth to Circle of confusion (CoC). */ + float coc_mul, coc_bias; + /** Maximum absolute allowed Circle of confusion (CoC). Min of computed max and user max. */ + float coc_abs_max; + /** Copy of camera type. */ + eCameraType camera_type; + /** Weights of spatial filtering in stabilize pass. Not array to avoid alignment restriction. */ + float4 filter_samples_weight; + float filter_center_weight; + /** Max number of sprite in the scatter pass for each ground. */ + int scatter_max_rect; + + int _pad0, _pad1; +}; +BLI_STATIC_ASSERT_ALIGN(DepthOfFieldData, 16) + +struct ScatterRect { + /** Color and CoC of the 4 pixels the scatter sprite represents. */ + float4 color_and_coc[4]; + /** Rect center position in half pixel space. */ + float2 offset; + /** Rect half extent in half pixel space. */ + float2 half_extent; +}; +BLI_STATIC_ASSERT_ALIGN(ScatterRect, 16) + +/** WORKAROUND(@fclem): This is because this file is included before common_math_lib.glsl. */ +#ifndef M_PI +# define EEVEE_PI +# define M_PI 3.14159265358979323846 /* pi */ +#endif + +static inline float coc_radius_from_camera_depth(DepthOfFieldData dof, float depth) +{ + depth = (dof.camera_type != CAMERA_ORTHO) ? 1.0f / depth : depth; + return dof.coc_mul * depth + dof.coc_bias; +} + +static inline float regular_polygon_side_length(float sides_count) +{ + return 2.0f * sinf(M_PI / sides_count); +} + +/* Returns intersection ratio between the radius edge at theta and the regular polygon edge. + * Start first corners at theta == 0. */ +static inline float circle_to_polygon_radius(float sides_count, float theta) +{ + /* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide + * 36). */ + float side_angle = (2.0f * M_PI) / sides_count; + return cosf(side_angle * 0.5f) / + cosf(theta - side_angle * floorf((sides_count * theta + M_PI) / (2.0f * M_PI))); +} + +/* Remap input angle to have homogenous spacing of points along a polygon edge. + * Expects theta to be in [0..2pi] range. */ +static inline float circle_to_polygon_angle(float sides_count, float theta) +{ + float side_angle = (2.0f * M_PI) / sides_count; + float halfside_angle = side_angle * 0.5f; + float side = floorf(theta / side_angle); + /* Length of segment from center to the middle of polygon side. */ + float adjacent = circle_to_polygon_radius(sides_count, 0.0f); + + /* This is the relative position of the sample on the polygon half side. */ + float local_theta = theta - side * side_angle; + float ratio = (local_theta - halfside_angle) / halfside_angle; + + float halfside_len = regular_polygon_side_length(sides_count) * 0.5f; + float opposite = ratio * halfside_len; + + /* NOTE: atan(y_over_x) has output range [-M_PI_2..M_PI_2]. */ + float final_local_theta = atanf(opposite / adjacent); + + return side * side_angle + final_local_theta; +} + +#ifdef EEVEE_PI +# undef M_PI +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Light Culling + * \{ */ + +/* Number of items we can cull. Limited by how we store CullingZBin. */ +#define CULLING_MAX_ITEM 65536 +/* Fine grained subdivision in the Z direction. Limited by the LDS in z-binning compute shader. */ +#define CULLING_ZBIN_COUNT 4096 +/* Max tile map resolution per axes. */ +#define CULLING_TILE_RES 16 + +struct LightCullingData { + /** Scale applied to tile pixel coordinates to get target UV coordinate. */ + float2 tile_to_uv_fac; + /** Scale and bias applied to linear Z to get zbin. */ + float zbin_scale; + float zbin_bias; + /** Valid item count in the source data array. */ + uint items_count; + /** Items that are processed by the 2.5D culling. */ + uint local_lights_len; + /** Items that are **NOT** processed by the 2.5D culling (i.e: Sun Lights). */ + uint sun_lights_len; + /** Number of items that passes the first culling test. */ + uint visible_count; + /** Extent of one square tile in pixels. */ + float tile_size; + /** Number of tiles on the X/Y axis. */ + uint tile_x_len; + uint tile_y_len; + /** Number of word per tile. Depends on the maximum number of lights. */ + uint tile_word_len; +}; +BLI_STATIC_ASSERT_ALIGN(LightCullingData, 16) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Lights + * \{ */ + +#define LIGHT_NO_SHADOW -1 + +enum eLightType : uint32_t { + LIGHT_SUN = 0u, + LIGHT_POINT = 1u, + LIGHT_SPOT = 2u, + LIGHT_RECT = 3u, + LIGHT_ELLIPSE = 4u +}; + +static inline bool is_area_light(eLightType type) +{ + return type >= LIGHT_RECT; +} + +struct LightData { + /** Normalized object matrix. Last column contains data accessible using the following macros. */ + float4x4 object_mat; + /** Packed data in the last column of the object_mat. */ +#define _area_size_x object_mat[0][3] +#define _area_size_y object_mat[1][3] +#define _radius _area_size_x +#define _spot_mul object_mat[2][3] +#define _spot_bias object_mat[3][3] + /** Aliases for axes. */ +#ifndef USE_GPU_SHADER_CREATE_INFO +# define _right object_mat[0] +# define _up object_mat[1] +# define _back object_mat[2] +# define _position object_mat[3] +#else +# define _right object_mat[0].xyz +# define _up object_mat[1].xyz +# define _back object_mat[2].xyz +# define _position object_mat[3].xyz +#endif + /** Influence radius (inverted and squared) adjusted for Surface / Volume power. */ + float influence_radius_invsqr_surface; + float influence_radius_invsqr_volume; + /** Maximum influence radius. Used for culling. */ + float influence_radius_max; + /** Index of the shadow struct on CPU. -1 means no shadow. */ + int shadow_id; + /** NOTE: It is ok to use float3 here. A float is declared right after it. + * float3 is also aligned to 16 bytes. */ + float3 color; + /** Power depending on shader type. */ + float diffuse_power; + float specular_power; + float volume_power; + float transmit_power; + /** Special radius factor for point lighting. */ + float radius_squared; + /** Light Type. */ + eLightType type; + /** Spot angle tangent. */ + float spot_tan; + /** Spot size. Aligned to size of float2. */ + float2 spot_size_inv; + /** Associated shadow data. Only valid if shadow_id is not LIGHT_NO_SHADOW. */ + // ShadowData shadow_data; +}; +BLI_STATIC_ASSERT_ALIGN(LightData, 16) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Hierarchical-Z Buffer + * \{ */ + +struct HiZData { + /** Scale factor to remove HiZBuffer padding. */ + float2 uv_scale; + + float2 _pad0; +}; +BLI_STATIC_ASSERT_ALIGN(HiZData, 16) + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Ray-Tracing * \{ */ @@ -332,6 +662,34 @@ enum eClosureBits : uint32_t { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Subsurface + * \{ */ + +#define SSS_SAMPLE_MAX 64 +#define SSS_BURLEY_TRUNCATE 16.0 +#define SSS_BURLEY_TRUNCATE_CDF 0.9963790093708328 +#define SSS_TRANSMIT_LUT_SIZE 64.0 +#define SSS_TRANSMIT_LUT_RADIUS 1.218 +#define SSS_TRANSMIT_LUT_SCALE ((SSS_TRANSMIT_LUT_SIZE - 1.0) / float(SSS_TRANSMIT_LUT_SIZE)) +#define SSS_TRANSMIT_LUT_BIAS (0.5 / float(SSS_TRANSMIT_LUT_SIZE)) +#define SSS_TRANSMIT_LUT_STEP_RES 64.0 + +struct SubsurfaceData { + /** xy: 2D sample position [-1..1], zw: sample_bounds. */ + /* NOTE(fclem) Using float4 for alignment. */ + float4 samples[SSS_SAMPLE_MAX]; + /** Sample index after which samples are not randomly rotated anymore. */ + int jitter_threshold; + /** Number of samples precomputed in the set. */ + int sample_len; + int _pad0; + int _pad1; +}; +BLI_STATIC_ASSERT_ALIGN(SubsurfaceData, 16) + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Utility Texture * \{ */ @@ -370,7 +728,19 @@ float4 utility_tx_sample(sampler2DArray util_tx, float2 uv, float layer) using AOVsInfoDataBuf = draw::StorageBuffer<AOVsInfoData>; using CameraDataBuf = draw::UniformBuffer<CameraData>; +using DepthOfFieldDataBuf = draw::UniformBuffer<DepthOfFieldData>; +using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, true>; +using DrawIndirectBuf = draw::StorageBuffer<DrawCommand, true>; using FilmDataBuf = draw::UniformBuffer<FilmData>; +using HiZDataBuf = draw::UniformBuffer<HiZData>; +using LightCullingDataBuf = draw::StorageBuffer<LightCullingData>; +using LightCullingKeyBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>; +using LightCullingTileBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>; +using LightCullingZbinBuf = draw::StorageArrayBuffer<uint, CULLING_ZBIN_COUNT, true>; +using LightCullingZdistBuf = draw::StorageArrayBuffer<float, LIGHT_CHUNK, true>; +using LightDataBuf = draw::StorageArrayBuffer<LightData, LIGHT_CHUNK>; +using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>; +using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>; using SamplingDataBuf = draw::StorageBuffer<SamplingData>; using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>; using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>; 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..44067aff9ca 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), @@ -109,6 +102,8 @@ void ShadingView::render() update_view(); + inst_.hiz_buffer.set_dirty(); + DRW_stats_group_start(name_); DRW_view_set_active(render_view_); @@ -125,6 +120,9 @@ void ShadingView::render() inst_.pipelines.world.render(); + /* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */ + inst_.lights.set_view(render_view_, extent_); + // inst_.pipelines.deferred.render( // render_view_, rt_buffer_opaque_, rt_buffer_refract_, depth_tx_, combined_tx_); @@ -132,15 +130,16 @@ void ShadingView::render() // inst_.lookdev.render_overlay(view_fb_); - inst_.pipelines.forward.render( - render_view_, prepass_fb_, combined_fb_, rbufs.depth_tx, rbufs.combined_tx); + inst_.pipelines.forward.render(render_view_, prepass_fb_, combined_fb_, rbufs.combined_tx); - // inst_.lights.debug_draw(view_fb_); - // inst_.shadows.debug_draw(view_fb_); + inst_.lights.debug_draw(combined_fb_); + inst_.hiz_buffer.debug_draw(combined_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); + + // inst_.shadows.debug_draw(); rbufs.release(); postfx_tx_.release(); @@ -148,23 +147,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 (!inst_.depth_of_field.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 + inst_.depth_of_field.render(&input_tx, &output_tx, dof_buffer_); + inst_.motion_blur.render(&input_tx, &output_tx); + return input_tx; } @@ -187,13 +182,10 @@ void ShadingView::update_view() window_translate_m4(winmat.ptr(), winmat.ptr(), UNPACK2(jitter)); DRW_view_update_sub(sub_view_, viewmat.ptr(), winmat.ptr()); - /* FIXME(fclem): The offset may be is noticeably large and the culling might make object pop + /* FIXME(fclem): The offset may be noticeably large and the culling might make object pop * out of the blurring radius. To fix this, use custom enlarged culling matrix. */ - // dof_.jitter_apply(winmat, viewmat); + inst_.depth_of_field.jitter_apply(winmat, viewmat); DRW_view_update_sub(render_view_, viewmat.ptr(), winmat.ptr()); - - // inst_.lightprobes.set_view(render_view_, extent_); - // inst_.lights.set_view(render_view_, extent_, !inst_.use_scene_lights()); } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_view.hh b/source/blender/draw/engines/eevee_next/eevee_view.hh index c6faebdd0e5..65f27aba795 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.hh +++ b/source/blender/draw/engines/eevee_next/eevee_view.hh @@ -41,13 +41,10 @@ class ShadingView { /** Matrix to apply to the viewmat. */ const float (*face_matrix_)[4]; - /** Post-FX modules. */ - // DepthOfField dof_; - // MotionBlur mb_; - /** Raytracing persistent buffers. Only opaque and refraction can have surface tracing. */ // RaytraceBuffer rt_buffer_opaque_; // RaytraceBuffer rt_buffer_refract_; + DepthOfFieldBuffer dof_buffer_; Framebuffer prepass_fb_; Framebuffer combined_fb_; @@ -78,7 +75,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_colorspace_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl new file mode 100644 index 00000000000..d5fdaae6fc1 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl @@ -0,0 +1,37 @@ + +/* -------------------------------------------------------------------- */ +/** \name YCoCg + * \{ */ + +vec3 colorspace_YCoCg_from_scene_linear(vec3 rgb_color) +{ + const mat3 colorspace_tx = transpose(mat3(vec3(1, 2, 1), /* Y */ + vec3(2, 0, -2), /* Co */ + vec3(-1, 2, -1))); /* Cg */ + return colorspace_tx * rgb_color; +} + +vec4 colorspace_YCoCg_from_scene_linear(vec4 rgba_color) +{ + return vec4(colorspace_YCoCg_from_scene_linear(rgba_color.rgb), rgba_color.a); +} + +vec3 colorspace_scene_linear_from_YCoCg(vec3 ycocg_color) +{ + float Y = ycocg_color.x; + float Co = ycocg_color.y; + float Cg = ycocg_color.z; + + vec3 rgb_color; + rgb_color.r = Y + Co - Cg; + rgb_color.g = Y + Cg; + rgb_color.b = Y - Co - Cg; + return rgb_color * 0.25; +} + +vec4 colorspace_scene_linear_from_YCoCg(vec4 ycocg_color) +{ + return vec4(colorspace_scene_linear_from_YCoCg(ycocg_color.rgb), ycocg_color.a); +} + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl new file mode 100644 index 00000000000..99a47c541e9 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl @@ -0,0 +1,680 @@ + +/** + * Depth of Field Gather accumulator. + * We currently have only 2 which are very similar. + * One is for the halfres gather passes and the other one for slight in focus regions. + **/ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +/* -------------------------------------------------------------------- */ +/** \name Options. + * \{ */ + +/* Quality options */ +#ifdef DOF_HOLEFILL_PASS +/* No need for very high density for hole_fill. */ +const int gather_ring_count = 3; +const int gather_ring_density = 3; +const int gather_max_density_change = 0; +const int gather_density_change_ring = 1; +#else +const int gather_ring_count = DOF_GATHER_RING_COUNT; +const int gather_ring_density = 3; +const int gather_max_density_change = 50; /* Dictates the maximum good quality blur. */ +const int gather_density_change_ring = 1; +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Constants. + * \{ */ + +const float unit_ring_radius = 1.0 / float(gather_ring_count); +const float unit_sample_radius = 1.0 / float(gather_ring_count + 0.5); +const float large_kernel_radius = 0.5 + float(gather_ring_count); +const float smaller_kernel_radius = 0.5 + float(gather_ring_count - gather_density_change_ring); +/* NOTE(fclem) the bias is reducing issues with density change visible transition. */ +const float radius_downscale_factor = smaller_kernel_radius / large_kernel_radius; +const int change_density_at_ring = (gather_ring_count - gather_density_change_ring + 1); +const float coc_radius_error = 2.0; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Gather common. + * \{ */ + +struct DofGatherData { + vec4 color; + float weight; + float dist; /* TODO remove */ + /* For scatter occlusion. */ + float coc; + float coc_sqr; + /* For ring bucket merging. */ + float transparency; + + float layer_opacity; +}; + +#define GATHER_DATA_INIT DofGatherData(vec4(0.0), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) + +/* Intersection with the center of the kernel. */ +float dof_intersection_weight(float coc, float distance_from_center, float intersection_multiplier) +{ + if (no_smooth_intersection) { + return step(0.0, (abs(coc) - distance_from_center)); + } + else { + /* (Slide 64). */ + return saturate((abs(coc) - distance_from_center) * intersection_multiplier + 0.5); + } +} + +/* Returns weight of the sample for the outer bucket (containing previous + * rings). */ +float dof_gather_accum_weight(float coc, float bordering_radius, bool first_ring) +{ + /* First ring has nothing to be mixed against. */ + if (first_ring) { + return 0.0; + } + return saturate(coc - bordering_radius); +} + +void dof_gather_ammend_weight(inout DofGatherData sample_data, float weight) +{ + sample_data.color *= weight; + sample_data.coc *= weight; + sample_data.coc_sqr *= weight; + sample_data.weight *= weight; +} + +void dof_gather_accumulate_sample(DofGatherData sample_data, + float weight, + inout DofGatherData accum_data) +{ + accum_data.color += sample_data.color * weight; + accum_data.coc += sample_data.coc * weight; + accum_data.coc_sqr += sample_data.coc * (sample_data.coc * weight); + accum_data.weight += weight; +} + +void dof_gather_accumulate_sample_pair(DofGatherData pair_data[2], + float bordering_radius, + float intersection_multiplier, + bool first_ring, + const bool do_fast_gather, + const bool is_foreground, + inout DofGatherData ring_data, + inout DofGatherData accum_data) +{ + if (do_fast_gather) { + for (int i = 0; i < 2; i++) { + dof_gather_accumulate_sample(pair_data[i], 1.0, accum_data); + accum_data.layer_opacity += 1.0; + } + return; + } + +#if 0 + const float mirroring_threshold = -dof_layer_threshold - dof_layer_offset; + /* TODO(fclem) Promote to parameter? dither with Noise? */ + const float mirroring_min_distance = 15.0; + if (pair_data[0].coc < mirroring_threshold && + (pair_data[1].coc - mirroring_min_distance) > pair_data[0].coc) { + pair_data[1].coc = pair_data[0].coc; + } + else if (pair_data[1].coc < mirroring_threshold && + (pair_data[0].coc - mirroring_min_distance) > pair_data[1].coc) { + pair_data[0].coc = pair_data[1].coc; + } +#endif + + for (int i = 0; i < 2; i++) { + float sample_weight = dof_sample_weight(pair_data[i].coc); + float layer_weight = dof_layer_weight(pair_data[i].coc, is_foreground); + float inter_weight = dof_intersection_weight( + pair_data[i].coc, pair_data[i].dist, intersection_multiplier); + float weight = inter_weight * layer_weight * sample_weight; + + /** + * If a CoC is larger than bordering radius we accumulate it to the general accumulator. + * If not, we accumulate to the ring bucket. This is to have more consistent sample occlusion. + **/ + float accum_weight = dof_gather_accum_weight(pair_data[i].coc, bordering_radius, first_ring); + dof_gather_accumulate_sample(pair_data[i], weight * accum_weight, accum_data); + dof_gather_accumulate_sample(pair_data[i], weight * (1.0 - accum_weight), ring_data); + + accum_data.layer_opacity += layer_weight; + + if (is_foreground) { + ring_data.transparency += 1.0 - inter_weight * layer_weight; + } + else { + float coc = is_foreground ? -pair_data[i].coc : pair_data[i].coc; + ring_data.transparency += saturate(coc - bordering_radius); + } + } +} + +void dof_gather_accumulate_sample_ring(DofGatherData ring_data, + int sample_count, + bool first_ring, + const bool do_fast_gather, + /* accum_data occludes the ring_data if true. */ + const bool reversed_occlusion, + inout DofGatherData accum_data) +{ + if (do_fast_gather) { + /* Do nothing as ring_data contains nothing. All samples are already in + * accum_data. */ + return; + } + + if (first_ring) { + /* Layer opacity is directly accumulated into accum_data data. */ + accum_data.color = ring_data.color; + accum_data.coc = ring_data.coc; + accum_data.coc_sqr = ring_data.coc_sqr; + accum_data.weight = ring_data.weight; + + accum_data.transparency = ring_data.transparency / float(sample_count); + return; + } + + if (ring_data.weight == 0.0) { + return; + } + + float ring_avg_coc = ring_data.coc / ring_data.weight; + float accum_avg_coc = accum_data.coc / accum_data.weight; + + /* Smooth test to set opacity to see if the ring average coc occludes the + * accumulation. Test is reversed to be multiplied against opacity. */ + float ring_occlu = saturate(accum_avg_coc - ring_avg_coc); + /* The bias here is arbitrary. Seems to avoid weird looking foreground in most + * cases. We might need to make it a parameter or find a relative bias. */ + float accum_occlu = saturate((ring_avg_coc - accum_avg_coc) * 0.1 - 1.0); + + if (is_resolve) { + ring_occlu = accum_occlu = 0.0; + } + + if (no_gather_occlusion) { + ring_occlu = 0.0; + accum_occlu = 0.0; + } + + /* (Slide 40) */ + float ring_opacity = saturate(1.0 - ring_data.transparency / float(sample_count)); + float accum_opacity = 1.0 - accum_data.transparency; + + if (reversed_occlusion) { + /* Accum_data occludes the ring. */ + float alpha = (accum_data.weight == 0.0) ? 0.0 : accum_opacity * accum_occlu; + float one_minus_alpha = 1.0 - alpha; + + accum_data.color += ring_data.color * one_minus_alpha; + accum_data.coc += ring_data.coc * one_minus_alpha; + accum_data.coc_sqr += ring_data.coc_sqr * one_minus_alpha; + accum_data.weight += ring_data.weight * one_minus_alpha; + + accum_data.transparency *= 1.0 - ring_opacity; + } + else { + /* Ring occludes the accum_data (Same as reference). */ + float alpha = (accum_data.weight == 0.0) ? 1.0 : (ring_opacity * ring_occlu); + float one_minus_alpha = 1.0 - alpha; + + accum_data.color = accum_data.color * one_minus_alpha + ring_data.color; + accum_data.coc = accum_data.coc * one_minus_alpha + ring_data.coc; + accum_data.coc_sqr = accum_data.coc_sqr * one_minus_alpha + ring_data.coc_sqr; + accum_data.weight = accum_data.weight * one_minus_alpha + ring_data.weight; + } +} + +/* FIXME(fclem) Seems to be wrong since it needs ringcount+1 as input for + * slightfocus gather. */ +/* This should be replaced by web_sample_count_get() but doing so is breaking other things. */ +int dof_gather_total_sample_count(const int ring_count, const int ring_density) +{ + return (ring_count * ring_count - ring_count) * ring_density + 1; +} + +void dof_gather_accumulate_center_sample(DofGatherData center_data, + float bordering_radius, + int i_radius, + const bool do_fast_gather, + const bool is_foreground, + const bool is_resolve, + inout DofGatherData accum_data) +{ + float layer_weight = dof_layer_weight(center_data.coc, is_foreground); + float sample_weight = dof_sample_weight(center_data.coc); + float weight = layer_weight * sample_weight; + float accum_weight = dof_gather_accum_weight(center_data.coc, bordering_radius, false); + + if (do_fast_gather) { + /* Hope for the compiler to optimize the above. */ + layer_weight = 1.0; + sample_weight = 1.0; + accum_weight = 1.0; + weight = 1.0; + } + + center_data.transparency = 1.0 - weight; + + dof_gather_accumulate_sample(center_data, weight * accum_weight, accum_data); + + if (!do_fast_gather) { + if (is_resolve) { + /* NOTE(fclem): Hack to smooth transition to full in-focus opacity. */ + int total_sample_count = dof_gather_total_sample_count(i_radius + 1, + DOF_SLIGHT_FOCUS_DENSITY); + float fac = saturate(1.0 - abs(center_data.coc) / float(dof_layer_threshold)); + accum_data.layer_opacity += float(total_sample_count) * fac * fac; + } + accum_data.layer_opacity += layer_weight; + + /* Logic of dof_gather_accumulate_sample(). */ + weight *= (1.0 - accum_weight); + center_data.coc_sqr = center_data.coc * (center_data.coc * weight); + center_data.color *= weight; + center_data.coc *= weight; + center_data.weight = weight; + + if (is_foreground && !is_resolve) { + /* Reduce issue with closer foreground over distant foreground. */ + float ring_area = sqr(bordering_radius); + dof_gather_ammend_weight(center_data, ring_area); + } + + /* Accumulate center as its own ring. */ + dof_gather_accumulate_sample_ring( + center_data, 1, false, do_fast_gather, is_foreground, accum_data); + } +} + +int dof_gather_total_sample_count_with_density_change(const int ring_count, + const int ring_density, + int density_change) +{ + int sample_count_per_density_change = dof_gather_total_sample_count(ring_count, ring_density) - + dof_gather_total_sample_count( + ring_count - gather_density_change_ring, ring_density); + + return dof_gather_total_sample_count(ring_count, ring_density) + + sample_count_per_density_change * density_change; +} + +void dof_gather_accumulate_resolve(int total_sample_count, + DofGatherData accum_data, + out vec4 out_col, + out float out_weight, + out vec2 out_occlusion) +{ + float weight_inv = safe_rcp(accum_data.weight); + out_col = accum_data.color * weight_inv; + out_occlusion = vec2(abs(accum_data.coc), accum_data.coc_sqr) * weight_inv; + + if (is_foreground) { + out_weight = 1.0 - accum_data.transparency; + } + else if (accum_data.weight > 0.0) { + out_weight = accum_data.layer_opacity / float(total_sample_count); + } + else { + out_weight = 0.0; + } + /* Gathering may not accumulate to 1.0 alpha because of float precision. */ + if (out_weight > 0.99) { + out_weight = 1.0; + } + else if (out_weight < 0.01) { + out_weight = 0.0; + } + /* Same thing for alpha channel. */ + if (out_col.a > 0.993) { + out_col.a = 1.0; + } + else if (out_col.a < 0.003) { + out_col.a = 0.0; + } +} + +float dof_load_gather_coc(sampler2D gather_input_coc_tx, vec2 uv, float lod) +{ + float coc = textureLod(gather_input_coc_tx, uv, lod).r; + /* We gather at halfres. CoC must be divided by 2 to be compared against radii. */ + return coc * 0.5; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Common Gather accumulator. + * \{ */ + +/* Radii needs to be halfres CoC sizes. */ +bool dof_do_density_change(float base_radius, float min_intersectable_radius) +{ + /* Reduce artifact for very large blur. */ + min_intersectable_radius *= 0.1; + + bool need_new_density = (base_radius * unit_ring_radius > min_intersectable_radius); + bool larger_than_min_density = (base_radius * radius_downscale_factor > + float(gather_ring_count)); + + return need_new_density && larger_than_min_density; +} + +void dof_gather_init(float base_radius, + vec2 noise, + out vec2 center_co, + out float lod, + out float intersection_multiplier) +{ + /* Jitter center half a ring to reduce undersampling. */ + vec2 jitter_ofs = 0.499 * sample_disk(noise); + if (DOF_BOKEH_TEXTURE) { + jitter_ofs *= dof_buf.bokeh_anisotropic_scale; + } + vec2 frag_coord = vec2(gl_GlobalInvocationID.xy) + 0.5; + center_co = frag_coord + jitter_ofs * base_radius * unit_sample_radius; + + /* TODO(fclem) Seems like the default lod selection is too big. Bias to avoid blocky moving out + * of focus shapes. */ + const float lod_bias = -2.0; + lod = max(floor(log2(base_radius * unit_sample_radius) + 0.5) + lod_bias, 0.0); + + if (no_gather_mipmaps) { + lod = 0.0; + } + /* (Slide 64). */ + intersection_multiplier = pow(0.5, lod); +} + +void dof_gather_accumulator(sampler2D color_tx, + sampler2D color_bilinear_tx, + sampler2D coc_tx, + sampler2D bkh_lut_tx, /* Renamed because of ugly macro. */ + float base_radius, + float min_intersectable_radius, + const bool do_fast_gather, + const bool do_density_change, + out vec4 out_color, + out float out_weight, + out vec2 out_occlusion) +{ + vec2 frag_coord = vec2(gl_GlobalInvocationID.xy); + vec2 noise_offset = sampling_rng_2D_get(SAMPLING_LENS_U); + vec2 noise = no_gather_random ? vec2(0.0, 0.0) : + vec2(interlieved_gradient_noise(frag_coord, 0, noise_offset.x), + interlieved_gradient_noise(frag_coord, 1, noise_offset.y)); + + if (!do_fast_gather) { + /* Jitter the radius to reduce noticeable density changes. */ + base_radius += noise.x * unit_ring_radius * base_radius; + } + else { + /* Jittering the radius more than we need means we are going to feather the bokeh shape half a + * ring. So we need to compensate for fast gather that does not check CoC intersection. */ + base_radius += (0.5 - noise.x) * 1.5 * unit_ring_radius * base_radius; + } + /* TODO(fclem) another seed? For now Cranly-Partterson rotation with golden ratio. */ + noise.x = fract(noise.x * 6.1803398875); + + float lod, isect_mul; + vec2 center_co; + dof_gather_init(base_radius, noise, center_co, lod, isect_mul); + + bool first_ring = true; + + DofGatherData accum_data = GATHER_DATA_INIT; + + int density_change = 0; + for (int ring = gather_ring_count; ring > 0; ring--) { + int sample_pair_count = gather_ring_density * ring; + + float step_rot = M_PI / float(sample_pair_count); + mat2 step_rot_mat = rot2_from_angle(step_rot); + + float angle_offset = noise.y * step_rot; + vec2 offset = vec2(cos(angle_offset), sin(angle_offset)); + + float ring_radius = float(ring) * unit_sample_radius * base_radius; + + /* Slide 38. */ + float bordering_radius = ring_radius + + (0.5 + coc_radius_error) * base_radius * unit_sample_radius; + DofGatherData ring_data = GATHER_DATA_INIT; + for (int sample_pair = 0; sample_pair < sample_pair_count; sample_pair++) { + offset = step_rot_mat * offset; + + DofGatherData pair_data[2]; + for (int i = 0; i < 2; i++) { + vec2 offset_co = ((i == 0) ? offset : -offset); + if (DOF_BOKEH_TEXTURE) { + /* Scaling to 0.25 for speed. Improves texture cache hit. */ + offset_co = texture(bkh_lut_tx, offset_co * 0.25 + 0.5).rg; + offset_co *= (is_foreground) ? -dof_buf.bokeh_anisotropic_scale : + dof_buf.bokeh_anisotropic_scale; + } + vec2 sample_co = center_co + offset_co * ring_radius; + vec2 sample_uv = sample_co * dof_buf.gather_uv_fac; + if (do_fast_gather) { + pair_data[i].color = textureLod(color_bilinear_tx, sample_uv, lod); + } + else { + pair_data[i].color = textureLod(color_tx, sample_uv, lod); + } + pair_data[i].coc = dof_load_gather_coc(coc_tx, sample_uv, lod); + pair_data[i].dist = ring_radius; + } + + dof_gather_accumulate_sample_pair(pair_data, + bordering_radius, + isect_mul, + first_ring, + do_fast_gather, + is_foreground, + ring_data, + accum_data); + } + + if (is_foreground) { + /* Reduce issue with closer foreground over distant foreground. */ + /* TODO(fclem) this seems to not be completely correct as the issue remains. */ + float ring_area = (sqr(float(ring) + 0.5 + coc_radius_error) - + sqr(float(ring) - 0.5 + coc_radius_error)) * + sqr(base_radius * unit_sample_radius); + dof_gather_ammend_weight(ring_data, ring_area); + } + + dof_gather_accumulate_sample_ring( + ring_data, sample_pair_count * 2, first_ring, do_fast_gather, is_foreground, accum_data); + + first_ring = false; + + if (do_density_change && (ring == change_density_at_ring) && + (density_change < gather_max_density_change)) { + if (dof_do_density_change(base_radius, min_intersectable_radius)) { + base_radius *= radius_downscale_factor; + ring += gather_density_change_ring; + /* We need to account for the density change in the weights (slide 62). + * For that multiply old kernel data by its area divided by the new kernel area. */ + const float outer_rings_weight = 1.0 / (radius_downscale_factor * radius_downscale_factor); + /* Samples are already weighted per ring in foreground pass. */ + if (!is_foreground) { + dof_gather_ammend_weight(accum_data, outer_rings_weight); + } + /* Re-init kernel position & sampling parameters. */ + dof_gather_init(base_radius, noise, center_co, lod, isect_mul); + density_change++; + } + } + } + + { + /* Center sample. */ + vec2 sample_uv = center_co * dof_buf.gather_uv_fac; + DofGatherData center_data; + if (do_fast_gather) { + center_data.color = textureLod(color_bilinear_tx, sample_uv, lod); + } + else { + center_data.color = textureLod(color_tx, sample_uv, lod); + } + center_data.coc = dof_load_gather_coc(coc_tx, sample_uv, lod); + center_data.dist = 0.0; + + /* Slide 38. */ + float bordering_radius = (0.5 + coc_radius_error) * base_radius * unit_sample_radius; + + dof_gather_accumulate_center_sample( + center_data, bordering_radius, 0, do_fast_gather, is_foreground, false, accum_data); + } + + int total_sample_count = dof_gather_total_sample_count_with_density_change( + gather_ring_count, gather_ring_density, density_change); + dof_gather_accumulate_resolve( + total_sample_count, accum_data, out_color, out_weight, out_occlusion); + + if (debug_gather_perf && density_change > 0) { + float fac = saturate(float(density_change) / float(10.0)); + out_color.rgb = avg(out_color.rgb) * neon_gradient(fac); + } + if (debug_gather_perf && do_fast_gather) { + out_color.rgb = avg(out_color.rgb) * vec3(0.0, 1.0, 0.0); + } + if (debug_scatter_perf) { + out_color.rgb = avg(out_color.rgb) * vec3(0.0, 1.0, 0.0); + } + + /* Output premultiplied color so we can use bilinear sampler in resolve pass. */ + out_color *= out_weight; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Slight focus accumulator. + * + * The full pixel neighborhood is gathered. + * \{ */ + +void dof_slight_focus_gather(sampler2D depth_tx, + sampler2D color_tx, + sampler2D bkh_lut_tx, /* Renamed because of ugly macro job. */ + float radius, + out vec4 out_color, + out float out_weight, + out float out_center_coc) +{ + vec2 frag_coord = vec2(gl_GlobalInvocationID.xy) + 0.5; + vec2 noise_offset = sampling_rng_2D_get(SAMPLING_LENS_U); + vec2 noise = no_gather_random ? vec2(0.0) : + vec2(interlieved_gradient_noise(frag_coord, 3, noise_offset.x), + interlieved_gradient_noise(frag_coord, 5, noise_offset.y)); + + DofGatherData fg_accum = GATHER_DATA_INIT; + DofGatherData bg_accum = GATHER_DATA_INIT; + + int i_radius = clamp(int(radius), 0, int(dof_layer_threshold)); + + const float sample_count_max = float(DOF_SLIGHT_FOCUS_SAMPLE_MAX); + /* Scale by search area. */ + float sample_count = sample_count_max * saturate(sqr(radius) / sqr(dof_layer_threshold)); + + bool first_ring = true; + + for (float s = 0.0; s < sample_count; s++) { + vec2 rand2 = fract(hammersley_2d(s, sample_count) + noise); + vec2 offset = sample_disk(rand2); + float ring_dist = sqrt(rand2.y); + + DofGatherData pair_data[2]; + for (int i = 0; i < 2; i++) { + vec2 sample_offset = ((i == 0) ? offset : -offset); + /* OPTI: could precompute the factor. */ + vec2 sample_uv = (frag_coord + sample_offset) / vec2(textureSize(depth_tx, 0)); + float depth = textureLod(depth_tx, sample_uv, 0.0).r; + pair_data[i].coc = dof_coc_from_depth(dof_buf, sample_uv, depth); + pair_data[i].color = safe_color(textureLod(color_tx, sample_uv, 0.0)); + pair_data[i].dist = ring_dist; + if (DOF_BOKEH_TEXTURE) { + /* Contains subpixel distance to bokeh shape. */ + ivec2 lut_texel = ivec2(round(sample_offset)) + dof_max_slight_focus_radius; + pair_data[i].dist = texelFetch(bkh_lut_tx, lut_texel, 0).r; + } + pair_data[i].coc = clamp(pair_data[i].coc, -dof_buf.coc_abs_max, dof_buf.coc_abs_max); + } + + float bordering_radius = ring_dist + 0.5; + const float isect_mul = 1.0; + DofGatherData bg_ring = GATHER_DATA_INIT; + dof_gather_accumulate_sample_pair( + pair_data, bordering_radius, isect_mul, first_ring, false, false, bg_ring, bg_accum); + /* Treat each sample as a ring. */ + dof_gather_accumulate_sample_ring(bg_ring, 2, first_ring, false, false, bg_accum); + + if (DOF_BOKEH_TEXTURE) { + /* Swap distances in order to flip bokeh shape for foreground. */ + float tmp = pair_data[0].dist; + pair_data[0].dist = pair_data[1].dist; + pair_data[1].dist = tmp; + } + DofGatherData fg_ring = GATHER_DATA_INIT; + dof_gather_accumulate_sample_pair( + pair_data, bordering_radius, isect_mul, first_ring, false, true, fg_ring, fg_accum); + /* Treat each sample as a ring. */ + dof_gather_accumulate_sample_ring(fg_ring, 2, first_ring, false, true, fg_accum); + + first_ring = false; + } + + /* Center sample. */ + vec2 sample_uv = frag_coord / vec2(textureSize(depth_tx, 0)); + DofGatherData center_data; + center_data.color = safe_color(textureLod(color_tx, sample_uv, 0.0)); + center_data.coc = dof_coc_from_depth(dof_buf, sample_uv, textureLod(depth_tx, sample_uv, 0.0).r); + center_data.coc = clamp(center_data.coc, -dof_buf.coc_abs_max, dof_buf.coc_abs_max); + center_data.dist = 0.0; + + out_center_coc = center_data.coc; + + /* Slide 38. */ + float bordering_radius = 0.5; + + dof_gather_accumulate_center_sample( + center_data, bordering_radius, i_radius, false, true, true, fg_accum); + dof_gather_accumulate_center_sample( + center_data, bordering_radius, i_radius, false, false, true, bg_accum); + + vec4 bg_col, fg_col; + float bg_weight, fg_weight; + vec2 unused_occlusion; + + int total_sample_count = int(sample_count) * 2 + 1; + dof_gather_accumulate_resolve(total_sample_count, bg_accum, bg_col, bg_weight, unused_occlusion); + dof_gather_accumulate_resolve(total_sample_count, fg_accum, fg_col, fg_weight, unused_occlusion); + + /* Fix weighting issues on perfectly focus to slight focus transitioning areas. */ + if (abs(center_data.coc) < 0.5) { + bg_col = center_data.color; + bg_weight = 1.0; + } + + /* Alpha Over */ + float alpha = 1.0 - fg_weight; + out_weight = bg_weight * alpha + fg_weight; + out_color = bg_col * bg_weight * alpha + fg_col * fg_weight; +} + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl new file mode 100644 index 00000000000..26a597b04e8 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl @@ -0,0 +1,55 @@ + +/** + * Bokeh Look Up Table: This outputs a radius multiplier to shape the sampling in gather pass or + * the scatter sprite appearance. This is only used if bokeh shape is either anamorphic or is not + * a perfect circle. + * We correct samples spacing for polygonal bokeh shapes. However, we do not for anamorphic bokeh + * as it is way more complex and expensive to do. + */ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +void main() +{ + vec2 gather_uv = ((vec2(gl_GlobalInvocationID.xy) + 0.5) / float(DOF_BOKEH_LUT_SIZE)); + /* Center uv in range [-1..1]. */ + gather_uv = gather_uv * 2.0 - 1.0; + + vec2 slight_focus_texel = vec2(gl_GlobalInvocationID.xy) - float(dof_max_slight_focus_radius); + + float radius = length(gather_uv); + + if (dof_buf.bokeh_blades > 0.0) { + /* NOTE: atan(y,x) has output range [-M_PI..M_PI], so add 2pi to avoid negative angles. */ + float theta = atan(gather_uv.y, gather_uv.x) + M_2PI; + float r = length(gather_uv); + + radius /= circle_to_polygon_radius(dof_buf.bokeh_blades, theta - dof_buf.bokeh_rotation); + + float theta_new = circle_to_polygon_angle(dof_buf.bokeh_blades, theta); + float r_new = circle_to_polygon_radius(dof_buf.bokeh_blades, theta_new); + + theta_new -= dof_buf.bokeh_rotation; + + gather_uv = r_new * vec2(-cos(theta_new), sin(theta_new)); + + { + /* Slight focus distance */ + slight_focus_texel *= dof_buf.bokeh_anisotropic_scale_inv; + float theta = atan(slight_focus_texel.y, -slight_focus_texel.x) + M_2PI; + slight_focus_texel /= circle_to_polygon_radius(dof_buf.bokeh_blades, + theta + dof_buf.bokeh_rotation); + } + } + else { + gather_uv *= safe_rcp(length(gather_uv)); + } + + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + /* For gather store the normalized UV. */ + imageStore(out_gather_lut_img, texel, gather_uv.xyxy); + /* For scatter store distance. LUT will be scaled by COC. */ + imageStore(out_scatter_lut_img, texel, vec4(radius)); + /* For slight focus gather store pixel perfect distance. */ + imageStore(out_resolve_lut_img, texel, vec4(length(slight_focus_texel))); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl new file mode 100644 index 00000000000..3d45f285da9 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl @@ -0,0 +1,32 @@ + +/** + * Downsample pass: CoC aware downsample to quarter resolution. + * + * Pretty much identical to the setup pass but get CoC from buffer. + * Also does not weight luma for the bilateral weights. + */ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +void main() +{ + vec2 halfres_texel_size = 1.0 / vec2(textureSize(color_tx, 0).xy); + /* Center uv around the 4 halfres pixels. */ + vec2 quad_center = vec2(gl_GlobalInvocationID * 2 + 1) * halfres_texel_size; + + vec4 colors[4]; + vec4 cocs; + for (int i = 0; i < 4; i++) { + vec2 sample_uv = quad_center + quad_offsets[i] * halfres_texel_size; + colors[i] = textureLod(color_tx, sample_uv, 0.0); + cocs[i] = textureLod(coc_tx, sample_uv, 0.0).r; + } + + vec4 weights = dof_bilateral_coc_weights(cocs); + /* Normalize so that the sum is 1. */ + weights *= safe_rcp(sum(weights)); + + vec4 out_color = weighted_sum_array(colors, weights); + + imageStore(out_color_img, ivec2(gl_GlobalInvocationID.xy), out_color); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_filter_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_filter_comp.glsl new file mode 100644 index 00000000000..49c93ca63cd --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_filter_comp.glsl @@ -0,0 +1,163 @@ + +/** + * Gather Filter pass: Filter the gather pass result to reduce noise. + * + * This is a simple 3x3 median filter to avoid dilating highlights with a 3x3 max filter even if + * cheaper. + */ + +struct FilterSample { + vec4 color; + float weight; +}; + +/* -------------------------------------------------------------------- */ +/** \name Pixel cache. + * \{ */ + +const uint cache_size = gl_WorkGroupSize.x + 2; +shared vec4 color_cache[cache_size][cache_size]; +shared float weight_cache[cache_size][cache_size]; + +void cache_init() +{ + /** + * Load enough values into LDS to perform the filter. + * + * ┌──────────────────────────────┐ + * │ │ < Border texels that needs to be loaded. + * │ x x x x x x x x │ ─┐ + * │ x x x x x x x x │ │ + * │ x x x x x x x x │ │ + * │ x x x x x x x x │ │ Thread Group Size 8x8. + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ ─┘ + * │ L L L L L │ < Border texels that needs to be loaded. + * └──────────────────────────────┘ + * └───────────┘ + * Load using 5x5 threads. + */ + + ivec2 texel = ivec2(gl_GlobalInvocationID.xy) - 1; + if (all(lessThan(gl_LocalInvocationID.xy, uvec2(cache_size / 2u)))) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + ivec2 offset = ivec2(x, y) * ivec2(cache_size / 2u); + ivec2 cache_texel = ivec2(gl_LocalInvocationID.xy) + offset; + ivec2 load_texel = clamp(texel + offset, ivec2(0), textureSize(color_tx, 0) - 1); + + color_cache[cache_texel.y][cache_texel.x] = texelFetch(color_tx, load_texel, 0); + weight_cache[cache_texel.y][cache_texel.x] = texelFetch(weight_tx, load_texel, 0).r; + } + } + } + barrier(); +} + +FilterSample cache_sample(int x, int y) +{ + return FilterSample(color_cache[y][x], weight_cache[y][x]); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Median filter + * From: + * Implementing Median Filters in XC4000E FPGAs + * JOHN L. SMITH, Univision Technologies Inc., Billerica, MA + * http://users.utcluj.ro/~baruch/resources/Image/xl23_16.pdf + * Figure 1 + * \{ */ + +FilterSample filter_min(FilterSample a, FilterSample b) +{ + return FilterSample(min(a.color, b.color), min(a.weight, b.weight)); +} + +FilterSample filter_max(FilterSample a, FilterSample b) +{ + return FilterSample(max(a.color, b.color), max(a.weight, b.weight)); +} + +FilterSample filter_min(FilterSample a, FilterSample b, FilterSample c) +{ + return FilterSample(min(a.color, min(c.color, b.color)), min(a.weight, min(c.weight, b.weight))); +} + +FilterSample filter_max(FilterSample a, FilterSample b, FilterSample c) +{ + return FilterSample(max(a.color, max(c.color, b.color)), max(a.weight, max(c.weight, b.weight))); +} + +FilterSample filter_median(FilterSample s1, FilterSample s2, FilterSample s3) +{ + /* From diagram, with nodes numbered from top to bottom. */ + FilterSample l1 = filter_min(s2, s3); + FilterSample h1 = filter_max(s2, s3); + FilterSample h2 = filter_max(s1, l1); + FilterSample l3 = filter_min(h2, h1); + return l3; +} + +struct FilterLmhResult { + FilterSample low; + FilterSample median; + FilterSample high; +}; + +FilterLmhResult filter_lmh(FilterSample s1, FilterSample s2, FilterSample s3) +{ + /* From diagram, with nodes numbered from top to bottom. */ + FilterSample h1 = filter_max(s2, s3); + FilterSample l1 = filter_min(s2, s3); + + FilterSample h2 = filter_max(s1, l1); + FilterSample l2 = filter_min(s1, l1); + + FilterSample h3 = filter_max(h2, h1); + FilterSample l3 = filter_min(h2, h1); + + FilterLmhResult result; + result.low = l2; + result.median = l3; + result.high = h3; + + return result; +} + +/** \} */ + +void main() +{ + /** + * NOTE: We can **NOT** optimize by discarding some tiles as the result is sampled using bilinear + * filtering in the resolve pass. Not outputting to a tile means that border texels have + * undefined value and tile border will be noticeable in the final image. + */ + + cache_init(); + + ivec2 texel = ivec2(gl_LocalInvocationID.xy); + + FilterLmhResult rows[3]; + for (int y = 0; y < 3; y++) { + rows[y] = filter_lmh(cache_sample(texel.x + 0, texel.y + y), + cache_sample(texel.x + 1, texel.y + y), + cache_sample(texel.x + 2, texel.y + y)); + } + /* Left nodes. */ + FilterSample high = filter_max(rows[0].low, rows[1].low, rows[2].low); + /* Right nodes. */ + FilterSample low = filter_min(rows[0].high, rows[1].high, rows[2].high); + /* Center nodes. */ + FilterSample median = filter_median(rows[0].median, rows[1].median, rows[2].median); + /* Last bottom nodes. */ + median = filter_median(low, median, high); + + ivec2 out_texel = ivec2(gl_GlobalInvocationID.xy); + imageStore(out_color_img, out_texel, median.color); + imageStore(out_weight_img, out_texel, vec4(median.weight)); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_gather_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_gather_comp.glsl new file mode 100644 index 00000000000..cf8dd7a36e6 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_gather_comp.glsl @@ -0,0 +1,99 @@ + +/** + * Gather pass: Convolve foreground and background parts in separate passes. + * + * Using the min&max CoC tile buffer, we select the best appropriate method to blur the scene + *color. A fast gather path is taken if there is not many CoC variation inside the tile. + * + * We sample using an octaweb sampling pattern. We randomize the kernel center and each ring + * rotation to ensure maximum coverage. + * + * Outputs: + * - Color * Weight, Weight, Occlusion 'CoC' Depth (mean and variance) + **/ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_accumulator_lib.glsl) + +void main() +{ + ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy / DOF_TILES_SIZE); + CocTile coc_tile = dof_coc_tile_load(in_tiles_fg_img, in_tiles_bg_img, tile_co); + CocTilePrediction prediction = dof_coc_tile_prediction_get(coc_tile); + + float base_radius, min_radius, min_intersectable_radius; + bool can_early_out; + if (is_foreground) { + base_radius = -coc_tile.fg_min_coc; + min_radius = -coc_tile.fg_max_coc; + min_intersectable_radius = -coc_tile.fg_max_intersectable_coc; + can_early_out = !prediction.do_foreground; + } + else { + base_radius = coc_tile.bg_max_coc; + min_radius = coc_tile.bg_min_coc; + min_intersectable_radius = coc_tile.bg_min_intersectable_coc; + can_early_out = !prediction.do_background; + } + + bool do_fast_gather = dof_do_fast_gather(base_radius, min_radius, is_foreground); + + /* Gather at half resolution. Divide CoC by 2. */ + base_radius *= 0.5; + min_intersectable_radius *= 0.5; + + bool do_density_change = dof_do_density_change(base_radius, min_intersectable_radius); + + vec4 out_color; + float out_weight; + vec2 out_occlusion; + + if (can_early_out) { + out_color = vec4(0.0); + out_weight = 0.0; + out_occlusion = vec2(0.0, 0.0); + } + else if (do_fast_gather) { + dof_gather_accumulator(color_tx, + color_bilinear_tx, + coc_tx, + bokeh_lut_tx, + base_radius, + min_intersectable_radius, + true, + false, + out_color, + out_weight, + out_occlusion); + } + else if (do_density_change) { + dof_gather_accumulator(color_tx, + color_bilinear_tx, + coc_tx, + bokeh_lut_tx, + base_radius, + min_intersectable_radius, + false, + true, + out_color, + out_weight, + out_occlusion); + } + else { + dof_gather_accumulator(color_tx, + color_bilinear_tx, + coc_tx, + bokeh_lut_tx, + base_radius, + min_intersectable_radius, + false, + false, + out_color, + out_weight, + out_occlusion); + } + + ivec2 out_texel = ivec2(gl_GlobalInvocationID.xy); + imageStore(out_color_img, out_texel, out_color); + imageStore(out_weight_img, out_texel, vec4(out_weight)); + imageStore(out_occlusion_img, out_texel, out_occlusion.xyxy); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_hole_fill_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_hole_fill_comp.glsl new file mode 100644 index 00000000000..5cdabbc2d4b --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_hole_fill_comp.glsl @@ -0,0 +1,70 @@ + +/** + * Holefill pass: Gather background parts where foreground is present. + * + * Using the min&max CoC tile buffer, we select the best appropriate method to blur the scene + *color. A fast gather path is taken if there is not many CoC variation inside the tile. + * + * We sample using an octaweb sampling pattern. We randomize the kernel center and each ring + * rotation to ensure maximum coverage. + **/ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_accumulator_lib.glsl) + +void main() +{ + ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy / DOF_TILES_SIZE); + CocTile coc_tile = dof_coc_tile_load(in_tiles_fg_img, in_tiles_bg_img, tile_co); + CocTilePrediction prediction = dof_coc_tile_prediction_get(coc_tile); + + float base_radius = -coc_tile.fg_min_coc; + float min_radius = -coc_tile.fg_max_coc; + float min_intersectable_radius = dof_tile_large_coc; + bool can_early_out = !prediction.do_hole_fill; + + bool do_fast_gather = dof_do_fast_gather(base_radius, min_radius, is_foreground); + + /* Gather at half resolution. Divide CoC by 2. */ + base_radius *= 0.5; + min_intersectable_radius *= 0.5; + + bool do_density_change = dof_do_density_change(base_radius, min_intersectable_radius); + + vec4 out_color = vec4(0.0); + float out_weight = 0.0; + vec2 unused_occlusion = vec2(0.0, 0.0); + + if (can_early_out) { + /* Early out. */ + } + else if (do_fast_gather) { + dof_gather_accumulator(color_tx, + color_bilinear_tx, + coc_tx, + coc_tx, + base_radius, + min_intersectable_radius, + true, + false, + out_color, + out_weight, + unused_occlusion); + } + else { + dof_gather_accumulator(color_tx, + color_bilinear_tx, + coc_tx, + coc_tx, + base_radius, + min_intersectable_radius, + false, + false, + out_color, + out_weight, + unused_occlusion); + } + + ivec2 out_texel = ivec2(gl_GlobalInvocationID.xy); + imageStore(out_color_img, out_texel, out_color); + imageStore(out_weight_img, out_texel, vec4(out_weight)); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl new file mode 100644 index 00000000000..f89da641446 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl @@ -0,0 +1,327 @@ + +/** + * Depth of Field utils. + **/ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) + +/* -------------------------------------------------------------------- */ +/** \name Constants. + * \{ */ + +#ifndef DOF_SLIGHT_FOCUS_DENSITY +# define DOF_SLIGHT_FOCUS_DENSITY 2 +#endif + +#ifdef DOF_RESOLVE_PASS +const bool is_resolve = true; +#else +const bool is_resolve = false; +#endif +#ifdef DOF_FOREGROUND_PASS +const bool is_foreground = DOF_FOREGROUND_PASS; +#else +const bool is_foreground = false; +#endif +/* Debug options */ +const bool debug_gather_perf = false; +const bool debug_scatter_perf = false; +const bool debug_resolve_perf = false; + +const bool no_smooth_intersection = false; +const bool no_gather_occlusion = false; +const bool no_gather_mipmaps = false; +const bool no_gather_random = false; +const bool no_gather_filtering = false; +const bool no_scatter_occlusion = false; +const bool no_scatter_pass = false; +const bool no_foreground_pass = false; +const bool no_background_pass = false; +const bool no_slight_focus_pass = false; +const bool no_focus_pass = false; +const bool no_hole_fill_pass = false; + +/* Distribute weights between near/slightfocus/far fields (slide 117). */ +const float dof_layer_threshold = 4.0; +/* Make sure it overlaps. */ +const float dof_layer_offset_fg = 0.5 + 1.0; +/* Extra offset for convolution layers to avoid light leaking from background. */ +const float dof_layer_offset = 0.5 + 0.5; + +const int dof_max_slight_focus_radius = DOF_MAX_SLIGHT_FOCUS_RADIUS; + +const vec2 quad_offsets[4] = vec2[4]( + vec2(-0.5, 0.5), vec2(0.5, 0.5), vec2(0.5, -0.5), vec2(-0.5, -0.5)); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Weighting and downsampling utils. + * \{ */ + +float dof_hdr_color_weight(vec4 color) +{ + /* Very fast "luma" weighting. */ + float luma = (color.g * 2.0) + (color.r + color.b); + /* TODO(fclem) Pass correct exposure. */ + const float exposure = 1.0; + return 1.0 / (luma * exposure + 4.0); +} + +float dof_coc_select(vec4 cocs) +{ + /* Select biggest coc. */ + float selected_coc = cocs.x; + if (abs(cocs.y) > abs(selected_coc)) { + selected_coc = cocs.y; + } + if (abs(cocs.z) > abs(selected_coc)) { + selected_coc = cocs.z; + } + if (abs(cocs.w) > abs(selected_coc)) { + selected_coc = cocs.w; + } + return selected_coc; +} + +/* NOTE: Do not forget to normalize weights afterwards. */ +vec4 dof_bilateral_coc_weights(vec4 cocs) +{ + float chosen_coc = dof_coc_select(cocs); + + const float scale = 4.0; /* TODO(fclem) revisit. */ + /* NOTE: The difference between the cocs should be inside a abs() function, + * but we follow UE4 implementation to improve how dithered transparency looks (see slide 19). */ + return saturate(1.0 - (chosen_coc - cocs) * scale); +} + +/* NOTE: Do not forget to normalize weights afterwards. */ +vec4 dof_bilateral_color_weights(vec4 colors[4]) +{ + vec4 weights; + for (int i = 0; i < 4; i++) { + weights[i] = dof_hdr_color_weight(colors[i]); + } + return weights; +} + +/* Returns signed Circle of confusion radius (in pixel) based on depth buffer value [0..1]. */ +float dof_coc_from_depth(DepthOfFieldData dof_data, vec2 uv, float depth) +{ + if (is_panoramic(dof_data.camera_type)) { + /* Use radial depth. */ + depth = -length(get_view_space_from_depth(uv, depth)); + } + else { + depth = get_view_z_from_depth(depth); + } + return coc_radius_from_camera_depth(dof_data, depth); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Gather & Scatter Weighting + * \{ */ + +float dof_layer_weight(float coc, const bool is_foreground) +{ + /* NOTE: These are fullres pixel CoC value. */ + if (is_resolve) { + return saturate(-abs(coc) + dof_layer_threshold + dof_layer_offset) * + float(is_foreground ? (coc <= 0.5) : (coc > -0.5)); + } + else { + coc *= 2.0; /* Account for half pixel gather. */ + float threshold = dof_layer_threshold - + ((is_foreground) ? dof_layer_offset_fg : dof_layer_offset); + return saturate(((is_foreground) ? -coc : coc) - threshold); + } +} +vec4 dof_layer_weight(vec4 coc) +{ + /* NOTE: Used for scatter pass which already flipped the sign correctly. */ + coc *= 2.0; /* Account for half pixel gather. */ + return saturate(coc - dof_layer_threshold + dof_layer_offset); +} + +/* NOTE: This is halfres CoC radius. */ +float dof_sample_weight(float coc) +{ +#if 1 /* Optimized */ + return min(1.0, 1.0 / sqr(coc)); +#else + /* Full intensity if CoC radius is below the pixel footprint. */ + const float min_coc = 1.0; + coc = max(min_coc, abs(coc)); + return (M_PI * min_coc * min_coc) / (M_PI * coc * coc); +#endif +} +vec4 dof_sample_weight(vec4 coc) +{ +#if 1 /* Optimized */ + return min(vec4(1.0), 1.0 / sqr(coc)); +#else + /* Full intensity if CoC radius is below the pixel footprint. */ + const float min_coc = 1.0; + coc = max(vec4(min_coc), abs(coc)); + return (M_PI * min_coc * min_coc) / (M_PI * coc * coc); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Circle of Confusion tiles + * \{ */ + +struct CocTile { + float fg_min_coc; + float fg_max_coc; + float fg_max_intersectable_coc; + float bg_min_coc; + float bg_max_coc; + float bg_min_intersectable_coc; +}; + +/* WATCH: Might have to change depending on the texture format. */ +const float dof_tile_large_coc = 1024.0; + +/* Init a CoC tile for reduction algorithms. */ +CocTile dof_coc_tile_init() +{ + CocTile tile; + tile.fg_min_coc = 0.0; + tile.fg_max_coc = -dof_tile_large_coc; + tile.fg_max_intersectable_coc = dof_tile_large_coc; + tile.bg_min_coc = dof_tile_large_coc; + tile.bg_max_coc = 0.0; + tile.bg_min_intersectable_coc = dof_tile_large_coc; + return tile; +} + +CocTile dof_coc_tile_unpack(vec3 fg, vec3 bg) +{ + CocTile tile; + tile.fg_min_coc = -fg.x; + tile.fg_max_coc = -fg.y; + tile.fg_max_intersectable_coc = -fg.z; + tile.bg_min_coc = bg.x; + tile.bg_max_coc = bg.y; + tile.bg_min_intersectable_coc = bg.z; + return tile; +} + +/* WORKAROUND(fclem): GLSL compilers differs in what qualifiers are requires to pass images as + * parameters. Workaround by using defines. */ +#define dof_coc_tile_load(tiles_fg_img_, tiles_bg_img_, texel_) \ + dof_coc_tile_unpack( \ + imageLoad(tiles_fg_img_, clamp(texel_, ivec2(0), imageSize(tiles_fg_img_) - 1)).xyz, \ + imageLoad(tiles_bg_img_, clamp(texel_, ivec2(0), imageSize(tiles_bg_img_) - 1)).xyz) + +void dof_coc_tile_pack(CocTile tile, out vec3 out_fg, out vec3 out_bg) +{ + out_fg.x = -tile.fg_min_coc; + out_fg.y = -tile.fg_max_coc; + out_fg.z = -tile.fg_max_intersectable_coc; + out_bg.x = tile.bg_min_coc; + out_bg.y = tile.bg_max_coc; + out_bg.z = tile.bg_min_intersectable_coc; +} + +#define dof_coc_tile_store(tiles_fg_img_, tiles_bg_img_, texel_out_, tile_data_) \ + if (true) { \ + vec3 out_fg; \ + vec3 out_bg; \ + dof_coc_tile_pack(tile_data_, out_fg, out_bg); \ + imageStore(tiles_fg_img_, texel_out_, out_fg.xyzz); \ + imageStore(tiles_bg_img_, texel_out_, out_bg.xyzz); \ + } + +bool dof_do_fast_gather(float max_absolute_coc, float min_absolute_coc, const bool is_foreground) +{ + float min_weight = dof_layer_weight((is_foreground) ? -min_absolute_coc : min_absolute_coc, + is_foreground); + if (min_weight < 1.0) { + return false; + } + /* FIXME(fclem): This is a workaround to fast gather triggering too early. Since we use custom + * opacity mask, the opacity is not given to be 100% even for after normal threshold. */ + if (is_foreground && min_absolute_coc < dof_layer_threshold) { + return false; + } + return (max_absolute_coc - min_absolute_coc) < (DOF_FAST_GATHER_COC_ERROR * max_absolute_coc); +} + +struct CocTilePrediction { + bool do_foreground; + bool do_slight_focus; + bool do_focus; + bool do_background; + bool do_hole_fill; +}; + +/** + * Using the tile CoC infos, predict which convolutions are required and the ones that can be + * skipped. + */ +CocTilePrediction dof_coc_tile_prediction_get(CocTile tile) +{ + /* Based on tile value, predict what pass we need to load. */ + CocTilePrediction predict; + + predict.do_foreground = (-tile.fg_min_coc > dof_layer_threshold - dof_layer_offset_fg); + bool fg_fully_opaque = predict.do_foreground && + dof_do_fast_gather(-tile.fg_min_coc, -tile.fg_max_coc, true); + predict.do_background = !fg_fully_opaque && + (tile.bg_max_coc > dof_layer_threshold - dof_layer_offset); + bool bg_fully_opaque = predict.do_background && + dof_do_fast_gather(-tile.bg_max_coc, tile.bg_min_coc, false); + predict.do_hole_fill = !fg_fully_opaque && -tile.fg_min_coc > 0.0; + predict.do_focus = !fg_fully_opaque; + predict.do_slight_focus = !fg_fully_opaque; + +#if 0 /* Debug */ + predict.do_foreground = predict.do_background = predict.do_hole_fill = true; +#endif + return predict; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Gathering + * \{ */ + +/** + * Generate samples in a square pattern with the ring radius. X is the center tile. + * + * Dist1 Dist2 + * 6 5 4 3 2 + * 3 2 1 7 1 + * . X 0 . X 0 + * . . . . . + * . . . . . + * + * Samples are expected to be mirrored to complete the pattern. + **/ +ivec2 dof_square_ring_sample_offset(int ring_distance, int sample_id) +{ + ivec2 offset; + if (sample_id < ring_distance) { + offset.x = ring_distance; + offset.y = sample_id; + } + else if (sample_id < ring_distance * 3) { + offset.x = ring_distance - sample_id + ring_distance; + offset.y = ring_distance; + } + else { + offset.x = -ring_distance; + offset.y = ring_distance - sample_id + 3 * ring_distance; + } + return offset; +} + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl new file mode 100644 index 00000000000..80555367478 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl @@ -0,0 +1,247 @@ + +/** + * Reduce copy pass: filter fireflies and split color between scatter and gather input. + * + * NOTE: The texture can end up being too big because of the mipmap padding. We correct for + * that during the convolution phase. + * + * Inputs: + * - Output of setup pass (halfres) and reduce downsample pass (quarter res). + * Outputs: + * - Halfres padded to avoid mipmap misalignment (so possibly not matching input size). + * - Gather input color (whole mip chain), Scatter rect list, Signed CoC (whole mip chain). + **/ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +/* NOTE: Do not compare alpha as it is not scattered by the scatter pass. */ +float dof_scatter_neighborhood_rejection(vec3 color) +{ + color = min(vec3(dof_buf.scatter_neighbor_max_color), color); + + float validity = 0.0; + + /* Centered in the middle of 4 quarter res texel. */ + vec2 texel_size = 1.0 / vec2(textureSize(downsample_tx, 0).xy); + vec2 uv = ((vec2(gl_GlobalInvocationID.xy) + 0.5) * 0.5) * texel_size; + + vec3 max_diff = vec3(0.0); + for (int i = 0; i < 4; i++) { + vec2 sample_uv = uv + quad_offsets[i] * texel_size; + vec3 ref = textureLod(downsample_tx, sample_uv, 0.0).rgb; + + ref = min(vec3(dof_buf.scatter_neighbor_max_color), ref); + float diff = max_v3(max(vec3(0.0), abs(ref - color))); + + const float rejection_threshold = 0.7; + diff = saturate(diff / rejection_threshold - 1.0); + validity = max(validity, diff); + } + + return validity; +} + +/* This avoids Bokeh sprite popping in and out at the screen border and + * drawing Bokeh sprites larger than the screen. */ +float dof_scatter_screen_border_rejection(float coc, ivec2 texel) +{ + vec2 screen_size = vec2(imageSize(inout_color_lod0_img)); + vec2 uv = (vec2(texel) + 0.5) / screen_size; + vec2 screen_pos = uv * screen_size; + float min_screen_border_distance = min_v2(min(screen_pos, screen_size - screen_pos)); + /* Fullres to halfres CoC. */ + coc *= 0.5; + /* Allow 10px transition. */ + const float rejection_hardeness = 1.0 / 10.0; + return saturate((min_screen_border_distance - abs(coc)) * rejection_hardeness + 1.0); +} + +float dof_scatter_luminosity_rejection(vec3 color) +{ + const float rejection_hardness = 1.0; + return saturate(max_v3(color - dof_buf.scatter_color_threshold) * rejection_hardness); +} + +float dof_scatter_coc_radius_rejection(float coc) +{ + const float rejection_hardness = 0.3; + return saturate((abs(coc) - dof_buf.scatter_coc_threshold) * rejection_hardness); +} + +float fast_luma(vec3 color) +{ + return (2.0 * color.g) + color.r + color.b; +} + +const uint cache_size = gl_WorkGroupSize.x; +shared vec4 color_cache[cache_size][cache_size]; +shared float coc_cache[cache_size][cache_size]; +shared float do_scatter[cache_size][cache_size]; + +void main() +{ + ivec2 texel = min(ivec2(gl_GlobalInvocationID.xy), imageSize(inout_color_lod0_img) - 1); + uvec2 texel_local = gl_LocalInvocationID.xy; + /* Increase readablility. */ +#define LOCAL_INDEX texel_local.y][texel_local.x +#define LOCAL_OFFSET(x_, y_) texel_local.y + (y_)][texel_local.x + (x_) + + /* Load level 0 into cache. */ + color_cache[LOCAL_INDEX] = imageLoad(inout_color_lod0_img, texel); + coc_cache[LOCAL_INDEX] = imageLoad(in_coc_lod0_img, texel).r; + + /* Only scatter if luminous enough. */ + do_scatter[LOCAL_INDEX] = dof_scatter_luminosity_rejection(color_cache[LOCAL_INDEX].rgb); + /* Only scatter if CoC is big enough. */ + do_scatter[LOCAL_INDEX] *= dof_scatter_coc_radius_rejection(coc_cache[LOCAL_INDEX]); + /* Only scatter if CoC is not too big to avoid performance issues. */ + do_scatter[LOCAL_INDEX] *= dof_scatter_screen_border_rejection(coc_cache[LOCAL_INDEX], texel); + /* Only scatter if neighborhood is different enough. */ + do_scatter[LOCAL_INDEX] *= dof_scatter_neighborhood_rejection(color_cache[LOCAL_INDEX].rgb); + /* For debugging. */ + if (no_scatter_pass) { + do_scatter[LOCAL_INDEX] = 0.0; + } + + barrier(); + + /* Add a scatter sprite for each 2x2 pixel neighborhood passing the threshold. */ + if (all(equal(texel_local & 1u, uvec2(0)))) { + vec4 do_scatter4; + /* Follows quad_offsets order. */ + do_scatter4.x = do_scatter[LOCAL_OFFSET(0, 1)]; + do_scatter4.y = do_scatter[LOCAL_OFFSET(1, 1)]; + do_scatter4.z = do_scatter[LOCAL_OFFSET(1, 0)]; + do_scatter4.w = do_scatter[LOCAL_OFFSET(0, 0)]; + if (any(greaterThan(do_scatter4, vec4(0.0)))) { + /* Apply energy conservation to anamorphic scattered bokeh. */ + do_scatter4 *= max_v2(dof_buf.bokeh_anisotropic_scale_inv); + + /* Circle of Confusion. */ + vec4 coc4; + coc4.x = coc_cache[LOCAL_OFFSET(0, 1)]; + coc4.y = coc_cache[LOCAL_OFFSET(1, 1)]; + coc4.z = coc_cache[LOCAL_OFFSET(1, 0)]; + coc4.w = coc_cache[LOCAL_OFFSET(0, 0)]; + /* We are scattering at half resolution, so divide CoC by 2. */ + coc4 *= 0.5; + /* Sprite center position. Center sprite around the 4 texture taps. */ + vec2 offset = vec2(gl_GlobalInvocationID.xy) + 1; + /* Add 2.5 to max_coc because the max_coc may not be centered on the sprite origin + * and because we smooth the bokeh shape a bit in the pixel shader. */ + vec2 half_extent = max_v4(abs(coc4)) * dof_buf.bokeh_anisotropic_scale + 2.5; + /* Issue a sprite for each field if any CoC matches. */ + if (any(lessThan(do_scatter4 * sign(coc4), vec4(0.0)))) { + /* Same value for all threads. Not an issue if we don't sync access to it. */ + scatter_fg_indirect_buf.v_count = 4u; + /* Issue 1 strip instance per sprite. */ + uint rect_id = atomicAdd(scatter_fg_indirect_buf.i_count, 1u); + if (rect_id < dof_buf.scatter_max_rect) { + + vec4 coc4_fg = max(vec4(0.0), -coc4); + vec4 fg_weights = dof_layer_weight(coc4_fg) * dof_sample_weight(coc4_fg) * do_scatter4; + /* Filter NaNs. */ + fg_weights = select(fg_weights, vec4(0.0), equal(coc4_fg, vec4(0.0))); + + ScatterRect rect_fg; + rect_fg.offset = offset; + /* Negate extent to flip the sprite. Mimics optical phenomenon. */ + rect_fg.half_extent = -half_extent; + /* NOTE: Since we fliped the quad along (1,-1) line, we need to also swap the (1,1) and + * (0,0) values so that quad_offsets is in the right order in the vertex shader. */ + + /* Circle of Confusion absolute radius in halfres pixels. */ + rect_fg.color_and_coc[0].a = coc4_fg[0]; + rect_fg.color_and_coc[1].a = coc4_fg[3]; + rect_fg.color_and_coc[2].a = coc4_fg[2]; + rect_fg.color_and_coc[3].a = coc4_fg[1]; + /* Apply weights. */ + rect_fg.color_and_coc[0].rgb = color_cache[LOCAL_OFFSET(0, 1)].rgb * fg_weights[0]; + rect_fg.color_and_coc[1].rgb = color_cache[LOCAL_OFFSET(0, 0)].rgb * fg_weights[3]; + rect_fg.color_and_coc[2].rgb = color_cache[LOCAL_OFFSET(1, 0)].rgb * fg_weights[2]; + rect_fg.color_and_coc[3].rgb = color_cache[LOCAL_OFFSET(1, 1)].rgb * fg_weights[1]; + + scatter_fg_list_buf[rect_id] = rect_fg; + } + } + if (any(greaterThan(do_scatter4 * sign(coc4), vec4(0.0)))) { + /* Same value for all threads. Not an issue if we don't sync access to it. */ + scatter_bg_indirect_buf.v_count = 4u; + /* Issue 1 strip instance per sprite. */ + uint rect_id = atomicAdd(scatter_bg_indirect_buf.i_count, 1u); + if (rect_id < dof_buf.scatter_max_rect) { + vec4 coc4_bg = max(vec4(0.0), coc4); + vec4 bg_weights = dof_layer_weight(coc4_bg) * dof_sample_weight(coc4_bg) * do_scatter4; + /* Filter NaNs. */ + bg_weights = select(bg_weights, vec4(0.0), equal(coc4_bg, vec4(0.0))); + + ScatterRect rect_bg; + rect_bg.offset = offset; + rect_bg.half_extent = half_extent; + + /* Circle of Confusion absolute radius in halfres pixels. */ + rect_bg.color_and_coc[0].a = coc4_bg[0]; + rect_bg.color_and_coc[1].a = coc4_bg[1]; + rect_bg.color_and_coc[2].a = coc4_bg[2]; + rect_bg.color_and_coc[3].a = coc4_bg[3]; + /* Apply weights. */ + rect_bg.color_and_coc[0].rgb = color_cache[LOCAL_OFFSET(0, 1)].rgb * bg_weights[0]; + rect_bg.color_and_coc[1].rgb = color_cache[LOCAL_OFFSET(1, 1)].rgb * bg_weights[1]; + rect_bg.color_and_coc[2].rgb = color_cache[LOCAL_OFFSET(1, 0)].rgb * bg_weights[2]; + rect_bg.color_and_coc[3].rgb = color_cache[LOCAL_OFFSET(0, 0)].rgb * bg_weights[3]; + + scatter_bg_list_buf[rect_id] = rect_bg; + } + } + } + } + + /* Remove scatter color from gather. */ + color_cache[LOCAL_INDEX].rgb *= 1.0 - do_scatter[LOCAL_INDEX]; + imageStore(inout_color_lod0_img, texel, color_cache[LOCAL_INDEX]); + + /* Recursive downsample. */ + for (uint i = 1u; i < DOF_MIP_COUNT; i++) { + barrier(); + uint mask = ~(~0u << i); + if (all(equal(gl_LocalInvocationID.xy & mask, uvec2(0)))) { + uint ofs = 1u << (i - 1u); + + /* TODO(fclem): Could use wave shuffle intrinsics to avoid LDS as suggested by the paper. */ + vec4 coc4; + coc4.x = coc_cache[LOCAL_OFFSET(0, ofs)]; + coc4.y = coc_cache[LOCAL_OFFSET(ofs, ofs)]; + coc4.z = coc_cache[LOCAL_OFFSET(ofs, 0)]; + coc4.w = coc_cache[LOCAL_OFFSET(0, 0)]; + + vec4 colors[4]; + colors[0] = color_cache[LOCAL_OFFSET(0, ofs)]; + colors[1] = color_cache[LOCAL_OFFSET(ofs, ofs)]; + colors[2] = color_cache[LOCAL_OFFSET(ofs, 0)]; + colors[3] = color_cache[LOCAL_OFFSET(0, 0)]; + + vec4 weights = dof_bilateral_coc_weights(coc4); + weights *= dof_bilateral_color_weights(colors); + /* Normalize so that the sum is 1. */ + weights *= safe_rcp(sum(weights)); + + color_cache[LOCAL_INDEX] = weighted_sum_array(colors, weights); + coc_cache[LOCAL_INDEX] = dot(coc4, weights); + + ivec2 texel = ivec2(gl_GlobalInvocationID.xy >> i); + + if (i == 1) { + imageStore(out_color_lod1_img, texel, color_cache[LOCAL_INDEX]); + imageStore(out_coc_lod1_img, texel, vec4(coc_cache[LOCAL_INDEX])); + } + else if (i == 2) { + imageStore(out_color_lod2_img, texel, color_cache[LOCAL_INDEX]); + imageStore(out_coc_lod2_img, texel, vec4(coc_cache[LOCAL_INDEX])); + } + else /* if (i == 3) */ { + imageStore(out_color_lod3_img, texel, color_cache[LOCAL_INDEX]); + imageStore(out_coc_lod3_img, texel, vec4(coc_cache[LOCAL_INDEX])); + } + } + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl new file mode 100644 index 00000000000..8873a9da235 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl @@ -0,0 +1,178 @@ + +/** + * Recombine Pass: Load separate convolution layer and composite with self + * slight defocus convolution and in-focus fields. + * + * The halfres gather methods are fast but lack precision for small CoC areas. + * To fix this we do a bruteforce gather to have a smooth transition between + * in-focus and defocus regions. + */ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_accumulator_lib.glsl) + +shared uint shared_max_slight_focus_abs_coc; + +/** + * Returns The max CoC in the Slight Focus range inside this compute tile. + */ +float dof_slight_focus_coc_tile_get(vec2 frag_coord) +{ + if (all(equal(gl_LocalInvocationID, uvec3(0)))) { + shared_max_slight_focus_abs_coc = floatBitsToUint(0.0); + } + barrier(); + + float local_abs_max = 0.0; + /* Sample in a cross (X) pattern. This covers all pixels over the whole tile, as long as + * dof_max_slight_focus_radius is less than the group size. */ + for (int i = 0; i < 4; i++) { + vec2 sample_uv = (frag_coord + quad_offsets[i] * 2.0 * dof_max_slight_focus_radius) / + vec2(textureSize(color_tx, 0)); + float coc = dof_coc_from_depth(dof_buf, sample_uv, textureLod(depth_tx, sample_uv, 0.0).r); + coc = clamp(coc, -dof_buf.coc_abs_max, dof_buf.coc_abs_max); + if (abs(coc) < dof_max_slight_focus_radius) { + local_abs_max = max(local_abs_max, abs(coc)); + } + } + /* Use atomic reduce operation. */ + atomicMax(shared_max_slight_focus_abs_coc, floatBitsToUint(local_abs_max)); + /* "Broadcast" result across all threads. */ + barrier(); + + return uintBitsToFloat(shared_max_slight_focus_abs_coc); +} + +vec3 dof_neighborhood_clamp(vec2 frag_coord, vec3 color, float center_coc, float weight) +{ + /* Stabilize color by clamping with the stable half res neighborhood. */ + vec3 neighbor_min, neighbor_max; + const vec2 corners[4] = vec2[4](vec2(-1, -1), vec2(1, -1), vec2(-1, 1), vec2(1, 1)); + for (int i = 0; i < 4; i++) { + /** + * Visit the 4 half-res texels around (and containing) the fullres texel. + * Here a diagram of a fullscreen texel (f) in the bottom left corner of a half res texel. + * We sample the stable half-resolution texture at the 4 location denoted by (h). + * ┌───────┬───────┐ + * │ h │ h │ + * │ │ │ + * │ │ f │ + * ├───────┼───────┤ + * │ h │ h │ + * │ │ │ + * │ │ │ + * └───────┴───────┘ + */ + vec2 uv_sample = ((frag_coord + corners[i]) * 0.5) / vec2(textureSize(stable_color_tx, 0)); + /* Reminder: The content of this buffer is YCoCg + CoC. */ + vec3 ycocg_sample = textureLod(stable_color_tx, uv_sample, 0.0).rgb; + neighbor_min = (i == 0) ? ycocg_sample : min(neighbor_min, ycocg_sample); + neighbor_max = (i == 0) ? ycocg_sample : max(neighbor_max, ycocg_sample); + } + /* Pad the bounds in the near in focus region to get back a bit of detail. */ + float padding = 0.125 * saturate(1.0 - sqr(center_coc) / sqr(8.0)); + neighbor_max += abs(neighbor_min) * padding; + neighbor_min -= abs(neighbor_min) * padding; + /* Progressively apply the clamp to avoid harsh transition. Also mask by weight. */ + float fac = saturate(sqr(center_coc) * 4.0) * weight; + /* Clamp in YCoCg space to avoid too much color drift. */ + color = colorspace_YCoCg_from_scene_linear(color); + color = mix(color, clamp(color, neighbor_min, neighbor_max), fac); + color = colorspace_scene_linear_from_YCoCg(color); + return color; +} + +void main() +{ + vec2 frag_coord = vec2(gl_GlobalInvocationID.xy) + 0.5; + ivec2 tile_co = ivec2(frag_coord / float(DOF_TILES_SIZE * 2)); + + CocTile coc_tile = dof_coc_tile_load(in_tiles_fg_img, in_tiles_bg_img, tile_co); + CocTilePrediction prediction = dof_coc_tile_prediction_get(coc_tile); + + vec2 uv = frag_coord / vec2(textureSize(color_tx, 0)); + vec2 uv_halfres = (frag_coord * 0.5) / vec2(textureSize(color_bg_tx, 0)); + + float slight_focus_max_coc = 0.0; + if (prediction.do_slight_focus) { + slight_focus_max_coc = dof_slight_focus_coc_tile_get(frag_coord); + prediction.do_slight_focus = slight_focus_max_coc >= 0.5; + if (prediction.do_slight_focus) { + prediction.do_focus = false; + } + } + + if (prediction.do_focus) { + float center_coc = (dof_coc_from_depth(dof_buf, uv, textureLod(depth_tx, uv, 0.0).r)); + prediction.do_focus = abs(center_coc) <= 0.5; + } + + vec4 out_color = vec4(0.0); + float weight = 0.0; + + vec4 layer_color; + float layer_weight; + + if (!no_hole_fill_pass && prediction.do_hole_fill) { + layer_color = textureLod(color_hole_fill_tx, uv_halfres, 0.0); + layer_weight = textureLod(weight_hole_fill_tx, uv_halfres, 0.0).r; + out_color = layer_color * safe_rcp(layer_weight); + weight = float(layer_weight > 0.0); + } + + if (!no_background_pass && prediction.do_background) { + layer_color = textureLod(color_bg_tx, uv_halfres, 0.0); + layer_weight = textureLod(weight_bg_tx, uv_halfres, 0.0).r; + /* Always prefer background to hole_fill pass. */ + layer_color *= safe_rcp(layer_weight); + layer_weight = float(layer_weight > 0.0); + /* Composite background. */ + out_color = out_color * (1.0 - layer_weight) + layer_color; + weight = weight * (1.0 - layer_weight) + layer_weight; + /* Fill holes with the composited background. */ + out_color *= safe_rcp(weight); + weight = float(weight > 0.0); + } + + if (!no_slight_focus_pass && prediction.do_slight_focus) { + float center_coc; + dof_slight_focus_gather(depth_tx, + color_tx, + bokeh_lut_tx, + slight_focus_max_coc, + layer_color, + layer_weight, + center_coc); + + /* Composite slight defocus. */ + out_color = out_color * (1.0 - layer_weight) + layer_color; + weight = weight * (1.0 - layer_weight) + layer_weight; + + out_color.rgb = dof_neighborhood_clamp(frag_coord, out_color.rgb, center_coc, layer_weight); + } + + if (!no_focus_pass && prediction.do_focus) { + layer_color = safe_color(textureLod(color_tx, uv, 0.0)); + layer_weight = 1.0; + /* Composite in focus. */ + out_color = out_color * (1.0 - layer_weight) + layer_color; + weight = weight * (1.0 - layer_weight) + layer_weight; + } + + if (!no_foreground_pass && prediction.do_foreground) { + layer_color = textureLod(color_fg_tx, uv_halfres, 0.0); + layer_weight = textureLod(weight_fg_tx, uv_halfres, 0.0).r; + /* Composite foreground. */ + out_color = out_color * (1.0 - layer_weight) + layer_color; + } + + /* Fix float precision issue in alpha compositing. */ + if (out_color.a > 0.99) { + out_color.a = 1.0; + } + + if (debug_resolve_perf && prediction.do_slight_focus) { + out_color.rgb *= vec3(1.0, 0.1, 0.1); + } + + imageStore(out_color_img, ivec2(gl_GlobalInvocationID.xy), out_color); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl new file mode 100644 index 00000000000..cfb7fd2568b --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl @@ -0,0 +1,62 @@ + +/** + * Scatter pass: Use sprites to scatter the color of very bright pixel to have higher quality blur. + * + * We only scatter one quad per sprite and one sprite per 4 pixels to reduce vertex shader + * invocations and overdraw. + */ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +#define linearstep(p0, p1, v) (clamp(((v) - (p0)) / abs((p1) - (p0)), 0.0, 1.0)) + +void main() +{ + vec4 coc4 = vec4(interp.color_and_coc1.w, + interp.color_and_coc2.w, + interp.color_and_coc3.w, + interp.color_and_coc4.w); + vec4 shapes; + if (use_bokeh_lut) { + shapes = vec4(texture(bokeh_lut_tx, interp.rect_uv1).r, + texture(bokeh_lut_tx, interp.rect_uv2).r, + texture(bokeh_lut_tx, interp.rect_uv3).r, + texture(bokeh_lut_tx, interp.rect_uv4).r); + } + else { + shapes = vec4(length(interp.rect_uv1), + length(interp.rect_uv2), + length(interp.rect_uv3), + length(interp.rect_uv4)); + } + shapes *= interp.distance_scale; + /* Becomes signed distance field in pixel units. */ + shapes -= coc4; + /* Smooth the edges a bit to fade out the undersampling artifacts. */ + shapes = saturate(1.0 - linearstep(-0.8, 0.8, shapes)); + /* Outside of bokeh shape. Try to avoid overloading ROPs. */ + if (max_v4(shapes) == 0.0) { + discard; + } + + if (!no_scatter_occlusion) { + /* Works because target is the same size as occlusion_tx. */ + vec2 uv = gl_FragCoord.xy / vec2(textureSize(occlusion_tx, 0).xy); + vec2 occlusion_data = texture(occlusion_tx, uv).rg; + /* Fix tilling artifacts. (Slide 90) */ + const float correction_fac = 1.0 - DOF_FAST_GATHER_COC_ERROR; + /* Occlude the sprite with geometry from the same field using a chebychev test (slide 85). */ + float mean = occlusion_data.x; + float variance = occlusion_data.y; + shapes *= variance * safe_rcp(variance + sqr(max(coc4 * correction_fac - mean, 0.0))); + } + + out_color = (interp.color_and_coc1 * shapes[0] + interp.color_and_coc2 * shapes[1] + + interp.color_and_coc3 * shapes[2] + interp.color_and_coc4 * shapes[3]); + /* Do not accumulate alpha. This has already been accumulated by the gather pass. */ + out_color.a = 0.0; + + if (debug_scatter_perf) { + out_color.rgb = avg(out_color.rgb) * vec3(1.0, 0.0, 0.0); + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl new file mode 100644 index 00000000000..d870496a06c --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl @@ -0,0 +1,45 @@ + +/** + * Scatter pass: Use sprites to scatter the color of very bright pixel to have higher quality blur. + * + * We only scatter one triangle per sprite and one sprite per 4 pixels to reduce vertex shader + * invocations and overdraw. + **/ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +void main() +{ + ScatterRect rect = scatter_list_buf[gl_InstanceID]; + + interp.color_and_coc1 = rect.color_and_coc[0]; + interp.color_and_coc2 = rect.color_and_coc[1]; + interp.color_and_coc3 = rect.color_and_coc[2]; + interp.color_and_coc4 = rect.color_and_coc[3]; + + vec2 uv = vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0 - 1.0; + uv = uv * rect.half_extent; + + gl_Position = vec4(uv + rect.offset, 0.0, 1.0); + /* NDC range [-1..1]. */ + gl_Position.xy = (gl_Position.xy / vec2(textureSize(occlusion_tx, 0).xy)) * 2.0 - 1.0; + + if (use_bokeh_lut) { + /* Bias scale to avoid sampling at the texture's border. */ + interp.distance_scale = (float(DOF_BOKEH_LUT_SIZE) / float(DOF_BOKEH_LUT_SIZE - 1)); + vec2 uv_div = 1.0 / (interp.distance_scale * abs(rect.half_extent)); + interp.rect_uv1 = ((uv + quad_offsets[0]) * uv_div) * 0.5 + 0.5; + interp.rect_uv2 = ((uv + quad_offsets[1]) * uv_div) * 0.5 + 0.5; + interp.rect_uv3 = ((uv + quad_offsets[2]) * uv_div) * 0.5 + 0.5; + interp.rect_uv4 = ((uv + quad_offsets[3]) * uv_div) * 0.5 + 0.5; + /* Only for sampling. */ + interp.distance_scale *= max_v2(abs(rect.half_extent)); + } + else { + interp.distance_scale = 1.0; + interp.rect_uv1 = uv + quad_offsets[0]; + interp.rect_uv2 = uv + quad_offsets[1]; + interp.rect_uv3 = uv + quad_offsets[2]; + interp.rect_uv4 = uv + quad_offsets[3]; + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl new file mode 100644 index 00000000000..c017a5aa965 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl @@ -0,0 +1,46 @@ + +/** + * Setup pass: CoC and luma aware downsample to half resolution of the input scene color buffer. + * + * An addition to the downsample CoC, we output the maximum slight out of focus CoC to be + * sure we don't miss a pixel. + * + * Input: + * Full-resolution color & depth buffer + * Output: + * Half-resolution Color, signed CoC (out_coc.x), and max slight focus abs CoC (out_coc.y). + **/ + +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +void main() +{ + vec2 fullres_texel_size = 1.0 / vec2(textureSize(color_tx, 0).xy); + /* Center uv around the 4 fullres pixels. */ + vec2 quad_center = vec2(gl_GlobalInvocationID.xy * 2 + 1) * fullres_texel_size; + + vec4 colors[4]; + vec4 cocs; + for (int i = 0; i < 4; i++) { + vec2 sample_uv = quad_center + quad_offsets[i] * fullres_texel_size; + /* NOTE: We use samplers without filtering. */ + colors[i] = safe_color(textureLod(color_tx, sample_uv, 0.0)); + cocs[i] = dof_coc_from_depth(dof_buf, sample_uv, textureLod(depth_tx, sample_uv, 0.0).r); + } + + cocs = clamp(cocs, -dof_buf.coc_abs_max, dof_buf.coc_abs_max); + + vec4 weights = dof_bilateral_coc_weights(cocs); + weights *= dof_bilateral_color_weights(colors); + /* Normalize so that the sum is 1. */ + weights *= safe_rcp(sum(weights)); + + ivec2 out_texel = ivec2(gl_GlobalInvocationID.xy); + vec4 out_color = weighted_sum_array(colors, weights); + imageStore(out_color_img, out_texel, out_color); + + float out_coc = dot(cocs, weights); + imageStore(out_coc_img, out_texel, vec4(out_coc)); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl new file mode 100644 index 00000000000..5ffedf3068b --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl @@ -0,0 +1,367 @@ + +/** + * Temporal Stabilization of the Depth of field input. + * Corresponds to the TAA pass in the paper. + * We actually duplicate the TAA logic but with a few changes: + * - We run this pass at half resolution. + * - We store CoC instead of Opacity in the alpha channel of the history. + * + * This is and adaption of the code found in eevee_film_lib.glsl + * + * Inputs: + * - Output of setup pass (halfres). + * Outputs: + * - Stabilized Color and CoC (halfres). + **/ + +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) + +struct DofSample { + vec4 color; + float coc; +}; + +/* -------------------------------------------------------------------- */ +/** \name LDS Cache + * \{ */ + +const uint cache_size = gl_WorkGroupSize.x + 2; +shared vec4 color_cache[cache_size][cache_size]; +shared float coc_cache[cache_size][cache_size]; +/* Need 2 pixel border for depth. */ +const uint cache_depth_size = gl_WorkGroupSize.x + 4; +shared float depth_cache[cache_depth_size][cache_depth_size]; + +void dof_cache_init() +{ + /** + * Load enough values into LDS to perform the filter. + * + * ┌──────────────────────────────┐ + * │ │ < Border texels that needs to be loaded. + * │ x x x x x x x x │ ─┐ + * │ x x x x x x x x │ │ + * │ x x x x x x x x │ │ + * │ x x x x x x x x │ │ Thread Group Size 8x8. + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ │ + * │ L L L L L x x x x │ ─┘ + * │ L L L L L │ < Border texels that needs to be loaded. + * └──────────────────────────────┘ + * └───────────┘ + * Load using 5x5 threads. + */ + + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + /* 1 Pixel border. */ + if (all(lessThan(gl_LocalInvocationID.xy, uvec2(cache_size / 2u)))) { + ivec2 offset = ivec2(x, y) * ivec2(cache_size / 2u); + ivec2 cache_texel = ivec2(gl_LocalInvocationID.xy) + offset; + ivec2 load_texel = clamp(texel + offset - 1, ivec2(0), textureSize(color_tx, 0) - 1); + + vec4 color = texelFetch(color_tx, load_texel, 0); + color_cache[cache_texel.y][cache_texel.x] = colorspace_YCoCg_from_scene_linear(color); + coc_cache[cache_texel.y][cache_texel.x] = texelFetch(coc_tx, load_texel, 0).x; + } + /* 2 Pixels border. */ + if (all(lessThan(gl_LocalInvocationID.xy, uvec2(cache_depth_size / 2u)))) { + ivec2 offset = ivec2(x, y) * ivec2(cache_depth_size / 2u); + ivec2 cache_texel = ivec2(gl_LocalInvocationID.xy) + offset; + /* Depth is fullres. Load every 2 pixels. */ + ivec2 load_texel = clamp((texel + offset - 2) * 2, ivec2(0), textureSize(depth_tx, 0) - 1); + + depth_cache[cache_texel.y][cache_texel.x] = texelFetch(depth_tx, load_texel, 0).x; + } + } + } + barrier(); +} + +/* Note: Sample color space is already in YCoCg space. */ +DofSample dof_fetch_input_sample(ivec2 offset) +{ + ivec2 coord = offset + 1 + ivec2(gl_LocalInvocationID.xy); + return DofSample(color_cache[coord.y][coord.x], coc_cache[coord.y][coord.x]); +} + +float dof_fetch_half_depth(ivec2 offset) +{ + ivec2 coord = offset + 2 + ivec2(gl_LocalInvocationID.xy); + return depth_cache[coord.y][coord.x]; +} + +/** \} */ + +float dof_luma_weight(float luma) +{ + /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */ + /* To preserve more details in dark areas, we use a bigger bias. */ + const float exposure_scale = 1.0; /* TODO. */ + return 1.0 / (4.0 + luma * exposure_scale); +} + +float dof_bilateral_weight(float reference_coc, float sample_coc) +{ + /* NOTE: The difference between the cocs should be inside a abs() function, + * but we follow UE4 implementation to improve how dithered transparency looks (see slide 19). + * Effectively bleed background into foreground. + * Compared to dof_bilateral_coc_weights() this saturates as 2x the reference CoC. */ + return saturate(1.0 - (sample_coc - reference_coc) / max(1.0, abs(reference_coc))); +} + +DofSample dof_spatial_filtering() +{ + /* Plus (+) shape offsets. */ + const ivec2 plus_offsets[4] = ivec2[4](ivec2(-1, 0), ivec2(0, -1), ivec2(1, 0), ivec2(0, 1)); + DofSample center = dof_fetch_input_sample(ivec2(0)); + DofSample accum = DofSample(vec4(0.0), 0.0); + float accum_weight = 0.0; + for (int i = 0; i < 4; i++) { + DofSample samp = dof_fetch_input_sample(plus_offsets[i]); + float weight = dof_buf.filter_samples_weight[i] * dof_luma_weight(samp.color.x) * + dof_bilateral_weight(center.coc, samp.coc); + + accum.color += samp.color * weight; + accum.coc += samp.coc * weight; + accum_weight += weight; + } + /* Accumulate center sample last as it does not need bilateral_weights. */ + float weight = dof_buf.filter_center_weight * dof_luma_weight(center.color.x); + accum.color += center.color * weight; + accum.coc += center.coc * weight; + accum_weight += weight; + + float rcp_weight = 1.0 / accum_weight; + accum.color *= rcp_weight; + accum.coc *= rcp_weight; + return accum; +} + +struct DofNeighborhoodMinMax { + DofSample min; + DofSample max; +}; + +/* Return history clipping bounding box in YCoCg color space. */ +DofNeighborhoodMinMax dof_neighbor_boundbox() +{ + /* Plus (+) shape offsets. */ + const ivec2 plus_offsets[4] = ivec2[4](ivec2(-1, 0), ivec2(0, -1), ivec2(1, 0), ivec2(0, 1)); + /** + * Simple bounding box calculation in YCoCg as described in: + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 + */ + DofSample min_c = dof_fetch_input_sample(ivec2(0)); + DofSample max_c = min_c; + for (int i = 0; i < 4; i++) { + DofSample samp = dof_fetch_input_sample(plus_offsets[i]); + min_c.color = min(min_c.color, samp.color); + max_c.color = max(max_c.color, samp.color); + min_c.coc = min(min_c.coc, samp.coc); + max_c.coc = max(max_c.coc, samp.coc); + } + /* (Slide 32) Simple clamp to min/max of 8 neighbors results in 3x3 box artifacts. + * Round bbox shape by averaging 2 different min/max from 2 different neighborhood. */ + DofSample min_c_3x3 = min_c; + DofSample max_c_3x3 = max_c; + const ivec2 corners[4] = ivec2[4](ivec2(-1, -1), ivec2(1, -1), ivec2(-1, 1), ivec2(1, 1)); + for (int i = 0; i < 4; i++) { + DofSample samp = dof_fetch_input_sample(corners[i]); + min_c_3x3.color = min(min_c_3x3.color, samp.color); + max_c_3x3.color = max(max_c_3x3.color, samp.color); + min_c_3x3.coc = min(min_c_3x3.coc, samp.coc); + max_c_3x3.coc = max(max_c_3x3.coc, samp.coc); + } + min_c.color = (min_c.color + min_c_3x3.color) * 0.5; + max_c.color = (max_c.color + max_c_3x3.color) * 0.5; + min_c.coc = (min_c.coc + min_c_3x3.coc) * 0.5; + max_c.coc = (max_c.coc + max_c_3x3.coc) * 0.5; + + return DofNeighborhoodMinMax(min_c, max_c); +} + +/* Returns motion in pixel space to retrieve the pixel history. */ +vec2 dof_pixel_history_motion_vector(ivec2 texel_sample) +{ + /** + * Dilate velocity by using the nearest pixel in a cross pattern. + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 27) + */ + const ivec2 corners[4] = ivec2[4](ivec2(-2, -2), ivec2(2, -2), ivec2(-2, 2), ivec2(2, 2)); + float min_depth = dof_fetch_half_depth(ivec2(0)); + ivec2 nearest_texel = ivec2(0); + for (int i = 0; i < 4; i++) { + float depth = dof_fetch_half_depth(corners[i]); + if (min_depth > depth) { + min_depth = depth; + nearest_texel = corners[i]; + } + } + /* Convert to full resolution buffer pixel. */ + ivec2 velocity_texel = (texel_sample + nearest_texel) * 2; + velocity_texel = clamp(velocity_texel, ivec2(0), textureSize(velocity_tx, 0).xy - 1); + vec4 vector = velocity_resolve(velocity_tx, velocity_texel, min_depth); + /* Transform to **half** pixel space. */ + return vector.xy * vec2(textureSize(color_tx, 0)); +} + +/* Load color using a special filter to avoid losing detail. + * \a texel is sample position with subpixel accuracy. */ +DofSample dof_sample_history(vec2 input_texel) +{ +#if 1 /* Bilinar. */ + vec2 uv = vec2(input_texel + 0.5) / textureSize(in_history_tx, 0); + vec4 color = textureLod(in_history_tx, uv, 0.0); + +#else /* Catmull Rom interpolation. 5 Bilinear Taps. */ + vec2 center_texel; + vec2 inter_texel = modf(input_texel, center_texel); + vec2 weights[4]; + film_get_catmull_rom_weights(inter_texel, weights); + + /** + * Use optimized version by leveraging bilinear filtering from hardware sampler and by removing + * corner taps. + * From "Filmic SMAA" by Jorge Jimenez at Siggraph 2016 + * http://advances.realtimerendering.com/s2016/Filmic%20SMAA%20v7.pptx + */ + center_texel += 0.5; + + /* Slide 92. */ + vec2 weight_12 = weights[1] + weights[2]; + vec2 uv_12 = (center_texel + weights[2] / weight_12) * film_buf.extent_inv; + vec2 uv_0 = (center_texel - 1.0) * film_buf.extent_inv; + vec2 uv_3 = (center_texel + 2.0) * film_buf.extent_inv; + + vec4 color; + vec4 weight_cross = weight_12.xyyx * vec4(weights[0].yx, weights[3].xy); + float weight_center = weight_12.x * weight_12.y; + + color = textureLod(in_history_tx, uv_12, 0.0) * weight_center; + color += textureLod(in_history_tx, vec2(uv_12.x, uv_0.y), 0.0) * weight_cross.x; + color += textureLod(in_history_tx, vec2(uv_0.x, uv_12.y), 0.0) * weight_cross.y; + color += textureLod(in_history_tx, vec2(uv_3.x, uv_12.y), 0.0) * weight_cross.z; + color += textureLod(in_history_tx, vec2(uv_12.x, uv_3.y), 0.0) * weight_cross.w; + /* Re-normalize for the removed corners. */ + color /= (weight_center + sum(weight_cross)); +#endif + /* NOTE(fclem): Opacity is wrong on purpose. Final Opacity does not rely on history. */ + return DofSample(color.xyzz, color.w); +} + +/* Modulate the history color to avoid ghosting artifact. */ +DofSample dof_amend_history(DofNeighborhoodMinMax bbox, DofSample history, DofSample src) +{ +#if 0 + /* Clip instead of clamping to avoid color accumulating in the AABB corners. */ + vec3 clip_dir = src.color.rgb - history.color.rgb; + + float t = line_aabb_clipping_dist( + history.color.rgb, clip_dir, bbox.min.color.rgb, bbox.max.color.rgb); + history.color.rgb += clip_dir * saturate(t); +#else + /* More responsive. */ + history.color = clamp(history.color, bbox.min.color, bbox.max.color); +#endif + /* Clamp CoC to reduce convergence time. Otherwise the result is laggy. */ + history.coc = clamp(history.coc, bbox.min.coc, bbox.max.coc); + + return history; +} + +float dof_history_blend_factor( + float velocity, vec2 texel, DofNeighborhoodMinMax bbox, DofSample src, DofSample dst) +{ + float luma_min = bbox.min.color.x; + float luma_max = bbox.max.color.x; + float luma_incoming = src.color.x; + float luma_history = dst.color.x; + + /* 5% of incoming color by default. */ + float blend = 0.05; + /* Blend less history if the pixel has substantial velocity. */ + /* NOTE(fclem): velocity threshold multiplied by 2 because of half resolution. */ + blend = mix(blend, 0.20, saturate(velocity * 0.02 * 2.0)); + /** + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43) + * Bias towards history if incoming pixel is near clamping. Reduces flicker. + */ + float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history)); + /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */ + distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min); + /* Linearly blend when history gets below to 25% of the bbox size. */ + blend *= saturate(distance_to_luma_clip * 4.0 + 0.1); + /* Progressively discard history until history CoC is twice as big as the filtered CoC. + * Note we use absolute diff here because we are not comparing neighbors and thus do not risk to + * dilate thin features like hair (slide 19). */ + float coc_diff_ratio = saturate(abs(src.coc - dst.coc) / max(1.0, abs(src.coc))); + blend = mix(blend, 1.0, coc_diff_ratio); + /* Discard out of view history. */ + if (any(lessThan(texel, vec2(0))) || + any(greaterThanEqual(texel, vec2(imageSize(out_history_img))))) { + blend = 1.0; + } + /* Discard history if invalid. */ + if (use_history == false) { + blend = 1.0; + } + return blend; +} + +void main() +{ + dof_cache_init(); + + ivec2 src_texel = ivec2(gl_GlobalInvocationID.xy); + + /** + * Naming convention is taken from the film implementation. + * SRC is incoming new data. + * DST is history data. + */ + DofSample src = dof_spatial_filtering(); + + /* Reproject by finding where this pixel was in the previous frame. */ + vec2 motion = dof_pixel_history_motion_vector(src_texel); + vec2 history_texel = vec2(src_texel) + motion; + + float velocity = length(motion); + + DofSample dst = dof_sample_history(history_texel); + + /* Get local color bounding box of source neighborhood. */ + DofNeighborhoodMinMax bbox = dof_neighbor_boundbox(); + + float blend = dof_history_blend_factor(velocity, history_texel, bbox, src, dst); + + dst = dof_amend_history(bbox, dst, src); + + /* Luma weighted blend to reduce flickering. */ + float weight_dst = dof_luma_weight(dst.color.x) * (1.0 - blend); + float weight_src = dof_luma_weight(src.color.x) * (blend); + + DofSample result; + /* Weighted blend. */ + result.color = vec4(dst.color.rgb, dst.coc) * weight_dst + + vec4(src.color.rgb, src.coc) * weight_src; + result.color /= weight_src + weight_dst; + + /* Save history for next iteration. Still in YCoCg space with CoC in alpha. */ + imageStore(out_history_img, src_texel, result.color); + + /* Un-swizzle. */ + result.coc = result.color.a; + /* Clamp opacity since we don't store it in history. */ + result.color.a = clamp(src.color.a, bbox.min.color.a, bbox.max.color.a); + + result.color = colorspace_scene_linear_from_YCoCg(result.color); + + imageStore(out_color_img, src_texel, result.color); + imageStore(out_coc_img, src_texel, vec4(result.coc)); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_dilate_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_dilate_comp.glsl new file mode 100644 index 00000000000..dba8b5fd79d --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_dilate_comp.glsl @@ -0,0 +1,97 @@ + +/** + * Tile dilate pass: Takes the 8x8 Tiles buffer and converts dilates the tiles with large CoC to + * their neighborhood. This pass is repeated multiple time until the maximum CoC can be covered. + * + * Input & Output: + * - Separated foreground and background CoC. 1/8th of half-res resolution. So 1/16th of full-res. + **/ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +/* Error introduced by the random offset of the gathering kernel's center. */ +const float bluring_radius_error = 1.0 + 1.0 / (float(DOF_GATHER_RING_COUNT) + 0.5); +const float tile_to_fullres_factor = float(DOF_TILES_SIZE * 2); + +void main() +{ + ivec2 center_tile_pos = ivec2(gl_GlobalInvocationID.xy); + + CocTile ring_buckets[DOF_DILATE_RING_COUNT]; + + for (int ring = 0; ring < ring_count && ring < DOF_DILATE_RING_COUNT; ring++) { + ring_buckets[ring] = dof_coc_tile_init(); + + int ring_distance = ring + 1; + for (int sample_id = 0; sample_id < 4 * ring_distance; sample_id++) { + ivec2 offset = dof_square_ring_sample_offset(ring_distance, sample_id); + + offset *= ring_width_multiplier; + + for (int i = 0; i < 2; i++) { + ivec2 adj_tile_pos = center_tile_pos + ((i == 0) ? offset : -offset); + + CocTile adj_tile = dof_coc_tile_load(in_tiles_fg_img, in_tiles_bg_img, adj_tile_pos); + + if (DILATE_MODE_MIN_MAX) { + /* Actually gather the "absolute" biggest coc but keeping the sign. */ + ring_buckets[ring].fg_min_coc = min(ring_buckets[ring].fg_min_coc, adj_tile.fg_min_coc); + ring_buckets[ring].bg_max_coc = max(ring_buckets[ring].bg_max_coc, adj_tile.bg_max_coc); + } + else { /* DILATE_MODE_MIN_ABS */ + ring_buckets[ring].fg_max_coc = max(ring_buckets[ring].fg_max_coc, adj_tile.fg_max_coc); + ring_buckets[ring].bg_min_coc = min(ring_buckets[ring].bg_min_coc, adj_tile.bg_min_coc); + + /* Should be tight as possible to reduce gather overhead (see slide 61). */ + float closest_neighbor_distance = length(max(abs(vec2(offset)) - 1.0, 0.0)) * + tile_to_fullres_factor; + + ring_buckets[ring].fg_max_intersectable_coc = max( + ring_buckets[ring].fg_max_intersectable_coc, + adj_tile.fg_max_intersectable_coc + closest_neighbor_distance); + ring_buckets[ring].bg_min_intersectable_coc = min( + ring_buckets[ring].bg_min_intersectable_coc, + adj_tile.bg_min_intersectable_coc + closest_neighbor_distance); + } + } + } + } + + /* Load center tile. */ + CocTile out_tile = dof_coc_tile_load(in_tiles_fg_img, in_tiles_bg_img, center_tile_pos); + + for (int ring = 0; ring < ring_count && ring < DOF_DILATE_RING_COUNT; ring++) { + float ring_distance = float(ring + 1); + + ring_distance = (ring_distance * ring_width_multiplier - 1) * tile_to_fullres_factor; + + if (DILATE_MODE_MIN_MAX) { + /* NOTE(fclem): Unsure if both sides of the inequalities have the same unit. */ + if (-ring_buckets[ring].fg_min_coc * bluring_radius_error > ring_distance) { + out_tile.fg_min_coc = min(out_tile.fg_min_coc, ring_buckets[ring].fg_min_coc); + } + + if (ring_buckets[ring].bg_max_coc * bluring_radius_error > ring_distance) { + out_tile.bg_max_coc = max(out_tile.bg_max_coc, ring_buckets[ring].bg_max_coc); + } + } + else { /* DILATE_MODE_MIN_ABS */ + /* Find minimum absolute CoC radii that will be intersected for the previously + * computed maximum CoC values. */ + if (-out_tile.fg_min_coc * bluring_radius_error > ring_distance) { + out_tile.fg_max_coc = max(out_tile.fg_max_coc, ring_buckets[ring].fg_max_coc); + out_tile.fg_max_intersectable_coc = max(out_tile.fg_max_intersectable_coc, + ring_buckets[ring].fg_max_intersectable_coc); + } + + if (out_tile.bg_max_coc * bluring_radius_error > ring_distance) { + out_tile.bg_min_coc = min(out_tile.bg_min_coc, ring_buckets[ring].bg_min_coc); + out_tile.bg_min_intersectable_coc = min(out_tile.bg_min_intersectable_coc, + ring_buckets[ring].bg_min_intersectable_coc); + } + } + } + + ivec2 texel_out = ivec2(gl_GlobalInvocationID.xy); + dof_coc_tile_store(out_tiles_fg_img, out_tiles_bg_img, texel_out, out_tile); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_flatten_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_flatten_comp.glsl new file mode 100644 index 00000000000..88737ade386 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_tiles_flatten_comp.glsl @@ -0,0 +1,78 @@ + +/** + * Tile flatten pass: Takes the halfres CoC buffer and converts it to 8x8 tiles. + * + * Output min and max values for each tile and for both foreground & background. + * Also outputs min intersectable CoC for the background, which is the minimum CoC + * that comes from the background pixels. + * + * Input: + * - Half-resolution Circle of confusion. Out of setup pass. + * Output: + * - Separated foreground and background CoC. 1/8th of half-res resolution. So 1/16th of full-res. + */ + +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) + +/** + * In order to use atomic operations, we have to use uints. But this means having to deal with the + * negative number ourselves. Luckily, each ground have a nicely defined range of values we can + * remap to positive float. + */ +shared uint fg_min_coc; +shared uint fg_max_coc; +shared uint fg_max_intersectable_coc; +shared uint bg_min_coc; +shared uint bg_max_coc; +shared uint bg_min_intersectable_coc; + +const uint dof_tile_large_coc_uint = floatBitsToUint(dof_tile_large_coc); + +void main() +{ + if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) { + /* NOTE: Min/Max flipped because of inverted fg_coc sign. */ + fg_min_coc = floatBitsToUint(0.0); + fg_max_coc = dof_tile_large_coc_uint; + fg_max_intersectable_coc = dof_tile_large_coc_uint; + bg_min_coc = dof_tile_large_coc_uint; + bg_max_coc = floatBitsToUint(0.0); + bg_min_intersectable_coc = dof_tile_large_coc_uint; + } + barrier(); + + ivec2 sample_texel = min(ivec2(gl_GlobalInvocationID.xy), textureSize(coc_tx, 0).xy - 1); + vec2 sample_data = texelFetch(coc_tx, sample_texel, 0).rg; + + float sample_coc = sample_data.x; + uint fg_coc = floatBitsToUint(max(-sample_coc, 0.0)); + /* NOTE: atomicMin/Max flipped because of inverted fg_coc sign. */ + atomicMax(fg_min_coc, fg_coc); + atomicMin(fg_max_coc, fg_coc); + atomicMin(fg_max_intersectable_coc, (sample_coc < 0.0) ? fg_coc : dof_tile_large_coc_uint); + + uint bg_coc = floatBitsToUint(max(sample_coc, 0.0)); + atomicMin(bg_min_coc, bg_coc); + atomicMax(bg_max_coc, bg_coc); + atomicMin(bg_min_intersectable_coc, (sample_coc > 0.0) ? bg_coc : dof_tile_large_coc_uint); + + barrier(); + + if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) { + if (fg_max_intersectable_coc == dof_tile_large_coc_uint) { + fg_max_intersectable_coc = floatBitsToUint(0.0); + } + + CocTile tile; + /* Foreground sign is flipped since we compare unsigned representation. */ + tile.fg_min_coc = -uintBitsToFloat(fg_min_coc); + tile.fg_max_coc = -uintBitsToFloat(fg_max_coc); + tile.fg_max_intersectable_coc = -uintBitsToFloat(fg_max_intersectable_coc); + tile.bg_min_coc = uintBitsToFloat(bg_min_coc); + tile.bg_max_coc = uintBitsToFloat(bg_max_coc); + tile.bg_min_intersectable_coc = uintBitsToFloat(bg_min_intersectable_coc); + + ivec2 tile_co = ivec2(gl_WorkGroupID.xy); + dof_coc_tile_store(out_tiles_fg_img, out_tiles_bg_img, tile_co, tile); + } +} 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..964c078036b 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 @@ -7,6 +7,7 @@ #pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_camera_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) /* Return scene linear Z depth from the camera or radial depth for panoramic cameras. */ float film_depth_convert_to_scene(float depth) @@ -18,32 +19,6 @@ float film_depth_convert_to_scene(float depth) return abs(get_view_z_from_depth(depth)); } -vec3 film_YCoCg_from_scene_linear(vec3 rgb_color) -{ - const mat3 colorspace_tx = transpose(mat3(vec3(1, 2, 1), /* Y */ - vec3(2, 0, -2), /* Co */ - vec3(-1, 2, -1))); /* Cg */ - return colorspace_tx * rgb_color; -} - -vec4 film_YCoCg_from_scene_linear(vec4 rgba_color) -{ - return vec4(film_YCoCg_from_scene_linear(rgba_color.rgb), rgba_color.a); -} - -vec3 film_scene_linear_from_YCoCg(vec3 ycocg_color) -{ - float Y = ycocg_color.x; - float Co = ycocg_color.y; - float Cg = ycocg_color.z; - - vec3 rgb_color; - rgb_color.r = Y + Co - Cg; - rgb_color.g = Y + Cg; - rgb_color.b = Y - Co - Cg; - return rgb_color * 0.25; -} - /* Load a texture sample in a specific format. Combined pass needs to use this. */ vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel) { @@ -51,7 +26,7 @@ vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel) /* Convert transmittance to opacity. */ color.a = saturate(1.0 - color.a); /* Transform to YCoCg for accumulation. */ - color.rgb = film_YCoCg_from_scene_linear(color.rgb); + color.rgb = colorspace_YCoCg_from_scene_linear(color.rgb); return color; } @@ -130,12 +105,18 @@ void film_sample_accum(FilmSample samp, int pass_id, sampler2D tex, inout float accum += texelFetch(tex, samp.texel, 0).x * samp.weight; } -void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout vec4 accum) +void film_sample_accum( + FilmSample samp, int pass_id, uint layer, sampler2DArray tex, inout vec4 accum) { if (pass_id == -1) { return; } - accum += texelFetch(tex, ivec3(samp.texel, pass_id), 0) * samp.weight; + accum += texelFetch(tex, ivec3(samp.texel, layer), 0) * samp.weight; +} + +void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout vec4 accum) +{ + film_sample_accum(samp, pass_id, pass_id, tex, accum); } void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout float accum) @@ -220,7 +201,7 @@ vec2 film_pixel_history_motion_vector(ivec2 texel_sample) float min_depth = texelFetch(depth_tx, texel_sample, 0).x; ivec2 nearest_texel = texel_sample; for (int i = 0; i < 4; i++) { - ivec2 texel = clamp(texel_sample + corners[i], ivec2(0), textureSize(depth_tx, 0).xy); + ivec2 texel = clamp(texel_sample + corners[i], ivec2(0), textureSize(depth_tx, 0).xy - 1); float depth = texelFetch(depth_tx, texel, 0).x; if (min_depth > depth) { min_depth = depth; @@ -254,7 +235,7 @@ void film_get_catmull_rom_weights(vec2 t, out vec2 weights[4]) weights[3] = fct3 - fct2; } -/* Load color using a special filter to avoid loosing detail. +/* Load color using a special filter to avoid losing detail. * \a texel is sample position with subpixel accuracy. */ vec4 film_sample_catmull_rom(sampler2D color_tx, vec2 input_texel) { @@ -390,7 +371,7 @@ vec4 film_amend_combined_history( float t = line_aabb_clipping_dist(color_history.rgb, clip_dir.rgb, min_color.rgb, max_color.rgb); color_history.rgb += clip_dir.rgb * saturate(t); - /* Clip alpha on its own to avoid interference with other chanels. */ + /* Clip alpha on its own to avoid interference with other channels. */ float t_a = film_aabb_clipping_dist_alpha(color_history.a, clip_dir.a, min_color.a, max_color.a); color_history.a += clip_dir.a * saturate(t_a); @@ -406,16 +387,16 @@ float film_history_blend_factor(float velocity, { /* 5% of incoming color by default. */ float blend = 0.05; - /* Blend less history if the pixel has substential velocity. */ + /* Blend less history if the pixel has substantial velocity. */ blend = mix(blend, 0.20, saturate(velocity * 0.02)); /** * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43) - * Bias towards history if incomming pixel is near clamping. Reduces flicker. + * Bias towards history if incoming pixel is near clamping. Reduces flicker. */ float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history)); /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */ distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min); - /* Linearly blend when history gets bellow to 25% of the bbox size. */ + /* Linearly blend when history gets below to 25% of the bbox size. */ blend *= saturate(distance_to_luma_clip * 4.0 + 0.1); /* Discard out of view history. */ if (any(lessThan(texel, vec2(0))) || any(greaterThanEqual(texel, film_buf.extent))) { @@ -451,13 +432,13 @@ void film_store_combined( float velocity = length(motion); - /* Load weight if it is not uniform accross the whole buffer (i.e: upsampling, panoramic). */ + /* Load weight if it is not uniform across the whole buffer (i.e: upsampling, panoramic). */ // dst.weight = film_weight_load(texel_combined); color_dst = film_sample_catmull_rom(in_combined_tx, history_texel); - color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb); + color_dst.rgb = colorspace_YCoCg_from_scene_linear(color_dst.rgb); - /* Get local color bounding box of source neighboorhood. */ + /* Get local color bounding box of source neighborhood. */ vec4 min_color, max_color; film_combined_neighbor_boundbox(src_texel, min_color, max_color); @@ -473,7 +454,7 @@ void film_store_combined( else { /* Everything is static. Use render accumulation. */ color_dst = texelFetch(in_combined_tx, dst.texel, 0); - color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb); + color_dst.rgb = colorspace_YCoCg_from_scene_linear(color_dst.rgb); /* Luma weighted blend to avoid flickering. */ weight_dst = film_luma_weight(color_dst.x) * dst.weight; @@ -483,7 +464,7 @@ void film_store_combined( color = color_dst * weight_dst + color_src * weight_src; color /= weight_src + weight_dst; - color.rgb = film_scene_linear_from_YCoCg(color.rgb); + color.rgb = colorspace_scene_linear_from_YCoCg(color.rgb); /* Fix alpha not accumulating to 1 because of float imprecision. */ if (color.a > 0.995) { @@ -622,7 +603,7 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth src = film_sample_get(i, texel_film); film_sample_accum_combined(src, combined_accum, weight_accum); } - /* NOTE: src.texel is center texel in incomming data buffer. */ + /* NOTE: src.texel is center texel in incoming data buffer. */ film_store_combined(dst, src.texel, combined_accum, weight_accum, out_color); } @@ -636,6 +617,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); @@ -655,8 +638,16 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth for (int i = 0; i < film_buf.samples_len; i++) { FilmSample src = film_sample_get(i, texel_film); - film_sample_accum(src, film_buf.diffuse_light_id, diffuse_light_tx, diffuse_light_accum); - film_sample_accum(src, film_buf.specular_light_id, specular_light_tx, specular_light_accum); + film_sample_accum(src, + film_buf.diffuse_light_id, + RENDER_PASS_LAYER_DIFFUSE_LIGHT, + light_tx, + diffuse_light_accum); + film_sample_accum(src, + film_buf.specular_light_id, + RENDER_PASS_LAYER_SPECULAR_LIGHT, + light_tx, + specular_light_accum); film_sample_accum(src, film_buf.volume_light_id, volume_light_tx, volume_light_accum); film_sample_accum(src, film_buf.emission_id, emission_tx, emission_accum); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl new file mode 100644 index 00000000000..e93d0f472fa --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl @@ -0,0 +1,24 @@ + +/** + * Debug hiz down sampling pass. + * Output red if above any max pixels, blue otherwise. + */ + +void main() +{ + ivec2 texel = ivec2(gl_FragCoord.xy); + + float depth0 = texelFetch(hiz_tx, texel, 0).r; + + vec4 color = vec4(0.1, 0.1, 1.0, 1.0); + for (int i = 1; i < HIZ_MIP_COUNT; i++) { + ivec2 lvl_texel = texel / ivec2(uvec2(1) << uint(i)); + lvl_texel = min(lvl_texel, textureSize(hiz_tx, i) - 1); + if (texelFetch(hiz_tx, lvl_texel, i).r < depth0) { + color = vec4(1.0, 0.1, 0.1, 1.0); + break; + } + } + out_debug_color_add = vec4(color.rgb, 0.0) * 0.2; + out_debug_color_mul = color; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl new file mode 100644 index 00000000000..597bc73e2ad --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl @@ -0,0 +1,121 @@ + +/** + * Shader that down-sample depth buffer, creating a Hierarchical-Z buffer. + * Saves max value of each 2x2 texel in the mipmap above the one we are + * rendering to. Adapted from + * http://rastergrid.com/blog/2010/10/hierarchical-z-map-based-occlusion-culling/ + * + * Major simplification has been made since we pad the buffer to always be + * bigger than input to avoid mipmapping misalignement. + * + * Start by copying the base level by quad loading the depth. + * Then each thread compute it's local depth for level 1. + * After that we use shared variables to do inter thread comunication and + * downsample to max level. + */ + +#pragma BLENDER_REQUIRE(common_math_lib.glsl) + +shared float local_depths[gl_WorkGroupSize.y][gl_WorkGroupSize.x]; + +/* Load values from the previous lod level. */ +vec4 load_local_depths(ivec2 pixel) +{ + pixel *= 2; + return vec4(local_depths[pixel.y + 1][pixel.x + 0], + local_depths[pixel.y + 1][pixel.x + 1], + local_depths[pixel.y + 0][pixel.x + 1], + local_depths[pixel.y + 0][pixel.x + 0]); +} + +void store_local_depth(ivec2 pixel, float depth) +{ + local_depths[pixel.y][pixel.x] = depth; +} + +void main() +{ + ivec2 local_px = ivec2(gl_LocalInvocationID.xy); + /* Bottom left corner of the kernel. */ + ivec2 kernel_origin = ivec2(gl_WorkGroupSize.xy * gl_WorkGroupID.xy); + + /* Copy level 0. */ + ivec2 src_px = ivec2(kernel_origin + local_px) * 2; + vec2 samp_co = (vec2(src_px) + 0.5) / vec2(textureSize(depth_tx, 0)); + vec4 samp = textureGather(depth_tx, samp_co); + + if (update_mip_0) { + imageStore(out_mip_0, src_px + ivec2(0, 1), samp.xxxx); + imageStore(out_mip_0, src_px + ivec2(1, 1), samp.yyyy); + imageStore(out_mip_0, src_px + ivec2(1, 0), samp.zzzz); + imageStore(out_mip_0, src_px + ivec2(0, 0), samp.wwww); + } + + /* Level 1. (No load) */ + float max_depth = max_v4(samp); + ivec2 dst_px = ivec2(kernel_origin + local_px); + imageStore(out_mip_1, dst_px, vec4(max_depth)); + store_local_depth(local_px, max_depth); + + /* Level 2-5. */ + bool active_thread; + int mask_shift = 1; + +#define downsample_level(out_mip__, lod_) \ + active_thread = all(lessThan(local_px, gl_WorkGroupSize.xy >> uint(mask_shift))); \ + barrier(); /* Wait for previous writes to finish. */ \ + if (active_thread) { \ + max_depth = max_v4(load_local_depths(local_px)); \ + dst_px = ivec2((kernel_origin >> mask_shift) + local_px); \ + imageStore(out_mip__, dst_px, vec4(max_depth)); \ + } \ + barrier(); /* Wait for previous reads to finish. */ \ + if (active_thread) { \ + store_local_depth(local_px, max_depth); \ + } \ + mask_shift++; + + downsample_level(out_mip_2, 2); + downsample_level(out_mip_3, 3); + downsample_level(out_mip_4, 4); + downsample_level(out_mip_5, 5); + + /* Since we pad the destination texture, the mip size is equal to the dispatch size. */ + uint tile_count = uint(imageSize(out_mip_5).x * imageSize(out_mip_5).y); + /* Let the last tile handle the remaining LOD. */ + bool last_tile = atomicAdd(finished_tile_counter, 1u) + 1u < tile_count; + if (last_tile == false) { + return; + } + finished_tile_counter = 0u; + + ivec2 iter = divide_ceil(imageSize(out_mip_5), ivec2(gl_WorkGroupSize * 2u)); + ivec2 image_border = imageSize(out_mip_5) - 1; + for (int y = 0; y < iter.y; y++) { + for (int x = 0; x < iter.x; x++) { + /* Load result of the other work groups. */ + kernel_origin = ivec2(gl_WorkGroupSize) * ivec2(x, y); + src_px = ivec2(kernel_origin + local_px) * 2; + vec4 samp; + samp.x = imageLoad(out_mip_5, min(src_px + ivec2(0, 1), image_border)).x; + samp.y = imageLoad(out_mip_5, min(src_px + ivec2(1, 1), image_border)).x; + samp.z = imageLoad(out_mip_5, min(src_px + ivec2(1, 0), image_border)).x; + samp.w = imageLoad(out_mip_5, min(src_px + ivec2(0, 0), image_border)).x; + /* Level 6. */ + float max_depth = max_v4(samp); + ivec2 dst_px = ivec2(kernel_origin + local_px); + imageStore(out_mip_6, dst_px, vec4(max_depth)); + store_local_depth(local_px, max_depth); + + mask_shift = 1; + + /* Level 7. */ + downsample_level(out_mip_7, 7); + + /* Limited by OpenGL maximum of 8 image slot. */ + // downsample_level(out_mip_8, 8); + // downsample_level(out_mip_9, 9); + // downsample_level(out_mip_10, 10); + } + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl new file mode 100644 index 00000000000..9d231c6bd37 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl @@ -0,0 +1,54 @@ + +/** + * Debug Shader outputing a gradient of orange - white - blue to mark culling hotspots. + * Green pixels are error pixels that are missing lights from the culling pass (i.e: when culling + * pass is not conservative enough). + */ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) + +void main() +{ + ivec2 texel = ivec2(gl_FragCoord.xy); + + float depth = texelFetch(hiz_tx, texel, 0).r; + float vP_z = get_view_z_from_depth(depth); + vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth); + + float light_count = 0.0; + uint light_cull = 0u; + vec2 px = gl_FragCoord.xy; + LIGHT_FOREACH_BEGIN_LOCAL(light_cull_buf, light_zbin_buf, light_tile_buf, px, vP_z, l_idx) + { + LightData light = light_buf[l_idx]; + light_cull |= 1u << l_idx; + light_count += 1.0; + } + LIGHT_FOREACH_END + + uint light_nocull = 0u; + LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx) + { + LightData light = light_buf[l_idx]; + vec3 L; + float dist; + light_vector_get(light, P, L, dist); + if (light_attenuation(light_buf[l_idx], L, dist) > 0.0) { + light_nocull |= 1u << l_idx; + } + } + LIGHT_FOREACH_END + + vec4 color = vec4(heatmap_gradient(light_count / 4.0), 1.0); + + if ((light_cull & light_nocull) != light_nocull) { + /* ERROR. Some lights were culled incorrectly. */ + color = vec4(0.0, 1.0, 0.0, 1.0); + } + + out_debug_color_add = vec4(color.rgb, 0.0) * 0.2; + out_debug_color_mul = color; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl new file mode 100644 index 00000000000..9c12b0e50e6 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl @@ -0,0 +1,62 @@ + +/** + * Select the visible items inside the active view and put them inside the sorting buffer. + */ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_intersect_lib.glsl) + +void main() +{ + uint l_idx = gl_GlobalInvocationID.x; + if (l_idx >= light_cull_buf.items_count) { + return; + } + + LightData light = in_light_buf[l_idx]; + + /* Do not select 0 power lights. */ + if (light.influence_radius_max < 1e-8) { + return; + } + + /* Sun lights are packed at the end of the array. Perform early copy. */ + if (light.type == LIGHT_SUN) { + /* NOTE: We know the index because sun lights are packed at the start of the input buffer. */ + out_light_buf[light_cull_buf.local_lights_len + l_idx] = light; + return; + } + + Sphere sphere; + switch (light.type) { + case LIGHT_SPOT: + /* Only for < ~170° Cone due to plane extraction precision. */ + if (light.spot_tan < 10.0) { + Pyramid pyramid = shape_pyramid_non_oblique( + light._position, + light._position - light._back * light.influence_radius_max, + light._right * light.influence_radius_max * light.spot_tan / light.spot_size_inv.x, + light._up * light.influence_radius_max * light.spot_tan / light.spot_size_inv.y); + if (!intersect_view(pyramid)) { + return; + } + } + case LIGHT_RECT: + case LIGHT_ELLIPSE: + case LIGHT_POINT: + sphere = Sphere(light._position, light.influence_radius_max); + break; + } + + /* TODO(fclem): HiZ culling? Could be quite beneficial given the nature of the 2.5D culling. */ + + /* TODO(fclem): Small light culling / fading? */ + + if (intersect_view(sphere)) { + uint index = atomicAdd(light_cull_buf.visible_count, 1u); + + out_zdist_buf[index] = dot(cameraForward, light._position) - dot(cameraForward, cameraPos); + out_key_buf[index] = l_idx; + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl new file mode 100644 index 00000000000..e98b170cd4c --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl @@ -0,0 +1,57 @@ + +/** + * Sort the lights by their Z distance to the camera. + * Outputs ordered light buffer. + * One thread processes one Light entity. + */ + +#pragma BLENDER_REQUIRE(common_math_lib.glsl) + +shared float zdists_cache[gl_WorkGroupSize.x]; + +void main() +{ + uint src_index = gl_GlobalInvocationID.x; + bool valid_thread = true; + + if (src_index >= light_cull_buf.visible_count) { + /* Do not return because we use barriers later on (which need uniform control flow). + * Just process the same last item but avoid insertion. */ + src_index = light_cull_buf.visible_count - 1; + valid_thread = false; + } + + float local_zdist = in_zdist_buf[src_index]; + + int prefix_sum = 0; + /* Iterate over the whole key buffer. */ + uint iter = divide_ceil(light_cull_buf.visible_count, gl_WorkGroupSize.x); + for (uint i = 0u; i < iter; i++) { + uint index = gl_WorkGroupSize.x * i + gl_LocalInvocationID.x; + /* NOTE: This will load duplicated values, but they will be discarded. */ + index = min(index, light_cull_buf.visible_count - 1); + zdists_cache[gl_LocalInvocationID.x] = in_zdist_buf[index]; + + barrier(); + + /* Iterate over the cache line. */ + uint line_end = min(gl_WorkGroupSize.x, light_cull_buf.visible_count - gl_WorkGroupSize.x * i); + for (uint j = 0u; j < line_end; j++) { + if (zdists_cache[j] < local_zdist) { + prefix_sum++; + } + else if (zdists_cache[j] == local_zdist) { + /* Same depth, use index to order and avoid same prefix for 2 different lights. */ + if ((gl_WorkGroupSize.x * i + j) < src_index) { + prefix_sum++; + } + } + } + } + + if (valid_thread) { + /* Copy sorted light to render light buffer. */ + uint input_index = in_key_buf[src_index]; + out_light_buf[prefix_sum] = in_light_buf[input_index]; + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl new file mode 100644 index 00000000000..37705e22b22 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl @@ -0,0 +1,188 @@ + +/** + * 2D Culling pass for lights. + * We iterate over all items and check if they intersect with the tile frustum. + * Dispatch one thread per word. + */ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) + +/* ---------------------------------------------------------------------- */ +/** \name Culling shapes extraction + * \{ */ + +struct CullingTile { + IsectFrustum frustum; + vec4 bounds; +}; + +/* Corners are expected to be in viewspace so that the cone is starting from the origin. + * Corner order does not matter. */ +vec4 tile_bound_cone(vec3 v00, vec3 v01, vec3 v10, vec3 v11) +{ + v00 = normalize(v00); + v01 = normalize(v01); + v10 = normalize(v10); + v11 = normalize(v11); + vec3 center = normalize(v00 + v01 + v10 + v11); + float angle_cosine = dot(center, v00); + angle_cosine = max(angle_cosine, dot(center, v01)); + angle_cosine = max(angle_cosine, dot(center, v10)); + angle_cosine = max(angle_cosine, dot(center, v11)); + return vec4(center, angle_cosine); +} + +/* Corners are expected to be in viewspace. Returns Z-aligned bounding cylinder. + * Corner order does not matter. */ +vec4 tile_bound_cylinder(vec3 v00, vec3 v01, vec3 v10, vec3 v11) +{ + vec3 center = (v00 + v01 + v10 + v11) * 0.25; + vec4 corners_dist; + float dist_sqr = distance_squared(center, v00); + dist_sqr = max(dist_sqr, distance_squared(center, v01)); + dist_sqr = max(dist_sqr, distance_squared(center, v10)); + dist_sqr = max(dist_sqr, distance_squared(center, v11)); + /* Return a cone. Later converted to cylinder. */ + return vec4(center, sqrt(dist_sqr)); +} + +vec2 tile_to_ndc(vec2 tile_co, vec2 offset) +{ + /* Add a margin to prevent culling too much if the frustum becomes too much unstable. */ + const float margin = 0.02; + tile_co += margin * (offset * 2.0 - 1.0); + + tile_co += offset; + return tile_co * light_cull_buf.tile_to_uv_fac * 2.0 - 1.0; +} + +CullingTile tile_culling_get(uvec2 tile_co) +{ + vec2 ftile = vec2(tile_co); + /* Culling frustum corners for this tile. */ + vec3 corners[8]; + /* Follow same corners order as view frustum. */ + corners[1].xy = corners[0].xy = tile_to_ndc(ftile, vec2(0, 0)); + corners[5].xy = corners[4].xy = tile_to_ndc(ftile, vec2(1, 0)); + corners[6].xy = corners[7].xy = tile_to_ndc(ftile, vec2(1, 1)); + corners[2].xy = corners[3].xy = tile_to_ndc(ftile, vec2(0, 1)); + corners[1].z = corners[5].z = corners[6].z = corners[2].z = -1.0; + corners[0].z = corners[4].z = corners[7].z = corners[3].z = 1.0; + + for (int i = 0; i < 8; i++) { + /* Culling in view space for precision. */ + corners[i] = project_point(ProjectionMatrixInverse, corners[i]); + } + + bool is_persp = ProjectionMatrix[3][3] == 0.0; + CullingTile tile; + tile.bounds = (is_persp) ? tile_bound_cone(corners[0], corners[4], corners[7], corners[3]) : + tile_bound_cylinder(corners[0], corners[4], corners[7], corners[3]); + + tile.frustum = isect_data_setup(shape_frustum(corners)); + return tile; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Intersection Tests + * \{ */ + +bool intersect(CullingTile tile, Sphere sphere) +{ + bool isect = true; + /* Test tile intersection using bounding cone or bounding cylinder. + * This has less false positive cases when the sphere is large. */ + if (ProjectionMatrix[3][3] == 0.0) { + isect = intersect(shape_cone(tile.bounds.xyz, tile.bounds.w), sphere); + } + else { + /* Simplify to a 2D circle test on the view Z axis plane. */ + isect = intersect(shape_circle(tile.bounds.xy, tile.bounds.w), + shape_circle(sphere.center.xy, sphere.radius)); + } + /* Refine using frustum test. If the sphere is small it avoids intersection + * with a neighbor tile. */ + if (isect) { + isect = intersect(tile.frustum, sphere); + } + return isect; +} + +bool intersect(CullingTile tile, Box bbox) +{ + return intersect(tile.frustum, bbox); +} + +bool intersect(CullingTile tile, Pyramid pyramid) +{ + return intersect(tile.frustum, pyramid); +} + +/** \} */ + +void main() +{ + uint word_idx = gl_GlobalInvocationID.x % light_cull_buf.tile_word_len; + uint tile_idx = gl_GlobalInvocationID.x / light_cull_buf.tile_word_len; + uvec2 tile_co = uvec2(tile_idx % light_cull_buf.tile_x_len, + tile_idx / light_cull_buf.tile_x_len); + + if (tile_co.y >= light_cull_buf.tile_y_len) { + return; + } + + /* TODO(fclem): We could stop the tile at the HiZ depth. */ + CullingTile tile = tile_culling_get(tile_co); + + uint l_idx = word_idx * 32u; + uint l_end = min(l_idx + 32u, light_cull_buf.visible_count); + uint word = 0u; + for (; l_idx < l_end; l_idx++) { + LightData light = light_buf[l_idx]; + + /* Culling in view space for precision and simplicity. */ + vec3 vP = transform_point(ViewMatrix, light._position); + vec3 v_right = transform_direction(ViewMatrix, light._right); + vec3 v_up = transform_direction(ViewMatrix, light._up); + vec3 v_back = transform_direction(ViewMatrix, light._back); + float radius = light.influence_radius_max; + + Sphere sphere = shape_sphere(vP, radius); + bool intersect_tile = intersect(tile, sphere); + + switch (light.type) { + case LIGHT_SPOT: + /* Only for < ~170° Cone due to plane extraction precision. */ + if (light.spot_tan < 10.0) { + Pyramid pyramid = shape_pyramid_non_oblique( + vP, + vP - v_back * radius, + v_right * radius * light.spot_tan / light.spot_size_inv.x, + v_up * radius * light.spot_tan / light.spot_size_inv.y); + intersect_tile = intersect_tile && intersect(tile, pyramid); + break; + } + /* Fallthrough to the hemispheric case. */ + case LIGHT_RECT: + case LIGHT_ELLIPSE: + vec3 v000 = vP - v_right * radius - v_up * radius; + vec3 v100 = v000 + v_right * (radius * 2.0); + vec3 v010 = v000 + v_up * (radius * 2.0); + vec3 v001 = v000 - v_back * radius; + Box bbox = shape_box(v000, v100, v010, v001); + intersect_tile = intersect_tile && intersect(tile, bbox); + default: + break; + } + + if (intersect_tile) { + word |= 1u << (l_idx % 32u); + } + } + + out_light_tile_buf[gl_GlobalInvocationID.x] = word; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl new file mode 100644 index 00000000000..ae20153f26c --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl @@ -0,0 +1,56 @@ + +/** + * Create the Zbins from Z-sorted lights. + * Perform min-max operation in LDS memory for speed. + * For this reason, we only dispatch 1 thread group. + */ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) + +/* Fits the limit of 32KB. */ +shared uint zbin_max[CULLING_ZBIN_COUNT]; +shared uint zbin_min[CULLING_ZBIN_COUNT]; + +void main() +{ + const uint zbin_iter = CULLING_ZBIN_COUNT / gl_WorkGroupSize.x; + const uint zbin_local = gl_LocalInvocationID.x * zbin_iter; + + uint src_index = gl_GlobalInvocationID.x; + + for (uint i = 0u, l = zbin_local; i < zbin_iter; i++, l++) { + zbin_max[l] = 0x0u; + zbin_min[l] = ~0x0u; + } + barrier(); + + uint light_iter = divide_ceil(light_cull_buf.visible_count, gl_WorkGroupSize.x); + for (uint i = 0u; i < light_iter; i++) { + uint index = i * gl_WorkGroupSize.x + gl_LocalInvocationID.x; + if (index >= light_cull_buf.visible_count) { + continue; + } + vec3 P = light_buf[index]._position; + /* TODO(fclem): Could have better bounds for spot and area lights. */ + float radius = light_buf[index].influence_radius_max; + float z_dist = dot(cameraForward, P) - dot(cameraForward, cameraPos); + int z_min = culling_z_to_zbin( + light_cull_buf.zbin_scale, light_cull_buf.zbin_bias, z_dist + radius); + int z_max = culling_z_to_zbin( + light_cull_buf.zbin_scale, light_cull_buf.zbin_bias, z_dist - radius); + z_min = clamp(z_min, 0, CULLING_ZBIN_COUNT - 1); + z_max = clamp(z_max, 0, CULLING_ZBIN_COUNT - 1); + /* Register to Z bins. */ + for (int z = z_min; z <= z_max; z++) { + atomicMin(zbin_min[z], index); + atomicMax(zbin_max[z], index); + } + } + barrier(); + + /* Write result to zbins buffer. Pack min & max into 1 uint. */ + for (uint i = 0u, l = zbin_local; i < zbin_iter; i++, l++) { + out_zbin_buf[l] = (zbin_max[l] << 16u) | (zbin_min[l] & 0xFFFFu); + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl new file mode 100644 index 00000000000..d4abdd43aa4 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl @@ -0,0 +1,129 @@ + +/** + * The resources expected to be defined are: + * - light_buf + * - light_zbin_buf + * - light_cull_buf + * - light_tile_buf + * - shadow_atlas_tx + * - shadow_tilemaps_tx + * - sss_transmittance_tx + * - utility_tx + */ + +#pragma BLENDER_REQUIRE(eevee_light_lib.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) + +/* TODO(fclem): We could reduce register pressure by only having static branches for sun lights. */ +void light_eval_ex(ClosureDiffuse diffuse, + ClosureReflection reflection, + const bool is_directional, + vec3 P, + vec3 V, + float vP_z, + float thickness, + vec4 ltc_mat, + uint l_idx, + inout vec3 out_diffuse, + inout vec3 out_specular) +{ + LightData light = light_buf[l_idx]; + vec3 L; + float dist; + light_vector_get(light, P, L, dist); + + float visibility = light_attenuation(light, L, dist); + +#if 0 /* TODO(fclem): Shadows */ + if ((light.shadow_id != LIGHT_NO_SHADOW) && (visibility > 0.0)) { + vec3 lL = light_world_to_local(light, -L) * dist; + + float shadow_delta = shadow_delta_get( + shadow_atlas_tx, shadow_tilemaps_tx, light, light.shadow_data, lL, dist, P); + +# ifdef SSS_TRANSMITTANCE + /* Transmittance evaluation first to use initial visibility. */ + if (diffuse.sss_id != 0u && light.diffuse_power > 0.0) { + float delta = max(thickness, shadow_delta); + + vec3 intensity = visibility * light.transmit_power * + light_translucent(sss_transmittance_tx, + is_directional, + light, + diffuse.N, + L, + dist, + diffuse.sss_radius, + delta); + out_diffuse += light.color * intensity; + } +# endif + + visibility *= float(shadow_delta - light.shadow_data.bias <= 0.0); + } +#endif + + if (visibility < 1e-6) { + return; + } + + if (light.diffuse_power > 0.0) { + float intensity = visibility * light.diffuse_power * + light_diffuse(utility_tx, is_directional, light, diffuse.N, V, L, dist); + out_diffuse += light.color * intensity; + } + + if (light.specular_power > 0.0) { + float intensity = visibility * light.specular_power * + light_ltc( + utility_tx, is_directional, light, reflection.N, V, L, dist, ltc_mat); + out_specular += light.color * intensity; + } +} + +void light_eval(ClosureDiffuse diffuse, + ClosureReflection reflection, + vec3 P, + vec3 V, + float vP_z, + float thickness, + inout vec3 out_diffuse, + inout vec3 out_specular) +{ + vec2 uv = vec2(reflection.roughness, safe_sqrt(1.0 - dot(reflection.N, V))); + uv = uv * UTIL_TEX_UV_SCALE + UTIL_TEX_UV_BIAS; + vec4 ltc_mat = utility_tx_sample(utility_tx, uv, UTIL_LTC_MAT_LAYER); + + LIGHT_FOREACH_BEGIN_DIRECTIONAL(light_cull_buf, l_idx) + { + light_eval_ex(diffuse, + reflection, + true, + P, + V, + vP_z, + thickness, + ltc_mat, + l_idx, + out_diffuse, + out_specular); + } + LIGHT_FOREACH_END + + vec2 px = gl_FragCoord.xy; + LIGHT_FOREACH_BEGIN_LOCAL(light_cull_buf, light_zbin_buf, light_tile_buf, px, vP_z, l_idx) + { + light_eval_ex(diffuse, + reflection, + false, + P, + V, + vP_z, + thickness, + ltc_mat, + l_idx, + out_diffuse, + out_specular); + } + LIGHT_FOREACH_END +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl new file mode 100644 index 00000000000..22a5f98e6c3 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl @@ -0,0 +1,72 @@ + +#pragma BLENDER_REQUIRE(common_math_lib.glsl) + +uint zbin_mask(uint word_index, uint zbin_min, uint zbin_max) +{ + uint word_start = word_index * 32u; + uint word_end = word_start + 31u; + uint local_min = max(zbin_min, word_start); + uint local_max = min(zbin_max, word_end); + uint mask_width = local_max - local_min + 1; + return bit_field_mask(mask_width, local_min); +} + +int culling_z_to_zbin(float scale, float bias, float z) +{ + return int(z * scale + bias); +} + +/* Waiting to implement extensions support. We need: + * - GL_KHR_shader_subgroup_ballot + * - GL_KHR_shader_subgroup_arithmetic + * or + * - Vulkan 1.1 + */ +#if 1 +# define subgroupMin(a) a +# define subgroupMax(a) a +# define subgroupOr(a) a +# define subgroupBroadcastFirst(a) a +#endif + +#define LIGHT_FOREACH_BEGIN_DIRECTIONAL(_culling, _index) \ + { \ + { \ + for (uint _index = _culling.local_lights_len; _index < _culling.items_count; _index++) { + +#define LIGHT_FOREACH_BEGIN_LOCAL(_culling, _zbins, _words, _pixel, _linearz, _item_index) \ + { \ + uvec2 tile_co = uvec2(_pixel / _culling.tile_size); \ + uint tile_word_offset = (tile_co.x + tile_co.y * _culling.tile_x_len) * \ + _culling.tile_word_len; \ + int zbin_index = culling_z_to_zbin(_culling.zbin_scale, _culling.zbin_bias, _linearz); \ + zbin_index = clamp(zbin_index, 0, CULLING_ZBIN_COUNT - 1); \ + uint zbin_data = _zbins[zbin_index]; \ + uint min_index = zbin_data & 0xFFFFu; \ + uint max_index = zbin_data >> 16u; \ + /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ + min_index = subgroupBroadcastFirst(subgroupMin(min_index)); \ + max_index = subgroupBroadcastFirst(subgroupMax(max_index)); \ + /* Same as divide by 32 but avoid interger division. */ \ + uint word_min = min_index >> 5u; \ + uint word_max = max_index >> 5u; \ + for (uint word_idx = word_min; word_idx <= word_max; word_idx++) { \ + uint word = _words[tile_word_offset + word_idx]; \ + word &= zbin_mask(word_idx, min_index, max_index); \ + /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ + word = subgroupBroadcastFirst(subgroupOr(word)); \ + int bit_index; \ + while ((bit_index = findLSB(word)) != -1) { \ + word &= ~1u << uint(bit_index); \ + uint _item_index = word_idx * 32u + bit_index; + +/* No culling. Iterate over all items. */ +#define LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(_culling, _item_index) \ + { \ + { \ + for (uint _item_index = 0; _item_index < _culling.visible_count; _item_index++) { + +#define LIGHT_FOREACH_END \ + } \ + } \ + } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl new file mode 100644 index 00000000000..58608f6e1f0 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl @@ -0,0 +1,209 @@ + +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_ltc_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) + +/* ---------------------------------------------------------------------- */ +/** \name Light Functions + * \{ */ + +void light_vector_get(LightData ld, vec3 P, out vec3 L, out float dist) +{ + if (ld.type == LIGHT_SUN) { + L = ld._back; + dist = 1.0; + } + else { + L = ld._position - P; + dist = inversesqrt(len_squared(L)); + L *= dist; + dist = 1.0 / dist; + } +} + +/* Rotate vector to light's local space. Does not translate. */ +vec3 light_world_to_local(LightData ld, vec3 L) +{ + /* Avoid relying on compiler to optimize this. + * vec3 lL = transpose(mat3(ld.object_mat)) * L; */ + vec3 lL; + lL.x = dot(ld.object_mat[0].xyz, L); + lL.y = dot(ld.object_mat[1].xyz, L); + lL.z = dot(ld.object_mat[2].xyz, L); + return lL; +} + +/* From Frostbite PBR Course + * Distance based attenuation + * http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */ +float light_influence_attenuation(float dist, float inv_sqr_influence) +{ + float factor = sqr(dist) * inv_sqr_influence; + float fac = saturate(1.0 - sqr(factor)); + return sqr(fac); +} + +float light_spot_attenuation(LightData ld, vec3 L) +{ + vec3 lL = light_world_to_local(ld, L); + float ellipse = inversesqrt(1.0 + len_squared(lL.xy * ld.spot_size_inv / lL.z)); + float spotmask = smoothstep(0.0, 1.0, ellipse * ld._spot_mul + ld._spot_bias); + return spotmask; +} + +float light_attenuation(LightData ld, vec3 L, float dist) +{ + float vis = 1.0; + if (ld.type == LIGHT_SPOT) { + vis *= light_spot_attenuation(ld, L); + } + if (ld.type >= LIGHT_SPOT) { + vis *= step(0.0, -dot(L, -ld._back)); + } + if (ld.type != LIGHT_SUN) { +#ifdef VOLUME_LIGHTING + vis *= light_influence_attenuation(dist, ld.influence_radius_invsqr_volume); +#else + vis *= light_influence_attenuation(dist, ld.influence_radius_invsqr_surface); +#endif + } + return vis; +} + +/* Cheaper alternative than evaluating the LTC. + * The result needs to be multiplied by BSDF or Phase Function. */ +float light_point_light(LightData ld, const bool is_directional, vec3 L, float dist) +{ + if (is_directional) { + return 1.0; + } + /** + * Using "Point Light Attenuation Without Singularity" from Cem Yuksel + * http://www.cemyuksel.com/research/pointlightattenuation/pointlightattenuation.pdf + * http://www.cemyuksel.com/research/pointlightattenuation/ + **/ + float d_sqr = sqr(dist); + float r_sqr = ld.radius_squared; + /* Using reformulation that has better numerical percision. */ + float power = 2.0 / (d_sqr + r_sqr + dist * sqrt(d_sqr + r_sqr)); + + if (is_area_light(ld.type)) { + /* Modulate by light plane orientation / solid angle. */ + power *= saturate(dot(ld._back, L)); + } + return power; +} + +float light_diffuse(sampler2DArray utility_tx, + const bool is_directional, + LightData ld, + vec3 N, + vec3 V, + vec3 L, + float dist) +{ + if (is_directional || !is_area_light(ld.type)) { + float radius = ld._radius / dist; + return ltc_evaluate_disk_simple(utility_tx, radius, dot(N, L)); + } + else if (ld.type == LIGHT_RECT) { + vec3 corners[4]; + corners[0] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + corners[1] = ld._right * ld._area_size_x + ld._up * ld._area_size_y; + corners[2] = -corners[0]; + corners[3] = -corners[1]; + + corners[0] = normalize(L * dist + corners[0]); + corners[1] = normalize(L * dist + corners[1]); + corners[2] = normalize(L * dist + corners[2]); + corners[3] = normalize(L * dist + corners[3]); + + return ltc_evaluate_quad(utility_tx, corners, N); + } + else /* (ld.type == LIGHT_ELLIPSE) */ { + vec3 points[3]; + points[0] = ld._right * -ld._area_size_x + ld._up * -ld._area_size_y; + points[1] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + points[2] = -points[0]; + + points[0] += L * dist; + points[1] += L * dist; + points[2] += L * dist; + + return ltc_evaluate_disk(utility_tx, N, V, mat3(1.0), points); + } +} + +float light_ltc(sampler2DArray utility_tx, + const bool is_directional, + LightData ld, + vec3 N, + vec3 V, + vec3 L, + float dist, + vec4 ltc_mat) +{ + if (is_directional || ld.type != LIGHT_RECT) { + vec3 Px = ld._right; + vec3 Py = ld._up; + + if (is_directional || !is_area_light(ld.type)) { + make_orthonormal_basis(L, Px, Py); + } + + vec3 points[3]; + points[0] = Px * -ld._area_size_x + Py * -ld._area_size_y; + points[1] = Px * ld._area_size_x + Py * -ld._area_size_y; + points[2] = -points[0]; + + points[0] += L * dist; + points[1] += L * dist; + points[2] += L * dist; + + return ltc_evaluate_disk(utility_tx, N, V, ltc_matrix(ltc_mat), points); + } + else { + vec3 corners[4]; + corners[0] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + corners[1] = ld._right * ld._area_size_x + ld._up * ld._area_size_y; + corners[2] = -corners[0]; + corners[3] = -corners[1]; + + corners[0] += L * dist; + corners[1] += L * dist; + corners[2] += L * dist; + corners[3] += L * dist; + + ltc_transform_quad(N, V, ltc_matrix(ltc_mat), corners); + + return ltc_evaluate_quad(utility_tx, corners, vec3(0.0, 0.0, 1.0)); + } +} + +vec3 light_translucent(sampler1D transmittance_tx, + const bool is_directional, + LightData ld, + vec3 N, + vec3 L, + float dist, + vec3 sss_radius, + float delta) +{ + /* TODO(fclem): We should compute the power at the entry point. */ + /* NOTE(fclem): we compute the light attenuation using the light vector but the transmittance + * using the shadow depth delta. */ + float power = light_point_light(ld, is_directional, L, dist); + /* Do not add more energy on front faces. Also apply lambertian BSDF. */ + power *= max(0.0, dot(-N, L)) * M_1_PI; + + sss_radius *= SSS_TRANSMIT_LUT_RADIUS; + vec3 channels_co = saturate(delta / sss_radius) * SSS_TRANSMIT_LUT_SCALE + SSS_TRANSMIT_LUT_BIAS; + + vec3 translucency; + translucency.x = (sss_radius.x > 0.0) ? texture(transmittance_tx, channels_co.x).r : 0.0; + translucency.y = (sss_radius.y > 0.0) ? texture(transmittance_tx, channels_co.y).r : 0.0; + translucency.z = (sss_radius.z > 0.0) ? texture(transmittance_tx, channels_co.z).r : 0.0; + return translucency * power; +} + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl new file mode 100644 index 00000000000..57e92b0b9b4 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl @@ -0,0 +1,299 @@ + +/** + * Adapted from : + * Real-Time Polygonal-Light Shading with Linearly Transformed Cosines. + * Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt. + * ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016. + * Project page: https://eheitzresearch.wordpress.com/415-2/ + */ + +/* Diffuse *clipped* sphere integral. */ +float ltc_diffuse_sphere_integral(sampler2DArray utility_tx, float avg_dir_z, float form_factor) +{ +#if 1 + /* use tabulated horizon-clipped sphere */ + vec2 uv = vec2(avg_dir_z * 0.5 + 0.5, form_factor); + uv = uv * UTIL_TEX_UV_SCALE + UTIL_TEX_UV_BIAS; + + return texture(utility_tx, vec3(uv, UTIL_DISK_INTEGRAL_LAYER))[UTIL_DISK_INTEGRAL_COMP]; +#else + /* Cheap approximation. Less smooth and have energy issues. */ + return max((form_factor * form_factor + avg_dir_z) / (form_factor + 1.0), 0.0); +#endif +} + +/** + * An extended version of the implementation from + * "How to solve a cubic equation, revisited" + * http://momentsingraphics.de/?p=105 + */ +vec3 ltc_solve_cubic(vec4 coefs) +{ + /* Normalize the polynomial */ + coefs.xyz /= coefs.w; + /* Divide middle coefficients by three */ + coefs.yz /= 3.0; + + float A = coefs.w; + float B = coefs.z; + float C = coefs.y; + float D = coefs.x; + + /* Compute the Hessian and the discriminant */ + vec3 delta = vec3(-coefs.zy * coefs.zz + coefs.yx, dot(vec2(coefs.z, -coefs.y), coefs.xy)); + + /* Discriminant */ + float discr = dot(vec2(4.0 * delta.x, -delta.y), delta.zy); + + /* Clamping avoid NaN output on some platform. (see T67060) */ + float sqrt_discr = sqrt(clamp(discr, 0.0, FLT_MAX)); + + vec2 xlc, xsc; + + /* Algorithm A */ + { + float A_a = 1.0; + float C_a = delta.x; + float D_a = -2.0 * B * delta.x + delta.y; + + /* Take the cubic root of a normalized complex number */ + float theta = atan(sqrt_discr, -D_a) / 3.0; + + float _2_sqrt_C_a = 2.0 * sqrt(-C_a); + float x_1a = _2_sqrt_C_a * cos(theta); + float x_3a = _2_sqrt_C_a * cos(theta + (2.0 / 3.0) * M_PI); + + float xl; + if ((x_1a + x_3a) > 2.0 * B) { + xl = x_1a; + } + else { + xl = x_3a; + } + + xlc = vec2(xl - B, A); + } + + /* Algorithm D */ + { + float A_d = D; + float C_d = delta.z; + float D_d = -D * delta.y + 2.0 * C * delta.z; + + /* Take the cubic root of a normalized complex number */ + float theta = atan(D * sqrt_discr, -D_d) / 3.0; + + float _2_sqrt_C_d = 2.0 * sqrt(-C_d); + float x_1d = _2_sqrt_C_d * cos(theta); + float x_3d = _2_sqrt_C_d * cos(theta + (2.0 / 3.0) * M_PI); + + float xs; + if (x_1d + x_3d < 2.0 * C) { + xs = x_1d; + } + else { + xs = x_3d; + } + + xsc = vec2(-D, xs + C); + } + + float E = xlc.y * xsc.y; + float F = -xlc.x * xsc.y - xlc.y * xsc.x; + float G = xlc.x * xsc.x; + + vec2 xmc = vec2(C * F - B * G, -B * F + C * E); + + vec3 root = vec3(xsc.x / xsc.y, xmc.x / xmc.y, xlc.x / xlc.y); + + if (root.x < root.y && root.x < root.z) { + root.xyz = root.yxz; + } + else if (root.z < root.x && root.z < root.y) { + root.xyz = root.xzy; + } + + return root; +} + +/* from Real-Time Area Lighting: a Journey from Research to Production + * Stephen Hill and Eric Heitz */ +vec3 ltc_edge_integral_vec(vec3 v1, vec3 v2) +{ + float x = dot(v1, v2); + float y = abs(x); + + float a = 0.8543985 + (0.4965155 + 0.0145206 * y) * y; + float b = 3.4175940 + (4.1616724 + y) * y; + float v = a / b; + + float theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt(max(1.0 - x * x, 1e-7)) - v; + + return cross(v1, v2) * theta_sintheta; +} + +mat3 ltc_matrix(vec4 lut) +{ + /* Load inverse matrix. */ + return mat3(vec3(lut.x, 0, lut.y), vec3(0, 1, 0), vec3(lut.z, 0, lut.w)); +} + +void ltc_transform_quad(vec3 N, vec3 V, mat3 Minv, inout vec3 corners[4]) +{ + /* Avoid dot(N, V) == 1 in ortho mode, leading T1 normalize to fail. */ + V = normalize(V + 1e-8); + + /* Construct orthonormal basis around N. */ + vec3 T1, T2; + T1 = normalize(V - N * dot(N, V)); + T2 = cross(N, T1); + + /* Rotate area light in (T1, T2, R) basis. */ + Minv = Minv * transpose(mat3(T1, T2, N)); + + /* Apply LTC inverse matrix. */ + corners[0] = normalize(Minv * corners[0]); + corners[1] = normalize(Minv * corners[1]); + corners[2] = normalize(Minv * corners[2]); + corners[3] = normalize(Minv * corners[3]); +} + +/* If corners have already pass through ltc_transform_quad(), + * then N **MUST** be vec3(0.0, 0.0, 1.0), corresponding to the Up axis of the shading basis. */ +float ltc_evaluate_quad(sampler2DArray utility_tx, vec3 corners[4], vec3 N) +{ + /* Approximation using a sphere of the same solid angle than the quad. + * Finding the clipped sphere diffuse integral is easier than clipping the quad. */ + vec3 avg_dir; + avg_dir = ltc_edge_integral_vec(corners[0], corners[1]); + avg_dir += ltc_edge_integral_vec(corners[1], corners[2]); + avg_dir += ltc_edge_integral_vec(corners[2], corners[3]); + avg_dir += ltc_edge_integral_vec(corners[3], corners[0]); + + float form_factor = length(avg_dir); + float avg_dir_z = dot(N, avg_dir / form_factor); + return form_factor * ltc_diffuse_sphere_integral(utility_tx, avg_dir_z, form_factor); +} + +/* If disk does not need to be transformed and is already front facing. */ +float ltc_evaluate_disk_simple(sampler2DArray utility_tx, float disk_radius, float NL) +{ + float r_sqr = disk_radius * disk_radius; + float one_r_sqr = 1.0 + r_sqr; + float form_factor = r_sqr * inversesqrt(one_r_sqr * one_r_sqr); + return form_factor * ltc_diffuse_sphere_integral(utility_tx, NL, form_factor); +} + +/* disk_points are WS vectors from the shading point to the disk "bounding domain" */ +float ltc_evaluate_disk(sampler2DArray utility_tx, vec3 N, vec3 V, mat3 Minv, vec3 disk_points[3]) +{ + /* Avoid dot(N, V) == 1 in ortho mode, leading T1 normalize to fail. */ + V = normalize(V + 1e-8); + + /* construct orthonormal basis around N */ + vec3 T1, T2; + T1 = normalize(V - N * dot(V, N)); + T2 = cross(N, T1); + + /* rotate area light in (T1, T2, R) basis */ + mat3 R = transpose(mat3(T1, T2, N)); + + /* Intermediate step: init ellipse. */ + vec3 L_[3]; + L_[0] = mul(R, disk_points[0]); + L_[1] = mul(R, disk_points[1]); + L_[2] = mul(R, disk_points[2]); + + vec3 C = 0.5 * (L_[0] + L_[2]); + vec3 V1 = 0.5 * (L_[1] - L_[2]); + vec3 V2 = 0.5 * (L_[1] - L_[0]); + + /* Transform ellipse by Minv. */ + C = Minv * C; + V1 = Minv * V1; + V2 = Minv * V2; + + /* Compute eigenvectors of new ellipse. */ + + float d11 = dot(V1, V1); + float d22 = dot(V2, V2); + float d12 = dot(V1, V2); + float a, b; /* Eigenvalues */ + const float threshold = 0.0007; /* Can be adjusted. Fix artifacts. */ + if (abs(d12) / sqrt(d11 * d22) > threshold) { + float tr = d11 + d22; + float det = -d12 * d12 + d11 * d22; + + /* use sqrt matrix to solve for eigenvalues */ + det = sqrt(det); + float u = 0.5 * sqrt(tr - 2.0 * det); + float v = 0.5 * sqrt(tr + 2.0 * det); + float e_max = (u + v); + float e_min = (u - v); + e_max *= e_max; + e_min *= e_min; + + vec3 V1_, V2_; + if (d11 > d22) { + V1_ = d12 * V1 + (e_max - d11) * V2; + V2_ = d12 * V1 + (e_min - d11) * V2; + } + else { + V1_ = d12 * V2 + (e_max - d22) * V1; + V2_ = d12 * V2 + (e_min - d22) * V1; + } + + a = 1.0 / e_max; + b = 1.0 / e_min; + V1 = normalize(V1_); + V2 = normalize(V2_); + } + else { + a = 1.0 / d11; + b = 1.0 / d22; + V1 *= sqrt(a); + V2 *= sqrt(b); + } + + /* Now find front facing ellipse with same solid angle. */ + + vec3 V3 = normalize(cross(V1, V2)); + if (dot(C, V3) < 0.0) { + V3 *= -1.0; + } + + float L = dot(V3, C); + float inv_L = 1.0 / L; + float x0 = dot(V1, C) * inv_L; + float y0 = dot(V2, C) * inv_L; + + float L_sqr = L * L; + a *= L_sqr; + b *= L_sqr; + + float t = 1.0 + x0 * x0; + float c0 = a * b; + float c1 = c0 * (t + y0 * y0) - a - b; + float c2 = (1.0 - a * t) - b * (1.0 + y0 * y0); + float c3 = 1.0; + + vec3 roots = ltc_solve_cubic(vec4(c0, c1, c2, c3)); + float e1 = roots.x; + float e2 = roots.y; + float e3 = roots.z; + + vec3 avg_dir = vec3(a * x0 / (a - e2), b * y0 / (b - e2), 1.0); + + mat3 rotate = mat3(V1, V2, V3); + + avg_dir = rotate * avg_dir; + avg_dir = normalize(avg_dir); + + /* L1, L2 are the extends of the front facing ellipse. */ + float L1 = sqrt(-e2 / e3); + float L2 = sqrt(-e2 / e1); + + /* Find the sphere and compute lighting. */ + float form_factor = max(0.0, L1 * L2 * inversesqrt((1.0 + L1 * L1) * (1.0 + L2 * L2))); + return form_factor * ltc_diffuse_sphere_integral(utility_tx, avg_dir.z, form_factor); +} 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..99186ab6f67 --- /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 neighborhood. + * + */ + +#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 bounding 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..5249e6637b6 --- /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 resolved 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_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index c488216eeac..13ad387289d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -39,6 +39,8 @@ bool closure_select(float weight, inout float total_weight, inout float r) destination = candidate; \ } +float g_closure_rand; + void closure_weights_reset() { g_diffuse_data.weight = 0.0; @@ -58,18 +60,8 @@ void closure_weights_reset() g_refraction_data.roughness = 0.0; g_refraction_data.ior = 0.0; - /* TEMP */ -#define P(x) ((x + 0.5) / 16.0) - const vec4 dither_mat4x4[4] = vec4[4](vec4(P(0.0), P(8.0), P(2.0), P(10.0)), - vec4(P(12.0), P(4.0), P(14.0), P(6.0)), - vec4(P(3.0), P(11.0), P(1.0), P(9.0)), - vec4(P(15.0), P(7.0), P(13.0), P(5.0))); -#undef P #if defined(GPU_FRAGMENT_SHADER) - ivec2 pix = ivec2(gl_FragCoord.xy) % ivec2(4); - g_diffuse_rand = dither_mat4x4[pix.x][pix.y]; - g_reflection_rand = dither_mat4x4[pix.x][pix.y]; - g_refraction_rand = dither_mat4x4[pix.x][pix.y]; + g_diffuse_rand = g_reflection_rand = g_refraction_rand = g_closure_rand; #else g_diffuse_rand = 0.0; g_reflection_rand = 0.0; 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..0eea4a5ff33 --- /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 Discrepancy 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_surf_forward_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl index 48ced4e5374..3f2349b30a1 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl @@ -5,35 +5,36 @@ * This is used by alpha blended materials and materials using Shader to RGB nodes. **/ -#pragma BLENDER_REQUIRE(common_view_lib.glsl) -#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_hair_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) -float spec_light(ClosureReflection ref) -{ - float gloss = saturate(1.0 - ref.roughness); - float shininess = exp2(10.0 * gloss + 1.0); - vec3 N = ref.N; - vec3 L = vec3(0.0, 0.0, 1.0); - vec3 H = normalize(L + cameraVec(g_data.P)); - float spec_angle = saturate(dot(N, H)); - float normalization_factor = shininess * 0.125 + 1.0; - float spec_light = pow(spec_angle, shininess) * saturate(dot(N, L)) * normalization_factor; - return spec_light; -} - vec4 closure_to_rgba(Closure cl) { + vec3 diffuse_light = vec3(0.0); + vec3 reflection_light = vec3(0.0); + vec3 refraction_light = vec3(0.0); + + float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); + + light_eval(g_diffuse_data, + g_reflection_data, + g_data.P, + cameraVec(g_data.P), + vP_z, + 0.01 /* TODO(fclem) thickness. */, + diffuse_light, + reflection_light); + vec4 out_color; out_color.rgb = g_emission; - out_color.rgb += g_diffuse_data.color * g_diffuse_data.weight * - saturate(g_diffuse_data.N.z * 0.5 + 0.5); - out_color.rgb += g_reflection_data.color * g_reflection_data.weight * - spec_light(g_reflection_data); - out_color.rgb += g_refraction_data.color * g_refraction_data.weight * - saturate(g_refraction_data.N.z * 0.5 + 0.5); + out_color.rgb += g_diffuse_data.color * g_diffuse_data.weight * diffuse_light; + out_color.rgb += g_reflection_data.color * g_reflection_data.weight * reflection_light; + out_color.rgb += g_refraction_data.color * g_refraction_data.weight * refraction_light; out_color.a = saturate(1.0 - avg(g_transmittance)); @@ -47,15 +48,29 @@ void main() { init_globals(); + float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).r; + g_closure_rand = fract(noise + sampling_rng_1D_get(SAMPLING_CLOSURE)); + fragment_displacement(); nodetree_surface(); g_holdout = saturate(g_holdout); - vec3 diffuse_light = vec3(saturate(g_diffuse_data.N.z * 0.5 + 0.5)); - vec3 reflection_light = vec3(spec_light(g_reflection_data)); - vec3 refraction_light = vec3(saturate(g_refraction_data.N.z * 0.5 + 0.5)); + vec3 diffuse_light = vec3(0.0); + vec3 reflection_light = vec3(0.0); + vec3 refraction_light = vec3(0.0); + + float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); + + light_eval(g_diffuse_data, + g_reflection_data, + g_data.P, + cameraVec(g_data.P), + vP_z, + 0.01 /* TODO(fclem) thickness. */, + diffuse_light, + reflection_light); g_diffuse_data.color *= g_diffuse_data.weight; g_reflection_data.color *= g_reflection_data.weight; @@ -84,9 +99,11 @@ void main() ivec2 out_texel = ivec2(gl_FragCoord.xy); imageStore(rp_normal_img, out_texel, vec4(out_normal, 1.0)); - imageStore(rp_diffuse_light_img, out_texel, vec4(diffuse_light, 1.0)); + imageStore( + rp_light_img, ivec3(out_texel, RENDER_PASS_LAYER_DIFFUSE_LIGHT), vec4(diffuse_light, 1.0)); + imageStore( + rp_light_img, ivec3(out_texel, RENDER_PASS_LAYER_SPECULAR_LIGHT), vec4(specular_light, 1.0)); imageStore(rp_diffuse_color_img, out_texel, vec4(g_diffuse_data.color, 1.0)); - imageStore(rp_specular_light_img, out_texel, vec4(specular_light, 1.0)); imageStore(rp_specular_color_img, out_texel, vec4(specular_color, 1.0)); imageStore(rp_emission_img, out_texel, vec4(g_emission, 1.0)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl index ed75282a550..1ef1c1f84b8 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl @@ -26,9 +26,11 @@ void main() ivec2 out_texel = ivec2(gl_FragCoord.xy); imageStore(rp_normal_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); - imageStore(rp_diffuse_light_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); + imageStore( + rp_light_img, ivec3(out_texel, RENDER_PASS_LAYER_DIFFUSE_LIGHT), vec4(0.0, 0.0, 0.0, 1.0)); + imageStore( + rp_light_img, ivec3(out_texel, RENDER_PASS_LAYER_SPECULAR_LIGHT), vec4(0.0, 0.0, 0.0, 1.0)); imageStore(rp_diffuse_color_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); - imageStore(rp_specular_light_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); imageStore(rp_specular_color_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); imageStore(rp_emission_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0)); 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..8d02609fedc 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; @@ -39,13 +47,14 @@ vec4 velocity_surface(vec3 P_prv, vec3 P, vec3 P_nxt) */ vec4 velocity_background(vec3 vV) { - /* Only transform direction to avoid loosing precision. */ + /* Only transform direction to avoid losing precision. */ vec3 V = transform_direction(camera_curr.viewinv, vV); /* NOTE: We don't use the drw_view.winmat to avoid adding the TAA jitter to the velocity. */ 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_depth_of_field_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_depth_of_field_info.hh new file mode 100644 index 00000000000..b398a6cc4e7 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_depth_of_field_info.hh @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "eevee_defines.hh" +#include "gpu_shader_create_info.hh" + +/* -------------------------------------------------------------------- */ +/** \name Setup + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_bokeh_lut) + .do_static_compilation(true) + .local_group_size(DOF_BOKEH_LUT_SIZE, DOF_BOKEH_LUT_SIZE) + .additional_info("eevee_shared", "draw_view") + .uniform_buf(1, "DepthOfFieldData", "dof_buf") + .image(0, GPU_RG16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_gather_lut_img") + .image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_scatter_lut_img") + .image(2, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_resolve_lut_img") + .compute_source("eevee_depth_of_field_bokeh_lut_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_setup) + .do_static_compilation(true) + .local_group_size(DOF_DEFAULT_GROUP_SIZE, DOF_DEFAULT_GROUP_SIZE) + .additional_info("eevee_shared", "draw_view") + .uniform_buf(1, "DepthOfFieldData", "dof_buf") + .sampler(0, ImageType::FLOAT_2D, "color_tx") + .sampler(1, ImageType::DEPTH_2D, "depth_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_coc_img") + .compute_source("eevee_depth_of_field_setup_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_stabilize) + .do_static_compilation(true) + .local_group_size(DOF_STABILIZE_GROUP_SIZE, DOF_STABILIZE_GROUP_SIZE) + .additional_info("eevee_shared", "draw_view", "eevee_velocity_camera") + .uniform_buf(4, "DepthOfFieldData", "dof_buf") + .sampler(0, ImageType::FLOAT_2D, "coc_tx") + .sampler(1, ImageType::FLOAT_2D, "color_tx") + .sampler(2, ImageType::FLOAT_2D, "velocity_tx") + .sampler(3, ImageType::FLOAT_2D, "in_history_tx") + .sampler(4, ImageType::DEPTH_2D, "depth_tx") + .push_constant(Type::BOOL, "use_history") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_coc_img") + .image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_history_img") + .compute_source("eevee_depth_of_field_stabilize_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_downsample) + .do_static_compilation(true) + .local_group_size(DOF_DEFAULT_GROUP_SIZE, DOF_DEFAULT_GROUP_SIZE) + .additional_info("eevee_shared", "draw_view") + .sampler(0, ImageType::FLOAT_2D, "color_tx") + .sampler(1, ImageType::FLOAT_2D, "coc_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .compute_source("eevee_depth_of_field_downsample_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_reduce) + .do_static_compilation(true) + .local_group_size(DOF_REDUCE_GROUP_SIZE, DOF_REDUCE_GROUP_SIZE) + .additional_info("eevee_shared", "draw_view") + .uniform_buf(1, "DepthOfFieldData", "dof_buf") + .sampler(0, ImageType::FLOAT_2D, "downsample_tx") + .storage_buf(0, Qualifier::WRITE, "ScatterRect", "scatter_fg_list_buf[]") + .storage_buf(1, Qualifier::WRITE, "ScatterRect", "scatter_bg_list_buf[]") + .storage_buf(2, Qualifier::READ_WRITE, "DrawCommand", "scatter_fg_indirect_buf") + .storage_buf(3, Qualifier::READ_WRITE, "DrawCommand", "scatter_bg_indirect_buf") + .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "inout_color_lod0_img") + .image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_lod1_img") + .image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_lod2_img") + .image(3, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_lod3_img") + .image(4, GPU_R16F, Qualifier::READ, ImageType::FLOAT_2D, "in_coc_lod0_img") + .image(5, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_coc_lod1_img") + .image(6, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_coc_lod2_img") + .image(7, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_coc_lod3_img") + .compute_source("eevee_depth_of_field_reduce_comp.glsl"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Circle-Of-Confusion Tiles + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_tiles_flatten) + .do_static_compilation(true) + .local_group_size(DOF_TILES_FLATTEN_GROUP_SIZE, DOF_TILES_FLATTEN_GROUP_SIZE) + .additional_info("eevee_shared", "draw_view") + .sampler(0, ImageType::FLOAT_2D, "coc_tx") + .image(2, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_fg_img") + .image(3, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_bg_img") + .compute_source("eevee_depth_of_field_tiles_flatten_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_tiles_dilate) + .additional_info("eevee_shared", "draw_view", "eevee_depth_of_field_tiles_common") + .local_group_size(DOF_TILES_DILATE_GROUP_SIZE, DOF_TILES_DILATE_GROUP_SIZE) + .image(2, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_fg_img") + .image(3, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_bg_img") + .push_constant(Type::INT, "ring_count") + .push_constant(Type::INT, "ring_width_multiplier") + .compute_source("eevee_depth_of_field_tiles_dilate_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_tiles_dilate_minabs) + .do_static_compilation(true) + .define("DILATE_MODE_MIN_MAX", "false") + .additional_info("eevee_depth_of_field_tiles_dilate"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_tiles_dilate_minmax) + .do_static_compilation(true) + .define("DILATE_MODE_MIN_MAX", "true") + .additional_info("eevee_depth_of_field_tiles_dilate"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_tiles_common) + .image(0, GPU_R11F_G11F_B10F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_fg_img") + .image(1, GPU_R11F_G11F_B10F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_bg_img"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Variations + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_no_lut) + .define("DOF_BOKEH_TEXTURE", "false") + /** + * WORKAROUND(@fclem): This is to keep the code as is for now. The bokeh_lut_tx is referenced + * even if not used after optimization. But we don't want to include it in the create infos. + */ + .define("bokeh_lut_tx", "color_tx"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_lut) + .define("DOF_BOKEH_TEXTURE", "true") + .sampler(5, ImageType::FLOAT_2D, "bokeh_lut_tx"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_background).define("DOF_FOREGROUND_PASS", "false"); +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_foreground).define("DOF_FOREGROUND_PASS", "true"); + +#define EEVEE_DOF_FINAL_VARIATION(name, ...) \ + GPU_SHADER_CREATE_INFO(name).additional_info(__VA_ARGS__).do_static_compilation(true); + +#define EEVEE_DOF_LUT_VARIATIONS(prefix, ...) \ + EEVEE_DOF_FINAL_VARIATION(prefix##_lut, "eevee_depth_of_field_lut", __VA_ARGS__) \ + EEVEE_DOF_FINAL_VARIATION(prefix##_no_lut, "eevee_depth_of_field_no_lut", __VA_ARGS__) + +#define EEVEE_DOF_GROUND_VARIATIONS(name, ...) \ + EEVEE_DOF_LUT_VARIATIONS(name##_background, "eevee_depth_of_field_background", __VA_ARGS__) \ + EEVEE_DOF_LUT_VARIATIONS(name##_foreground, "eevee_depth_of_field_foreground", __VA_ARGS__) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Gather + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_gather_common) + .additional_info("eevee_shared", + "draw_view", + "eevee_depth_of_field_tiles_common", + "eevee_sampling_data") + .uniform_buf(2, "DepthOfFieldData", "dof_buf") + .local_group_size(DOF_GATHER_GROUP_SIZE, DOF_GATHER_GROUP_SIZE) + .sampler(0, ImageType::FLOAT_2D, "color_tx") + .sampler(1, ImageType::FLOAT_2D, "color_bilinear_tx") + .sampler(2, ImageType::FLOAT_2D, "coc_tx") + .image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .image(3, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_weight_img"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_gather) + .image(4, GPU_RG16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_occlusion_img") + .compute_source("eevee_depth_of_field_gather_comp.glsl") + .additional_info("eevee_depth_of_field_gather_common"); + +EEVEE_DOF_GROUND_VARIATIONS(eevee_depth_of_field_gather, "eevee_depth_of_field_gather") + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_hole_fill) + .do_static_compilation(true) + .compute_source("eevee_depth_of_field_hole_fill_comp.glsl") + .additional_info("eevee_depth_of_field_gather_common", "eevee_depth_of_field_no_lut"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_filter) + .do_static_compilation(true) + .local_group_size(DOF_FILTER_GROUP_SIZE, DOF_FILTER_GROUP_SIZE) + .additional_info("eevee_shared") + .sampler(0, ImageType::FLOAT_2D, "color_tx") + .sampler(1, ImageType::FLOAT_2D, "weight_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .image(1, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_weight_img") + .compute_source("eevee_depth_of_field_filter_comp.glsl"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Scatter + * \{ */ + +GPU_SHADER_INTERFACE_INFO(eevee_depth_of_field_scatter_iface, "interp") + /** Colors, weights, and Circle of confusion radii for the 4 pixels to scatter. */ + .flat(Type::VEC4, "color_and_coc1") + .flat(Type::VEC4, "color_and_coc2") + .flat(Type::VEC4, "color_and_coc3") + .flat(Type::VEC4, "color_and_coc4") + /** Sprite pixel position with origin at sprite center. In pixels. */ + .no_perspective(Type::VEC2, "rect_uv1") + .no_perspective(Type::VEC2, "rect_uv2") + .no_perspective(Type::VEC2, "rect_uv3") + .no_perspective(Type::VEC2, "rect_uv4") + /** Scaling factor for the bokeh distance. */ + .flat(Type::FLOAT, "distance_scale"); + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_scatter) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view") + .sampler(0, ImageType::FLOAT_2D, "occlusion_tx") + .sampler(1, ImageType::FLOAT_2D, "bokeh_lut_tx") + .storage_buf(0, Qualifier::READ, "ScatterRect", "scatter_list_buf[]") + .fragment_out(0, Type::VEC4, "out_color") + .push_constant(Type::BOOL, "use_bokeh_lut") + .vertex_out(eevee_depth_of_field_scatter_iface) + .vertex_source("eevee_depth_of_field_scatter_vert.glsl") + .fragment_source("eevee_depth_of_field_scatter_frag.glsl"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Resolve + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_depth_of_field_resolve) + .define("DOF_RESOLVE_PASS", "true") + .local_group_size(DOF_RESOLVE_GROUP_SIZE, DOF_RESOLVE_GROUP_SIZE) + .additional_info("eevee_shared", + "draw_view", + "eevee_depth_of_field_tiles_common", + "eevee_sampling_data") + .uniform_buf(2, "DepthOfFieldData", "dof_buf") + .sampler(0, ImageType::DEPTH_2D, "depth_tx") + .sampler(1, ImageType::FLOAT_2D, "color_tx") + .sampler(2, ImageType::FLOAT_2D, "color_bg_tx") + .sampler(3, ImageType::FLOAT_2D, "color_fg_tx") + .sampler(4, ImageType::FLOAT_2D, "color_hole_fill_tx") + .sampler(7, ImageType::FLOAT_2D, "weight_bg_tx") + .sampler(8, ImageType::FLOAT_2D, "weight_fg_tx") + .sampler(9, ImageType::FLOAT_2D, "weight_hole_fill_tx") + .sampler(10, ImageType::FLOAT_2D, "stable_color_tx") + .image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img") + .compute_source("eevee_depth_of_field_resolve_comp.glsl"); + +EEVEE_DOF_LUT_VARIATIONS(eevee_depth_of_field_resolve, "eevee_depth_of_field_resolve") + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh index a5baaca51f9..c94171db6a9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh @@ -9,19 +9,18 @@ GPU_SHADER_CREATE_INFO(eevee_film) .sampler(1, ImageType::FLOAT_2D, "combined_tx") .sampler(2, ImageType::FLOAT_2D, "normal_tx") .sampler(3, ImageType::FLOAT_2D, "vector_tx") - .sampler(4, ImageType::FLOAT_2D, "diffuse_light_tx") + .sampler(4, ImageType::FLOAT_2D_ARRAY, "light_tx") .sampler(5, ImageType::FLOAT_2D, "diffuse_color_tx") - .sampler(6, ImageType::FLOAT_2D, "specular_light_tx") - .sampler(7, ImageType::FLOAT_2D, "specular_color_tx") - .sampler(8, ImageType::FLOAT_2D, "volume_light_tx") - .sampler(9, ImageType::FLOAT_2D, "emission_tx") - .sampler(10, ImageType::FLOAT_2D, "environment_tx") - .sampler(11, ImageType::FLOAT_2D, "shadow_tx") - .sampler(12, ImageType::FLOAT_2D, "ambient_occlusion_tx") - .sampler(13, ImageType::FLOAT_2D_ARRAY, "aov_color_tx") - .sampler(14, ImageType::FLOAT_2D_ARRAY, "aov_value_tx") + .sampler(6, ImageType::FLOAT_2D, "specular_color_tx") + .sampler(7, ImageType::FLOAT_2D, "volume_light_tx") + .sampler(8, ImageType::FLOAT_2D, "emission_tx") + .sampler(9, ImageType::FLOAT_2D, "environment_tx") + .sampler(10, ImageType::FLOAT_2D, "shadow_tx") + .sampler(11, ImageType::FLOAT_2D, "ambient_occlusion_tx") + .sampler(12, ImageType::FLOAT_2D_ARRAY, "aov_color_tx") + .sampler(13, ImageType::FLOAT_2D_ARRAY, "aov_value_tx") /* Color History for TAA needs to be sampler to leverage bilinear sampling. */ - .sampler(15, ImageType::FLOAT_2D, "in_combined_tx") + .sampler(14, ImageType::FLOAT_2D, "in_combined_tx") // .sampler(15, ImageType::FLOAT_2D, "cryptomatte_tx") /* TODO */ .image(0, GPU_R32F, Qualifier::READ, ImageType::FLOAT_2D_ARRAY, "in_weight_img") .image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "out_weight_img") diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh new file mode 100644 index 00000000000..5e32631a8f8 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "eevee_defines.hh" +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(eevee_hiz_data) + .sampler(15, ImageType::FLOAT_2D, "hiz_tx") + .uniform_buf(5, "HiZData", "hiz_buf"); + +GPU_SHADER_CREATE_INFO(eevee_hiz_update) + .do_static_compilation(true) + .local_group_size(FILM_GROUP_SIZE, FILM_GROUP_SIZE) + .storage_buf(0, Qualifier::READ_WRITE, "uint", "finished_tile_counter") + .sampler(0, ImageType::DEPTH_2D, "depth_tx") + .image(0, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_0") + .image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_1") + .image(2, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_2") + .image(3, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_3") + .image(4, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_4") + .image(5, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "out_mip_5") + .image(6, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_6") + .image(7, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_7") + .push_constant(Type::BOOL, "update_mip_0") + .compute_source("eevee_hiz_update_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_hiz_debug) + .do_static_compilation(true) + .fragment_out(0, Type::VEC4, "out_debug_color_add", DualBlend::SRC_0) + .fragment_out(0, Type::VEC4, "out_debug_color_mul", DualBlend::SRC_1) + .fragment_source("eevee_hiz_debug_frag.glsl") + .additional_info("eevee_shared", "eevee_hiz_data", "draw_fullscreen"); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh new file mode 100644 index 00000000000..c54f05719d3 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "eevee_defines.hh" +#include "gpu_shader_create_info.hh" + +/* -------------------------------------------------------------------- */ +/** \name Shared + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_light_data) + .storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ, "LightData", "light_buf[]") + .storage_buf(2, Qualifier::READ, "uint", "light_zbin_buf[]") + .storage_buf(3, Qualifier::READ, "uint", "light_tile_buf[]"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Culling + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_light_culling_select) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view") + .local_group_size(CULLING_SELECT_GROUP_SIZE) + .storage_buf(0, Qualifier::READ_WRITE, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ, "LightData", "in_light_buf[]") + .storage_buf(2, Qualifier::WRITE, "LightData", "out_light_buf[]") + .storage_buf(3, Qualifier::WRITE, "float", "out_zdist_buf[]") + .storage_buf(4, Qualifier::WRITE, "uint", "out_key_buf[]") + .compute_source("eevee_light_culling_select_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_light_culling_sort) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view") + .storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ, "LightData", "in_light_buf[]") + .storage_buf(2, Qualifier::WRITE, "LightData", "out_light_buf[]") + .storage_buf(3, Qualifier::READ, "float", "in_zdist_buf[]") + .storage_buf(4, Qualifier::READ, "uint", "in_key_buf[]") + .local_group_size(CULLING_SORT_GROUP_SIZE) + .compute_source("eevee_light_culling_sort_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_light_culling_zbin) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view") + .local_group_size(CULLING_ZBIN_GROUP_SIZE) + .storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ, "LightData", "light_buf[]") + .storage_buf(2, Qualifier::WRITE, "uint", "out_zbin_buf[]") + .compute_source("eevee_light_culling_zbin_comp.glsl"); + +GPU_SHADER_CREATE_INFO(eevee_light_culling_tile) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view") + .local_group_size(CULLING_TILE_GROUP_SIZE) + .storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ, "LightData", "light_buf[]") + .storage_buf(2, Qualifier::WRITE, "uint", "out_light_tile_buf[]") + .compute_source("eevee_light_culling_tile_comp.glsl"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Debug + * \{ */ + +GPU_SHADER_CREATE_INFO(eevee_light_culling_debug) + .do_static_compilation(true) + .fragment_out(0, Type::VEC4, "out_debug_color_add", DualBlend::SRC_0) + .fragment_out(0, Type::VEC4, "out_debug_color_mul", DualBlend::SRC_1) + .fragment_source("eevee_light_culling_debug_frag.glsl") + .additional_info( + "eevee_shared", "draw_view", "draw_fullscreen", "eevee_light_data", "eevee_hiz_data"); + +/** \} */ 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..dad1f28ef8e 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,11 @@ 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"); + +GPU_SHADER_CREATE_INFO(eevee_utility_texture).sampler(8, ImageType::FLOAT_2D_ARRAY, "utility_tx"); /** \} */ @@ -75,8 +78,8 @@ GPU_SHADER_INTERFACE_INFO(eevee_surf_iface, "interp") GPU_SHADER_CREATE_INFO(eevee_aov_out) .define("MAT_AOV_SUPPORT") - .image_array_out(6, Qualifier::WRITE, GPU_RGBA16F, "aov_color_img") - .image_array_out(7, Qualifier::WRITE, GPU_R16F, "aov_value_img") + .image_array_out(5, Qualifier::WRITE, GPU_RGBA16F, "aov_color_img") + .image_array_out(6, Qualifier::WRITE, GPU_R16F, "aov_value_img") .storage_buf(7, Qualifier::READ, "AOVsInfoData", "aov_buf"); GPU_SHADER_CREATE_INFO(eevee_surf_deferred) @@ -110,19 +113,18 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward) .fragment_out(0, Type::VEC4, "out_transmittance", DualBlend::SRC_1) .fragment_source("eevee_surf_forward_frag.glsl") .image_out(0, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_normal_img") - .image_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_light_img") + .image_array_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_light_img") .image_out(2, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_color_img") - .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_light_img") - .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img") - .image_out(5, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img") - .additional_info("eevee_aov_out" - // "eevee_sampling_data", + .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img") + .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img") + .additional_info("eevee_aov_out", + "eevee_light_data", + "eevee_utility_texture", + "eevee_sampling_data" // "eevee_lightprobe_data", /* Optionally added depending on the material. */ // "eevee_raytrace_data", // "eevee_transmittance_data", - // "eevee_utility_texture", - // "eevee_light_data", // "eevee_shadow_data" ); @@ -135,11 +137,10 @@ GPU_SHADER_CREATE_INFO(eevee_surf_depth) GPU_SHADER_CREATE_INFO(eevee_surf_world) .vertex_out(eevee_surf_iface) .image_out(0, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_normal_img") - .image_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_light_img") + .image_array_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_light_img") .image_out(2, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_color_img") - .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_light_img") - .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img") - .image_out(5, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img") + .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img") + .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img") .push_constant(Type::FLOAT, "world_opacity_fade") .fragment_out(0, Type::VEC4, "out_background") .fragment_source("eevee_surf_world_frag.glsl") 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..d6ff34b0ed2 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "eevee_defines.hh" +#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/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 332c7f67c64..2f9d20b3902 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -19,6 +19,8 @@ extern "C" { #endif +#define GP_LIGHT + #include "gpencil_defines.h" #include "gpencil_shader_shared.h" diff --git a/source/blender/draw/engines/gpencil/gpencil_shader_shared.h b/source/blender/draw/engines/gpencil/gpencil_shader_shared.h index 50ff7e7efc7..4c621e955b9 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader_shared.h +++ b/source/blender/draw/engines/gpencil/gpencil_shader_shared.h @@ -7,7 +7,9 @@ typedef struct gpMaterial gpMaterial; typedef struct gpLight gpLight; typedef enum gpMaterialFlag gpMaterialFlag; +# ifdef GP_LIGHT typedef enum gpLightType gpLightType; +# endif # endif #endif @@ -75,8 +77,9 @@ struct gpMaterial { }; BLI_STATIC_ASSERT_ALIGN(gpMaterial, 16) +#ifdef GP_LIGHT struct gpLight { -#ifndef GPU_SHADER +# ifndef GPU_SHADER float3 color; gpLightType type; float3 right; @@ -87,7 +90,7 @@ struct gpLight { float _pad0; float3 position; float _pad1; -#else +# else /* Some drivers are completely messing the alignment or the fetches here. * We are forced to pack these into vec4 otherwise we only get 0.0 as value. */ /* NOTE(@fclem): This was the case on MacOS OpenGL implementation. @@ -97,17 +100,18 @@ struct gpLight { float4 packed2; float4 packed3; float4 packed4; -# define _color packed0.xyz -# define _type packed0.w -# define _right packed1.xyz -# define _spot_size packed1.w -# define _up packed2.xyz -# define _spot_blend packed2.w -# define _forward packed3.xyz -# define _position packed4.xyz -#endif +# define _color packed0.xyz +# define _type packed0.w +# define _right packed1.xyz +# define _spot_size packed1.w +# define _up packed2.xyz +# define _spot_blend packed2.w +# define _forward packed3.xyz +# define _position packed4.xyz +# endif }; BLI_STATIC_ASSERT_ALIGN(gpLight, 16) +#endif #ifndef GPU_SHADER # undef gpMaterialFlag diff --git a/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh b/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh index 3b4de704c00..1db98d13c4a 100644 --- a/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh +++ b/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh @@ -20,8 +20,8 @@ GPU_SHADER_INTERFACE_INFO(gpencil_geometry_iface, "gp_interp") GPU_SHADER_CREATE_INFO(gpencil_geometry) .do_static_compilation(true) + .define("GP_LIGHT") .typedef_source("gpencil_defines.h") - .typedef_source("gpencil_shader_shared.h") .sampler(0, ImageType::FLOAT_2D, "gpFillTexture") .sampler(1, ImageType::FLOAT_2D, "gpStrokeTexture") .sampler(2, ImageType::DEPTH_2D, "gpSceneDepthTexture") diff --git a/source/blender/draw/engines/overlay/overlay_armature.c b/source/blender/draw/engines/overlay/overlay_armature.c index e38695c76ab..df5ee6a18c0 100644 --- a/source/blender/draw/engines/overlay/overlay_armature.c +++ b/source/blender/draw/engines/overlay/overlay_armature.c @@ -2220,7 +2220,7 @@ static void draw_armature_edit(ArmatureDrawContext *ctx) const bool show_text = DRW_state_show_text(); const Object *ob_orig = DEG_get_original_object(ob); - /* FIXME(campbell): We should be able to use the CoW object, + /* FIXME(@campbellbarton): We should be able to use the CoW object, * however the active bone isn't updated. Long term solution is an 'EditArmature' struct. * for now we can draw from the original armature. See: T66773. */ // bArmature *arm = ob->data; diff --git a/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl b/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl index f28a809fdab..606292bbe83 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_antialiasing_frag.glsl @@ -96,7 +96,7 @@ void main() float dist_raw = texelFetch(lineTex, center_texel, 0).b; float dist = decode_line_dist(dist_raw); - /* TODO: Opti: use textureGather. */ + /* TODO: Optimization: use textureGather. */ vec4 neightbor_col0 = texelFetchOffset(colorTex, center_texel, 0, ivec2(1, 0)); vec4 neightbor_col1 = texelFetchOffset(colorTex, center_texel, 0, ivec2(-1, 0)); vec4 neightbor_col2 = texelFetchOffset(colorTex, center_texel, 0, ivec2(0, 1)); diff --git a/source/blender/draw/engines/overlay/shaders/overlay_armature_envelope_outline_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_armature_envelope_outline_vert.glsl index 612ce8c6300..ca5a6aff2ca 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_armature_envelope_outline_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_armature_envelope_outline_vert.glsl @@ -130,7 +130,7 @@ void main() gl_Position = p1; /* compute position from 3 vertex because the change in direction - * can happen very quicky and lead to very thin edges. */ + * can happen very quickly and lead to very thin edges. */ vec2 ss0 = proj(p0); vec2 ss1 = proj(p1); vec2 ss2 = proj(p2); diff --git a/source/blender/draw/engines/select/select_engine.c b/source/blender/draw/engines/select/select_engine.c index 88ae5ac707e..026a1f52ac1 100644 --- a/source/blender/draw/engines/select/select_engine.c +++ b/source/blender/draw/engines/select/select_engine.c @@ -201,7 +201,7 @@ static void select_cache_populate(void *vedata, Object *ob) if (!e_data.context.is_dirty && sel_data && sel_data->is_drawn) { /* The object indices have already been drawn. Fill depth pass. - * Opti: Most of the time this depth pass is not used. */ + * Optimization: Most of the time this depth pass is not used. */ struct Mesh *me = ob->data; if (e_data.context.select_mode & SCE_SELECT_FACE) { struct GPUBatch *geom_faces = DRW_mesh_batch_cache_get_triangles_with_select_id(me); diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh index 8e61c25be71..b32fdedaeb9 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" @@ -182,7 +183,7 @@ class UniformCommon : public DataBuffer<T, len, false>, NonMovable, NonCopyable GPU_uniformbuf_free(ubo_); } - void push_update(void) + void push_update() { GPU_uniformbuf_update(ubo_, this->data_); } @@ -227,12 +228,17 @@ class StorageCommon : public DataBuffer<T, len, false>, NonMovable, NonCopyable GPU_storagebuf_free(ssbo_); } - void push_update(void) + void push_update() { BLI_assert(device_only == false); GPU_storagebuf_update(ssbo_, this->data_); } + void clear_to_zero() + { + GPU_storagebuf_clear_to_zero(ssbo_); + } + operator GPUStorageBuf *() const { return ssbo_; @@ -319,6 +325,7 @@ class StorageArrayBuffer : public detail::StorageCommon<T, len, device_only> { MEM_freeN(this->data_); } + /* Resize to \a new_size elements. */ void resize(int64_t new_size) { BLI_assert(new_size > 0); @@ -555,6 +562,11 @@ class Texture : NonCopyable { return mip_views_[miplvl]; } + int mip_count() const + { + return GPU_texture_mip_count(tx_); + } + /** * Ensure the availability of mipmap views. * Layer views covers all layers of array textures. @@ -595,42 +607,47 @@ class Texture : NonCopyable { /** * Returns true if the texture has been allocated or acquired from the pool. */ - bool is_valid(void) const + bool is_valid() const { return tx_ != nullptr; } - int width(void) const + int width() const { return GPU_texture_width(tx_); } - int height(void) const + int height() const { return GPU_texture_height(tx_); } - bool depth(void) const + int pixel_count() const + { + return GPU_texture_width(tx_) * GPU_texture_height(tx_); + } + + bool depth() const { return GPU_texture_depth(tx_); } - bool is_stencil(void) const + bool is_stencil() const { return GPU_texture_stencil(tx_); } - bool is_integer(void) const + bool is_integer() const { return GPU_texture_integer(tx_); } - bool is_cube(void) const + bool is_cube() const { return GPU_texture_cube(tx_); } - bool is_array(void) const + bool is_array() const { return GPU_texture_array(tx_); } @@ -722,7 +739,7 @@ class Texture : NonCopyable { int3 size = this->size(); if (size != int3(w, h, d) || GPU_texture_format(tx_) != format || GPU_texture_cube(tx_) != cubemap || GPU_texture_array(tx_) != layered) { - GPU_TEXTURE_FREE_SAFE(tx_); + free(); } } if (tx_ == nullptr) { @@ -772,50 +789,45 @@ 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) + void release() { /* Allows multiple release. */ - if (this->tx_ != nullptr) { - tx_tmp_saved_ = this->tx_; - this->tx_ = nullptr; + if (this->tx_ == nullptr) { + return; } + DRW_texture_pool_texture_release(DST.vmempool->texture_pool, this->tx_); + this->tx_ = nullptr; } /** - * Clears any reference. Workaround for pool texture not being able to release on demand. - * Needs to be called at during the sync phase. + * Swap the content of the two textures. + * Also change ownership accordingly if needed. */ - void sync(void) + static void swap(TextureFromPool &a, Texture &b) + { + Texture::swap(a, b); + DRW_texture_pool_give_texture_ownership(DST.vmempool->texture_pool, a); + DRW_texture_pool_take_texture_ownership(DST.vmempool->texture_pool, b); + } + static void swap(Texture &a, TextureFromPool &b) { - tx_tmp_saved_ = nullptr; + swap(b, a); + } + static void swap(TextureFromPool &a, TextureFromPool &b) + { + Texture::swap(a, b); } /** Remove methods that are forbidden with this type of textures. */ @@ -902,45 +914,47 @@ class Framebuffer : NonCopyable { template<typename T, int64_t len> class SwapChain { private: + BLI_STATIC_ASSERT(len > 1, "A swap-chain needs more than 1 unit in length."); std::array<T, len> chain_; - int64_t index_ = 0; public: void swap() { - index_ = (index_ + 1) % len; + for (auto i : IndexRange(len - 1)) { + T::swap(chain_[i], chain_[(i + 1) % len]); + } } T ¤t() { - return chain_[index_]; + return chain_[0]; } T &previous() { /* Avoid modulo operation with negative numbers. */ - return chain_[(index_ + len - 1) % len]; + return chain_[(0 + len - 1) % len]; } T &next() { - return chain_[(index_ + 1) % len]; + return chain_[(0 + 1) % len]; } const T ¤t() const { - return chain_[index_]; + return chain_[0]; } const T &previous() const { /* Avoid modulo operation with negative numbers. */ - return chain_[(index_ + len - 1) % len]; + return chain_[(0 + len - 1) % len]; } const T &next() const { - return chain_[(index_ + 1) % len]; + return chain_[(0 + 1) % len]; } }; diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index c2d26badc4c..a3097251d35 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -454,6 +454,10 @@ void DRW_shgroup_call_compute_indirect(DRWShadingGroup *shgroup, GPUStorageBuf * void DRW_shgroup_call_procedural_points(DRWShadingGroup *sh, Object *ob, uint point_count); void DRW_shgroup_call_procedural_lines(DRWShadingGroup *sh, Object *ob, uint line_count); void DRW_shgroup_call_procedural_triangles(DRWShadingGroup *sh, Object *ob, uint tri_count); +void DRW_shgroup_call_procedural_indirect(DRWShadingGroup *shgroup, + GPUPrimType primitive_type, + Object *ob, + GPUStorageBuf *indirect_buf); /** * \warning Only use with Shaders that have `IN_PLACE_INSTANCES` defined. * TODO: Should be removed. @@ -791,7 +795,7 @@ bool DRW_culling_box_test(const DRWView *view, const BoundBox *bbox); bool DRW_culling_plane_test(const DRWView *view, const float plane[4]); /** * Return True if the given box intersect the current view frustum. - * This function will have to be replaced when world space bb per objects is implemented. + * This function will have to be replaced when world space bounding-box per objects is implemented. */ bool DRW_culling_min_max_test(const DRWView *view, float obmat[4][4], float min[3], float max[3]); diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index f846251c66b..4c0f025e934 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -90,6 +90,7 @@ static struct DRWShapeCache { GPUBatch *drw_procedural_verts; GPUBatch *drw_procedural_lines; GPUBatch *drw_procedural_tris; + GPUBatch *drw_procedural_tri_strips; GPUBatch *drw_cursor; GPUBatch *drw_cursor_only_circle; GPUBatch *drw_fullscreen_quad; @@ -208,6 +209,21 @@ GPUBatch *drw_cache_procedural_triangles_get(void) return SHC.drw_procedural_tris; } +GPUBatch *drw_cache_procedural_triangle_strips_get() +{ + if (!SHC.drw_procedural_tri_strips) { + /* TODO(fclem): get rid of this dummy VBO. */ + GPUVertFormat format = {0}; + GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(vbo, 1); + + SHC.drw_procedural_tri_strips = GPU_batch_create_ex( + GPU_PRIM_TRI_STRIP, vbo, NULL, GPU_BATCH_OWNS_VBO); + } + return SHC.drw_procedural_tri_strips; +} + /** \} */ /* -------------------------------------------------------------------- */ 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 af8e58c78f8..0159c9fc86e 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 @@ -228,9 +228,9 @@ static void mesh_render_data_polys_sorted_build(MeshRenderData *mr, MeshBufferCa } } else { - const MPoly *mp = &mr->mpoly[0]; - for (int i = 0; i < mr->poly_len; i++, mp++) { - if (!(mr->use_hide && (mp->flag & ME_HIDE))) { + for (int i = 0; i < mr->poly_len; i++) { + if (!(mr->use_hide && mr->hide_poly && mr->hide_poly[i])) { + const MPoly *mp = &mr->mpoly[i]; const int mat = min_ii(mp->mat_nr, mat_last); tri_first_index[i] = mat_tri_offs[mat]; mat_tri_offs[mat] += mp->totloop - 2; @@ -269,7 +269,7 @@ static void mesh_render_data_mat_tri_len_mesh_range_fn(void *__restrict userdata int *mat_tri_len = static_cast<int *>(tls->userdata_chunk); const MPoly *mp = &mr->mpoly[iter]; - if (!(mr->use_hide && (mp->flag & ME_HIDE))) { + if (!(mr->use_hide && mr->hide_poly && mr->hide_poly[iter])) { int mat = min_ii(mp->mat_nr, mr->mat_len - 1); mat_tri_len[mat] += mp->totloop - 2; } @@ -332,7 +332,7 @@ void mesh_render_data_update_looptris(MeshRenderData *mr, if (mr->extract_type != MR_EXTRACT_BMESH) { /* Mesh */ if ((iter_type & MR_ITER_LOOPTRI) || (data_flag & MR_DATA_LOOPTRI)) { - /* NOTE(campbell): It's possible to skip allocating tessellation, + /* NOTE(@campbellbarton): It's possible to skip allocating tessellation, * the tessellation can be calculated as part of the iterator, see: P2188. * The overall advantage is small (around 1%), so keep this as-is. */ mr->mlooptri = static_cast<MLoopTri *>( @@ -578,6 +578,13 @@ MeshRenderData *mesh_render_data_create(Object *object, mr->v_origindex = static_cast<const int *>(CustomData_get_layer(&mr->me->vdata, CD_ORIGINDEX)); mr->e_origindex = static_cast<const int *>(CustomData_get_layer(&mr->me->edata, CD_ORIGINDEX)); mr->p_origindex = static_cast<const int *>(CustomData_get_layer(&mr->me->pdata, CD_ORIGINDEX)); + + mr->hide_vert = static_cast<const bool *>( + CustomData_get_layer_named(&me->vdata, CD_PROP_BOOL, ".hide_vert")); + mr->hide_edge = static_cast<const bool *>( + CustomData_get_layer_named(&me->edata, CD_PROP_BOOL, ".hide_edge")); + mr->hide_poly = static_cast<const bool *>( + CustomData_get_layer_named(&me->pdata, CD_PROP_BOOL, ".hide_poly")); } else { /* #BMesh */ diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.cc b/source/blender/draw/intern/draw_cache_impl_mesh.cc index 5b7b3fd9a4a..3e2fd225e8e 100644 --- a/source/blender/draw/intern/draw_cache_impl_mesh.cc +++ b/source/blender/draw/intern/draw_cache_impl_mesh.cc @@ -293,26 +293,28 @@ static DRW_MeshCDMask mesh_cd_calc_used_gpu_layers(const Object *object, for (int i = 0; i < gpumat_array_len; i++) { GPUMaterial *gpumat = gpumat_array[i]; - if (gpumat) { - ListBase gpu_attrs = GPU_material_attributes(gpumat); - LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) { - const char *name = gpu_attr->name; - eCustomDataType type = static_cast<eCustomDataType>(gpu_attr->type); - int layer = -1; - std::optional<eAttrDomain> domain; - - if (gpu_attr->is_default_color) { - name = default_color_name.c_str(); - } + if (gpumat == nullptr) { + continue; + } + ListBase gpu_attrs = GPU_material_attributes(gpumat); + LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) { + const char *name = gpu_attr->name; + eCustomDataType type = static_cast<eCustomDataType>(gpu_attr->type); + int layer = -1; + std::optional<eAttrDomain> domain; + + if (gpu_attr->is_default_color) { + name = default_color_name.c_str(); + } - if (type == CD_AUTO_FROM_NAME) { - /* We need to deduce what exact layer is used. - * - * We do it based on the specified name. - */ - if (name[0] != '\0') { - layer = CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name); - type = CD_MTFACE; + if (type == CD_AUTO_FROM_NAME) { + /* We need to deduce what exact layer is used. + * + * We do it based on the specified name. + */ + if (name[0] != '\0') { + layer = CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name); + type = CD_MTFACE; #if 0 /* Tangents are always from UV's - this will never happen. */ if (layer == -1) { @@ -320,88 +322,87 @@ static DRW_MeshCDMask mesh_cd_calc_used_gpu_layers(const Object *object, type = CD_TANGENT; } #endif - if (layer == -1) { - /* Try to match a generic attribute, we use the first attribute domain with a - * matching name. */ - if (drw_custom_data_match_attribute(cd_vdata, name, &layer, &type)) { - domain = ATTR_DOMAIN_POINT; - } - else if (drw_custom_data_match_attribute(cd_ldata, name, &layer, &type)) { - domain = ATTR_DOMAIN_CORNER; - } - else if (drw_custom_data_match_attribute(cd_pdata, name, &layer, &type)) { - domain = ATTR_DOMAIN_FACE; - } - else if (drw_custom_data_match_attribute(cd_edata, name, &layer, &type)) { - domain = ATTR_DOMAIN_EDGE; - } - else { - layer = -1; - } + if (layer == -1) { + /* Try to match a generic attribute, we use the first attribute domain with a + * matching name. */ + if (drw_custom_data_match_attribute(cd_vdata, name, &layer, &type)) { + domain = ATTR_DOMAIN_POINT; } - - if (layer == -1) { - continue; + else if (drw_custom_data_match_attribute(cd_ldata, name, &layer, &type)) { + domain = ATTR_DOMAIN_CORNER; + } + else if (drw_custom_data_match_attribute(cd_pdata, name, &layer, &type)) { + domain = ATTR_DOMAIN_FACE; + } + else if (drw_custom_data_match_attribute(cd_edata, name, &layer, &type)) { + domain = ATTR_DOMAIN_EDGE; + } + else { + layer = -1; } } - else { - /* Fall back to the UV layer, which matches old behavior. */ - type = CD_MTFACE; + + if (layer == -1) { + continue; } } + else { + /* Fall back to the UV layer, which matches old behavior. */ + type = CD_MTFACE; + } + } - switch (type) { - case CD_MTFACE: { - if (layer == -1) { - layer = (name[0] != '\0') ? CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) : - CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); - } - if (layer != -1) { - cd_used.uv |= (1 << layer); - } - break; + switch (type) { + case CD_MTFACE: { + if (layer == -1) { + layer = (name[0] != '\0') ? CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) : + CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); } - case CD_TANGENT: { - if (layer == -1) { - layer = (name[0] != '\0') ? CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) : - CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); - - /* Only fallback to orco (below) when we have no UV layers, see: T56545 */ - if (layer == -1 && name[0] != '\0') { - layer = CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); - } - } - if (layer != -1) { - cd_used.tan |= (1 << layer); - } - else { - /* no UV layers at all => requesting orco */ - cd_used.tan_orco = 1; - cd_used.orco = 1; + if (layer != -1) { + cd_used.uv |= (1 << layer); + } + break; + } + case CD_TANGENT: { + if (layer == -1) { + layer = (name[0] != '\0') ? CustomData_get_named_layer(cd_ldata, CD_MLOOPUV, name) : + CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); + + /* Only fallback to orco (below) when we have no UV layers, see: T56545 */ + if (layer == -1 && name[0] != '\0') { + layer = CustomData_get_render_layer(cd_ldata, CD_MLOOPUV); } - break; } - - case CD_ORCO: { + if (layer != -1) { + cd_used.tan |= (1 << layer); + } + else { + /* no UV layers at all => requesting orco */ + cd_used.tan_orco = 1; cd_used.orco = 1; - break; } - case CD_PROP_BYTE_COLOR: - case CD_PROP_COLOR: - case CD_PROP_FLOAT3: - case CD_PROP_BOOL: - case CD_PROP_INT8: - case CD_PROP_INT32: - case CD_PROP_FLOAT: - case CD_PROP_FLOAT2: { - if (layer != -1 && domain.has_value()) { - drw_attributes_add_request(attributes, name, type, layer, *domain); - } - break; + break; + } + + case CD_ORCO: { + cd_used.orco = 1; + break; + } + case CD_PROP_BYTE_COLOR: + case CD_PROP_COLOR: + case CD_PROP_FLOAT3: + case CD_PROP_BOOL: + case CD_PROP_INT8: + case CD_PROP_INT32: + case CD_PROP_FLOAT: + case CD_PROP_FLOAT2: { + if (layer != -1 && domain.has_value()) { + drw_attributes_add_request(attributes, name, type, layer, *domain); } - default: - break; + break; } + default: + break; } } } diff --git a/source/blender/draw/intern/draw_cache_impl_pointcloud.c b/source/blender/draw/intern/draw_cache_impl_pointcloud.cc index 55d0eee00e5..d99af0c77e4 100644 --- a/source/blender/draw/intern/draw_cache_impl_pointcloud.c +++ b/source/blender/draw/intern/draw_cache_impl_pointcloud.cc @@ -13,24 +13,23 @@ #include "BLI_math_base.h" #include "BLI_math_vector.h" +#include "BLI_task.hh" #include "BLI_utildefines.h" #include "DNA_object_types.h" #include "DNA_pointcloud_types.h" -#include "BKE_customdata.h" +#include "BKE_attribute.hh" #include "BKE_pointcloud.h" #include "GPU_batch.h" #include "draw_cache_impl.h" /* own include */ -static void pointcloud_batch_cache_clear(PointCloud *pointcloud); - /* ---------------------------------------------------------------------- */ /* PointCloud GPUBatch Cache */ -typedef struct PointCloudBatchCache { +struct PointCloudBatchCache { GPUVertBuf *pos; /* Position and radius. */ GPUVertBuf *geom; /* Instanced geometry for each point in the cloud (small sphere). */ GPUIndexBuf *geom_indices; @@ -43,57 +42,50 @@ typedef struct PointCloudBatchCache { bool is_dirty; int mat_len; -} PointCloudBatchCache; +}; /* GPUBatch cache management. */ -static bool pointcloud_batch_cache_valid(PointCloud *pointcloud) +static PointCloudBatchCache *pointcloud_batch_cache_get(PointCloud &pointcloud) +{ + return static_cast<PointCloudBatchCache *>(pointcloud.batch_cache); +} + +static bool pointcloud_batch_cache_valid(PointCloud &pointcloud) { - PointCloudBatchCache *cache = pointcloud->batch_cache; + PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); if (cache == NULL) { return false; } - if (cache->mat_len != DRW_pointcloud_material_count_get(pointcloud)) { + if (cache->mat_len != DRW_pointcloud_material_count_get(&pointcloud)) { return false; } return cache->is_dirty == false; } -static void pointcloud_batch_cache_init(PointCloud *pointcloud) +static void pointcloud_batch_cache_init(PointCloud &pointcloud) { - PointCloudBatchCache *cache = pointcloud->batch_cache; + PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); if (!cache) { - cache = pointcloud->batch_cache = MEM_callocN(sizeof(*cache), __func__); + cache = MEM_cnew<PointCloudBatchCache>(__func__); + pointcloud.batch_cache = cache; } else { memset(cache, 0, sizeof(*cache)); } - cache->mat_len = DRW_pointcloud_material_count_get(pointcloud); - cache->surface_per_mat = MEM_callocN(sizeof(GPUBatch *) * cache->mat_len, - "pointcloud suface_per_mat"); + cache->mat_len = DRW_pointcloud_material_count_get(&pointcloud); + cache->surface_per_mat = static_cast<GPUBatch **>( + MEM_callocN(sizeof(GPUBatch *) * cache->mat_len, __func__)); cache->is_dirty = false; } -void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud) -{ - if (!pointcloud_batch_cache_valid(pointcloud)) { - pointcloud_batch_cache_clear(pointcloud); - pointcloud_batch_cache_init(pointcloud); - } -} - -static PointCloudBatchCache *pointcloud_batch_cache_get(PointCloud *pointcloud) -{ - return pointcloud->batch_cache; -} - void DRW_pointcloud_batch_cache_dirty_tag(PointCloud *pointcloud, int mode) { - PointCloudBatchCache *cache = pointcloud->batch_cache; + PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud); if (cache == NULL) { return; } @@ -106,9 +98,9 @@ void DRW_pointcloud_batch_cache_dirty_tag(PointCloud *pointcloud, int mode) } } -static void pointcloud_batch_cache_clear(PointCloud *pointcloud) +static void pointcloud_batch_cache_clear(PointCloud &pointcloud) { - PointCloudBatchCache *cache = pointcloud->batch_cache; + PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); if (!cache) { return; } @@ -127,54 +119,65 @@ static void pointcloud_batch_cache_clear(PointCloud *pointcloud) MEM_SAFE_FREE(cache->surface_per_mat); } +void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud) +{ + if (!pointcloud_batch_cache_valid(*pointcloud)) { + pointcloud_batch_cache_clear(*pointcloud); + pointcloud_batch_cache_init(*pointcloud); + } +} + void DRW_pointcloud_batch_cache_free(PointCloud *pointcloud) { - pointcloud_batch_cache_clear(pointcloud); + pointcloud_batch_cache_clear(*pointcloud); MEM_SAFE_FREE(pointcloud->batch_cache); } -static void pointcloud_batch_cache_ensure_pos(Object *ob, PointCloudBatchCache *cache) +static void pointcloud_batch_cache_ensure_pos(const PointCloud &pointcloud, + PointCloudBatchCache &cache) { - if (cache->pos != NULL) { + using namespace blender; + if (cache.pos != NULL) { return; } - PointCloud *pointcloud = ob->data; - const float(*positions)[3] = (float(*)[3])CustomData_get_layer_named( - &pointcloud->pdata, CD_PROP_FLOAT3, "position"); - const float *radii = (float *)CustomData_get_layer_named( - &pointcloud->pdata, CD_PROP_FLOAT, "radius"); - const bool has_radius = radii != NULL; - - static GPUVertFormat format = {0}; - static GPUVertFormat format_no_radius = {0}; - static uint pos; - if (format.attr_len == 0) { - /* initialize vertex format */ - /* From the opengl wiki: - * Note that size does not have to exactly match the size used by the vertex shader. If the - * vertex shader has fewer components than the attribute provides, then the extras are ignored. - * If the vertex shader has more components than the array provides, the extras are given - * values from the vector (0, 0, 0, 1) for the missing XYZW components. - */ - pos = GPU_vertformat_attr_add(&format_no_radius, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - } - - cache->pos = GPU_vertbuf_create_with_format(has_radius ? &format : &format_no_radius); - GPU_vertbuf_data_alloc(cache->pos, pointcloud->totpoint); - - if (has_radius) { - float(*vbo_data)[4] = (float(*)[4])GPU_vertbuf_get_data(cache->pos); - for (int i = 0; i < pointcloud->totpoint; i++) { - copy_v3_v3(vbo_data[i], positions[i]); - /* TODO(fclem): remove multiplication here. - * Here only for keeping the size correct for now. */ - vbo_data[i][3] = radii[i] * 100.0f; + const bke::AttributeAccessor attributes = bke::pointcloud_attributes(pointcloud); + const VArraySpan<float3> positions = attributes.lookup<float3>("position", ATTR_DOMAIN_POINT); + const VArray<float> radii = attributes.lookup<float>("radius", ATTR_DOMAIN_POINT); + /* From the opengl wiki: + * Note that size does not have to exactly match the size used by the vertex shader. If the + * vertex shader has fewer components than the attribute provides, then the extras are ignored. + * If the vertex shader has more components than the array provides, the extras are given + * values from the vector (0, 0, 0, 1) for the missing XYZW components. */ + if (radii) { + static GPUVertFormat format = {0}; + if (format.attr_len == 0) { + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); } + cache.pos = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(cache.pos, positions.size()); + const VArraySpan<float> radii_span(radii); + MutableSpan<float4> vbo_data{static_cast<float4 *>(GPU_vertbuf_get_data(cache.pos)), + pointcloud.totpoint}; + threading::parallel_for(vbo_data.index_range(), 4096, [&](IndexRange range) { + for (const int i : range) { + vbo_data[i].x = positions[i].x; + vbo_data[i].y = positions[i].y; + vbo_data[i].z = positions[i].z; + /* TODO(fclem): remove multiplication. Here only for keeping the size correct for now. */ + vbo_data[i].w = radii_span[i] * 100.0f; + } + }); } else { - GPU_vertbuf_attr_fill(cache->pos, pos, positions); + static GPUVertFormat format = {0}; + static uint pos; + if (format.attr_len == 0) { + pos = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + } + cache.pos = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(cache.pos, positions.size()); + GPU_vertbuf_attr_fill(cache.pos, pos, positions.data()); } } @@ -193,24 +196,23 @@ static const uint half_octahedron_tris[4][3] = { {0, 4, 1}, }; -static void pointcloud_batch_cache_ensure_geom(Object *UNUSED(ob), PointCloudBatchCache *cache) +static void pointcloud_batch_cache_ensure_geom(PointCloudBatchCache &cache) { - if (cache->geom != NULL) { + if (cache.geom != NULL) { return; } static GPUVertFormat format = {0}; static uint pos; if (format.attr_len == 0) { - /* initialize vertex format */ pos = GPU_vertformat_attr_add(&format, "pos_inst", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); GPU_vertformat_alias_add(&format, "nor"); } - cache->geom = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(cache->geom, ARRAY_SIZE(half_octahedron_normals)); + cache.geom = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(cache.geom, ARRAY_SIZE(half_octahedron_normals)); - GPU_vertbuf_attr_fill(cache->geom, pos, half_octahedron_normals); + GPU_vertbuf_attr_fill(cache.geom, pos, half_octahedron_normals); GPUIndexBufBuilder builder; GPU_indexbuf_init(&builder, @@ -222,16 +224,16 @@ static void pointcloud_batch_cache_ensure_geom(Object *UNUSED(ob), PointCloudBat GPU_indexbuf_add_tri_verts(&builder, UNPACK3(half_octahedron_tris[i])); } - cache->geom_indices = GPU_indexbuf_build(&builder); + cache.geom_indices = GPU_indexbuf_build(&builder); } GPUBatch *DRW_pointcloud_batch_cache_get_dots(Object *ob) { - PointCloud *pointcloud = ob->data; + PointCloud &pointcloud = *static_cast<PointCloud *>(ob->data); PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); if (cache->dots == NULL) { - pointcloud_batch_cache_ensure_pos(ob, cache); + pointcloud_batch_cache_ensure_pos(pointcloud, *cache); cache->dots = GPU_batch_create(GPU_PRIM_POINTS, cache->pos, NULL); } @@ -240,12 +242,12 @@ GPUBatch *DRW_pointcloud_batch_cache_get_dots(Object *ob) GPUBatch *DRW_pointcloud_batch_cache_get_surface(Object *ob) { - PointCloud *pointcloud = ob->data; + PointCloud &pointcloud = *static_cast<PointCloud *>(ob->data); PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); if (cache->surface == NULL) { - pointcloud_batch_cache_ensure_pos(ob, cache); - pointcloud_batch_cache_ensure_geom(ob, cache); + pointcloud_batch_cache_ensure_pos(pointcloud, *cache); + pointcloud_batch_cache_ensure_geom(*cache); cache->surface = GPU_batch_create(GPU_PRIM_TRIS, cache->geom, cache->geom_indices); GPU_batch_instbuf_add_ex(cache->surface, cache->pos, false); @@ -258,14 +260,14 @@ GPUBatch **DRW_cache_pointcloud_surface_shaded_get(Object *ob, struct GPUMaterial **UNUSED(gpumat_array), uint gpumat_array_len) { - PointCloud *pointcloud = ob->data; + PointCloud &pointcloud = *static_cast<PointCloud *>(ob->data); PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud); BLI_assert(cache->mat_len == gpumat_array_len); UNUSED_VARS(gpumat_array_len); if (cache->surface_per_mat[0] == NULL) { - pointcloud_batch_cache_ensure_pos(ob, cache); - pointcloud_batch_cache_ensure_geom(ob, cache); + pointcloud_batch_cache_ensure_pos(pointcloud, *cache); + pointcloud_batch_cache_ensure_geom(*cache); cache->surface_per_mat[0] = GPU_batch_create(GPU_PRIM_TRIS, cache->geom, cache->geom_indices); GPU_batch_instbuf_add_ex(cache->surface_per_mat[0], cache->pos, false); diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc index 2f8a2540776..075e08eb49f 100644 --- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc +++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc @@ -668,7 +668,9 @@ static void draw_subdiv_cache_extra_coarse_face_data_bm(BMesh *bm, } } -static void draw_subdiv_cache_extra_coarse_face_data_mesh(Mesh *mesh, uint32_t *flags_data) +static void draw_subdiv_cache_extra_coarse_face_data_mesh(const MeshRenderData *mr, + Mesh *mesh, + uint32_t *flags_data) { for (int i = 0; i < mesh->totpoly; i++) { uint32_t flag = 0; @@ -678,7 +680,7 @@ static void draw_subdiv_cache_extra_coarse_face_data_mesh(Mesh *mesh, uint32_t * if ((mesh->mpoly[i].flag & ME_FACE_SEL) != 0) { flag |= SUBDIV_COARSE_FACE_FLAG_SELECT; } - if ((mesh->mpoly[i].flag & ME_HIDE) != 0) { + if (mr->hide_poly && mr->hide_poly[i]) { flag |= SUBDIV_COARSE_FACE_FLAG_HIDDEN; } flags_data[i] = (uint)(mesh->mpoly[i].loopstart) | (flag << SUBDIV_COARSE_FACE_FLAG_OFFSET); @@ -691,7 +693,7 @@ static void draw_subdiv_cache_extra_coarse_face_data_mapped(Mesh *mesh, uint32_t *flags_data) { if (bm == nullptr) { - draw_subdiv_cache_extra_coarse_face_data_mesh(mesh, flags_data); + draw_subdiv_cache_extra_coarse_face_data_mesh(mr, mesh, flags_data); return; } @@ -726,7 +728,7 @@ static void draw_subdiv_cache_update_extra_coarse_face_data(DRWSubdivCache *cach draw_subdiv_cache_extra_coarse_face_data_mapped(mesh, cache->bm, mr, flags_data); } else { - draw_subdiv_cache_extra_coarse_face_data_mesh(mesh, flags_data); + draw_subdiv_cache_extra_coarse_face_data_mesh(mr, mesh, flags_data); } /* Make sure updated data is re-uploaded. */ diff --git a/source/blender/draw/intern/draw_debug.c b/source/blender/draw/intern/draw_debug.c deleted file mode 100644 index b568119627e..00000000000 --- a/source/blender/draw/intern/draw_debug.c +++ /dev/null @@ -1,196 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2018 Blender Foundation. */ - -/** \file - * \ingroup draw - * - * \brief Simple API to draw debug shapes in the viewport. - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_object_types.h" - -#include "BKE_object.h" - -#include "BLI_link_utils.h" - -#include "GPU_immediate.h" -#include "GPU_matrix.h" - -#include "draw_debug.h" -#include "draw_manager.h" - -/* --------- Register --------- */ - -/* Matrix applied to all points before drawing. Could be a stack if needed. */ -static float g_modelmat[4][4]; - -void DRW_debug_modelmat_reset(void) -{ - unit_m4(g_modelmat); -} - -void DRW_debug_modelmat(const float modelmat[4][4]) -{ - copy_m4_m4(g_modelmat, modelmat); -} - -void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]) -{ - DRWDebugLine *line = MEM_mallocN(sizeof(DRWDebugLine), "DRWDebugLine"); - mul_v3_m4v3(line->pos[0], g_modelmat, v1); - mul_v3_m4v3(line->pos[1], g_modelmat, v2); - copy_v4_v4(line->color, color); - BLI_LINKS_PREPEND(DST.debug.lines, line); -} - -void DRW_debug_polygon_v3(const float (*v)[3], const int vert_len, const float color[4]) -{ - BLI_assert(vert_len > 1); - - for (int i = 0; i < vert_len; i++) { - DRW_debug_line_v3v3(v[i], v[(i + 1) % vert_len], color); - } -} - -void DRW_debug_m4(const float m[4][4]) -{ - float v0[3] = {0.0f, 0.0f, 0.0f}; - float v1[3] = {1.0f, 0.0f, 0.0f}; - float v2[3] = {0.0f, 1.0f, 0.0f}; - float v3[3] = {0.0f, 0.0f, 1.0f}; - - mul_m4_v3(m, v0); - mul_m4_v3(m, v1); - mul_m4_v3(m, v2); - mul_m4_v3(m, v3); - - DRW_debug_line_v3v3(v0, v1, (float[4]){1.0f, 0.0f, 0.0f, 1.0f}); - DRW_debug_line_v3v3(v0, v2, (float[4]){0.0f, 1.0f, 0.0f, 1.0f}); - DRW_debug_line_v3v3(v0, v3, (float[4]){0.0f, 0.0f, 1.0f, 1.0f}); -} - -void DRW_debug_bbox(const BoundBox *bbox, const float color[4]) -{ - DRW_debug_line_v3v3(bbox->vec[0], bbox->vec[1], color); - DRW_debug_line_v3v3(bbox->vec[1], bbox->vec[2], color); - DRW_debug_line_v3v3(bbox->vec[2], bbox->vec[3], color); - DRW_debug_line_v3v3(bbox->vec[3], bbox->vec[0], color); - - DRW_debug_line_v3v3(bbox->vec[4], bbox->vec[5], color); - DRW_debug_line_v3v3(bbox->vec[5], bbox->vec[6], color); - DRW_debug_line_v3v3(bbox->vec[6], bbox->vec[7], color); - DRW_debug_line_v3v3(bbox->vec[7], bbox->vec[4], color); - - DRW_debug_line_v3v3(bbox->vec[0], bbox->vec[4], color); - DRW_debug_line_v3v3(bbox->vec[1], bbox->vec[5], color); - DRW_debug_line_v3v3(bbox->vec[2], bbox->vec[6], color); - DRW_debug_line_v3v3(bbox->vec[3], bbox->vec[7], color); -} - -void DRW_debug_m4_as_bbox(const float m[4][4], const float color[4], const bool invert) -{ - BoundBox bb; - const float min[3] = {-1.0f, -1.0f, -1.0f}, max[3] = {1.0f, 1.0f, 1.0f}; - float project_matrix[4][4]; - if (invert) { - invert_m4_m4(project_matrix, m); - } - else { - copy_m4_m4(project_matrix, m); - } - - BKE_boundbox_init_from_minmax(&bb, min, max); - for (int i = 0; i < 8; i++) { - mul_project_m4_v3(project_matrix, bb.vec[i]); - } - DRW_debug_bbox(&bb, color); -} - -void DRW_debug_sphere(const float center[3], const float radius, const float color[4]) -{ - float size_mat[4][4]; - DRWDebugSphere *sphere = MEM_mallocN(sizeof(DRWDebugSphere), "DRWDebugSphere"); - /* Bake all transform into a Matrix4 */ - scale_m4_fl(size_mat, radius); - copy_m4_m4(sphere->mat, g_modelmat); - translate_m4(sphere->mat, center[0], center[1], center[2]); - mul_m4_m4m4(sphere->mat, sphere->mat, size_mat); - - copy_v4_v4(sphere->color, color); - BLI_LINKS_PREPEND(DST.debug.spheres, sphere); -} - -/* --------- Render --------- */ - -static void drw_debug_draw_lines(void) -{ - int count = BLI_linklist_count((LinkNode *)DST.debug.lines); - - if (count == 0) { - return; - } - - GPUVertFormat *vert_format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(vert_format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - uint col = GPU_vertformat_attr_add(vert_format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); - - immBegin(GPU_PRIM_LINES, count * 2); - - while (DST.debug.lines) { - void *next = DST.debug.lines->next; - - immAttr4fv(col, DST.debug.lines->color); - immVertex3fv(pos, DST.debug.lines->pos[0]); - - immAttr4fv(col, DST.debug.lines->color); - immVertex3fv(pos, DST.debug.lines->pos[1]); - - MEM_freeN(DST.debug.lines); - DST.debug.lines = next; - } - immEnd(); - - immUnbindProgram(); -} - -static void drw_debug_draw_spheres(void) -{ - int count = BLI_linklist_count((LinkNode *)DST.debug.spheres); - - if (count == 0) { - return; - } - - float persmat[4][4]; - DRW_view_persmat_get(NULL, persmat, false); - - GPUBatch *empty_sphere = DRW_cache_empty_sphere_get(); - GPU_batch_program_set_builtin(empty_sphere, GPU_SHADER_3D_UNIFORM_COLOR); - while (DST.debug.spheres) { - void *next = DST.debug.spheres->next; - float MVP[4][4]; - - mul_m4_m4m4(MVP, persmat, DST.debug.spheres->mat); - GPU_batch_uniform_mat4(empty_sphere, "ModelViewProjectionMatrix", MVP); - GPU_batch_uniform_4fv(empty_sphere, "color", DST.debug.spheres->color); - GPU_batch_draw(empty_sphere); - - MEM_freeN(DST.debug.spheres); - DST.debug.spheres = next; - } -} - -void drw_debug_draw(void) -{ - drw_debug_draw_lines(); - drw_debug_draw_spheres(); -} - -void drw_debug_init(void) -{ - DRW_debug_modelmat_reset(); -} diff --git a/source/blender/draw/intern/draw_debug.cc b/source/blender/draw/intern/draw_debug.cc new file mode 100644 index 00000000000..b9d10302c1e --- /dev/null +++ b/source/blender/draw/intern/draw_debug.cc @@ -0,0 +1,732 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2018 Blender Foundation. */ + +/** \file + * \ingroup draw + * + * \brief Simple API to draw debug shapes in the viewport. + */ + +#include "BKE_object.h" +#include "BLI_link_utils.h" +#include "GPU_batch.h" +#include "GPU_capabilities.h" +#include "GPU_debug.h" + +#include "draw_debug.h" +#include "draw_debug.hh" +#include "draw_manager.h" +#include "draw_shader.h" +#include "draw_shader_shared.h" + +#include <iomanip> + +#ifdef DEBUG +# define DRAW_DEBUG +#else +/* Uncomment to forcibly enable debug draw in release mode. */ +//#define DRAW_DEBUG +#endif + +namespace blender::draw { + +/* -------------------------------------------------------------------- */ +/** \name Init and state + * \{ */ + +DebugDraw::DebugDraw() +{ + constexpr int circle_resolution = 16; + for (auto axis : IndexRange(3)) { + for (auto edge : IndexRange(circle_resolution)) { + for (auto vert : IndexRange(2)) { + const float angle = (2 * M_PI) * (edge + vert) / float(circle_resolution); + float point[3] = {cosf(angle), sinf(angle), 0.0f}; + sphere_verts_.append( + float3(point[(0 + axis) % 3], point[(1 + axis) % 3], point[(2 + axis) % 3])); + } + } + } + + constexpr int point_resolution = 4; + for (auto axis : IndexRange(3)) { + for (auto edge : IndexRange(point_resolution)) { + for (auto vert : IndexRange(2)) { + const float angle = (2 * M_PI) * (edge + vert) / float(point_resolution); + float point[3] = {cosf(angle), sinf(angle), 0.0f}; + point_verts_.append( + float3(point[(0 + axis) % 3], point[(1 + axis) % 3], point[(2 + axis) % 3])); + } + } + } +}; + +void DebugDraw::init() +{ + cpu_print_buf_.command.v_count = 0; + cpu_print_buf_.command.v_first = 0; + cpu_print_buf_.command.i_count = 1; + cpu_print_buf_.command.i_first = 0; + + cpu_draw_buf_.command.v_count = 0; + cpu_draw_buf_.command.v_first = 0; + cpu_draw_buf_.command.i_count = 1; + cpu_draw_buf_.command.i_first = 0; + + gpu_print_buf_.command.v_count = 0; + gpu_print_buf_.command.v_first = 0; + gpu_print_buf_.command.i_count = 1; + gpu_print_buf_.command.i_first = 0; + gpu_print_buf_used = false; + + gpu_draw_buf_.command.v_count = 0; + gpu_draw_buf_.command.v_first = 0; + gpu_draw_buf_.command.i_count = 1; + gpu_draw_buf_.command.i_first = 0; + gpu_draw_buf_used = false; + + modelmat_reset(); +} + +void DebugDraw::modelmat_reset() +{ + model_mat_ = float4x4::identity(); +} + +void DebugDraw::modelmat_set(const float modelmat[4][4]) +{ + model_mat_ = modelmat; +} + +GPUStorageBuf *DebugDraw::gpu_draw_buf_get() +{ + BLI_assert(GPU_shader_storage_buffer_objects_support()); + if (!gpu_draw_buf_used) { + gpu_draw_buf_used = true; + gpu_draw_buf_.push_update(); + } + return gpu_draw_buf_; +} + +GPUStorageBuf *DebugDraw::gpu_print_buf_get() +{ + BLI_assert(GPU_shader_storage_buffer_objects_support()); + if (!gpu_print_buf_used) { + gpu_print_buf_used = true; + gpu_print_buf_.push_update(); + } + return gpu_print_buf_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Draw functions + * \{ */ + +void DebugDraw::draw_line(float3 v1, float3 v2, float4 color) +{ + draw_line(v1, v2, color_pack(color)); +} + +void DebugDraw::draw_polygon(Span<float3> poly_verts, float4 color) +{ + BLI_assert(!poly_verts.is_empty()); + + uint col = color_pack(color); + float3 v0 = model_mat_ * poly_verts.last(); + for (auto vert : poly_verts) { + float3 v1 = model_mat_ * vert; + draw_line(v0, v1, col); + v0 = v1; + } +} + +void DebugDraw::draw_matrix(const float4x4 m4) +{ + float3 v0 = float3(0.0f, 0.0f, 0.0f); + float3 v1 = float3(1.0f, 0.0f, 0.0f); + float3 v2 = float3(0.0f, 1.0f, 0.0f); + float3 v3 = float3(0.0f, 0.0f, 1.0f); + + mul_project_m4_v3(m4.ptr(), v0); + mul_project_m4_v3(m4.ptr(), v1); + mul_project_m4_v3(m4.ptr(), v2); + mul_project_m4_v3(m4.ptr(), v3); + + draw_line(v0, v1, float4(1.0f, 0.0f, 0.0f, 1.0f)); + draw_line(v0, v2, float4(0.0f, 1.0f, 0.0f, 1.0f)); + draw_line(v0, v3, float4(0.0f, 0.0f, 1.0f, 1.0f)); +} + +void DebugDraw::draw_bbox(const BoundBox &bbox, const float4 color) +{ + uint col = color_pack(color); + draw_line(bbox.vec[0], bbox.vec[1], col); + draw_line(bbox.vec[1], bbox.vec[2], col); + draw_line(bbox.vec[2], bbox.vec[3], col); + draw_line(bbox.vec[3], bbox.vec[0], col); + + draw_line(bbox.vec[4], bbox.vec[5], col); + draw_line(bbox.vec[5], bbox.vec[6], col); + draw_line(bbox.vec[6], bbox.vec[7], col); + draw_line(bbox.vec[7], bbox.vec[4], col); + + draw_line(bbox.vec[0], bbox.vec[4], col); + draw_line(bbox.vec[1], bbox.vec[5], col); + draw_line(bbox.vec[2], bbox.vec[6], col); + draw_line(bbox.vec[3], bbox.vec[7], col); +} + +void DebugDraw::draw_matrix_as_bbox(float4x4 mat, const float4 color) +{ + BoundBox bb; + const float min[3] = {-1.0f, -1.0f, -1.0f}, max[3] = {1.0f, 1.0f, 1.0f}; + BKE_boundbox_init_from_minmax(&bb, min, max); + for (auto i : IndexRange(8)) { + mul_project_m4_v3(mat.ptr(), bb.vec[i]); + } + draw_bbox(bb, color); +} + +void DebugDraw::draw_sphere(const float3 center, float radius, const float4 color) +{ + uint col = color_pack(color); + for (auto i : IndexRange(sphere_verts_.size() / 2)) { + float3 v0 = sphere_verts_[i * 2] * radius + center; + float3 v1 = sphere_verts_[i * 2 + 1] * radius + center; + draw_line(v0, v1, col); + } +} + +void DebugDraw::draw_point(const float3 center, float radius, const float4 color) +{ + uint col = color_pack(color); + for (auto i : IndexRange(point_verts_.size() / 2)) { + float3 v0 = point_verts_[i * 2] * radius + center; + float3 v1 = point_verts_[i * 2 + 1] * radius + center; + draw_line(v0, v1, col); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Print functions + * \{ */ + +template<> void DebugDraw::print_value<uint>(const uint &value) +{ + print_value_uint(value, false, false, true); +} +template<> void DebugDraw::print_value<int>(const int &value) +{ + print_value_uint(uint(abs(value)), false, (value < 0), false); +} +template<> void DebugDraw::print_value<bool>(const bool &value) +{ + print_string(value ? "true " : "false"); +} +template<> void DebugDraw::print_value<float>(const float &val) +{ + std::stringstream ss; + ss << std::setw(12) << std::to_string(val); + print_string(ss.str()); +} +template<> void DebugDraw::print_value<double>(const double &val) +{ + print_value(float(val)); +} + +template<> void DebugDraw::print_value_hex<uint>(const uint &value) +{ + print_value_uint(value, true, false, false); +} +template<> void DebugDraw::print_value_hex<int>(const int &value) +{ + print_value_uint(uint(value), true, false, false); +} +template<> void DebugDraw::print_value_hex<float>(const float &value) +{ + print_value_uint(*reinterpret_cast<const uint *>(&value), true, false, false); +} +template<> void DebugDraw::print_value_hex<double>(const double &val) +{ + print_value_hex(float(val)); +} + +template<> void DebugDraw::print_value_binary<uint>(const uint &value) +{ + print_value_binary(value); +} +template<> void DebugDraw::print_value_binary<int>(const int &value) +{ + print_value_binary(uint(value)); +} +template<> void DebugDraw::print_value_binary<float>(const float &value) +{ + print_value_binary(*reinterpret_cast<const uint *>(&value)); +} +template<> void DebugDraw::print_value_binary<double>(const double &val) +{ + print_value_binary(float(val)); +} + +template<> void DebugDraw::print_value<float2>(const float2 &value) +{ + print_no_endl("float2(", value[0], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<float3>(const float3 &value) +{ + print_no_endl("float3(", value[0], ", ", value[1], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<float4>(const float4 &value) +{ + print_no_endl("float4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +template<> void DebugDraw::print_value<int2>(const int2 &value) +{ + print_no_endl("int2(", value[0], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<int3>(const int3 &value) +{ + print_no_endl("int3(", value[0], ", ", value[1], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<int4>(const int4 &value) +{ + print_no_endl("int4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +template<> void DebugDraw::print_value<uint2>(const uint2 &value) +{ + print_no_endl("uint2(", value[0], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<uint3>(const uint3 &value) +{ + print_no_endl("uint3(", value[0], ", ", value[1], ", ", value[1], ")"); +} +template<> void DebugDraw::print_value<uint4>(const uint4 &value) +{ + print_no_endl("uint4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internals + * + * IMPORTANT: All of these are copied from the shader libs (common_debug_draw_lib.glsl & + * common_debug_print_lib.glsl). They need to be kept in sync to write the same data. + * \{ */ + +void DebugDraw::draw_line(float3 v1, float3 v2, uint color) +{ + DebugDrawBuf &buf = cpu_draw_buf_; + uint index = buf.command.v_count; + if (index + 2 < DRW_DEBUG_DRAW_VERT_MAX) { + buf.verts[index + 0] = vert_pack(model_mat_ * v1, color); + buf.verts[index + 1] = vert_pack(model_mat_ * v2, color); + buf.command.v_count += 2; + } +} + +/* Keep in sync with drw_debug_color_pack(). */ +uint DebugDraw::color_pack(float4 color) +{ + color = math::clamp(color, 0.0f, 1.0f); + uint result = 0; + result |= uint(color.x * 255.0f) << 0u; + result |= uint(color.y * 255.0f) << 8u; + result |= uint(color.z * 255.0f) << 16u; + result |= uint(color.w * 255.0f) << 24u; + return result; +} + +DRWDebugVert DebugDraw::vert_pack(float3 pos, uint color) +{ + DRWDebugVert vert; + vert.pos0 = *reinterpret_cast<uint32_t *>(&pos.x); + vert.pos1 = *reinterpret_cast<uint32_t *>(&pos.y); + vert.pos2 = *reinterpret_cast<uint32_t *>(&pos.z); + vert.color = color; + return vert; +} + +void DebugDraw::print_newline() +{ + print_col_ = 0u; + print_row_ = ++cpu_print_buf_.command.i_first; +} + +void DebugDraw::print_string_start(uint len) +{ + /* Break before word. */ + if (print_col_ + len > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { + print_newline(); + } +} + +/* Copied from gpu_shader_dependency. */ +void DebugDraw::print_string(std::string str) +{ + size_t len_before_pad = str.length(); + /* Pad string to uint size to avoid out of bound reads. */ + while (str.length() % 4 != 0) { + str += " "; + } + + print_string_start(len_before_pad); + for (size_t i = 0; i < len_before_pad; i += 4) { + union { + uint8_t chars[4]; + uint32_t word; + }; + + chars[0] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 0); + chars[1] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 1); + chars[2] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 2); + chars[3] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 3); + + if (i + 4 > len_before_pad) { + chars[len_before_pad - i] = '\0'; + } + print_char4(word); + } +} + +/* Keep in sync with shader. */ +void DebugDraw::print_char4(uint data) +{ + /* Convert into char stream. */ + for (; data != 0u; data >>= 8u) { + uint char1 = data & 0xFFu; + /* Check for null terminator. */ + if (char1 == 0x00) { + break; + } + /* NOTE: Do not skip the header manually like in GPU. */ + uint cursor = cpu_print_buf_.command.v_count++; + if (cursor < DRW_DEBUG_PRINT_MAX) { + /* For future usage. (i.e: Color) */ + uint flags = 0u; + uint col = print_col_++; + uint print_header = (flags << 24u) | (print_row_ << 16u) | (col << 8u); + cpu_print_buf_.char_array[cursor] = print_header | char1; + /* Break word. */ + if (print_col_ > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { + print_newline(); + } + } + } +} + +void DebugDraw::print_append_char(uint char1, uint &char4) +{ + char4 = (char4 << 8u) | char1; +} + +void DebugDraw::print_append_digit(uint digit, uint &char4) +{ + const uint char_A = 0x41u; + const uint char_0 = 0x30u; + bool is_hexadecimal = digit > 9u; + char4 = (char4 << 8u) | (is_hexadecimal ? (char_A + digit - 10u) : (char_0 + digit)); +} + +void DebugDraw::print_append_space(uint &char4) +{ + char4 = (char4 << 8u) | 0x20u; +} + +void DebugDraw::print_value_binary(uint value) +{ + print_string("0b"); + print_string_start(10u * 4u); + uint digits[10] = {0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; + uint digit = 0u; + for (uint i = 0u; i < 32u; i++) { + print_append_digit(((value >> i) & 1u), digits[digit / 4u]); + digit++; + if ((i % 4u) == 3u) { + print_append_space(digits[digit / 4u]); + digit++; + } + } + /* Numbers are written from right to left. So we need to reverse the order. */ + for (int j = 9; j >= 0; j--) { + print_char4(digits[j]); + } +} + +void DebugDraw::print_value_uint(uint value, + const bool hex, + bool is_negative, + const bool is_unsigned) +{ + print_string_start(3u * 4u); + const uint blank_value = hex ? 0x30303030u : 0x20202020u; + const uint prefix = hex ? 0x78302020u : 0x20202020u; + uint digits[3] = {blank_value, blank_value, prefix}; + const uint base = hex ? 16u : 10u; + uint digit = 0u; + /* Add `u` suffix. */ + if (is_unsigned) { + print_append_char('u', digits[digit / 4u]); + digit++; + } + /* Number's digits. */ + for (; value != 0u || digit == uint(is_unsigned); value /= base) { + print_append_digit(value % base, digits[digit / 4u]); + digit++; + } + /* Add negative sign. */ + if (is_negative) { + print_append_char('-', digits[digit / 4u]); + digit++; + } + /* Need to pad to uint alignment because we are issuing chars in "reverse". */ + for (uint i = digit % 4u; i < 4u && i > 0u; i++) { + print_append_space(digits[digit / 4u]); + digit++; + } + /* Numbers are written from right to left. So we need to reverse the order. */ + for (int j = 2; j >= 0; j--) { + print_char4(digits[j]); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Display + * \{ */ + +void DebugDraw::display_lines() +{ + if (cpu_draw_buf_.command.v_count == 0 && gpu_draw_buf_used == false) { + return; + } + GPU_debug_group_begin("Lines"); + cpu_draw_buf_.push_update(); + + float4x4 persmat; + const DRWView *view = DRW_view_get_active(); + DRW_view_persmat_get(view, persmat.ptr(), false); + + drw_state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS); + + GPUBatch *batch = drw_cache_procedural_lines_get(); + GPUShader *shader = DRW_shader_debug_draw_display_get(); + GPU_batch_set_shader(batch, shader); + int slot = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_VERTS); + GPU_shader_uniform_mat4(shader, "persmat", persmat.ptr()); + + if (gpu_draw_buf_used) { + GPU_debug_group_begin("GPU"); + GPU_storagebuf_bind(gpu_draw_buf_, slot); + GPU_batch_draw_indirect(batch, gpu_draw_buf_); + GPU_storagebuf_unbind(gpu_draw_buf_); + GPU_debug_group_end(); + } + + GPU_debug_group_begin("CPU"); + GPU_storagebuf_bind(cpu_draw_buf_, slot); + GPU_batch_draw_indirect(batch, cpu_draw_buf_); + GPU_storagebuf_unbind(cpu_draw_buf_); + GPU_debug_group_end(); + + GPU_debug_group_end(); +} + +void DebugDraw::display_prints() +{ + if (cpu_print_buf_.command.v_count == 0 && gpu_print_buf_used == false) { + return; + } + GPU_debug_group_begin("Prints"); + cpu_print_buf_.push_update(); + + drw_state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_PROGRAM_POINT_SIZE); + + GPUBatch *batch = drw_cache_procedural_points_get(); + GPUShader *shader = DRW_shader_debug_print_display_get(); + GPU_batch_set_shader(batch, shader); + int slot = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_PRINT); + + if (gpu_print_buf_used) { + GPU_debug_group_begin("GPU"); + GPU_storagebuf_bind(gpu_print_buf_, slot); + GPU_batch_draw_indirect(batch, gpu_print_buf_); + GPU_storagebuf_unbind(gpu_print_buf_); + GPU_debug_group_end(); + } + + GPU_debug_group_begin("CPU"); + GPU_storagebuf_bind(cpu_print_buf_, slot); + GPU_batch_draw_indirect(batch, cpu_print_buf_); + GPU_storagebuf_unbind(cpu_print_buf_); + GPU_debug_group_end(); + + GPU_debug_group_end(); +} + +void DebugDraw::display_to_view() +{ + GPU_debug_group_begin("DebugDraw"); + + display_lines(); + /* Print 3D shapes before text to avoid overlaps. */ + display_prints(); + /* Init again so we don't draw the same thing twice. */ + init(); + + GPU_debug_group_end(); +} + +} // namespace blender::draw + +blender::draw::DebugDraw *DRW_debug_get() +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return nullptr; + } + return reinterpret_cast<blender::draw::DebugDraw *>(DST.debug); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name C-API private + * \{ */ + +void drw_debug_draw() +{ +#ifdef DRAW_DEBUG + if (!GPU_shader_storage_buffer_objects_support() || DST.debug == nullptr) { + return; + } + /* TODO(@fclem): Convenience for now. Will have to move to #DRWManager. */ + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->display_to_view(); +#endif +} + +/** + * NOTE: Init is once per draw manager cycle. + */ +void drw_debug_init() +{ + /* Module should not be used in release builds. */ + /* TODO(@fclem): Hide the functions declarations without using `ifdefs` everywhere. */ +#ifdef DRAW_DEBUG + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + /* TODO(@fclem): Convenience for now. Will have to move to #DRWManager. */ + if (DST.debug == nullptr) { + DST.debug = reinterpret_cast<DRWDebugModule *>(new blender::draw::DebugDraw()); + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->init(); +#endif +} + +void drw_debug_module_free(DRWDebugModule *module) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + if (module != nullptr) { + delete reinterpret_cast<blender::draw::DebugDraw *>(module); + } +} + +GPUStorageBuf *drw_debug_gpu_draw_buf_get() +{ + return reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->gpu_draw_buf_get(); +} + +GPUStorageBuf *drw_debug_gpu_print_buf_get() +{ + return reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->gpu_print_buf_get(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name C-API public + * \{ */ + +void DRW_debug_modelmat_reset() +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->modelmat_reset(); +} + +void DRW_debug_modelmat(const float modelmat[4][4]) +{ +#ifdef DRAW_DEBUG + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->modelmat_set(modelmat); +#endif +} + +void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_line(v1, v2, color); +} + +void DRW_debug_polygon_v3(const float (*v)[3], int vert_len, const float color[4]) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_polygon( + blender::Span<float3>((float3 *)v, vert_len), color); +} + +void DRW_debug_m4(const float m[4][4]) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_matrix(m); +} + +void DRW_debug_m4_as_bbox(const float m[4][4], bool invert, const float color[4]) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + blender::float4x4 m4 = m; + if (invert) { + m4 = m4.inverted(); + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_matrix_as_bbox(m4, color); +} + +void DRW_debug_bbox(const BoundBox *bbox, const float color[4]) +{ +#ifdef DRAW_DEBUG + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_bbox(*bbox, color); +#endif +} + +void DRW_debug_sphere(const float center[3], float radius, const float color[4]) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->draw_sphere(center, radius, color); +} + +/** \} */ diff --git a/source/blender/draw/intern/draw_debug.h b/source/blender/draw/intern/draw_debug.h index 333d734edb9..9a56a12242e 100644 --- a/source/blender/draw/intern/draw_debug.h +++ b/source/blender/draw/intern/draw_debug.h @@ -3,21 +3,38 @@ /** \file * \ingroup draw + * + * \brief Simple API to draw debug shapes in the viewport. + * IMPORTANT: This is the legacy API for C. Use draw_debug.hh instead in new C++ code. */ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DRWDebugModule DRWDebugModule; + struct BoundBox; void DRW_debug_modelmat_reset(void); void DRW_debug_modelmat(const float modelmat[4][4]); +/** + * IMPORTANT: For now there is a limit of DRW_DEBUG_DRAW_VERT_MAX that can be drawn + * using all the draw functions. + */ void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]); void DRW_debug_polygon_v3(const float (*v)[3], int vert_len, const float color[4]); /** * \note g_modelmat is still applied on top. */ void DRW_debug_m4(const float m[4][4]); -void DRW_debug_m4_as_bbox(const float m[4][4], const float color[4], bool invert); +void DRW_debug_m4_as_bbox(const float m[4][4], bool invert, const float color[4]); void DRW_debug_bbox(const BoundBox *bbox, const float color[4]); void DRW_debug_sphere(const float center[3], float radius, const float color[4]); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/intern/draw_debug.hh b/source/blender/draw/intern/draw_debug.hh new file mode 100644 index 00000000000..c83936bf1af --- /dev/null +++ b/source/blender/draw/intern/draw_debug.hh @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +/** \file + * \ingroup draw + * + * \brief Simple API to draw debug shapes and log in the viewport. + * + * Both CPU and GPU implementation are supported and symmetrical (meaning GPU shader can use it + * too, see common_debug_print/draw_lib.glsl). + * + * NOTE: CPU logging will overlap GPU logging on screen as it is drawn after. + */ + +#pragma once + +#include "BLI_math_vec_types.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" +#include "DNA_object_types.h" +#include "DRW_gpu_wrapper.hh" + +namespace blender::draw { + +/* Shortcuts to avoid boilerplate code and match shader API. */ +#define drw_debug_line(...) DRW_debug_get()->draw_line(__VA_ARGS__) +#define drw_debug_polygon(...) DRW_debug_get()->draw_polygon(__VA_ARGS__) +#define drw_debug_bbox(...) DRW_debug_get()->draw_bbox(__VA_ARGS__) +#define drw_debug_sphere(...) DRW_debug_get()->draw_sphere(__VA_ARGS__) +#define drw_debug_point(...) DRW_debug_get()->draw_point(__VA_ARGS__) +#define drw_debug_matrix(...) DRW_debug_get()->draw_matrix(__VA_ARGS__) +#define drw_debug_matrix_as_bbox(...) DRW_debug_get()->draw_matrix_as_bbox(__VA_ARGS__) +#define drw_print(...) DRW_debug_get()->print(__VA_ARGS__) +#define drw_print_hex(...) DRW_debug_get()->print_hex(__VA_ARGS__) +#define drw_print_binary(...) DRW_debug_get()->print_binary(__VA_ARGS__) +#define drw_print_no_endl(...) DRW_debug_get()->print_no_endl(__VA_ARGS__) + +/* Will log variable along with its name, like the shader version of print(). */ +#define drw_print_id(v_) DRW_debug_get()->print(#v_, "= ", v_) +#define drw_print_id_no_endl(v_) DRW_debug_get()->print_no_endl(#v_, "= ", v_) + +class DebugDraw { + private: + using DebugDrawBuf = StorageBuffer<DRWDebugDrawBuffer>; + using DebugPrintBuf = StorageBuffer<DRWDebugPrintBuffer>; + + /** Data buffers containing all verts or chars to draw. */ + DebugDrawBuf cpu_draw_buf_ = {"DebugDrawBuf-CPU"}; + DebugDrawBuf gpu_draw_buf_ = {"DebugDrawBuf-GPU"}; + DebugPrintBuf cpu_print_buf_ = {"DebugPrintBuf-CPU"}; + DebugPrintBuf gpu_print_buf_ = {"DebugPrintBuf-GPU"}; + /** True if the gpu buffer have been requested and may contain data to draw. */ + bool gpu_print_buf_used = false; + bool gpu_draw_buf_used = false; + /** Matrix applied to all points before drawing. Could be a stack if needed. */ + float4x4 model_mat_; + /** Precomputed shapes verts. */ + Vector<float3> sphere_verts_; + Vector<float3> point_verts_; + /** Cursor position for print functionality. */ + uint print_col_ = 0; + uint print_row_ = 0; + + public: + DebugDraw(); + ~DebugDraw(){}; + + /** + * Resets all buffers and reset model matrix state. + * Not to be called by user. + */ + void init(); + + /** + * Resets model matrix state to identity. + */ + void modelmat_reset(); + /** + * Sets model matrix transform to apply to any vertex passed to drawing functions. + */ + void modelmat_set(const float modelmat[4][4]); + + /** + * Drawing functions that will draw wire-frames with the given color. + */ + void draw_line(float3 v1, float3 v2, float4 color = {1, 0, 0, 1}); + void draw_polygon(Span<float3> poly_verts, float4 color = {1, 0, 0, 1}); + void draw_bbox(const BoundBox &bbox, const float4 color = {1, 0, 0, 1}); + void draw_sphere(const float3 center, float radius, const float4 color = {1, 0, 0, 1}); + void draw_point(const float3 center, float radius = 0.01f, const float4 color = {1, 0, 0, 1}); + /** + * Draw a matrix transformation as 3 colored axes. + */ + void draw_matrix(const float4x4 m4); + /** + * Draw a matrix as a 2 units length bounding box, centered on origin. + */ + void draw_matrix_as_bbox(float4x4 mat, const float4 color = {1, 0, 0, 1}); + + /** + * Will draw all debug shapes and text cached up until now to the current view / frame-buffer. + * Draw buffers will be emptied and ready for new debug data. + */ + void display_to_view(); + + /** + * Log variable or strings inside the viewport. + * Using a unique non string argument will print the variable name with it. + * Concatenate by using multiple arguments. i.e: `print("Looped ", n, "times.")`. + */ + template<typename... Ts> void print(StringRefNull str, Ts... args) + { + print_no_endl(str, args...); + print_newline(); + } + template<typename T> void print(const T &value) + { + print_value(value); + print_newline(); + } + template<typename T> void print_hex(const T &value) + { + print_value_hex(value); + print_newline(); + } + template<typename T> void print_binary(const T &value) + { + print_value_binary(value); + print_newline(); + } + + /** + * Same as `print()` but does not finish the line. + */ + void print_no_endl(std::string arg) + { + print_string(arg); + } + void print_no_endl(StringRef arg) + { + print_string(arg); + } + void print_no_endl(StringRefNull arg) + { + print_string(arg); + } + void print_no_endl(char const *arg) + { + print_string(StringRefNull(arg)); + } + template<typename T> void print_no_endl(T arg) + { + print_value(arg); + } + template<typename T, typename... Ts> void print_no_endl(T arg, Ts... args) + { + print_no_endl(arg); + print_no_endl(args...); + } + + /** + * Not to be called by user. Should become private. + */ + GPUStorageBuf *gpu_draw_buf_get(); + GPUStorageBuf *gpu_print_buf_get(); + + private: + uint color_pack(float4 color); + DRWDebugVert vert_pack(float3 pos, uint color); + + void draw_line(float3 v1, float3 v2, uint color); + + void print_newline(); + void print_string_start(uint len); + void print_string(std::string str); + void print_char4(uint data); + void print_append_char(uint char1, uint &char4); + void print_append_digit(uint digit, uint &char4); + void print_append_space(uint &char4); + void print_value_binary(uint value); + void print_value_uint(uint value, const bool hex, bool is_negative, const bool is_unsigned); + + template<typename T> void print_value(const T &value); + template<typename T> void print_value_hex(const T &value); + template<typename T> void print_value_binary(const T &value); + + void display_lines(); + void display_prints(); +}; + +} // namespace blender::draw + +/** + * Ease of use function to get the debug module. + * TODO(fclem): Should be removed once DRWManager is no longer global. + * IMPORTANT: Can return nullptr if storage buffer is not supported. + */ +blender::draw::DebugDraw *DRW_debug_get(); diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index b2422504825..4693e5f8e20 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -43,6 +43,7 @@ #include "DNA_camera_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_userdef_types.h" #include "DNA_world_types.h" #include "ED_gpencil.h" @@ -84,6 +85,7 @@ #include "draw_cache_impl.h" #include "engines/basic/basic_engine.h" +#include "engines/compositor/compositor_engine.h" #include "engines/eevee/eevee_engine.h" #include "engines/eevee_next/eevee_engine.h" #include "engines/external/external_engine.h" @@ -1214,6 +1216,31 @@ static void drw_engines_enable_editors(void) } } +static bool is_compositor_enabled(void) +{ + if (!U.experimental.use_realtime_compositor) { + return false; + } + + if (!(DST.draw_ctx.v3d->shading.flag & V3D_SHADING_COMPOSITOR)) { + return false; + } + + if (!(DST.draw_ctx.v3d->shading.type >= OB_MATERIAL)) { + return false; + } + + if (!DST.draw_ctx.scene->use_nodes) { + return false; + } + + if (!DST.draw_ctx.scene->nodetree) { + return false; + } + + return true; +} + static void drw_engines_enable(ViewLayer *UNUSED(view_layer), RenderEngineType *engine_type, bool gpencil_engine_needed) @@ -1226,6 +1253,11 @@ static void drw_engines_enable(ViewLayer *UNUSED(view_layer), if (gpencil_engine_needed && ((drawtype >= OB_SOLID) || !use_xray)) { use_drw_engine(&draw_engine_gpencil_type); } + + if (is_compositor_enabled()) { + use_drw_engine(&draw_engine_compositor_type); + } + drw_engines_enable_overlays(); #ifdef WITH_DRAW_DEBUG @@ -1597,7 +1629,6 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, GPUViewport *viewport, const bContext *evil_C) { - Scene *scene = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph); RegionView3D *rv3d = region->regiondata; @@ -2948,6 +2979,7 @@ void DRW_engines_register(void) DRW_engine_register(&draw_engine_overlay_type); DRW_engine_register(&draw_engine_select_type); DRW_engine_register(&draw_engine_basic_type); + DRW_engine_register(&draw_engine_compositor_type); #ifdef WITH_DRAW_DEBUG DRW_engine_register(&draw_engine_debug_select_type); #endif @@ -3028,6 +3060,9 @@ void DRW_engines_free(void) DRW_stats_free(); DRW_globals_free(); + drw_debug_module_free(DST.debug); + DST.debug = NULL; + DRW_UBO_FREE_SAFE(G_draw.block_ubo); DRW_UBO_FREE_SAFE(G_draw.view_ubo); DRW_TEXTURE_FREE_SAFE(G_draw.ramp); diff --git a/source/blender/draw/intern/draw_manager.h b/source/blender/draw/intern/draw_manager.h index 419b13edf1f..a29f2fa7507 100644 --- a/source/blender/draw/intern/draw_manager.h +++ b/source/blender/draw/intern/draw_manager.h @@ -188,6 +188,7 @@ typedef enum { DRW_CMD_DRAW_INSTANCE = 2, DRW_CMD_DRAW_INSTANCE_RANGE = 3, DRW_CMD_DRAW_PROCEDURAL = 4, + DRW_CMD_DRAW_INDIRECT = 5, /* Compute Commands. */ DRW_CMD_COMPUTE = 8, @@ -203,7 +204,7 @@ typedef enum { /* Needs to fit in 4bits */ } eDRWCommandType; -#define DRW_MAX_DRAW_CMD_TYPE DRW_CMD_DRAW_PROCEDURAL +#define DRW_MAX_DRAW_CMD_TYPE DRW_CMD_DRAW_INDIRECT typedef struct DRWCommandDraw { GPUBatch *batch; @@ -232,6 +233,12 @@ typedef struct DRWCommandDrawInstanceRange { uint inst_count; } DRWCommandDrawInstanceRange; +typedef struct DRWCommandDrawIndirect { + GPUBatch *batch; + DRWResourceHandle handle; + GPUStorageBuf *indirect_buf; +} DRWCommandDrawIndirect; + typedef struct DRWCommandCompute { int groups_x_len; int groups_y_len; @@ -286,6 +293,7 @@ typedef union DRWCommand { DRWCommandDrawInstance instance; DRWCommandDrawInstanceRange instance_range; DRWCommandDrawProcedural procedural; + DRWCommandDrawIndirect draw_indirect; DRWCommandCompute compute; DRWCommandComputeRef compute_ref; DRWCommandComputeIndirect compute_indirect; @@ -493,20 +501,6 @@ typedef struct DRWCommandSmallChunk { BLI_STATIC_ASSERT_ALIGN(DRWCommandChunk, 16); #endif -/* ------------- DRAW DEBUG ------------ */ - -typedef struct DRWDebugLine { - struct DRWDebugLine *next; /* linked list */ - float pos[2][3]; - float color[4]; -} DRWDebugLine; - -typedef struct DRWDebugSphere { - struct DRWDebugSphere *next; /* linked list */ - float mat[4][4]; - float color[4]; -} DRWDebugSphere; - /* ------------- Memory Pools ------------ */ /* Contains memory pools information */ @@ -648,11 +642,7 @@ typedef struct DRWManager { GPUDrawList *draw_list; - struct { - /* TODO(@fclem): optimize: use chunks. */ - DRWDebugLine *lines; - DRWDebugSphere *spheres; - } debug; + DRWDebugModule *debug; } DRWManager; extern DRWManager DST; /* TODO: get rid of this and allow multi-threaded rendering. */ @@ -667,6 +657,9 @@ void drw_state_set(DRWState state); void drw_debug_draw(void); void drw_debug_init(void); +void drw_debug_module_free(DRWDebugModule *module); +GPUStorageBuf *drw_debug_gpu_draw_buf_get(void); +GPUStorageBuf *drw_debug_gpu_print_buf_get(void); eDRWCommandType command_type_get(const uint64_t *command_type_bits, int index); @@ -685,6 +678,7 @@ void drw_resource_buffer_finish(DRWData *vmempool); GPUBatch *drw_cache_procedural_points_get(void); GPUBatch *drw_cache_procedural_lines_get(void); GPUBatch *drw_cache_procedural_triangles_get(void); +GPUBatch *drw_cache_procedural_triangle_strips_get(void); void drw_uniform_attrs_pool_update(struct GHash *table, struct GPUUniformAttrList *key, diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 188d9114cd7..913d1b4c3f4 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -17,9 +17,14 @@ #include "BKE_pbvh.h" #include "BKE_volume.h" +/* For debug cursor position. */ +#include "WM_api.h" +#include "wm_window.h" + #include "DNA_curve_types.h" #include "DNA_mesh_types.h" #include "DNA_meta_types.h" +#include "DNA_screen_types.h" #include "BLI_alloca.h" #include "BLI_hash.h" @@ -39,6 +44,16 @@ #include "intern/gpu_codegen.h" +/** + * IMPORTANT: + * In order to be able to write to the same print buffer sequentially, we add a barrier to allow + * multiple shader calls writing to the same buffer. + * However, this adds explicit synchronization events which might change the rest of the + * application behavior and hide some bugs. If you know you are using shader debug print in only + * one shader pass, you can comment this out to remove the aforementioned barrier. + */ +#define DISABLE_DEBUG_SHADER_PRINT_BARRIER + /* -------------------------------------------------------------------- */ /** \name Uniform Buffer Object (DRW_uniformbuffer) * \{ */ @@ -878,6 +893,17 @@ static void drw_command_draw_procedural(DRWShadingGroup *shgroup, cmd->vert_count = vert_count; } +static void drw_command_draw_indirect(DRWShadingGroup *shgroup, + GPUBatch *batch, + DRWResourceHandle handle, + GPUStorageBuf *indirect_buf) +{ + DRWCommandDrawIndirect *cmd = drw_command_create(shgroup, DRW_CMD_DRAW_INDIRECT); + cmd->batch = batch; + cmd->handle = handle; + cmd->indirect_buf = indirect_buf; +} + static void drw_command_set_select_id(DRWShadingGroup *shgroup, GPUVertBuf *buf, uint select_id) { /* Only one can be valid. */ @@ -1005,6 +1031,7 @@ void DRW_shgroup_call_compute_indirect(DRWShadingGroup *shgroup, GPUStorageBuf * drw_command_compute_indirect(shgroup, indirect_buf); } + void DRW_shgroup_barrier(DRWShadingGroup *shgroup, eGPUBarrier type) { BLI_assert(GPU_compute_shader_support()); @@ -1044,6 +1071,38 @@ void DRW_shgroup_call_procedural_triangles(DRWShadingGroup *shgroup, Object *ob, drw_shgroup_call_procedural_add_ex(shgroup, geom, ob, tri_count * 3); } +void DRW_shgroup_call_procedural_indirect(DRWShadingGroup *shgroup, + GPUPrimType primitive_type, + Object *ob, + GPUStorageBuf *indirect_buf) +{ + struct GPUBatch *geom = NULL; + switch (primitive_type) { + case GPU_PRIM_POINTS: + geom = drw_cache_procedural_points_get(); + break; + case GPU_PRIM_LINES: + geom = drw_cache_procedural_lines_get(); + break; + case GPU_PRIM_TRIS: + geom = drw_cache_procedural_triangles_get(); + break; + case GPU_PRIM_TRI_STRIP: + geom = drw_cache_procedural_triangle_strips_get(); + break; + default: + BLI_assert_msg(0, + "Unsupported primitive type in DRW_shgroup_call_procedural_indirect. Add new " + "one as needed."); + break; + } + if (G.f & G_FLAG_PICKSEL) { + drw_command_set_select_id(shgroup, NULL, DST.select_id); + } + DRWResourceHandle handle = drw_resource_handle(shgroup, ob ? ob->obmat : NULL, ob); + drw_command_draw_indirect(shgroup, geom, handle, indirect_buf); +} + void DRW_shgroup_call_instances(DRWShadingGroup *shgroup, Object *ob, struct GPUBatch *geom, @@ -1129,16 +1188,15 @@ static void sculpt_draw_cb(DRWSculptCallbackData *scd, GPU_PBVH_Buffers *buffers DRW_shgroup_uniform_vec3( shgrp, "materialDiffuseColor", SCULPT_DEBUG_COLOR(scd->debug_node_nr++), 1); } + /* DRW_shgroup_call_no_cull reuses matrices calculations for all the drawcalls of this * object. */ DRW_shgroup_call_no_cull(shgrp, geom, scd->ob); } } -static void sculpt_debug_cb(void *user_data, - const float bmin[3], - const float bmax[3], - PBVHNodeFlags flag) +static void sculpt_debug_cb( + PBVHNode *node, void *user_data, const float bmin[3], const float bmax[3], PBVHNodeFlags flag) { int *debug_node_nr = (int *)user_data; BoundBox bb; @@ -1153,7 +1211,10 @@ static void sculpt_debug_cb(void *user_data, } #else /* Color coded leaf bounds. */ if (flag & PBVH_Leaf) { - DRW_debug_bbox(&bb, SCULPT_DEBUG_COLOR((*debug_node_nr)++)); + int color = (*debug_node_nr)++; + color += BKE_pbvh_debug_draw_gen_get(node); + + DRW_debug_bbox(&bb, SCULPT_DEBUG_COLOR(color)); } #endif } @@ -1246,8 +1307,8 @@ static void drw_sculpt_generate_calls(DRWSculptCallbackData *scd) DRW_debug_modelmat(scd->ob->obmat); BKE_pbvh_draw_debug_cb( pbvh, - (void (*)( - void *d, const float min[3], const float max[3], PBVHNodeFlags f))sculpt_debug_cb, + (void (*)(PBVHNode * n, void *d, const float min[3], const float max[3], PBVHNodeFlags f)) + sculpt_debug_cb, &debug_node_nr); } } @@ -1466,6 +1527,27 @@ static void drw_shgroup_init(DRWShadingGroup *shgroup, GPUShader *shader) shgroup, view_ubo_location, DRW_UNIFORM_BLOCK, G_draw.view_ubo, 0, 0, 1); } +#ifdef DEBUG + int debug_print_location = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_PRINT); + if (debug_print_location != -1) { + GPUStorageBuf *buf = drw_debug_gpu_print_buf_get(); + drw_shgroup_uniform_create_ex( + shgroup, debug_print_location, DRW_UNIFORM_STORAGE_BLOCK, buf, 0, 0, 1); +# ifndef DISABLE_DEBUG_SHADER_PRINT_BARRIER + /* Add a barrier to allow multiple shader writing to the same buffer. */ + DRW_shgroup_barrier(shgroup, GPU_BARRIER_SHADER_STORAGE); +# endif + } + + int debug_draw_location = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_VERTS); + if (debug_draw_location != -1) { + GPUStorageBuf *buf = drw_debug_gpu_draw_buf_get(); + drw_shgroup_uniform_create_ex( + shgroup, debug_draw_location, DRW_UNIFORM_STORAGE_BLOCK, buf, 0, 0, 1); + /* NOTE(fclem): No barrier as ordering is not important. */ + } +#endif + /* Not supported. */ BLI_assert(GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_MODELVIEW_INV) == -1); BLI_assert(GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_MODELVIEW) == -1); @@ -1942,6 +2024,13 @@ DRWView *DRW_view_create(const float viewmat[4][4], copy_v4_fl4(view->storage.viewcamtexcofac, 1.0f, 1.0f, 0.0f, 0.0f); + if (DST.draw_ctx.evil_C && DST.draw_ctx.region) { + int region_origin[2] = {DST.draw_ctx.region->winrct.xmin, DST.draw_ctx.region->winrct.ymin}; + struct wmWindow *win = CTX_wm_window(DST.draw_ctx.evil_C); + wm_cursor_position_get(win, &view->storage.mouse_pixel[0], &view->storage.mouse_pixel[1]); + sub_v2_v2v2_int(view->storage.mouse_pixel, view->storage.mouse_pixel, region_origin); + } + DRW_view_update(view, viewmat, winmat, culling_viewmat, culling_winmat); return view; @@ -2041,6 +2130,14 @@ void DRW_view_update(DRWView *view, draw_frustum_bound_sphere_calc( &view->frustum_corners, viewinv, winmat, wininv, &view->frustum_bsphere); + /* TODO(fclem): Deduplicate. */ + for (int i = 0; i < 8; i++) { + copy_v3_v3(view->storage.frustum_corners[i], view->frustum_corners.vec[i]); + } + for (int i = 0; i < 6; i++) { + copy_v4_v4(view->storage.frustum_planes[i], view->frustum_planes[i]); + } + #ifdef DRW_DEBUG_CULLING if (G.debug_value != 0) { DRW_debug_sphere( diff --git a/source/blender/draw/intern/draw_manager_exec.c b/source/blender/draw/intern/draw_manager_exec.c index e7e0e0ce41f..4dda0ceb2ef 100644 --- a/source/blender/draw/intern/draw_manager_exec.c +++ b/source/blender/draw/intern/draw_manager_exec.c @@ -318,6 +318,7 @@ void DRW_state_reset(void) DRW_state_reset_ex(DRW_STATE_DEFAULT); GPU_texture_unbind_all(); + GPU_texture_image_unbind_all(); GPU_uniformbuf_unbind_all(); GPU_storagebuf_unbind_all(); @@ -874,6 +875,25 @@ static void draw_call_single_do(DRWShadingGroup *shgroup, state->baseinst_loc); } +/* Not to be mistaken with draw_indirect_call which does batch many drawcalls together. This one + * only execute an indirect drawcall with user indirect buffer. */ +static void draw_call_indirect(DRWShadingGroup *shgroup, + DRWCommandsState *state, + GPUBatch *batch, + DRWResourceHandle handle, + GPUStorageBuf *indirect_buf) +{ + draw_call_batching_flush(shgroup, state); + draw_call_resource_bind(state, &handle); + + if (G.f & G_FLAG_PICKSEL) { + GPU_select_load_id(state->select_id); + } + + GPU_batch_set_shader(batch, shgroup->shader); + GPU_batch_draw_indirect(batch, indirect_buf); +} + static void draw_call_batching_start(DRWCommandsState *state) { state->neg_scale = false; @@ -970,6 +990,7 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state) /* Unbinding can be costly. Skip in normal condition. */ if (G.debug & G_DEBUG_GPU) { GPU_texture_unbind_all(); + GPU_texture_image_unbind_all(); GPU_uniformbuf_unbind_all(); GPU_storagebuf_unbind_all(); } @@ -996,12 +1017,13 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state) while ((cmd = draw_command_iter_step(&iter, &cmd_type))) { switch (cmd_type) { + case DRW_CMD_DRAW_PROCEDURAL: case DRW_CMD_DRWSTATE: case DRW_CMD_STENCIL: draw_call_batching_flush(shgroup, &state); break; case DRW_CMD_DRAW: - case DRW_CMD_DRAW_PROCEDURAL: + case DRW_CMD_DRAW_INDIRECT: case DRW_CMD_DRAW_INSTANCE: if (draw_call_is_culled(&cmd->instance.handle, DST.view_active)) { continue; @@ -1055,6 +1077,13 @@ static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state) 1, true); break; + case DRW_CMD_DRAW_INDIRECT: + draw_call_indirect(shgroup, + &state, + cmd->draw_indirect.batch, + cmd->draw_indirect.handle, + cmd->draw_indirect.indirect_buf); + break; case DRW_CMD_DRAW_INSTANCE: draw_call_single_do(shgroup, &state, diff --git a/source/blender/draw/intern/draw_shader.cc b/source/blender/draw/intern/draw_shader.cc index 001ceb0ae8d..ecb30d54b64 100644 --- a/source/blender/draw/intern/draw_shader.cc +++ b/source/blender/draw/intern/draw_shader.cc @@ -24,6 +24,8 @@ extern "C" char datatoc_gpu_shader_3D_smooth_color_frag_glsl[]; static struct { struct GPUShader *hair_refine_sh[PART_REFINE_MAX_SHADER]; + struct GPUShader *debug_print_display_sh; + struct GPUShader *debug_draw_display_sh; } e_data = {{nullptr}}; /* -------------------------------------------------------------------- */ @@ -109,6 +111,22 @@ GPUShader *DRW_shader_curves_refine_get(CurvesEvalShader type, eParticleRefineSh return e_data.hair_refine_sh[type]; } +GPUShader *DRW_shader_debug_print_display_get() +{ + if (e_data.debug_print_display_sh == nullptr) { + e_data.debug_print_display_sh = GPU_shader_create_from_info_name("draw_debug_print_display"); + } + return e_data.debug_print_display_sh; +} + +GPUShader *DRW_shader_debug_draw_display_get() +{ + if (e_data.debug_draw_display_sh == nullptr) { + e_data.debug_draw_display_sh = GPU_shader_create_from_info_name("draw_debug_draw_display"); + } + return e_data.debug_draw_display_sh; +} + /** \} */ void DRW_shaders_free() @@ -116,4 +134,6 @@ void DRW_shaders_free() for (int i = 0; i < PART_REFINE_MAX_SHADER; i++) { DRW_SHADER_FREE_SAFE(e_data.hair_refine_sh[i]); } + DRW_SHADER_FREE_SAFE(e_data.debug_print_display_sh); + DRW_SHADER_FREE_SAFE(e_data.debug_draw_display_sh); } diff --git a/source/blender/draw/intern/draw_shader.h b/source/blender/draw/intern/draw_shader.h index 63d755cc334..dabb4b3327f 100644 --- a/source/blender/draw/intern/draw_shader.h +++ b/source/blender/draw/intern/draw_shader.h @@ -30,6 +30,9 @@ struct GPUShader *DRW_shader_hair_refine_get(ParticleRefineShader refinement, struct GPUShader *DRW_shader_curves_refine_get(CurvesEvalShader type, eParticleRefineShaderType sh_type); +struct GPUShader *DRW_shader_debug_print_display_get(void); +struct GPUShader *DRW_shader_debug_draw_display_get(void); + void DRW_shaders_free(void); #ifdef __cplusplus diff --git a/source/blender/draw/intern/draw_shader_shared.h b/source/blender/draw/intern/draw_shader_shared.h index e8944442607..90a6475c42b 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" @@ -9,6 +11,12 @@ typedef struct ObjectMatrices ObjectMatrices; typedef struct ObjectInfos ObjectInfos; typedef struct VolumeInfos VolumeInfos; typedef struct CurvesInfos CurvesInfos; +typedef struct DrawCommand DrawCommand; +typedef struct DrawCommandIndexed DrawCommandIndexed; +typedef struct DispatchCommand DispatchCommand; +typedef struct DRWDebugPrintBuffer DRWDebugPrintBuffer; +typedef struct DRWDebugVert DRWDebugVert; +typedef struct DRWDebugDrawBuffer DRWDebugDrawBuffer; #endif #define DRW_SHADER_SHARED_H @@ -43,6 +51,12 @@ struct ViewInfos { /** NOTE: vec3 arrays are padded to vec4. */ float4 frustum_corners[8]; float4 frustum_planes[6]; + + /** For debugging purpose */ + /* Mouse pixel. */ + int2 mouse_pixel; + + int2 _pad0; }; BLI_STATIC_ASSERT_ALIGN(ViewInfos, 16) @@ -96,3 +110,89 @@ BLI_STATIC_ASSERT_ALIGN(CurvesInfos, 16) #define OrcoTexCoFactors (drw_infos[resource_id].drw_OrcoTexCoFactors) #define ObjectInfo (drw_infos[resource_id].drw_Infos) #define ObjectColor (drw_infos[resource_id].drw_ObjectColor) + +/* Indirect commands structures. */ + +struct DrawCommand { + uint v_count; + uint i_count; + uint v_first; + uint i_first; +}; +BLI_STATIC_ASSERT_ALIGN(DrawCommand, 16) + +struct DrawCommandIndexed { + uint v_count; + uint i_count; + uint v_first; + uint base_index; + uint i_first; + uint _pad0; + uint _pad1; + uint _pad2; +}; +BLI_STATIC_ASSERT_ALIGN(DrawCommandIndexed, 16) + +struct DispatchCommand { + uint num_groups_x; + uint num_groups_y; + uint num_groups_z; + uint _pad0; +}; +BLI_STATIC_ASSERT_ALIGN(DispatchCommand, 16) + +/* -------------------------------------------------------------------- */ +/** \name Debug print + * \{ */ + +/* Take the header (DrawCommand) into account. */ +#define DRW_DEBUG_PRINT_MAX (8 * 1024) - 4 +/* NOTE: Cannot be more than 255 (because of column encoding). */ +#define DRW_DEBUG_PRINT_WORD_WRAP_COLUMN 120u + +/* The debug print buffer is laid-out as the following struct. + * But we use plain array in shader code instead because of driver issues. */ +struct DRWDebugPrintBuffer { + DrawCommand command; + /** Each character is encoded as 3 `uchar` with char_index, row and column position. */ + uint char_array[DRW_DEBUG_PRINT_MAX]; +}; +BLI_STATIC_ASSERT_ALIGN(DRWDebugPrintBuffer, 16) + +/* Use number of char as vertex count. Equivalent to `DRWDebugPrintBuffer.command.v_count`. */ +#define drw_debug_print_cursor drw_debug_print_buf[0] +/* Reuse first instance as row index as we don't use instancing. Equivalent to + * `DRWDebugPrintBuffer.command.i_first`. */ +#define drw_debug_print_row_shared drw_debug_print_buf[3] + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Debug draw shapes + * \{ */ + +struct DRWDebugVert { + /* This is a weird layout, but needed to be able to use DRWDebugVert as + * a DrawCommand and avoid alignment issues. See drw_debug_verts_buf[] definition. */ + uint pos0; + uint pos1; + uint pos2; + uint color; +}; +BLI_STATIC_ASSERT_ALIGN(DRWDebugVert, 16) + +/* Take the header (DrawCommand) into account. */ +#define DRW_DEBUG_DRAW_VERT_MAX (64 * 1024) - 1 + +/* The debug draw buffer is laid-out as the following struct. + * But we use plain array in shader code instead because of driver issues. */ +struct DRWDebugDrawBuffer { + DrawCommand command; + DRWDebugVert verts[DRW_DEBUG_DRAW_VERT_MAX]; +}; +BLI_STATIC_ASSERT_ALIGN(DRWDebugPrintBuffer, 16) + +/* Equivalent to `DRWDebugDrawBuffer.command.v_count`. */ +#define drw_debug_draw_v_count drw_debug_verts_buf[0].pos0 + +/** \} */ diff --git a/source/blender/draw/intern/draw_texture_pool.cc b/source/blender/draw/intern/draw_texture_pool.cc index b36cb5c809e..017ecec7be2 100644 --- a/source/blender/draw/intern/draw_texture_pool.cc +++ b/source/blender/draw/intern/draw_texture_pool.cc @@ -160,6 +160,19 @@ void DRW_texture_pool_texture_release(DRWTexturePool *pool, GPUTexture *tmp_tex) pool->tmp_tex_released.append(tmp_tex); } +void DRW_texture_pool_take_texture_ownership(DRWTexturePool *pool, GPUTexture *tex) +{ + pool->tmp_tex_acquired.remove_first_occurrence_and_reorder(tex); +} + +void DRW_texture_pool_give_texture_ownership(DRWTexturePool *pool, GPUTexture *tex) +{ + BLI_assert(pool->tmp_tex_acquired.first_index_of_try(tex) == -1 && + pool->tmp_tex_released.first_index_of_try(tex) == -1 && + pool->tmp_tex_pruned.first_index_of_try(tex) == -1); + pool->tmp_tex_acquired.append(tex); +} + void DRW_texture_pool_reset(DRWTexturePool *pool) { pool->last_user_id = -1; diff --git a/source/blender/draw/intern/draw_texture_pool.h b/source/blender/draw/intern/draw_texture_pool.h index 1c30ea88552..9fbbf630833 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); @@ -40,6 +41,22 @@ GPUTexture *DRW_texture_pool_texture_acquire(DRWTexturePool *pool, * Releases a previously acquired texture. */ void DRW_texture_pool_texture_release(DRWTexturePool *pool, GPUTexture *tmp_tex); + +/** + * This effectively remove a texture from the texture pool, giving full ownership to the caller. + * The given texture needs to be been acquired through DRW_texture_pool_texture_acquire(). + * IMPORTANT: This removes the need for a DRW_texture_pool_texture_release() call on this texture. + */ +void DRW_texture_pool_take_texture_ownership(DRWTexturePool *pool, GPUTexture *tex); +/** + * This Inserts a texture into the texture pool, giving full ownership to the texture pool. + * The texture needs not to be in the pool already. + * The texture may be reused in a latter call to DRW_texture_pool_texture_acquire(); + * IMPORTANT: DRW_texture_pool_texture_release() still needs to be called on this texture + * after usage. + */ +void DRW_texture_pool_give_texture_ownership(DRWTexturePool *pool, GPUTexture *tex); + /** * Resets the user bits for each texture in the pool and delete unused ones. */ diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh.hh b/source/blender/draw/intern/mesh_extractors/extract_mesh.hh index a4773736f0c..57abf088c16 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh.hh +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh.hh @@ -83,6 +83,9 @@ struct MeshRenderData { MLoopTri *mlooptri; const float (*vert_normals)[3]; const float (*poly_normals)[3]; + const bool *hide_vert; + const bool *hide_edge; + const bool *hide_poly; float (*loop_normals)[3]; int *lverts, *ledges; diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc index f51c96af0b0..fa39957a7fc 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc @@ -537,6 +537,7 @@ static void extract_edituv_fdots_iter_poly_mesh(const MeshRenderData *mr, void *_data) { MeshExtract_EditUvElem_Data *data = static_cast<MeshExtract_EditUvElem_Data *>(_data); + const BMFace *efa = bm_original_face_get(mr, mp_index); const bool mp_hidden = (efa) ? BM_elem_flag_test_bool(efa, BM_ELEM_HIDDEN) : true; const bool mp_select = (efa) ? BM_elem_flag_test_bool(efa, BM_ELEM_SELECT) : false; diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc index cc0b383f12b..8dc00617039 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc @@ -42,6 +42,8 @@ static void extract_fdots_iter_poly_mesh(const MeshRenderData *mr, const int mp_index, void *_userdata) { + const bool hidden = mr->use_hide && mr->hide_poly && mr->hide_poly[mp - mr->mpoly]; + GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(_userdata); if (mr->use_subsurf_fdots) { const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags; @@ -50,7 +52,7 @@ static void extract_fdots_iter_poly_mesh(const MeshRenderData *mr, const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { const MLoop *ml = &mloop[ml_index]; - if (BLI_BITMAP_TEST(facedot_tags, ml->v) && !(mr->use_hide && (mp->flag & ME_HIDE))) { + if (BLI_BITMAP_TEST(facedot_tags, ml->v) && !hidden) { GPU_indexbuf_set_point_vert(elb, mp_index, mp_index); return; } @@ -58,7 +60,7 @@ static void extract_fdots_iter_poly_mesh(const MeshRenderData *mr, GPU_indexbuf_set_point_restart(elb, mp_index); } else { - if (!(mr->use_hide && (mp->flag & ME_HIDE))) { + if (!hidden) { GPU_indexbuf_set_point_vert(elb, mp_index, mp_index); } else { diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc index e6c0d815963..fe883fb0c96 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc @@ -58,14 +58,12 @@ static void extract_lines_iter_poly_mesh(const MeshRenderData *mr, GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(data); /* Using poly & loop iterator would complicate accessing the adjacent loop. */ const MLoop *mloop = mr->mloop; - const MEdge *medge = mr->medge; if (mr->use_hide || (mr->extract_type == MR_EXTRACT_MAPPED) || (mr->e_origindex != nullptr)) { const int ml_index_last = mp->loopstart + (mp->totloop - 1); int ml_index = ml_index_last, ml_index_next = mp->loopstart; do { const MLoop *ml = &mloop[ml_index]; - const MEdge *med = &medge[ml->e]; - if (!((mr->use_hide && (med->flag & ME_HIDE)) || + if (!((mr->use_hide && mr->hide_edge && mr->hide_edge[ml->e]) || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->e_origindex) && (mr->e_origindex[ml->e] == ORIGINDEX_NONE)))) { GPU_indexbuf_set_line_verts(elb, ml->e, ml_index, ml_index_next); @@ -111,7 +109,7 @@ static void extract_lines_iter_ledge_mesh(const MeshRenderData *mr, GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(data); const int l_index_offset = mr->edge_len + ledge_index; const int e_index = mr->ledges[ledge_index]; - if (!((mr->use_hide && (med->flag & ME_HIDE)) || + if (!((mr->use_hide && mr->hide_edge && mr->hide_edge[med - mr->medge]) || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->e_origindex) && (mr->e_origindex[e_index] == ORIGINDEX_NONE)))) { const int l_index = mr->loop_len + ledge_index * 2; @@ -185,9 +183,14 @@ static void extract_lines_loose_geom_subdiv(const DRWSubdivCache *subdiv_cache, switch (mr->extract_type) { case MR_EXTRACT_MESH: { - const MEdge *medge = mr->medge; - for (DRWSubdivLooseEdge edge : loose_edges) { - *flags_data++ = (medge[edge.coarse_edge_index].flag & ME_HIDE) != 0; + const bool *hide_vert = mr->hide_vert; + if (hide_vert) { + for (DRWSubdivLooseEdge edge : loose_edges) { + *flags_data++ = hide_vert[edge.coarse_edge_index]; + } + } + else { + MutableSpan<uint>(flags_data, loose_edges.size()).fill(0); } break; } @@ -199,18 +202,23 @@ static void extract_lines_loose_geom_subdiv(const DRWSubdivCache *subdiv_cache, } } else { - for (DRWSubdivLooseEdge edge : loose_edges) { - int e = edge.coarse_edge_index; - - if (mr->e_origindex && mr->e_origindex[e] != ORIGINDEX_NONE) { - *flags_data++ = (mr->medge[mr->e_origindex[e]].flag & ME_HIDE) != 0; - } - else { - *flags_data++ = false; + const bool *hide_vert = mr->hide_vert; + if (hide_vert) { + for (DRWSubdivLooseEdge edge : loose_edges) { + int e = edge.coarse_edge_index; + + if (mr->e_origindex && mr->e_origindex[e] != ORIGINDEX_NONE) { + *flags_data++ = hide_vert[edge.coarse_edge_index]; + } + else { + *flags_data++ = false; + } } } + else { + MutableSpan<uint>(flags_data, loose_edges.size()).fill(0); + } } - break; } case MR_EXTRACT_BMESH: { diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_adjacency.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_adjacency.cc index c2cfb66ec28..d6c246c51a9 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_adjacency.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_adjacency.cc @@ -119,16 +119,17 @@ static void extract_lines_adjacency_iter_looptri_mesh(const MeshRenderData *mr, void *_data) { MeshExtract_LineAdjacency_Data *data = static_cast<MeshExtract_LineAdjacency_Data *>(_data); - const MPoly *mp = &mr->mpoly[mlt->poly]; - if (!(mr->use_hide && (mp->flag & ME_HIDE))) { - lines_adjacency_triangle(mr->mloop[mlt->tri[0]].v, - mr->mloop[mlt->tri[1]].v, - mr->mloop[mlt->tri[2]].v, - mlt->tri[0], - mlt->tri[1], - mlt->tri[2], - data); + const bool hidden = mr->use_hide && mr->hide_poly && mr->hide_poly[mlt->poly]; + if (hidden) { + return; } + lines_adjacency_triangle(mr->mloop[mlt->tri[0]].v, + mr->mloop[mlt->tri[1]].v, + mr->mloop[mlt->tri[2]].v, + mlt->tri[0], + mlt->tri[1], + mlt->tri[2], + data); } static void extract_lines_adjacency_finish(const MeshRenderData *UNUSED(mr), diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_paint_mask.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_paint_mask.cc index 11c71d61775..d5f31c08eaf 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_paint_mask.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines_paint_mask.cc @@ -47,8 +47,7 @@ static void extract_lines_paint_mask_iter_poly_mesh(const MeshRenderData *mr, const MLoop *ml = &mloop[ml_index]; const int e_index = ml->e; - const MEdge *me = &mr->medge[e_index]; - if (!((mr->use_hide && (me->flag & ME_HIDE)) || + if (!((mr->use_hide && mr->hide_edge && mr->hide_edge[e_index]) || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->e_origindex) && (mr->e_origindex[e_index] == ORIGINDEX_NONE)))) { @@ -122,8 +121,7 @@ static void extract_lines_paint_mask_iter_subdiv_mesh(const DRWSubdivCache *subd GPU_indexbuf_set_line_restart(&data->elb, subdiv_edge_index); } else { - const MEdge *me = &mr->medge[coarse_edge_index]; - if (!((mr->use_hide && (me->flag & ME_HIDE)) || + if (!((mr->use_hide && mr->hide_edge && mr->hide_edge[coarse_edge_index]) || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->e_origindex) && (mr->e_origindex[coarse_edge_index] == ORIGINDEX_NONE)))) { const uint ml_index_other = (loop_idx == (end_loop_idx - 1)) ? start_loop_idx : diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_points.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_points.cc index f7c5505422b..ca46a38823d 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_points.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_points.cc @@ -43,10 +43,10 @@ BLI_INLINE void vert_set_mesh(GPUIndexBufBuilder *elb, const int v_index, const int l_index) { - const MVert *mv = &mr->mvert[v_index]; - if (!((mr->use_hide && (mv->flag & ME_HIDE)) || - ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->v_origindex) && - (mr->v_origindex[v_index] == ORIGINDEX_NONE)))) { + const bool hidden = mr->use_hide && mr->hide_vert && mr->hide_vert[v_index]; + + if (!(hidden || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->v_origindex) && + (mr->v_origindex[v_index] == ORIGINDEX_NONE)))) { GPU_indexbuf_set_point_vert(elb, v_index, l_index); } else { @@ -181,8 +181,7 @@ static void extract_points_iter_subdiv_common(GPUIndexBufBuilder *elb, } } else { - const MVert *mv = &mr->mvert[coarse_vertex_index]; - if (mr->use_hide && (mv->flag & ME_HIDE)) { + if (mr->use_hide && mr->hide_vert && mr->hide_vert[coarse_vertex_index]) { GPU_indexbuf_set_point_restart(elb, coarse_vertex_index); continue; } diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_tris.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_tris.cc index 9fc18620d11..2e3e6c7b6b1 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_tris.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_tris.cc @@ -189,12 +189,12 @@ static void extract_tris_single_mat_iter_looptri_mesh(const MeshRenderData *mr, void *_data) { GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(_data); - const MPoly *mp = &mr->mpoly[mlt->poly]; - if (!(mr->use_hide && (mp->flag & ME_HIDE))) { - GPU_indexbuf_set_tri_verts(elb, mlt_index, mlt->tri[0], mlt->tri[1], mlt->tri[2]); + const bool hidden = mr->use_hide && mr->hide_poly && mr->hide_poly[mlt->poly]; + if (hidden) { + GPU_indexbuf_set_tri_restart(elb, mlt_index); } else { - GPU_indexbuf_set_tri_restart(elb, mlt_index); + GPU_indexbuf_set_tri_verts(elb, mlt_index, mlt->tri[0], mlt->tri[1], mlt->tri[2]); } } diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc index 7f16837022c..64ade020418 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_attributes.cc @@ -58,7 +58,6 @@ template<typename AttributeType, typename VBOType> struct AttributeTypeConverter } }; -/* Similar to the one in #extract_mesh_vcol_vbo.cc */ struct gpuMeshCol { ushort r, g, b, a; }; diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_lnor.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_lnor.cc index ac517269e7d..ef67e1b540d 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_lnor.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_lnor.cc @@ -62,6 +62,8 @@ static void extract_lnor_iter_poly_mesh(const MeshRenderData *mr, const int mp_index, void *data) { + const bool hidden = mr->hide_poly && mr->hide_poly[mp_index]; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { @@ -80,8 +82,8 @@ static void extract_lnor_iter_poly_mesh(const MeshRenderData *mr, /* Flag for paint mode overlay. * Only use MR_EXTRACT_MAPPED in edit mode where it is used to display the edge-normals. * In paint mode it will use the un-mapped data to draw the wire-frame. */ - if (mp->flag & ME_HIDE || (mr->edit_bmesh && mr->extract_type == MR_EXTRACT_MAPPED && - (mr->v_origindex) && mr->v_origindex[ml->v] == ORIGINDEX_NONE)) { + if (hidden || (mr->edit_bmesh && mr->extract_type == MR_EXTRACT_MAPPED && (mr->v_origindex) && + mr->v_origindex[ml->v] == ORIGINDEX_NONE)) { lnor_data->w = -1; } else if (mp->flag & ME_FACE_SEL) { @@ -185,6 +187,8 @@ static void extract_lnor_hq_iter_poly_mesh(const MeshRenderData *mr, const int mp_index, void *data) { + const bool hidden = mr->hide_poly && mr->hide_poly[mp_index]; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { @@ -203,8 +207,8 @@ static void extract_lnor_hq_iter_poly_mesh(const MeshRenderData *mr, /* Flag for paint mode overlay. * Only use #MR_EXTRACT_MAPPED in edit mode where it is used to display the edge-normals. * In paint mode it will use the un-mapped data to draw the wire-frame. */ - if (mp->flag & ME_HIDE || (mr->edit_bmesh && mr->extract_type == MR_EXTRACT_MAPPED && - (mr->v_origindex) && mr->v_origindex[ml->v] == ORIGINDEX_NONE)) { + if (hidden || (mr->edit_bmesh && mr->extract_type == MR_EXTRACT_MAPPED && (mr->v_origindex) && + mr->v_origindex[ml->v] == ORIGINDEX_NONE)) { lnor_data->w = -1; } else if (mp->flag & ME_FACE_SEL) { diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc index 9788beabeb5..313838be9e8 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc @@ -83,10 +83,11 @@ static void extract_pos_nor_iter_poly_bm(const MeshRenderData *mr, static void extract_pos_nor_iter_poly_mesh(const MeshRenderData *mr, const MPoly *mp, - const int UNUSED(mp_index), + const int mp_index, void *_data) { MeshExtract_PosNor_Data *data = static_cast<MeshExtract_PosNor_Data *>(_data); + const bool poly_hidden = mr->hide_poly && mr->hide_poly[mp_index]; const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; @@ -95,10 +96,11 @@ static void extract_pos_nor_iter_poly_mesh(const MeshRenderData *mr, PosNorLoop *vert = &data->vbo_data[ml_index]; const MVert *mv = &mr->mvert[ml->v]; + const bool vert_hidden = mr->hide_vert && mr->hide_vert[ml->v]; copy_v3_v3(vert->pos, mv->co); vert->nor = data->normals[ml->v].low; /* Flag for paint mode overlay. */ - if (mp->flag & ME_HIDE || mv->flag & ME_HIDE || + if (poly_hidden || vert_hidden || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->v_origindex) && (mr->v_origindex[ml->v] == ORIGINDEX_NONE))) { vert->nor.w = -1; @@ -432,18 +434,21 @@ static void extract_pos_nor_hq_iter_poly_mesh(const MeshRenderData *mr, void *_data) { MeshExtract_PosNorHQ_Data *data = static_cast<MeshExtract_PosNorHQ_Data *>(_data); + const bool poly_hidden = mr->hide_poly && mr->hide_poly[mp - mr->mpoly]; + const MLoop *mloop = mr->mloop; const int ml_index_end = mp->loopstart + mp->totloop; for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) { const MLoop *ml = &mloop[ml_index]; + const bool vert_hidden = mr->hide_vert && mr->hide_vert[ml->v]; PosNorHQLoop *vert = &data->vbo_data[ml_index]; const MVert *mv = &mr->mvert[ml->v]; copy_v3_v3(vert->pos, mv->co); copy_v3_v3_short(vert->nor, data->normals[ml->v].high); /* Flag for paint mode overlay. */ - if (mp->flag & ME_HIDE || mv->flag & ME_HIDE || + if (poly_hidden || vert_hidden || ((mr->extract_type == MR_EXTRACT_MAPPED) && (mr->v_origindex) && (mr->v_origindex[ml->v] == ORIGINDEX_NONE))) { vert->nor[3] = -1; diff --git a/source/blender/draw/intern/shaders/common_aabb_lib.glsl b/source/blender/draw/intern/shaders/common_aabb_lib.glsl new file mode 100644 index 00000000000..b5f664a6779 --- /dev/null +++ b/source/blender/draw/intern/shaders/common_aabb_lib.glsl @@ -0,0 +1,59 @@ + +#pragma BLENDER_REQUIRE(common_shape_lib.glsl) + +/* ---------------------------------------------------------------------- */ +/** \name Axis Aligned Bound Box + * \{ */ + +struct AABB { + vec3 min, max; +}; + +AABB aabb_init_min_max() +{ + AABB aabb; + aabb.min = vec3(1.0e30); + aabb.max = vec3(-1.0e30); + return aabb; +} + +void aabb_merge(inout AABB aabb, vec3 v) +{ + aabb.min = min(aabb.min, v); + aabb.max = max(aabb.max, v); +} + +/** + * Return true if there is any intersection. + */ +bool aabb_intersect(AABB a, AABB b) +{ + return all(greaterThanEqual(min(a.max, b.max), max(a.min, b.min))); +} + +/** + * Compute intersect intersection volume of \a a and \a b. + * Return true if the resulting volume is not empty. + */ +bool aabb_clip(AABB a, AABB b, out AABB c) +{ + c.min = max(a.min, b.min); + c.max = min(a.max, b.max); + return all(greaterThanEqual(c.max, c.min)); +} + +Box aabb_to_box(AABB aabb) +{ + Box box; + box.corners[0] = aabb.min; + box.corners[1] = vec3(aabb.max.x, aabb.min.y, aabb.min.z); + box.corners[2] = vec3(aabb.max.x, aabb.max.y, aabb.min.z); + box.corners[3] = vec3(aabb.min.x, aabb.max.y, aabb.min.z); + box.corners[4] = vec3(aabb.min.x, aabb.min.y, aabb.max.z); + box.corners[5] = vec3(aabb.max.x, aabb.min.y, aabb.max.z); + box.corners[6] = aabb.max; + box.corners[7] = vec3(aabb.min.x, aabb.max.y, aabb.max.z); + return box; +} + +/** \} */ diff --git a/source/blender/draw/intern/shaders/common_debug_draw_lib.glsl b/source/blender/draw/intern/shaders/common_debug_draw_lib.glsl new file mode 100644 index 00000000000..5f795d3abdb --- /dev/null +++ b/source/blender/draw/intern/shaders/common_debug_draw_lib.glsl @@ -0,0 +1,216 @@ + +/** + * Debugging drawing library + * + * Quick way to draw debug geometry. All input should be in world space and + * will be rendered in the default view. No additional setup required. + **/ + +/** Global switch option. */ +bool drw_debug_draw_enable = true; +const vec4 drw_debug_default_color = vec4(1.0, 0.0, 0.0, 1.0); + +/* -------------------------------------------------------------------- */ +/** \name Internals + * \{ */ + +uint drw_debug_start_draw(uint v_needed) +{ + uint vertid = atomicAdd(drw_debug_draw_v_count, v_needed); + /* NOTE: Skip the header manually. */ + vertid += 1; + return vertid; +} + +uint drw_debug_color_pack(vec4 color) +{ + color = clamp(color, 0.0, 1.0); + uint result = 0; + result |= uint(color.x * 255.0) << 0u; + result |= uint(color.y * 255.0) << 8u; + result |= uint(color.z * 255.0) << 16u; + result |= uint(color.w * 255.0) << 24u; + return result; +} + +void drw_debug_line(inout uint vertid, vec3 v1, vec3 v2, uint color) +{ + drw_debug_verts_buf[vertid++] = DRWDebugVert( + floatBitsToUint(v1.x), floatBitsToUint(v1.y), floatBitsToUint(v1.z), color); + drw_debug_verts_buf[vertid++] = DRWDebugVert( + floatBitsToUint(v2.x), floatBitsToUint(v2.y), floatBitsToUint(v2.z), color); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name API + * \{ */ + +/** + * Draw a line. + */ +void drw_debug_line(vec3 v1, vec3 v2, vec4 color) +{ + if (!drw_debug_draw_enable) { + return; + } + const uint v_needed = 2; + uint vertid = drw_debug_start_draw(v_needed); + if (vertid + v_needed < DRW_DEBUG_DRAW_VERT_MAX) { + drw_debug_line(vertid, v1, v2, drw_debug_color_pack(color)); + } +} +void drw_debug_line(vec3 v1, vec3 v2) +{ + drw_debug_line(v1, v2, drw_debug_default_color); +} + +/** + * Draw a quad contour. + */ +void drw_debug_quad(vec3 v1, vec3 v2, vec3 v3, vec3 v4, vec4 color) +{ + if (!drw_debug_draw_enable) { + return; + } + const uint v_needed = 8; + uint vertid = drw_debug_start_draw(v_needed); + if (vertid + v_needed < DRW_DEBUG_DRAW_VERT_MAX) { + uint pcolor = drw_debug_color_pack(color); + drw_debug_line(vertid, v1, v2, pcolor); + drw_debug_line(vertid, v2, v3, pcolor); + drw_debug_line(vertid, v3, v4, pcolor); + drw_debug_line(vertid, v4, v1, pcolor); + } +} +void drw_debug_quad(vec3 v1, vec3 v2, vec3 v3, vec3 v4) +{ + drw_debug_quad(v1, v2, v3, v4, drw_debug_default_color); +} + +/** + * Draw a point as octahedron wireframe. + */ +void drw_debug_point(vec3 p, float radius, vec4 color) +{ + if (!drw_debug_draw_enable) { + return; + } + vec3 c = vec3(radius, -radius, 0); + vec3 v1 = p + c.xzz; + vec3 v2 = p + c.zxz; + vec3 v3 = p + c.yzz; + vec3 v4 = p + c.zyz; + vec3 v5 = p + c.zzx; + vec3 v6 = p + c.zzy; + + const uint v_needed = 12 * 2; + uint vertid = drw_debug_start_draw(v_needed); + if (vertid + v_needed < DRW_DEBUG_DRAW_VERT_MAX) { + uint pcolor = drw_debug_color_pack(color); + drw_debug_line(vertid, v1, v2, pcolor); + drw_debug_line(vertid, v2, v3, pcolor); + drw_debug_line(vertid, v3, v4, pcolor); + drw_debug_line(vertid, v4, v1, pcolor); + drw_debug_line(vertid, v1, v5, pcolor); + drw_debug_line(vertid, v2, v5, pcolor); + drw_debug_line(vertid, v3, v5, pcolor); + drw_debug_line(vertid, v4, v5, pcolor); + drw_debug_line(vertid, v1, v6, pcolor); + drw_debug_line(vertid, v2, v6, pcolor); + drw_debug_line(vertid, v3, v6, pcolor); + drw_debug_line(vertid, v4, v6, pcolor); + } +} +void drw_debug_point(vec3 p, float radius) +{ + drw_debug_point(p, radius, drw_debug_default_color); +} +void drw_debug_point(vec3 p) +{ + drw_debug_point(p, 0.01); +} + +/** + * Draw a sphere wireframe as 3 axes circle. + */ +void drw_debug_sphere(vec3 p, float radius, vec4 color) +{ + if (!drw_debug_draw_enable) { + return; + } + const int circle_resolution = 16; + const uint v_needed = circle_resolution * 2 * 3; + uint vertid = drw_debug_start_draw(v_needed); + if (vertid + v_needed < DRW_DEBUG_DRAW_VERT_MAX) { + uint pcolor = drw_debug_color_pack(color); + for (int axis = 0; axis < 3; axis++) { + for (int edge = 0; edge < circle_resolution; edge++) { + float angle1 = (2.0 * 3.141592) * float(edge + 0) / float(circle_resolution); + vec3 p1 = vec3(cos(angle1), sin(angle1), 0.0) * radius; + p1 = vec3(p1[(0 + axis) % 3], p1[(1 + axis) % 3], p1[(2 + axis) % 3]); + + float angle2 = (2.0 * 3.141592) * float(edge + 1) / float(circle_resolution); + vec3 p2 = vec3(cos(angle2), sin(angle2), 0.0) * radius; + p2 = vec3(p2[(0 + axis) % 3], p2[(1 + axis) % 3], p2[(2 + axis) % 3]); + + drw_debug_line(vertid, p + p1, p + p2, pcolor); + } + } + } +} +void drw_debug_sphere(vec3 p, float radius) +{ + drw_debug_sphere(p, radius, drw_debug_default_color); +} + +/** + * Draw a matrix transformation as 3 colored axes. + */ +void drw_debug_matrix(mat4 mat, vec4 color) +{ + vec4 p[4] = vec4[4](vec4(0, 0, 0, 1), vec4(1, 0, 0, 1), vec4(0, 1, 0, 1), vec4(0, 0, 1, 1)); + for (int i = 0; i < 4; i++) { + p[i] = mat * p[i]; + p[i].xyz /= p[i].w; + } + drw_debug_line(p[0].xyz, p[0].xyz, vec4(1, 0, 0, 1)); + drw_debug_line(p[0].xyz, p[1].xyz, vec4(0, 1, 0, 1)); + drw_debug_line(p[0].xyz, p[2].xyz, vec4(0, 0, 1, 1)); +} +void drw_debug_matrix(mat4 mat) +{ + drw_debug_matrix(mat, drw_debug_default_color); +} + +/** + * Draw a matrix as a 2 units length bounding box, centered on origin. + */ +void drw_debug_matrix_as_bbox(mat4 mat, vec4 color) +{ + vec4 p[8] = vec4[8](vec4(-1, -1, -1, 1), + vec4(1, -1, -1, 1), + vec4(1, 1, -1, 1), + vec4(-1, 1, -1, 1), + vec4(-1, -1, 1, 1), + vec4(1, -1, 1, 1), + vec4(1, 1, 1, 1), + vec4(-1, 1, 1, 1)); + for (int i = 0; i < 8; i++) { + p[i] = mat * p[i]; + p[i].xyz /= p[i].w; + } + drw_debug_quad(p[0].xyz, p[1].xyz, p[2].xyz, p[3].xyz, color); + drw_debug_line(p[0].xyz, p[4].xyz, color); + drw_debug_line(p[1].xyz, p[5].xyz, color); + drw_debug_line(p[2].xyz, p[6].xyz, color); + drw_debug_line(p[3].xyz, p[7].xyz, color); + drw_debug_quad(p[4].xyz, p[5].xyz, p[6].xyz, p[7].xyz, color); +} +void drw_debug_matrix_as_bbox(mat4 mat) +{ + drw_debug_matrix_as_bbox(mat, drw_debug_default_color); +} + +/** \} */ diff --git a/source/blender/draw/intern/shaders/common_debug_print_lib.glsl b/source/blender/draw/intern/shaders/common_debug_print_lib.glsl new file mode 100644 index 00000000000..0c7f32bd00d --- /dev/null +++ b/source/blender/draw/intern/shaders/common_debug_print_lib.glsl @@ -0,0 +1,389 @@ + +/** + * Debug print implementation for shaders. + * + * `print()`: + * Log variable or strings inside the viewport. + * Using a unique non string argument will print the variable name with it. + * Concatenate by using multiple arguments. i.e: `print("Looped ", n, "times.")`. + * `drw_print_no_endl()`: + * Same as `print()` but does not finish the line. + * `drw_print_value()`: + * Display only the value of a variable. Does not finish the line. + * `drw_print_value_hex()`: + * Display only the hex representation of a variable. Does not finish the line. + * `drw_print_value_binary()`: Display only the binary representation of a + * variable. Does not finish the line. + * + * IMPORTANT: As it is now, it is not yet thread safe. Only print from one thread. You can use the + * IS_DEBUG_MOUSE_FRAGMENT macro in fragment shader to filter using mouse position or + * IS_FIRST_INVOCATION in compute shaders. + * + * NOTE: Floating point representation might not be very precise (see drw_print_value(float)). + * + * IMPORTANT: Multipler drawcalls can write to the buffer in sequence (if they are from different + * shgroups). However, we add barriers to support this case and it might change the application + * behavior. Uncomment DISABLE_DEBUG_SHADER_drw_print_BARRIER to remove the barriers if that + * happens. But then you are limited to a single invocation output. + * + * IMPORTANT: All of these are copied to the CPU debug libs (draw_debug.cc). They need to be kept + * in sync to write the same data. + */ + +/** Global switch option when you want to silence all prints from all shaders at once. */ +bool drw_debug_print_enable = true; + +/* Set drw_print_col to max value so we will start by creating a new line and get the correct + * threadsafe row. */ +uint drw_print_col = DRW_DEBUG_PRINT_WORD_WRAP_COLUMN; +uint drw_print_row = 0u; + +void drw_print_newline() +{ + if (!drw_debug_print_enable) { + return; + } + drw_print_col = 0u; + drw_print_row = atomicAdd(drw_debug_print_row_shared, 1u) + 1u; +} + +void drw_print_string_start(uint len) +{ + if (!drw_debug_print_enable) { + return; + } + /* Break before word. */ + if (drw_print_col + len > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { + drw_print_newline(); + } +} + +void drw_print_char4(uint data) +{ + if (!drw_debug_print_enable) { + return; + } + /* Convert into char stream. */ + for (; data != 0u; data >>= 8u) { + uint char1 = data & 0xFFu; + /* Check for null terminator. */ + if (char1 == 0x00) { + break; + } + uint cursor = atomicAdd(drw_debug_print_cursor, 1u); + /* NOTE: Skip the header manually. */ + cursor += 4; + if (cursor < DRW_DEBUG_PRINT_MAX) { + /* For future usage. (i.e: Color) */ + uint flags = 0u; + uint col = drw_print_col++; + uint drw_print_header = (flags << 24u) | (drw_print_row << 16u) | (col << 8u); + drw_debug_print_buf[cursor] = drw_print_header | char1; + /* Break word. */ + if (drw_print_col > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { + drw_print_newline(); + } + } + } +} + +/** + * NOTE(fclem): Strange behavior emerge when trying to increment the digit + * counter inside the append function. It looks like the compiler does not see + * it is referenced as an index for char4 and thus do not capture the right + * reference. I do not know if this is undefined behavior. As a matter of + * precaution, we implement all the append function separately. This behavior + * was observed on both Mesa & amdgpu-pro. + */ +/* Using ascii char code. Expect char1 to be less or equal to 0xFF. Appends chars to the right. */ +void drw_print_append_char(uint char1, inout uint char4) +{ + char4 = (char4 << 8u) | char1; +} + +void drw_print_append_digit(uint digit, inout uint char4) +{ + const uint char_A = 0x41u; + const uint char_0 = 0x30u; + bool is_hexadecimal = digit > 9u; + char4 = (char4 << 8u) | (is_hexadecimal ? (char_A + digit - 10u) : (char_0 + digit)); +} + +void drw_print_append_space(inout uint char4) +{ + char4 = (char4 << 8u) | 0x20u; +} + +void drw_print_value_binary(uint value) +{ + drw_print_no_endl("0b"); + drw_print_string_start(10u * 4u); + uint digits[10] = uint[10](0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u); + uint digit = 0u; + for (uint i = 0u; i < 32u; i++) { + drw_print_append_digit(((value >> i) & 1u), digits[digit / 4u]); + digit++; + if ((i % 4u) == 3u) { + drw_print_append_space(digits[digit / 4u]); + digit++; + } + } + /* Numbers are written from right to left. So we need to reverse the order. */ + for (int j = 9; j >= 0; j--) { + drw_print_char4(digits[j]); + } +} + +void drw_print_value_binary(int value) +{ + drw_print_value_binary(uint(value)); +} + +void drw_print_value_binary(float value) +{ + drw_print_value_binary(floatBitsToUint(value)); +} + +void drw_print_value_uint(uint value, const bool hex, bool is_negative, const bool is_unsigned) +{ + drw_print_string_start(3u * 4u); + const uint blank_value = hex ? 0x30303030u : 0x20202020u; + const uint prefix = hex ? 0x78302020u : 0x20202020u; + uint digits[3] = uint[3](blank_value, blank_value, prefix); + const uint base = hex ? 16u : 10u; + uint digit = 0u; + /* Add `u` suffix. */ + if (is_unsigned) { + drw_print_append_char('u', digits[digit / 4u]); + digit++; + } + /* Number's digits. */ + for (; value != 0u || digit == uint(is_unsigned); value /= base) { + drw_print_append_digit(value % base, digits[digit / 4u]); + digit++; + } + /* Add negative sign. */ + if (is_negative) { + drw_print_append_char('-', digits[digit / 4u]); + digit++; + } + /* Need to pad to uint alignment because we are issuing chars in "reverse". */ + for (uint i = digit % 4u; i < 4u && i > 0u; i++) { + drw_print_append_space(digits[digit / 4u]); + digit++; + } + /* Numbers are written from right to left. So we need to reverse the order. */ + for (int j = 2; j >= 0; j--) { + drw_print_char4(digits[j]); + } +} + +void drw_print_value_hex(uint value) +{ + drw_print_value_uint(value, true, false, false); +} + +void drw_print_value_hex(int value) +{ + drw_print_value_uint(uint(value), true, false, false); +} + +void drw_print_value_hex(float value) +{ + drw_print_value_uint(floatBitsToUint(value), true, false, false); +} + +void drw_print_value(uint value) +{ + drw_print_value_uint(value, false, false, true); +} + +void drw_print_value(int value) +{ + drw_print_value_uint(uint(abs(value)), false, (value < 0), false); +} + +void drw_print_value(bool value) +{ + if (value) { + drw_print_no_endl("true "); + } + else { + drw_print_no_endl("false"); + } +} + +/* NOTE(@fclem): This is homebrew and might not be 100% accurate (accuracy has + * not been tested and might dependent on compiler implementation). If unsure, + * use drw_print_value_hex and transcribe the value manually with another tool. */ +void drw_print_value(float val) +{ + /* We pad the string to match normal float values length. */ + if (isnan(val)) { + drw_print_no_endl(" NaN"); + return; + } + if (isinf(val)) { + if (sign(val) < 0.0) { + drw_print_no_endl(" -Inf"); + } + else { + drw_print_no_endl(" Inf"); + } + return; + } + + /* Adjusted for significant digits (6) with sign (1), decimal separator (1) + * and exponent (4). */ + const float significant_digits = 6.0; + drw_print_string_start(3u * 4u); + uint digits[3] = uint[3](0x20202020u, 0x20202020u, 0x20202020u); + + float exponent = floor(log(abs(val)) / log(10.0)); + bool display_exponent = exponent >= (significant_digits) || + exponent <= (-significant_digits + 1.0); + + float int_significant_digits = min(exponent + 1.0, significant_digits); + float dec_significant_digits = max(0.0, significant_digits - int_significant_digits); + /* Power to get to the rounding point. */ + float rounding_power = dec_significant_digits; + + if (val == 0.0 || isinf(exponent)) { + display_exponent = false; + int_significant_digits = dec_significant_digits = 1.0; + } + /* Remap to keep significant numbers count. */ + if (display_exponent) { + int_significant_digits = 1.0; + dec_significant_digits = significant_digits - int_significant_digits; + rounding_power = -exponent + dec_significant_digits; + } + /* Round at the last significant digit. */ + val = round(val * pow(10.0, rounding_power)); + /* Get back to final exponent. */ + val *= pow(10.0, -dec_significant_digits); + + float int_part; + float dec_part = modf(val, int_part); + + dec_part *= pow(10.0, dec_significant_digits); + + const uint base = 10u; + uint digit = 0u; + /* Exponent */ + uint value = uint(abs(exponent)); + if (display_exponent) { + for (int i = 0; value != 0u || i == 0; i++, value /= base) { + drw_print_append_digit(value % base, digits[digit / 4u]); + digit++; + } + /* Exponent sign. */ + uint sign_char = (exponent < 0.0) ? '-' : '+'; + drw_print_append_char(sign_char, digits[digit / 4u]); + digit++; + /* Exponent `e` suffix. */ + drw_print_append_char(0x65u, digits[digit / 4u]); + digit++; + } + /* Decimal part. */ + value = uint(abs(dec_part)); +#if 0 /* We don't do that because it makes unstable values really hard to \ + read. */ + /* Trim trailing zeros. */ + while ((value % base) == 0u) { + value /= base; + if (value == 0u) { + break; + } + } +#endif + if (value != 0u) { + for (int i = 0; value != 0u || i == 0; i++, value /= base) { + drw_print_append_digit(value % base, digits[digit / 4u]); + digit++; + } + /* Point separator. */ + drw_print_append_char('.', digits[digit / 4u]); + digit++; + } + /* Integer part. */ + value = uint(abs(int_part)); + for (int i = 0; value != 0u || i == 0; i++, value /= base) { + drw_print_append_digit(value % base, digits[digit / 4u]); + digit++; + } + /* Negative sign. */ + if (val < 0.0) { + drw_print_append_char('-', digits[digit / 4u]); + digit++; + } + /* Need to pad to uint alignment because we are issuing chars in "reverse". */ + for (uint i = digit % 4u; i < 4u && i > 0u; i++) { + drw_print_append_space(digits[digit / 4u]); + digit++; + } + /* Numbers are written from right to left. So we need to reverse the order. */ + for (int j = 2; j >= 0; j--) { + drw_print_char4(digits[j]); + } +} + +void drw_print_value(vec2 value) +{ + drw_print_no_endl("vec2(", value[0], ", ", value[1], ")"); +} + +void drw_print_value(vec3 value) +{ + drw_print_no_endl("vec3(", value[0], ", ", value[1], ", ", value[1], ")"); +} + +void drw_print_value(vec4 value) +{ + drw_print_no_endl("vec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +void drw_print_value(ivec2 value) +{ + drw_print_no_endl("ivec2(", value[0], ", ", value[1], ")"); +} + +void drw_print_value(ivec3 value) +{ + drw_print_no_endl("ivec3(", value[0], ", ", value[1], ", ", value[1], ")"); +} + +void drw_print_value(ivec4 value) +{ + drw_print_no_endl("ivec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +void drw_print_value(uvec2 value) +{ + drw_print_no_endl("uvec2(", value[0], ", ", value[1], ")"); +} + +void drw_print_value(uvec3 value) +{ + drw_print_no_endl("uvec3(", value[0], ", ", value[1], ", ", value[1], ")"); +} + +void drw_print_value(uvec4 value) +{ + drw_print_no_endl("uvec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} + +void drw_print_value(bvec2 value) +{ + drw_print_no_endl("bvec2(", value[0], ", ", value[1], ")"); +} + +void drw_print_value(bvec3 value) +{ + drw_print_no_endl("bvec3(", value[0], ", ", value[1], ", ", value[1], ")"); +} + +void drw_print_value(bvec4 value) +{ + drw_print_no_endl("bvec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); +} diff --git a/source/blender/draw/intern/shaders/common_debug_shape_lib.glsl b/source/blender/draw/intern/shaders/common_debug_shape_lib.glsl new file mode 100644 index 00000000000..538c55ce544 --- /dev/null +++ b/source/blender/draw/intern/shaders/common_debug_shape_lib.glsl @@ -0,0 +1,57 @@ + +/** + * Debug drawing of shapes. + */ + +#pragma BLENDER_REQUIRE(common_debug_draw_lib.glsl) +#pragma BLENDER_REQUIRE(common_shape_lib.glsl) + +void drw_debug(Box shape, vec4 color) +{ + drw_debug_quad(shape.corners[0], shape.corners[1], shape.corners[2], shape.corners[3], color); + drw_debug_line(shape.corners[0], shape.corners[4], color); + drw_debug_line(shape.corners[1], shape.corners[5], color); + drw_debug_line(shape.corners[2], shape.corners[6], color); + drw_debug_line(shape.corners[3], shape.corners[7], color); + drw_debug_quad(shape.corners[4], shape.corners[5], shape.corners[6], shape.corners[7], color); +} +void drw_debug(Box shape) +{ + drw_debug(shape, drw_debug_default_color); +} + +void drw_debug(Frustum shape, vec4 color) +{ + drw_debug_quad(shape.corners[0], shape.corners[1], shape.corners[2], shape.corners[3], color); + drw_debug_line(shape.corners[0], shape.corners[4], color); + drw_debug_line(shape.corners[1], shape.corners[5], color); + drw_debug_line(shape.corners[2], shape.corners[6], color); + drw_debug_line(shape.corners[3], shape.corners[7], color); + drw_debug_quad(shape.corners[4], shape.corners[5], shape.corners[6], shape.corners[7], color); +} +void drw_debug(Frustum shape) +{ + drw_debug(shape, drw_debug_default_color); +} + +void drw_debug(Pyramid shape, vec4 color) +{ + drw_debug_line(shape.corners[0], shape.corners[1], color); + drw_debug_line(shape.corners[0], shape.corners[2], color); + drw_debug_line(shape.corners[0], shape.corners[3], color); + drw_debug_line(shape.corners[0], shape.corners[4], color); + drw_debug_quad(shape.corners[1], shape.corners[2], shape.corners[3], shape.corners[4], color); +} +void drw_debug(Pyramid shape) +{ + drw_debug(shape, drw_debug_default_color); +} + +void drw_debug(Sphere shape, vec4 color) +{ + drw_debug_sphere(shape.center, shape.radius, color); +} +void drw_debug(Sphere shape) +{ + drw_debug(shape, drw_debug_default_color); +} diff --git a/source/blender/draw/intern/shaders/common_intersect_lib.glsl b/source/blender/draw/intern/shaders/common_intersect_lib.glsl new file mode 100644 index 00000000000..708d361029a --- /dev/null +++ b/source/blender/draw/intern/shaders/common_intersect_lib.glsl @@ -0,0 +1,399 @@ + +/** + * Intersection library used for culling. + * Results are meant to be conservative. + */ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_shape_lib.glsl) + +/* ---------------------------------------------------------------------- */ +/** \name Plane extraction functions. + * \{ */ + +/** \a v1 and \a v2 are vectors on the plane. \a p is a point on the plane. */ +vec4 isect_plane_setup(vec3 p, vec3 v1, vec3 v2) +{ + vec3 normal_to_plane = normalize(cross(v1, v2)); + return vec4(normal_to_plane, -dot(normal_to_plane, p)); +} + +struct IsectPyramid { + vec3 corners[5]; + vec4 planes[5]; +}; + +IsectPyramid isect_data_setup(Pyramid shape) +{ + vec3 A1 = shape.corners[1] - shape.corners[0]; + vec3 A2 = shape.corners[2] - shape.corners[0]; + vec3 A3 = shape.corners[3] - shape.corners[0]; + vec3 A4 = shape.corners[4] - shape.corners[0]; + vec3 S4 = shape.corners[4] - shape.corners[1]; + vec3 S2 = shape.corners[2] - shape.corners[1]; + + IsectPyramid data; + data.planes[0] = isect_plane_setup(shape.corners[0], A2, A1); + data.planes[1] = isect_plane_setup(shape.corners[0], A3, A2); + data.planes[2] = isect_plane_setup(shape.corners[0], A4, A3); + data.planes[3] = isect_plane_setup(shape.corners[0], A1, A4); + data.planes[4] = isect_plane_setup(shape.corners[1], S2, S4); + for (int i = 0; i < 5; i++) { + data.corners[i] = shape.corners[i]; + } + return data; +} + +struct IsectBox { + vec3 corners[8]; + vec4 planes[6]; +}; + +IsectBox isect_data_setup(Box shape) +{ + vec3 A1 = shape.corners[1] - shape.corners[0]; + vec3 A3 = shape.corners[3] - shape.corners[0]; + vec3 A4 = shape.corners[4] - shape.corners[0]; + + IsectBox data; + data.planes[0] = isect_plane_setup(shape.corners[0], A3, A1); + data.planes[1] = isect_plane_setup(shape.corners[0], A4, A3); + data.planes[2] = isect_plane_setup(shape.corners[0], A1, A4); + /* Assumes that the box is actually a box! */ + data.planes[3] = vec4(-data.planes[0].xyz, -dot(-data.planes[0].xyz, shape.corners[6])); + data.planes[4] = vec4(-data.planes[1].xyz, -dot(-data.planes[1].xyz, shape.corners[6])); + data.planes[5] = vec4(-data.planes[2].xyz, -dot(-data.planes[2].xyz, shape.corners[6])); + for (int i = 0; i < 8; i++) { + data.corners[i] = shape.corners[i]; + } + return data; +} + +struct IsectFrustum { + vec3 corners[8]; + vec4 planes[6]; +}; + +IsectFrustum isect_data_setup(Frustum shape) +{ + vec3 A1 = shape.corners[1] - shape.corners[0]; + vec3 A3 = shape.corners[3] - shape.corners[0]; + vec3 A4 = shape.corners[4] - shape.corners[0]; + vec3 B5 = shape.corners[5] - shape.corners[6]; + vec3 B7 = shape.corners[7] - shape.corners[6]; + vec3 B2 = shape.corners[2] - shape.corners[6]; + + IsectFrustum data; + data.planes[0] = isect_plane_setup(shape.corners[0], A3, A1); + data.planes[1] = isect_plane_setup(shape.corners[0], A4, A3); + data.planes[2] = isect_plane_setup(shape.corners[0], A1, A4); + data.planes[3] = isect_plane_setup(shape.corners[6], B7, B5); + data.planes[4] = isect_plane_setup(shape.corners[6], B5, B2); + data.planes[5] = isect_plane_setup(shape.corners[6], B2, B7); + for (int i = 0; i < 8; i++) { + data.corners[i] = shape.corners[i]; + } + return data; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name View Intersection functions. + * \{ */ + +bool intersect_view(Pyramid pyramid) +{ + bool intersects = true; + + /* Do Pyramid vertices vs Frustum planes. */ + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 5; ++v) { + float test = dot(drw_view.frustum_planes[p], vec4(pyramid.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + if (!intersects) { + return intersects; + } + + /* Now do Frustum vertices vs Pyramid planes. */ + IsectPyramid i_pyramid = isect_data_setup(pyramid); + for (int p = 0; p < 5; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_pyramid.planes[p], vec4(drw_view.frustum_corners[v].xyz, 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + return intersects; +} + +bool intersect_view(Box box) +{ + bool intersects = true; + + /* Do Box vertices vs Frustum planes. */ + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(drw_view.frustum_planes[p], vec4(box.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + if (!intersects) { + return intersects; + } + + /* Now do Frustum vertices vs Box planes. */ + IsectBox i_box = isect_data_setup(box); + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_box.planes[p], vec4(drw_view.frustum_corners[v].xyz, 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + return intersects; +} + +bool intersect_view(Sphere sphere) +{ + bool intersects = true; + + for (int p = 0; p < 6 && intersects; ++p) { + float dist_to_plane = dot(drw_view.frustum_planes[p], vec4(sphere.center, 1.0)); + if (dist_to_plane < -sphere.radius) { + intersects = false; + } + } + /* TODO reject false positive. */ + return intersects; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Shape vs. Shape Intersection functions. + * \{ */ + +bool intersect(IsectPyramid i_pyramid, Box box) +{ + bool intersects = true; + + /* Do Box vertices vs Pyramid planes. */ + for (int p = 0; p < 5; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_pyramid.planes[p], vec4(box.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + if (!intersects) { + return intersects; + } + + /* Now do Pyramid vertices vs Box planes. */ + IsectBox i_box = isect_data_setup(box); + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 5; ++v) { + float test = dot(i_box.planes[p], vec4(i_pyramid.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + return intersects; +} + +bool intersect(IsectFrustum i_frustum, Pyramid pyramid) +{ + bool intersects = true; + + /* Do Pyramid vertices vs Frustum planes. */ + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 5; ++v) { + float test = dot(i_frustum.planes[p], vec4(pyramid.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + if (!intersects) { + return intersects; + } + + /* Now do Frustum vertices vs Pyramid planes. */ + IsectPyramid i_pyramid = isect_data_setup(pyramid); + for (int p = 0; p < 5; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_pyramid.planes[p], vec4(i_frustum.corners[v].xyz, 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + return intersects; +} + +bool intersect(IsectFrustum i_frustum, Box box) +{ + bool intersects = true; + + /* Do Box vertices vs Frustum planes. */ + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_frustum.planes[p], vec4(box.corners[v], 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + if (!intersects) { + return intersects; + } + + /* Now do Frustum vertices vs Box planes. */ + IsectBox i_box = isect_data_setup(box); + for (int p = 0; p < 6; ++p) { + bool is_any_vertex_on_positive_side = false; + for (int v = 0; v < 8; ++v) { + float test = dot(i_box.planes[p], vec4(i_frustum.corners[v].xyz, 1.0)); + if (test > 0.0) { + is_any_vertex_on_positive_side = true; + break; + } + } + bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; + if (all_vertex_on_negative_side) { + intersects = false; + break; + } + } + + return intersects; +} + +bool intersect(IsectFrustum i_frustum, Sphere sphere) +{ + bool intersects = true; + + for (int p = 0; p < 8; ++p) { + float dist_to_plane = dot(i_frustum.planes[p], vec4(sphere.center, 1.0)); + if (dist_to_plane < -sphere.radius) { + intersects = false; + break; + } + } + return intersects; +} + +bool intersect(Cone cone, Sphere sphere) +{ + /** + * Following "Improve Tile-based Light Culling with Spherical-sliced Cone" + * by Eric Zhang + * https://lxjk.github.io/2018/03/25/Improve-Tile-based-Light-Culling-with-Spherical-sliced-Cone.html + */ + float sphere_distance = length(sphere.center); + float sphere_distance_rcp = safe_rcp(sphere_distance); + float sphere_sin = saturate(sphere.radius * sphere_distance_rcp); + float sphere_cos = sqrt(1.0 - sphere_sin * sphere_sin); + float cone_aperture_sin = sqrt(1.0 - cone.angle_cos * cone.angle_cos); + + float cone_sphere_center_cos = dot(sphere.center * sphere_distance_rcp, cone.direction); + /* cos(A+B) = cos(A) * cos(B) - sin(A) * sin(B). */ + float cone_sphere_angle_sum_cos = (sphere.radius > sphere_distance) ? + -1.0 : + (cone.angle_cos * sphere_cos - + cone_aperture_sin * sphere_sin); + /* Comparing cosines instead of angles since we are interested + * only in the monotonic region [0 .. M_PI / 2]. This saves costly acos() calls. */ + bool intersects = (cone_sphere_center_cos >= cone_sphere_angle_sum_cos); + + return intersects; +} + +bool intersect(Circle circle_a, Circle circle_b) +{ + return distance_squared(circle_a.center, circle_b.center) < + sqr(circle_a.radius + circle_b.radius); +} + +/** \} */ 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..71460c39285 100644 --- a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl +++ b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl @@ -5,11 +5,28 @@ /** \name Math intersection & projection functions. * \{ */ +vec4 plane_from_quad(vec3 v0, vec3 v1, vec3 v2, vec3 v3) +{ + vec3 nor = normalize(cross(v2 - v1, v0 - v1) + cross(v0 - v3, v2 - v3)); + return vec4(nor, -dot(nor, v2)); +} + +vec4 plane_from_tri(vec3 v0, vec3 v1, vec3 v2) +{ + vec3 nor = normalize(cross(v2 - v1, v0 - v1)); + return vec4(nor, -dot(nor, v2)); +} + float point_plane_projection_dist(vec3 line_origin, vec3 plane_origin, vec3 plane_normal) { 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 +121,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. diff --git a/source/blender/draw/intern/shaders/common_math_lib.glsl b/source/blender/draw/intern/shaders/common_math_lib.glsl index 51f3c890df8..e3734939b3f 100644 --- a/source/blender/draw/intern/shaders/common_math_lib.glsl +++ b/source/blender/draw/intern/shaders/common_math_lib.glsl @@ -116,8 +116,8 @@ bool flag_test(int flag, int val) { return (flag & val) != 0; } void set_flag_from_test(inout uint value, bool test, uint flag) { if (test) { value |= flag; } else { value &= ~flag; } } void set_flag_from_test(inout int value, bool test, int flag) { if (test) { value |= flag; } else { value &= ~flag; } } -#define weighted_sum(val0, val1, val2, val3, weights) ((val0 * weights[0] + val1 * weights[1] + val2 * weights[2] + val3 * weights[3]) * safe_rcp(sum(weights))); -#define weighted_sum_array(val, weights) ((val[0] * weights[0] + val[1] * weights[1] + val[2] * weights[2] + val[3] * weights[3]) * safe_rcp(sum(weights))); +#define weighted_sum(val0, val1, val2, val3, weights) ((val0 * weights[0] + val1 * weights[1] + val2 * weights[2] + val3 * weights[3]) * safe_rcp(sum(weights))) +#define weighted_sum_array(val, weights) ((val[0] * weights[0] + val[1] * weights[1] + val[2] * weights[2] + val[3] * weights[3]) * safe_rcp(sum(weights))) /* clang-format on */ @@ -130,12 +130,17 @@ void set_flag_from_test(inout int value, bool test, int flag) { if (test) { valu #define in_texture_range(texel, tex) \ (all(greaterThanEqual(texel, ivec2(0))) && all(lessThan(texel, textureSize(tex, 0).xy))) -uint divide_ceil_u(uint visible_count, uint divisor) +uint divide_ceil(uint visible_count, uint divisor) { return (visible_count + (divisor - 1u)) / divisor; } -int divide_ceil_i(int visible_count, int divisor) +int divide_ceil(int visible_count, int divisor) +{ + return (visible_count + (divisor - 1)) / divisor; +} + +ivec2 divide_ceil(ivec2 visible_count, ivec2 divisor) { return (visible_count + (divisor - 1)) / divisor; } diff --git a/source/blender/draw/intern/shaders/common_shape_lib.glsl b/source/blender/draw/intern/shaders/common_shape_lib.glsl new file mode 100644 index 00000000000..f2c8bf0faaf --- /dev/null +++ b/source/blender/draw/intern/shaders/common_shape_lib.glsl @@ -0,0 +1,202 @@ + +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) + +/** + * Geometric shape structures. + * Some constructors might seems redundant but are here to make the API cleaner and + * allow for more than one constructor per type. + */ + +/* ---------------------------------------------------------------------- */ +/** \name Circle + * \{ */ + +struct Circle { + vec2 center; + float radius; +}; + +Circle shape_circle(vec2 center, float radius) +{ + return Circle(center, radius); +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Sphere + * \{ */ + +struct Sphere { + vec3 center; + float radius; +}; + +Sphere shape_sphere(vec3 center, float radius) +{ + return Sphere(center, radius); +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Box + * \{ */ + +struct Box { + vec3 corners[8]; +}; + +/* Construct box from 4 basis points. */ +Box shape_box(vec3 v000, vec3 v100, vec3 v010, vec3 v001) +{ + v100 -= v000; + v010 -= v000; + v001 -= v000; + Box box; + box.corners[0] = v000; + box.corners[1] = v000 + v100; + box.corners[2] = v000 + v010 + v100; + box.corners[3] = v000 + v010; + box.corners[4] = box.corners[0] + v001; + box.corners[5] = box.corners[1] + v001; + box.corners[6] = box.corners[2] + v001; + box.corners[7] = box.corners[3] + v001; + return box; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Square Pyramid + * \{ */ + +struct Pyramid { + /* Apex is the first. Base vertices are in clockwise order from front view. */ + vec3 corners[5]; +}; + +/** + * Regular Square Pyramid (can be oblique). + * Use this corner order. + * (Top-Down View of the pyramid) + * <pre> + * + * Y + * | + * | + * .-----X + * + * 4-----------3 + * | \ / | + * | \ / | + * | 0 | + * | / \ | + * | / \ | + * 1-----------2 + * </pre> + * base_corner_00 is vertex 1 + * base_corner_01 is vertex 2 + * base_corner_10 is vertex 4 + */ +Pyramid shape_pyramid(vec3 apex, vec3 base_corner_00, vec3 base_corner_01, vec3 base_corner_10) +{ + Pyramid pyramid; + pyramid.corners[0] = apex; + pyramid.corners[1] = base_corner_00; + pyramid.corners[2] = base_corner_01; + pyramid.corners[3] = base_corner_10 + (base_corner_01 - base_corner_00); + pyramid.corners[4] = base_corner_10; + return pyramid; +} + +/** + * Regular Square Pyramid. + * <pre> + * + * Y + * | + * | + * .-----X + * + * 4-----Y-----3 + * | \ | / | + * | \ | / | + * | 0-----X + * | / \ | + * | / \ | + * 1-----------2 + * </pre> + * base_center_pos_x is vector from base center to X + * base_center_pos_y is vector from base center to Y + */ +Pyramid shape_pyramid_non_oblique(vec3 apex, + vec3 base_center, + vec3 base_center_pos_x, + vec3 base_center_pos_y) +{ + Pyramid pyramid; + pyramid.corners[0] = apex; + pyramid.corners[1] = base_center - base_center_pos_x - base_center_pos_y; + pyramid.corners[2] = base_center + base_center_pos_x - base_center_pos_y; + pyramid.corners[3] = base_center + base_center_pos_x + base_center_pos_y; + pyramid.corners[4] = base_center - base_center_pos_x + base_center_pos_y; + return pyramid; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Frustum + * \{ */ + +struct Frustum { + vec3 corners[8]; +}; + +/** + * Use this corner order. + * <pre> + * + * Z Y + * | / + * |/ + * .-----X + * 2----------6 + * /| /| + * / | / | + * 1----------5 | + * | | | | + * | 3-------|--7 + * | / | / + * |/ |/ + * 0----------4 + * </pre> + */ +Frustum shape_frustum(vec3 corners[8]) +{ + Frustum frustum; + for (int i = 0; i < 8; i++) { + frustum.corners[i] = corners[i]; + } + return frustum; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Cone + * \{ */ + +/* Cone at orign with no height. */ +struct Cone { + vec3 direction; + float angle_cos; +}; + +Cone shape_cone(vec3 direction, float angle_cosine) +{ + return Cone(direction, angle_cosine); +} + +/** \} */ diff --git a/source/blender/draw/intern/shaders/common_view_lib.glsl b/source/blender/draw/intern/shaders/common_view_lib.glsl index 8eecaa46b58..8ab2ef10e4c 100644 --- a/source/blender/draw/intern/shaders/common_view_lib.glsl +++ b/source/blender/draw/intern/shaders/common_view_lib.glsl @@ -37,6 +37,9 @@ layout(std140) uniform viewBlock # endif #endif +#define IS_DEBUG_MOUSE_FRAGMENT (ivec2(gl_FragCoord) == drw_view.mouse_pixel) +#define IS_FIRST_INVOCATION (gl_GlobalInvocationID == uvec3(0)) + #define ViewNear (ViewVecs[0].w) #define ViewFar (ViewVecs[1].w) diff --git a/source/blender/draw/intern/shaders/draw_debug_draw_display_frag.glsl b/source/blender/draw/intern/shaders/draw_debug_draw_display_frag.glsl new file mode 100644 index 00000000000..3fc5294b024 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_debug_draw_display_frag.glsl @@ -0,0 +1,9 @@ + +/** + * Display debug edge list. + **/ + +void main() +{ + out_color = interp.color; +} diff --git a/source/blender/draw/intern/shaders/draw_debug_draw_display_vert.glsl b/source/blender/draw/intern/shaders/draw_debug_draw_display_vert.glsl new file mode 100644 index 00000000000..ab76df819d5 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_debug_draw_display_vert.glsl @@ -0,0 +1,15 @@ + +/** + * Display debug edge list. + **/ + +void main() +{ + /* Skip the first vertex containing header data. */ + DRWDebugVert vert = drw_debug_verts_buf[gl_VertexID + 1]; + vec3 pos = uintBitsToFloat(uvec3(vert.pos0, vert.pos1, vert.pos2)); + vec4 col = vec4((uvec4(vert.color) >> uvec4(0, 8, 16, 24)) & 0xFFu) / 255.0; + + interp.color = col; + gl_Position = persmat * vec4(pos, 1.0); +} diff --git a/source/blender/draw/intern/shaders/draw_debug_info.hh b/source/blender/draw/intern/shaders/draw_debug_info.hh new file mode 100644 index 00000000000..893a5e537d9 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_debug_info.hh @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +/* -------------------------------------------------------------------- */ +/** \name Debug print + * + * Allows print() function to have logging support inside shaders. + * \{ */ + +GPU_SHADER_CREATE_INFO(draw_debug_print) + .typedef_source("draw_shader_shared.h") + .storage_buf(7, Qualifier::READ_WRITE, "uint", "drw_debug_print_buf[]"); + +GPU_SHADER_INTERFACE_INFO(draw_debug_print_display_iface, "").flat(Type::UINT, "char_index"); + +GPU_SHADER_CREATE_INFO(draw_debug_print_display) + .do_static_compilation(true) + .typedef_source("draw_shader_shared.h") + .storage_buf(7, Qualifier::READ, "uint", "drw_debug_print_buf[]") + .vertex_out(draw_debug_print_display_iface) + .fragment_out(0, Type::VEC4, "out_color") + .vertex_source("draw_debug_print_display_vert.glsl") + .fragment_source("draw_debug_print_display_frag.glsl") + .additional_info("draw_view"); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Debug draw shapes + * + * Allows to draw lines and points just like the DRW_debug module functions. + * \{ */ + +GPU_SHADER_CREATE_INFO(draw_debug_draw) + .typedef_source("draw_shader_shared.h") + .storage_buf(6, Qualifier::READ_WRITE, "DRWDebugVert", "drw_debug_verts_buf[]"); + +GPU_SHADER_INTERFACE_INFO(draw_debug_draw_display_iface, "interp").flat(Type::VEC4, "color"); + +GPU_SHADER_CREATE_INFO(draw_debug_draw_display) + .do_static_compilation(true) + .typedef_source("draw_shader_shared.h") + .storage_buf(6, Qualifier::READ, "DRWDebugVert", "drw_debug_verts_buf[]") + .vertex_out(draw_debug_draw_display_iface) + .fragment_out(0, Type::VEC4, "out_color") + .push_constant(Type::MAT4, "persmat") + .vertex_source("draw_debug_draw_display_vert.glsl") + .fragment_source("draw_debug_draw_display_frag.glsl") + .additional_info("draw_view"); + +/** \} */ diff --git a/source/blender/draw/intern/shaders/draw_debug_print_display_frag.glsl b/source/blender/draw/intern/shaders/draw_debug_print_display_frag.glsl new file mode 100644 index 00000000000..4e0d980637f --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_debug_print_display_frag.glsl @@ -0,0 +1,133 @@ + +/** + * Display characters using an ascii table. + **/ + +#pragma BLENDER_REQUIRE(common_math_lib.glsl) + +bool char_intersect(uvec2 bitmap_position) +{ + /* Using 8x8 = 64bits = uvec2. */ + uvec2 ascii_bitmap[96] = uvec2[96](uvec2(0x00000000u, 0x00000000u), + uvec2(0x18001800u, 0x183c3c18u), + uvec2(0x00000000u, 0x36360000u), + uvec2(0x7f363600u, 0x36367f36u), + uvec2(0x301f0c00u, 0x0c3e031eu), + uvec2(0x0c666300u, 0x00633318u), + uvec2(0x3b336e00u, 0x1c361c6eu), + uvec2(0x00000000u, 0x06060300u), + uvec2(0x060c1800u, 0x180c0606u), + uvec2(0x180c0600u, 0x060c1818u), + uvec2(0x3c660000u, 0x00663cffu), + uvec2(0x0c0c0000u, 0x000c0c3fu), + uvec2(0x000c0c06u, 0x00000000u), + uvec2(0x00000000u, 0x0000003fu), + uvec2(0x000c0c00u, 0x00000000u), + uvec2(0x06030100u, 0x6030180cu), + uvec2(0x6f673e00u, 0x3e63737bu), + uvec2(0x0c0c3f00u, 0x0c0e0c0cu), + uvec2(0x06333f00u, 0x1e33301cu), + uvec2(0x30331e00u, 0x1e33301cu), + uvec2(0x7f307800u, 0x383c3633u), + uvec2(0x30331e00u, 0x3f031f30u), + uvec2(0x33331e00u, 0x1c06031fu), + uvec2(0x0c0c0c00u, 0x3f333018u), + uvec2(0x33331e00u, 0x1e33331eu), + uvec2(0x30180e00u, 0x1e33333eu), + uvec2(0x000c0c00u, 0x000c0c00u), + uvec2(0x000c0c06u, 0x000c0c00u), + uvec2(0x060c1800u, 0x180c0603u), + uvec2(0x003f0000u, 0x00003f00u), + uvec2(0x180c0600u, 0x060c1830u), + uvec2(0x0c000c00u, 0x1e333018u), + uvec2(0x7b031e00u, 0x3e637b7bu), + uvec2(0x3f333300u, 0x0c1e3333u), + uvec2(0x66663f00u, 0x3f66663eu), + uvec2(0x03663c00u, 0x3c660303u), + uvec2(0x66361f00u, 0x1f366666u), + uvec2(0x16467f00u, 0x7f46161eu), + uvec2(0x16060f00u, 0x7f46161eu), + uvec2(0x73667c00u, 0x3c660303u), + uvec2(0x33333300u, 0x3333333fu), + uvec2(0x0c0c1e00u, 0x1e0c0c0cu), + uvec2(0x33331e00u, 0x78303030u), + uvec2(0x36666700u, 0x6766361eu), + uvec2(0x46667f00u, 0x0f060606u), + uvec2(0x6b636300u, 0x63777f7fu), + uvec2(0x73636300u, 0x63676f7bu), + uvec2(0x63361c00u, 0x1c366363u), + uvec2(0x06060f00u, 0x3f66663eu), + uvec2(0x3b1e3800u, 0x1e333333u), + uvec2(0x36666700u, 0x3f66663eu), + uvec2(0x38331e00u, 0x1e33070eu), + uvec2(0x0c0c1e00u, 0x3f2d0c0cu), + uvec2(0x33333f00u, 0x33333333u), + uvec2(0x331e0c00u, 0x33333333u), + uvec2(0x7f776300u, 0x6363636bu), + uvec2(0x1c366300u, 0x6363361cu), + uvec2(0x0c0c1e00u, 0x3333331eu), + uvec2(0x4c667f00u, 0x7f633118u), + uvec2(0x06061e00u, 0x1e060606u), + uvec2(0x30604000u, 0x03060c18u), + uvec2(0x18181e00u, 0x1e181818u), + uvec2(0x00000000u, 0x081c3663u), + uvec2(0x000000ffu, 0x00000000u), + uvec2(0x00000000u, 0x0c0c1800u), + uvec2(0x3e336e00u, 0x00001e30u), + uvec2(0x66663b00u, 0x0706063eu), + uvec2(0x03331e00u, 0x00001e33u), + uvec2(0x33336e00u, 0x3830303eu), + uvec2(0x3f031e00u, 0x00001e33u), + uvec2(0x06060f00u, 0x1c36060fu), + uvec2(0x333e301fu, 0x00006e33u), + uvec2(0x66666700u, 0x0706366eu), + uvec2(0x0c0c1e00u, 0x0c000e0cu), + uvec2(0x3033331eu, 0x30003030u), + uvec2(0x1e366700u, 0x07066636u), + uvec2(0x0c0c1e00u, 0x0e0c0c0cu), + uvec2(0x7f6b6300u, 0x0000337fu), + uvec2(0x33333300u, 0x00001f33u), + uvec2(0x33331e00u, 0x00001e33u), + uvec2(0x663e060fu, 0x00003b66u), + uvec2(0x333e3078u, 0x00006e33u), + uvec2(0x66060f00u, 0x00003b6eu), + uvec2(0x1e301f00u, 0x00003e03u), + uvec2(0x0c2c1800u, 0x080c3e0cu), + uvec2(0x33336e00u, 0x00003333u), + uvec2(0x331e0c00u, 0x00003333u), + uvec2(0x7f7f3600u, 0x0000636bu), + uvec2(0x1c366300u, 0x00006336u), + uvec2(0x333e301fu, 0x00003333u), + uvec2(0x0c263f00u, 0x00003f19u), + uvec2(0x0c0c3800u, 0x380c0c07u), + uvec2(0x18181800u, 0x18181800u), + uvec2(0x0c0c0700u, 0x070c0c38u), + uvec2(0x00000000u, 0x6e3b0000u), + uvec2(0x00000000u, 0x00000000u)); + + if (!in_range_inclusive(bitmap_position, uvec2(0), uvec2(7))) { + return false; + } + uint char_bits = ascii_bitmap[char_index][bitmap_position.y >> 2u & 1u]; + char_bits = (char_bits >> ((bitmap_position.y & 3u) * 8u + bitmap_position.x)); + return (char_bits & 1u) != 0u; +} + +void main() +{ + uvec2 bitmap_position = uvec2(gl_PointCoord.xy * 8.0); + /* Point coord start from top left corner. But layout is from bottom to top. */ + bitmap_position.y = 7 - bitmap_position.y; + + if (char_intersect(bitmap_position)) { + out_color = vec4(1); + } + else if (char_intersect(bitmap_position + uvec2(0, 1))) { + /* Shadow */ + out_color = vec4(0, 0, 0, 1); + } + else { + /* Transparent Background for ease of read. */ + out_color = vec4(0, 0, 0, 0.2); + } +} diff --git a/source/blender/draw/intern/shaders/draw_debug_print_display_vert.glsl b/source/blender/draw/intern/shaders/draw_debug_print_display_vert.glsl new file mode 100644 index 00000000000..f67e9d3f9e0 --- /dev/null +++ b/source/blender/draw/intern/shaders/draw_debug_print_display_vert.glsl @@ -0,0 +1,29 @@ + +/** + * Display characters using an ascii table. Outputs one point per character. + **/ + +#pragma BLENDER_REQUIRE(common_view_lib.glsl) + +void main() +{ + /* Skip first 4 chars containing header data. */ + uint char_data = drw_debug_print_buf[gl_VertexID + 4]; + char_index = (char_data & 0xFFu) - 0x20u; + + /* Discard invalid chars. */ + if (char_index >= 96u) { + gl_Position = vec4(-1); + gl_PointSize = 0.0; + return; + } + uint row = (char_data >> 16u) & 0xFFu; + uint col = (char_data >> 8u) & 0xFFu; + + float char_size = 16.0; + /* Change anchor point to the top left. */ + vec2 pos_on_screen = char_size * vec2(col, row) + char_size * 4; + gl_Position = vec4( + pos_on_screen * drw_view.viewport_size_inverse * vec2(2.0, -2.0) - vec2(1.0, -1.0), 0, 1); + gl_PointSize = char_size; +} |