diff options
Diffstat (limited to 'source/blender/draw/engines/eevee/eevee_depth_of_field.c')
-rw-r--r-- | source/blender/draw/engines/eevee/eevee_depth_of_field.c | 1052 |
1 files changed, 931 insertions, 121 deletions
diff --git a/source/blender/draw/engines/eevee/eevee_depth_of_field.c b/source/blender/draw/engines/eevee/eevee_depth_of_field.c index 92ba526c67c..8c0a44b2c9b 100644 --- a/source/blender/draw/engines/eevee/eevee_depth_of_field.c +++ b/source/blender/draw/engines/eevee/eevee_depth_of_field.c @@ -20,6 +20,13 @@ * \ingroup draw_engine * * 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 undersampling. + * - 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" @@ -40,10 +47,158 @@ #include "GPU_texture.h" #include "eevee_private.h" +#define CAMERA_JITTER_RING_DENSITY 6 + +static float coc_radius_from_camera_depth(bool is_ortho, EEVEE_EffectsInfo *fx, float camera_depth) +{ + float multiplier = fx->dof_coc_params[0]; + float bias = fx->dof_coc_params[1]; + if (multiplier == 0.0f || bias == 0.0f) { + return 0.0f; + } + else if (is_ortho) { + return (camera_depth + multiplier / bias) * multiplier; + } + else { + return multiplier / camera_depth - bias; + } +} + +static float polygon_sides_length(float sides_count) +{ + return 2.0 * sin(M_PI / sides_count); +} + +/* Returns intersection ratio between the radius edge at theta and the polygon edge. + * Start first corners at theta == 0. */ +static 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. + * Expect theta to be in [0..2pi] range. */ +static 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 = polygon_sides_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; +} + +static int dof_jitter_total_sample_count(int ring_density, int ring_count) +{ + return ((ring_count * ring_count + ring_count) / 2) * ring_density + 1; +} + +bool EEVEE_depth_of_field_jitter_get(EEVEE_EffectsInfo *fx, + float r_jitter[2], + float *r_focus_distance) +{ + if (fx->dof_jitter_radius == 0.0f) { + return false; + } + + int ring_density = CAMERA_JITTER_RING_DENSITY; + int ring_count = fx->dof_jitter_ring_count; + int sample_count = dof_jitter_total_sample_count(ring_density, ring_count); + + int s = fx->taa_current_sample - 1; + + int ring = 0; + int ring_sample_count = 1; + int ring_sample = 1; + + s = s * (ring_density - 1); + s = s % sample_count; + + int samples_passed = 1; + while (s >= samples_passed) { + ring++; + ring_sample_count = ring * ring_density; + ring_sample = s - samples_passed; + ring_sample = (ring_sample + 1) % ring_sample_count; + samples_passed += ring_sample_count; + } + + r_jitter[0] = (float)ring / ring_count; + r_jitter[1] = (float)ring_sample / ring_sample_count; + + { + /* Bokeh shape parametrisation */ + float r = r_jitter[0]; + float T = r_jitter[1] * 2.0f * M_PI; + + if (fx->dof_jitter_blades >= 3.0f) { + T = circle_to_polygon_angle(fx->dof_jitter_blades, T); + r *= circle_to_polygon_radius(fx->dof_jitter_blades, T); + } + + T += fx->dof_bokeh_rotation; + + r_jitter[0] = r * cosf(T); + r_jitter[1] = r * sinf(T); + + mul_v2_v2(r_jitter, fx->dof_bokeh_aniso); + } + + mul_v2_fl(r_jitter, fx->dof_jitter_radius); + + *r_focus_distance = fx->dof_jitter_focus; + return true; +} + +int EEVEE_depth_of_field_sample_count_get(EEVEE_EffectsInfo *fx, + int sample_count, + int *r_ring_count) +{ + if (fx->dof_jitter_radius == 0.0f) { + if (r_ring_count != NULL) { + *r_ring_count = 0; + } + return 1; + } + + if (sample_count == TAA_MAX_SAMPLE) { + /* Special case for viewport continuous rendering. We clamp to a max sample to avoid the + * jittered dof never converging. */ + sample_count = 1024; + } + /* Inversion of dof_jitter_total_sample_count. */ + float x = 2.0f * (sample_count - 1.0f) / CAMERA_JITTER_RING_DENSITY; + /* Solving polynomial. We only search positive solution. */ + float discriminant = 1.0f + 4.0f * x; + int ring_count = ceilf(0.5f * (sqrt(discriminant) - 1.0f)); + + sample_count = dof_jitter_total_sample_count(CAMERA_JITTER_RING_DENSITY, ring_count); + + if (r_ring_count != NULL) { + *r_ring_count = ring_count; + } + return sample_count; +} + int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata, Object *camera) { + EEVEE_TextureList *txl = vedata->txl; EEVEE_StorageList *stl = vedata->stl; EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_EffectsInfo *effects = stl->effects; @@ -57,59 +212,30 @@ int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata), RegionView3D *rv3d = draw_ctx->rv3d; const float *viewport_size = DRW_viewport_size_get(); - /* Retrieve Near and Far distance */ - effects->dof_near_far[0] = -cam->clip_start; - effects->dof_near_far[1] = -cam->clip_end; - - int buffer_size[2] = {(int)viewport_size[0] / 2, (int)viewport_size[1] / 2}; + effects->dof_hq_slight_focus = (scene_eval->eevee.flag & SCE_EEVEE_DOF_HQ_SLIGHT_FOCUS) != 0; - buffer_size[0] = max_ii(1, buffer_size[0]); - buffer_size[1] = max_ii(1, buffer_size[1]); - - eGPUTextureFormat down_format = DRW_state_draw_background() ? GPU_R11F_G11F_B10F : GPU_RGBA16F; - - effects->dof_down_near = DRW_texture_pool_query_2d( - buffer_size[0], buffer_size[1], down_format, &draw_engine_eevee_type); - effects->dof_down_far = DRW_texture_pool_query_2d( - buffer_size[0], buffer_size[1], down_format, &draw_engine_eevee_type); - effects->dof_coc = DRW_texture_pool_query_2d( - buffer_size[0], buffer_size[1], GPU_RG16F, &draw_engine_eevee_type); - - GPU_framebuffer_ensure_config(&fbl->dof_down_fb, - {GPU_ATTACHMENT_NONE, - GPU_ATTACHMENT_TEXTURE(effects->dof_down_near), - GPU_ATTACHMENT_TEXTURE(effects->dof_down_far), - GPU_ATTACHMENT_TEXTURE(effects->dof_coc)}); - - /* Go full 32bits for rendering and reduce the color artifacts. */ - eGPUTextureFormat fb_format = DRW_state_is_image_render() ? GPU_RGBA32F : GPU_RGBA16F; - - effects->dof_blur = DRW_texture_pool_query_2d( - buffer_size[0] * 2, buffer_size[1], fb_format, &draw_engine_eevee_type); - - GPU_framebuffer_ensure_config(&fbl->dof_scatter_fb, - { - GPU_ATTACHMENT_NONE, - GPU_ATTACHMENT_TEXTURE(effects->dof_blur), - }); - - if (!DRW_state_draw_background()) { - effects->dof_blur_alpha = DRW_texture_pool_query_2d( - buffer_size[0] * 2, buffer_size[1], GPU_R32F, &draw_engine_eevee_type); - GPU_framebuffer_texture_attach(fbl->dof_scatter_fb, effects->dof_blur_alpha, 1, 0); - } + /* Retrieve Near and Far distance */ + effects->dof_coc_near_dist = -cam->clip_start; + effects->dof_coc_far_dist = -cam->clip_end; /* Parameters */ - /* TODO UI Options */ + bool is_ortho = cam->type == CAM_ORTHO; float fstop = cam->dof.aperture_fstop; float blades = cam->dof.aperture_blades; float rotation = cam->dof.aperture_rotation; - float ratio = 1.0f / cam->dof.aperture_ratio; + float ratio = 1.0f / max_ff(cam->dof.aperture_ratio, 0.00001f); float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y); float focus_dist = BKE_camera_object_dof_distance(camera); float focal_len = cam->lens; - const float scale_camera = 0.001f; + if (is_ortho) { + /* (fclem) A bit of black magic here. I don't know if this is correct. */ + fstop *= 1.3f; + focal_len = 1.0f; + sensor = cam->ortho_scale; + } + + const float scale_camera = (is_ortho) ? 1.0 : 0.001f; /* we want radius here for the aperture number */ float aperture = 0.5f * scale_camera * focal_len / fstop; float focal_len_scaled = scale_camera * focal_len; @@ -119,93 +245,724 @@ int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata), sensor_scaled *= rv3d->viewcamtexcofac[0]; } - effects->dof_params[1] = aperture * fabsf(focal_len_scaled / (focus_dist - focal_len_scaled)); - effects->dof_params[1] *= viewport_size[0] / sensor_scaled; - effects->dof_params[0] = -focus_dist * effects->dof_params[1]; + if (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 *= ratio; + } + + effects->dof_coc_params[1] = -aperture * + fabsf(focal_len_scaled / (focus_dist - focal_len_scaled)); + /* FIXME(fclem) This is broken for vertically fit sensor. */ + effects->dof_coc_params[1] *= viewport_size[0] / sensor_scaled; + + if ((scene_eval->eevee.flag & SCE_EEVEE_DOF_JITTER) != 0) { + effects->dof_jitter_radius = effects->dof_coc_params[1]; + effects->dof_jitter_focus = focus_dist; + effects->dof_jitter_blades = blades; + + int sample_count = EEVEE_temporal_sampling_sample_count_get(scene_eval, stl); + sample_count = EEVEE_depth_of_field_sample_count_get( + effects, sample_count, &effects->dof_jitter_ring_count); + + if (effects->dof_jitter_ring_count == 0) { + effects->dof_jitter_radius = 0.0f; + } + else { + /* 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(sample_count); + float user_overblur = scene_eval->eevee.bokeh_overblur / 100.0f; + + effects->dof_coc_params[1] *= minimal_overblur + user_overblur; + /* Avoid dilating the shape. Overblur only soften. */ + effects->dof_jitter_radius -= effects->dof_coc_params[1]; + } + } + else { + effects->dof_jitter_radius = 0.0f; + } + + if (is_ortho) { + /* (fclem) A bit of black magic here. Needed to match cycles. */ + effects->dof_coc_params[1] *= 0.225; + } + + effects->dof_coc_params[0] = -focus_dist * effects->dof_coc_params[1]; + + effects->dof_bokeh_blades = blades; + effects->dof_bokeh_rotation = rotation; + effects->dof_bokeh_aniso[0] = min_ff(ratio, 1.0f); + effects->dof_bokeh_aniso[1] = min_ff(1.0f / ratio, 1.0f); + effects->dof_bokeh_max_size = scene_eval->eevee.bokeh_max_size; + + copy_v2_v2(effects->dof_bokeh_aniso_inv, effects->dof_bokeh_aniso); + invert_v2(effects->dof_bokeh_aniso_inv); + + effects->dof_scatter_color_threshold = scene_eval->eevee.bokeh_threshold; + effects->dof_scatter_neighbor_max_color = scene_eval->eevee.bokeh_neighbor_max; + effects->dof_denoise_factor = clamp_f(scene_eval->eevee.bokeh_denoise_fac, 0.0f, 1.0f); - effects->dof_bokeh[0] = rotation; - effects->dof_bokeh[1] = ratio; - effects->dof_bokeh[2] = scene_eval->eevee.bokeh_max_size; + float max_abs_fg_coc, max_abs_bg_coc; + if (is_ortho) { + max_abs_fg_coc = fabsf(coc_radius_from_camera_depth(true, effects, -cam->clip_start)); + max_abs_bg_coc = fabsf(coc_radius_from_camera_depth(true, effects, -cam->clip_end)); + } + else { + max_abs_fg_coc = fabsf(coc_radius_from_camera_depth(false, effects, -cam->clip_start)); + /* Background is at infinity so maximum CoC is the limit of the function at -inf. */ + max_abs_bg_coc = fabsf(effects->dof_coc_params[1]); + } - /* Precompute values to save instructions in fragment shader. */ - effects->dof_bokeh_sides[0] = blades; - effects->dof_bokeh_sides[1] = blades > 0.0f ? 2.0f * M_PI / blades : 0.0f; - effects->dof_bokeh_sides[2] = blades / (2.0f * M_PI); - effects->dof_bokeh_sides[3] = blades > 0.0f ? cosf(M_PI / blades) : 0.0f; + float max_coc = max_ff(max_abs_bg_coc, max_abs_fg_coc); + /* Clamp with user defined max. */ + effects->dof_fx_max_coc = min_ff(scene_eval->eevee.bokeh_max_size, max_coc); + + if (effects->dof_fx_max_coc < 0.5f) { + return 0; + } return EFFECT_DOF | EFFECT_POST_BUFFER; } + effects->dof_jitter_radius = 0.0f; + /* Cleanup to release memory */ - GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_down_fb); - GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_scatter_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_setup_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_flatten_tiles_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_dilate_tiles_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_reduce_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_reduce_copy_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_gather_fg_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_gather_bg_fb); + GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_scatter_bg_fb); + DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_color); + DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_coc); return 0; } -void EEVEE_depth_of_field_cache_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata) +#define WITH_FILTERING (GPU_SAMPLER_MIPMAP | GPU_SAMPLER_FILTER) +#define NO_FILTERING GPU_SAMPLER_MIPMAP +#define COLOR_FORMAT fx->dof_color_format +#define FG_TILE_FORMAT GPU_RGBA16F +#define BG_TILE_FORMAT GPU_R11F_G11F_B10F + +/** + * Create bokeh texture. + **/ +static void dof_bokeh_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + if ((fx->dof_bokeh_aniso[0] == 1.0f) && (fx->dof_bokeh_aniso[1] == 1.0f) && + (fx->dof_bokeh_blades == 0.0)) { + fx->dof_bokeh_gather_lut_tx = NULL; + fx->dof_bokeh_scatter_lut_tx = NULL; + fx->dof_bokeh_resolve_lut_tx = NULL; + return; + } + + void *owner = (void *)&EEVEE_depth_of_field_init; + int res[2] = {DOF_BOKEH_LUT_SIZE, DOF_BOKEH_LUT_SIZE}; + + DRW_PASS_CREATE(psl->dof_bokeh, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_bokeh_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_bokeh); + DRW_shgroup_uniform_float_copy(grp, "bokehSides", fx->dof_bokeh_blades); + DRW_shgroup_uniform_float_copy(grp, "bokehRotation", fx->dof_bokeh_rotation); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + fx->dof_bokeh_gather_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_RG16F, owner); + fx->dof_bokeh_scatter_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner); + fx->dof_bokeh_resolve_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_bokeh_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_gather_lut_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_scatter_lut_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_resolve_lut_tx), + }); +} + +/** + * Ouputs halfResColorBuffer and halfResCocBuffer. + **/ +static void dof_setup_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) { - EEVEE_PassList *psl = vedata->psl; - EEVEE_StorageList *stl = vedata->stl; - EEVEE_EffectsInfo *effects = stl->effects; DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); - if ((effects->enabled_effects & EFFECT_DOF) != 0) { - /** Depth of Field algorithm - * - * Overview : - * - Down-sample the color buffer into 2 buffers weighted with - * CoC values. Also output CoC into a texture. - * - Shoot quads for every pixel and expand it depending on the CoC. - * Do one pass for near Dof and one pass for far Dof. - * - Finally composite the 2 blurred buffers with the original render. - */ - DRWShadingGroup *grp; - struct GPUBatch *quad = DRW_cache_fullscreen_quad_get(); - const bool use_alpha = !DRW_state_draw_background(); - - DRW_PASS_CREATE(psl->dof_down, DRW_STATE_WRITE_COLOR); - - grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_downsample_get(use_alpha), - psl->dof_down); - DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &effects->source_buffer); - DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth); - DRW_shgroup_uniform_vec2(grp, "nearFar", effects->dof_near_far, 1); - DRW_shgroup_uniform_vec2(grp, "dofParams", effects->dof_params, 1); - DRW_shgroup_call(grp, quad, NULL); - - DRW_PASS_CREATE(psl->dof_scatter, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL); - - /* This create an empty batch of N triangles to be positioned - * by the vertex shader 0.4ms against 6ms with instancing */ - const float *viewport_size = DRW_viewport_size_get(); - const int sprite_len = ((int)viewport_size[0] / 2) * - ((int)viewport_size[1] / 2); /* brackets matters */ - grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_scatter_get(use_alpha), - psl->dof_scatter); - DRW_shgroup_uniform_texture_ref(grp, "nearBuffer", &effects->dof_down_near); - DRW_shgroup_uniform_texture_ref(grp, "farBuffer", &effects->dof_down_far); - DRW_shgroup_uniform_texture_ref(grp, "cocBuffer", &effects->dof_coc); - DRW_shgroup_uniform_vec4(grp, "bokehParams", effects->dof_bokeh, 2); + void *owner = (void *)&EEVEE_depth_of_field_init; + const float *fullres = DRW_viewport_size_get(); + int res[2] = {divide_ceil_u(fullres[0], 2), divide_ceil_u(fullres[1], 2)}; + + DRW_PASS_CREATE(psl->dof_setup, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_setup_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_setup); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->source_buffer, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "depthBuffer", &dtxl->depth, NO_FILTERING); + DRW_shgroup_uniform_vec4_copy(grp, "cocParams", fx->dof_coc_params); + DRW_shgroup_uniform_float_copy(grp, "bokehMaxSize", fx->dof_bokeh_max_size); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + fx->dof_half_res_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner); + fx->dof_half_res_coc_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_RG16F, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_setup_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_half_res_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_half_res_coc_tx), + }); +} + +/** + * Ouputs min & max coc in each 8x8 half res pixel tiles (so 1/16th of fullres). + **/ +static void dof_flatten_tiles_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + void *owner = (void *)&EEVEE_depth_of_field_init; + const float *fullres = DRW_viewport_size_get(); + int res[2] = {divide_ceil_u(fullres[0], DOF_TILE_DIVISOR), + divide_ceil_u(fullres[1], DOF_TILE_DIVISOR)}; + + DRW_PASS_CREATE(psl->dof_flatten_tiles, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_flatten_tiles_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_flatten_tiles); + DRW_shgroup_uniform_texture_ref_ex( + grp, "halfResCocBuffer", &fx->dof_half_res_coc_tx, NO_FILTERING); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + fx->dof_coc_tiles_fg_tx = DRW_texture_pool_query_2d(UNPACK2(res), FG_TILE_FORMAT, owner); + fx->dof_coc_tiles_bg_tx = DRW_texture_pool_query_2d(UNPACK2(res), BG_TILE_FORMAT, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_flatten_tiles_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_coc_tiles_fg_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_coc_tiles_bg_tx), + }); +} + +/** + * Dilates the min & max cocs to cover maximum coc values. + * Output format/dimensions should be the same as coc_flatten_pass as they are swapped for + * doing multiple dilation passes. + **/ +static void dof_dilate_tiles_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + void *owner = (void *)&EEVEE_depth_of_field_init; + const float *fullres = DRW_viewport_size_get(); + int res[2] = {divide_ceil_u(fullres[0], DOF_TILE_DIVISOR), + divide_ceil_u(fullres[1], DOF_TILE_DIVISOR)}; + + DRW_PASS_CREATE(psl->dof_dilate_tiles_minmax, DRW_STATE_WRITE_COLOR); + DRW_PASS_CREATE(psl->dof_dilate_tiles_minabs, DRW_STATE_WRITE_COLOR); + + for (int pass = 0; pass < 2; pass++) { + DRWPass *drw_pass = (pass == 0) ? psl->dof_dilate_tiles_minmax : psl->dof_dilate_tiles_minabs; + GPUShader *sh = EEVEE_shaders_depth_of_field_dilate_tiles_get(pass); + DRWShadingGroup *grp = DRW_shgroup_create(sh, drw_pass); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_tiles_fg_tx); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_tiles_bg_tx); + DRW_shgroup_uniform_bool(grp, "dilateSlightFocus", &fx->dof_dilate_slight_focus, 1); + DRW_shgroup_uniform_int(grp, "ringCount", &fx->dof_dilate_ring_count, 1); + DRW_shgroup_uniform_int(grp, "ringWidthMultiplier", &fx->dof_dilate_ring_width_multiplier, 1); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + } + + fx->dof_coc_dilated_tiles_fg_tx = DRW_texture_pool_query_2d(UNPACK2(res), FG_TILE_FORMAT, owner); + fx->dof_coc_dilated_tiles_bg_tx = DRW_texture_pool_query_2d(UNPACK2(res), BG_TILE_FORMAT, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_dilate_tiles_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_coc_dilated_tiles_fg_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_coc_dilated_tiles_bg_tx), + }); +} + +static void dof_dilate_tiles_pass_draw(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + for (int pass = 0; pass < 2; pass++) { + DRWPass *drw_pass = (pass == 0) ? psl->dof_dilate_tiles_minmax : psl->dof_dilate_tiles_minabs; + + /* 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->dof_fx_max_coc * error_multiplier) / DOF_TILE_DIVISOR); - DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_len); + /* This algorithm produce the exact dilation radius by dividing it in multiple passes. */ + int dilation_radius = 0; + while (dilation_radius < dilation_end_radius) { + /* Dilate slight focus only on first iteration. */ + fx->dof_dilate_slight_focus = (dilation_radius == 0) ? 1 : 0; - DRW_PASS_CREATE(psl->dof_resolve, DRW_STATE_WRITE_COLOR); + int remainder = dilation_end_radius - dilation_radius; + /* Do not step over any unvisited tile. */ + int max_multiplier = dilation_radius + 1; - grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_resolve_get(use_alpha), - psl->dof_resolve); - DRW_shgroup_uniform_texture_ref(grp, "scatterBuffer", &effects->dof_blur); - DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &effects->source_buffer); - DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth); - DRW_shgroup_uniform_vec2(grp, "nearFar", effects->dof_near_far, 1); - DRW_shgroup_uniform_vec2(grp, "dofParams", effects->dof_params, 1); - DRW_shgroup_call(grp, quad, NULL); + int ring_count = min_ii(DOF_DILATE_RING_COUNT, ceilf(remainder / (float)max_multiplier)); + int multiplier = min_ii(max_multiplier, floor(remainder / (float)ring_count)); - if (use_alpha) { - DRW_shgroup_uniform_texture_ref(grp, "scatterAlphaBuffer", &effects->dof_blur_alpha); - DRW_shgroup_uniform_bool_copy(grp, "unpremult", DRW_state_is_image_render()); + dilation_radius += ring_count * multiplier; + + fx->dof_dilate_ring_count = ring_count; + fx->dof_dilate_ring_width_multiplier = multiplier; + + GPU_framebuffer_bind(fbl->dof_dilate_tiles_fb); + DRW_draw_pass(drw_pass); + + SWAP(GPUFrameBuffer *, fbl->dof_dilate_tiles_fb, fbl->dof_flatten_tiles_fb); + SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_bg_tx, fx->dof_coc_tiles_bg_tx); + SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_fg_tx, fx->dof_coc_tiles_fg_tx); } } + /* Swap again so that final textures are dof_coc_dilated_tiles_*_tx. */ + SWAP(GPUFrameBuffer *, fbl->dof_dilate_tiles_fb, fbl->dof_flatten_tiles_fb); + SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_bg_tx, fx->dof_coc_tiles_bg_tx); + SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_fg_tx, fx->dof_coc_tiles_fg_tx); +} + +/** + * Create mipmaped color & coc textures for gather passes. + **/ +static void dof_reduce_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_TextureList *txl, + EEVEE_EffectsInfo *fx) +{ + const float *fullres = DRW_viewport_size_get(); + + /* Divide by 2 because dof_fx_max_coc is in fullres CoC radius and the reduce texture begins at + * half resolution. */ + float max_space_between_sample = fx->dof_fx_max_coc * 0.5f / DOF_GATHER_RING_COUNT; + + int mip_count = max_ii(1, log2_ceil_u(max_space_between_sample)); + + fx->dof_reduce_steps = mip_count - 1; + /* This ensure the mipmaps are aligned for the needed 4 mip levels. + * Starts at 2 because already at half resolution. */ + int multiple = 2 << (mip_count - 1); + int res[2] = {(multiple * divide_ceil_u(fullres[0], multiple)) / 2, + (multiple * divide_ceil_u(fullres[1], multiple)) / 2}; + + int quater_res[2] = {divide_ceil_u(fullres[0], 4), divide_ceil_u(fullres[1], 4)}; + + /* TODO(fclem): Make this dependent of the quality of the gather pass. */ + fx->dof_scatter_coc_threshold = 4.0f; + + { + DRW_PASS_CREATE(psl->dof_downsample, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_downsample_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_downsample); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex( + grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + void *owner = (void *)&EEVEE_depth_of_field_init; + fx->dof_downsample_tx = DRW_texture_pool_query_2d(UNPACK2(quater_res), COLOR_FORMAT, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_downsample_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_downsample_tx), + }); + } + + { + DRW_PASS_CREATE(psl->dof_reduce_copy, DRW_STATE_WRITE_COLOR); + + const bool is_copy_pass = true; + GPUShader *sh = EEVEE_shaders_depth_of_field_reduce_get(is_copy_pass); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_reduce_copy); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex( + grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex( + grp, "downsampledBuffer", &fx->dof_downsample_tx, NO_FILTERING); + DRW_shgroup_uniform_float_copy(grp, "scatterColorThreshold", fx->dof_scatter_color_threshold); + DRW_shgroup_uniform_float_copy( + grp, "scatterColorNeighborMax", fx->dof_scatter_neighbor_max_color); + DRW_shgroup_uniform_float_copy(grp, "scatterCocThreshold", fx->dof_scatter_coc_threshold); + DRW_shgroup_uniform_float_copy(grp, "colorNeighborClamping", fx->dof_denoise_factor); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + void *owner = (void *)&EEVEE_depth_of_field_init; + fx->dof_scatter_src_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R11F_G11F_B10F, owner); + } + + { + DRW_PASS_CREATE(psl->dof_reduce, DRW_STATE_WRITE_COLOR); + + const bool is_copy_pass = false; + GPUShader *sh = EEVEE_shaders_depth_of_field_reduce_get(is_copy_pass); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_reduce); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex( + grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + } + + if (txl->dof_reduced_color) { + /* TODO(fclem) In the future, we need to check if mip_count did not change. + * For now it's ok as we always define all mip level.*/ + if (res[0] != GPU_texture_width(txl->dof_reduced_color) || + res[1] != GPU_texture_width(txl->dof_reduced_color)) { + DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_color); + DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_coc); + } + } + + if (txl->dof_reduced_color == NULL) { + /* Color needs to be signed format here. See note in shader for explanation. */ + /* Do not use texture pool because of needs mipmaps. */ + txl->dof_reduced_color = GPU_texture_create_2d( + "dof_reduced_color", UNPACK2(res), mip_count, GPU_RGBA16F, NULL); + txl->dof_reduced_coc = GPU_texture_create_2d( + "dof_reduced_coc", UNPACK2(res), mip_count, GPU_R16F, NULL); + + /* TODO(fclem) Remove once we have immutable storage or when mips are generated on creation. */ + GPU_texture_generate_mipmap(txl->dof_reduced_color); + GPU_texture_generate_mipmap(txl->dof_reduced_coc); + } + + GPU_framebuffer_ensure_config(&fbl->dof_reduce_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_color), + GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_coc), + }); + + GPU_framebuffer_ensure_config(&fbl->dof_reduce_copy_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_color), + GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_coc), + GPU_ATTACHMENT_TEXTURE(fx->dof_scatter_src_tx), + }); +} + +/** + * Do the gather convolution. For each pixels we gather multiple pixels in its neighborhood + * depending on the min & max CoC tiles. + **/ +static void dof_gather_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_TextureList *txl, + EEVEE_EffectsInfo *fx) +{ + void *owner = (void *)&EEVEE_depth_of_field_init; + const float *fullres = DRW_viewport_size_get(); + int res[2] = {divide_ceil_u(fullres[0], 2), divide_ceil_u(fullres[1], 2)}; + int input_size[2]; + GPU_texture_get_mipmap_size(txl->dof_reduced_color, 0, input_size); + float uv_correction_fac[2] = {res[0] / (float)input_size[0], res[1] / (float)input_size[1]}; + float output_texel_size[2] = {1.0f / res[0], 1.0f / res[1]}; + const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL); + + { + DRW_PASS_CREATE(psl->dof_gather_fg_holefill, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_HOLEFILL, false); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_fg_holefill); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx); + DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex()); + DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac); + DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + /* Reuse textures from the setup pass. */ + /* NOTE: We could use the texture pool do that for us but it does not track usage and it might + * backfire (it does in practice). */ + fx->dof_fg_holefill_color_tx = fx->dof_half_res_color_tx; + fx->dof_fg_holefill_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner); + + GPU_framebuffer_ensure_config(&fbl->dof_gather_fg_holefill_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx), + }); + } + { + DRW_PASS_CREATE(psl->dof_gather_fg, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_FOREGROUND, use_bokeh_tx); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_fg); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx); + DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex()); + DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac); + DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size); + if (use_bokeh_tx) { + /* Negate to flip bokeh shape. Mimics optical phenomenon. */ + negate_v2(fx->dof_bokeh_aniso); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso); + DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_gather_lut_tx); + /* Restore. */ + negate_v2(fx->dof_bokeh_aniso); + } + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + fx->dof_fg_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner); + fx->dof_fg_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner); + /* Reuse textures from the setup pass. */ + /* NOTE: We could use the texture pool do that for us but it does not track usage and it might + * backfire (it does in practice). */ + fx->dof_fg_occlusion_tx = fx->dof_half_res_coc_tx; + + /* NOTE: First target is holefill texture so we can use the median filter on it. + * See the filter function. */ + GPU_framebuffer_ensure_config(&fbl->dof_gather_fg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_occlusion_tx), + }); + } + { + DRW_PASS_CREATE(psl->dof_gather_bg, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_BACKGROUND, use_bokeh_tx); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_bg); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx); + DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx); + DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex()); + DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac); + DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size); + if (use_bokeh_tx) { + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso); + DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_gather_lut_tx); + } + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + fx->dof_bg_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner); + fx->dof_bg_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner); + /* Reuse, since only used for scatter. Foreground is processed before background. */ + fx->dof_bg_occlusion_tx = fx->dof_fg_occlusion_tx; + + /* NOTE: First target is holefill texture so we can use the median filter on it. + * See the filter function. */ + GPU_framebuffer_ensure_config(&fbl->dof_gather_bg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_bg_occlusion_tx), + }); + } +} + +/** + * Filter an input buffer using a median filter to reduce noise. + * NOTE: We use the holefill texture as our input to reduce memory usage. + * Thus, the holefill pass cannot be filtered. + **/ +static void dof_filter_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + DRW_PASS_CREATE(psl->dof_filter, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_filter_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_filter); + DRW_shgroup_uniform_texture_ref_ex( + grp, "colorBuffer", &fx->dof_fg_holefill_color_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex( + grp, "weightBuffer", &fx->dof_fg_holefill_weight_tx, NO_FILTERING); + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); + + GPU_framebuffer_ensure_config(&fbl->dof_filter_fg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_weight_tx), + }); + + GPU_framebuffer_ensure_config(&fbl->dof_filter_bg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_bg_color_tx), + GPU_ATTACHMENT_TEXTURE(fx->dof_bg_weight_tx), + }); +} + +/** + * Do the Scatter convolution. A sprite is emited for every 4 pixels but is only expanded if the + * pixels are bright enough to be scattered. + **/ +static void dof_scatter_pass_init(EEVEE_FramebufferList *fbl, + EEVEE_PassList *psl, + EEVEE_TextureList *txl, + EEVEE_EffectsInfo *fx) +{ + int input_size[2], target_size[2]; + GPU_texture_get_mipmap_size(fx->dof_half_res_color_tx, 0, input_size); + GPU_texture_get_mipmap_size(fx->dof_bg_color_tx, 0, target_size); + /* Draw a sprite for every four halfres pixels. */ + int sprite_count = (input_size[0] / 2) * (input_size[1] / 2); + float target_texel_size[2] = {1.0f / target_size[0], 1.0f / target_size[1]}; + const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL); + + { + DRW_PASS_CREATE(psl->dof_scatter_fg, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL); + + const bool is_foreground = true; + GPUShader *sh = EEVEE_shaders_depth_of_field_scatter_get(is_foreground, use_bokeh_tx); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_scatter_fg); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->dof_scatter_src_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "occlusionBuffer", &fx->dof_fg_occlusion_tx); + DRW_shgroup_uniform_vec2_copy(grp, "targetTexelSize", target_texel_size); + DRW_shgroup_uniform_int_copy(grp, "spritePerRow", input_size[0] / 2); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso); + if (use_bokeh_tx) { + /* Negate to flip bokeh shape. Mimics optical phenomenon. */ + negate_v2(fx->dof_bokeh_aniso_inv); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv); + DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_scatter_lut_tx); + /* Restore. */ + negate_v2(fx->dof_bokeh_aniso_inv); + } + DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_count); + + GPU_framebuffer_ensure_config(&fbl->dof_scatter_fg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_fg_color_tx), + }); + } + { + DRW_PASS_CREATE(psl->dof_scatter_bg, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL); + + const bool is_foreground = false; + GPUShader *sh = EEVEE_shaders_depth_of_field_scatter_get(is_foreground, use_bokeh_tx); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_scatter_bg); + DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->dof_scatter_src_tx, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "occlusionBuffer", &fx->dof_bg_occlusion_tx); + DRW_shgroup_uniform_vec2_copy(grp, "targetTexelSize", target_texel_size); + DRW_shgroup_uniform_int_copy(grp, "spritePerRow", input_size[0] / 2); + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso); + if (use_bokeh_tx) { + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv); + DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_scatter_lut_tx); + } + DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_count); + + GPU_framebuffer_ensure_config(&fbl->dof_scatter_bg_fb, + { + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(fx->dof_bg_color_tx), + }); + } +} + +/** + * Recombine the result of the foreground and background processing. Also perform a slight out of + * focus blur to improve geometric continuity. + **/ +static void dof_recombine_pass_init(EEVEE_FramebufferList *UNUSED(fbl), + EEVEE_PassList *psl, + EEVEE_EffectsInfo *fx) +{ + DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); + const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL); + + DRW_PASS_CREATE(psl->dof_resolve, DRW_STATE_WRITE_COLOR); + + GPUShader *sh = EEVEE_shaders_depth_of_field_resolve_get(use_bokeh_tx, fx->dof_hq_slight_focus); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_resolve); + DRW_shgroup_uniform_texture_ref_ex(grp, "fullResColorBuffer", &fx->source_buffer, NO_FILTERING); + DRW_shgroup_uniform_texture_ref_ex(grp, "fullResDepthBuffer", &dtxl->depth, NO_FILTERING); + DRW_shgroup_uniform_texture_ref(grp, "bgColorBuffer", &fx->dof_bg_color_tx); + DRW_shgroup_uniform_texture_ref(grp, "bgWeightBuffer", &fx->dof_bg_weight_tx); + DRW_shgroup_uniform_texture_ref(grp, "bgTileBuffer", &fx->dof_coc_dilated_tiles_bg_tx); + DRW_shgroup_uniform_texture_ref(grp, "fgColorBuffer", &fx->dof_fg_color_tx); + DRW_shgroup_uniform_texture_ref(grp, "fgWeightBuffer", &fx->dof_fg_weight_tx); + DRW_shgroup_uniform_texture_ref(grp, "holefillColorBuffer", &fx->dof_fg_holefill_color_tx); + DRW_shgroup_uniform_texture_ref(grp, "holefillWeightBuffer", &fx->dof_fg_holefill_weight_tx); + DRW_shgroup_uniform_texture_ref(grp, "fgTileBuffer", &fx->dof_coc_dilated_tiles_fg_tx); + DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex()); + DRW_shgroup_uniform_vec4_copy(grp, "cocParams", fx->dof_coc_params); + DRW_shgroup_uniform_float_copy(grp, "bokehMaxSize", fx->dof_bokeh_max_size); + if (use_bokeh_tx) { + DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv); + DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_resolve_lut_tx); + } + DRW_shgroup_call_procedural_triangles(grp, NULL, 1); +} + +void EEVEE_depth_of_field_cache_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata) +{ + EEVEE_TextureList *txl = vedata->txl; + EEVEE_FramebufferList *fbl = vedata->fbl; + EEVEE_PassList *psl = vedata->psl; + EEVEE_StorageList *stl = vedata->stl; + EEVEE_EffectsInfo *fx = stl->effects; + + if ((fx->enabled_effects & EFFECT_DOF) != 0) { + /* GPU_RGBA16F is sufficient now that all scattered bokeh are premultiplied. + * GPU_R11F_G11F_B10F is not enough when lots of scattered sprites are big and offers + * relatively small benefits. */ + fx->dof_color_format = GPU_RGBA16F; + + dof_bokeh_pass_init(fbl, psl, fx); + dof_setup_pass_init(fbl, psl, fx); + dof_flatten_tiles_pass_init(fbl, psl, fx); + dof_dilate_tiles_pass_init(fbl, psl, fx); + dof_reduce_pass_init(fbl, psl, txl, fx); + dof_gather_pass_init(fbl, psl, txl, fx); + dof_filter_pass_init(fbl, psl, fx); + dof_scatter_pass_init(fbl, psl, txl, fx); + dof_recombine_pass_init(fbl, psl, fx); + } +} + +static void dof_recursive_reduce(void *vedata, int UNUSED(level)) +{ + EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl; + EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl; + EEVEE_EffectsInfo *fx = ((EEVEE_Data *)vedata)->stl->effects; + + fx->dof_reduce_input_color_tx = txl->dof_reduced_color; + fx->dof_reduce_input_coc_tx = txl->dof_reduced_coc; + + DRW_draw_pass(psl->dof_reduce); } void EEVEE_depth_of_field_draw(EEVEE_Data *vedata) @@ -214,24 +971,77 @@ void EEVEE_depth_of_field_draw(EEVEE_Data *vedata) EEVEE_TextureList *txl = vedata->txl; EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_StorageList *stl = vedata->stl; - EEVEE_EffectsInfo *effects = stl->effects; + EEVEE_EffectsInfo *effects = stl->effects; /* TODO(fclem): Because of silly SWAP_BUFFERS. */ + EEVEE_EffectsInfo *fx = effects; /* Depth Of Field */ if ((effects->enabled_effects & EFFECT_DOF) != 0) { - const float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + DRW_stats_group_start("Depth of Field"); + + if (fx->dof_bokeh_gather_lut_tx != NULL) { + GPU_framebuffer_bind(fbl->dof_bokeh_fb); + DRW_draw_pass(psl->dof_bokeh); + } + + GPU_framebuffer_bind(fbl->dof_setup_fb); + DRW_draw_pass(psl->dof_setup); + + GPU_framebuffer_bind(fbl->dof_flatten_tiles_fb); + DRW_draw_pass(psl->dof_flatten_tiles); - /* Downsample */ - GPU_framebuffer_bind(fbl->dof_down_fb); - DRW_draw_pass(psl->dof_down); + dof_dilate_tiles_pass_draw(fbl, psl, fx); - /* Scatter */ - GPU_framebuffer_bind(fbl->dof_scatter_fb); - GPU_framebuffer_clear_color(fbl->dof_scatter_fb, clear_col); - DRW_draw_pass(psl->dof_scatter); + fx->dof_reduce_input_color_tx = fx->dof_half_res_color_tx; + fx->dof_reduce_input_coc_tx = fx->dof_half_res_coc_tx; - /* Resolve */ - GPU_framebuffer_bind(effects->target_buffer); + /* First step is just a copy. */ + GPU_framebuffer_bind(fbl->dof_downsample_fb); + DRW_draw_pass(psl->dof_downsample); + + /* First step is just a copy. */ + GPU_framebuffer_bind(fbl->dof_reduce_copy_fb); + DRW_draw_pass(psl->dof_reduce_copy); + + GPU_framebuffer_recursive_downsample( + fbl->dof_reduce_fb, fx->dof_reduce_steps, &dof_recursive_reduce, vedata); + + { + /* Foreground convolution. */ + GPU_framebuffer_bind(fbl->dof_gather_fg_fb); + DRW_draw_pass(psl->dof_gather_fg); + + GPU_framebuffer_bind(fbl->dof_filter_fg_fb); + DRW_draw_pass(psl->dof_filter); + + GPU_framebuffer_bind(fbl->dof_scatter_fg_fb); + DRW_draw_pass(psl->dof_scatter_fg); + } + + { + /* Background convolution. */ + GPU_framebuffer_bind(fbl->dof_gather_bg_fb); + DRW_draw_pass(psl->dof_gather_bg); + + GPU_framebuffer_bind(fbl->dof_filter_bg_fb); + DRW_draw_pass(psl->dof_filter); + + GPU_framebuffer_bind(fbl->dof_scatter_bg_fb); + DRW_draw_pass(psl->dof_scatter_bg); + } + + { + /* Holefill convolution. */ + GPU_framebuffer_bind(fbl->dof_gather_fg_holefill_fb); + DRW_draw_pass(psl->dof_gather_fg_holefill); + + /* NOTE: do not filter the holefill pass as we use it as out filter input buffer. */ + } + + GPU_framebuffer_bind(fx->target_buffer); DRW_draw_pass(psl->dof_resolve); + SWAP_BUFFERS(); + + DRW_stats_group_end(); } } |