Skip to content

Commit

Permalink
shaders/colorspace: add pl_peak_detect_params.black_cutoff
Browse files Browse the repository at this point in the history
Fixes unnatural black flickering in some scenes where encoding noise
pushes the background between 0 and 1 PQ. Adding a 1% PQ cutoff allows
censoring such values to 0.

I could also have hard-coded this constant but I decided it's simple
enough to expose, and it being tunable may help with tracking down
related issues in the future, and also allows users to opt out of this
functionality altogether.

Fixes: https://yabb.jriver.com/interact/index.php/topic,136378.msg950343.html#msg950343
  • Loading branch information
haasn committed Sep 30, 2023
1 parent 31ee55c commit d6ae48b
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 7 deletions.
1 change: 1 addition & 0 deletions demos/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ void update_settings(struct plplay *p, const struct pl_frame *target)
nk_property_float(nk, "Threshold high", 0.0, &ppar->scene_threshold_high, 20.0, 0.5, 0.005);
nk_property_float(nk, "Smoothing period", 0.0, &ppar->smoothing_period, 1000.0, 5.0, 1.0);
nk_property_float(nk, "Peak percentile", 95.0, &ppar->percentile, 100.0, 0.01, 0.001);
nk_property_float(nk, "Black cutoff", 0.0, &ppar->black_cutoff, 100.0, 0.01, 0.001);
nk_checkbox_label(nk, "Allow 1-frame delay", &ppar->allow_delayed);

struct pl_hdr_metadata metadata;
Expand Down
9 changes: 9 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,15 @@ highlights.
Defaults to `100.0`. The `high_quality` preset instead sets this to `99.995`,
which is very conservative and should cause no major issues in typical content.

### `black_cutoff=<0.0..100.0>`

Black cutoff strength. To prevent unnatural pixel shimmer and excessive
darkness in mostly black scenes, as well as avoid black bars from affecting the
content, (smoothly) cut off any value below this (PQ%) threshold. Defaults to
`1.0`, or 1% PQ.

Setting this to `0.0` (or a negative value) disables this functionality.

### `allow_delayed_peak=<yes|no>`

Allows the peak detection result to be delayed by up to a single frame, which
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ project('libplacebo', ['c', 'cpp'],
7,
# API version
{
'339': 'add pl_peak_detect_params.black_cutoff',
'338': 'split pl_filter_nearest into pl_filter_nearest and pl_filter_box',
'337': 'fix PL_FILTER_DOWNSCALING constant',
'336': 'deprecate pl_filter.radius_cutoff in favor of pl_filter.radius',
Expand Down
11 changes: 10 additions & 1 deletion src/include/libplacebo/shaders/colorspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ struct pl_peak_detect_params {
// cause no major issues in typical content.
float percentile;

// Black cutoff strength. To prevent unnatural pixel shimmer and excessive
// darkness in mostly black scenes, as well as avoid black bars from
// affecting the content, (smoothly) cut off any value below this (PQ%)
// threshold. Defaults to 1.0, or 1% PQ.
//
// Setting this to 0.0 (or a negative value) disables this functionality.
float black_cutoff;

// Allows the peak detection result to be delayed by up to a single frame,
// which can sometimes improve thoughput, at the cost of introducing the
// possibility of 1-frame flickers on transitions. Disabled by default.
Expand All @@ -146,7 +154,8 @@ struct pl_peak_detect_params {
.smoothing_period = 20.0f, \
.scene_threshold_low = 1.0f, \
.scene_threshold_high = 3.0f, \
.percentile = 100.0f,
.percentile = 100.0f, \
.black_cutoff = 1.0f,

#define PL_PEAK_DETECT_HQ_DEFAULTS \
PL_PEAK_DETECT_DEFAULTS \
Expand Down
1 change: 1 addition & 0 deletions src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ const struct pl_opt_t pl_option_list[] = {
OPT_FLOAT("scene_threshold_high", "Scene change threshold high", peak_detect_params.scene_threshold_high, .max = 100.0),
OPT_FLOAT("minimum_peak", "Minimum detected peak", peak_detect_params.minimum_peak, .max = 100.0, .deprecated = true),
OPT_FLOAT("peak_percentile", "Peak detection percentile", peak_detect_params.percentile, .max = 100.0),
OPT_FLOAT("black_cutoff", "Peak detection black cutoff", peak_detect_params.black_cutoff, .max = 100.0),
OPT_BOOL("allow_delayed_peak", "Allow delayed peak detection", peak_detect_params.allow_delayed),

// Color mapping
Expand Down
21 changes: 15 additions & 6 deletions src/shaders/colorspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1315,13 +1315,16 @@ bool pl_shader_detect_peak(pl_shader sh, struct pl_color_space csp,
pl_shader_linearize(sh, &csp);

bool has_subgroups = sh_glsl(sh).subgroup_size > 0;
const float cutoff = fmaxf(params->black_cutoff, 0.0f) * 1e-2f;
#pragma GLSL /* Measure luminance as N-bit PQ */ \
float luma = dot(${sh_luma_coeffs(sh, &csp)}, color.rgb); \
luma *= ${const float: PL_COLOR_SDR_WHITE / 10000.0}; \
luma = pow(clamp(luma, 0.0, 1.0), ${const float: PQ_M1}); \
luma = (${const float: PQ_C1} + ${const float: PQ_C2} * luma) / \
(1.0 + ${const float: PQ_C3} * luma); \
luma = pow(luma, ${const float: PQ_M2}); \
@if (cutoff) \
luma *= smoothstep(0.0, ${float: cutoff}, luma); \
uint y_pq = uint(${const float: PQ_MAX} * luma); \
\
/* Update the work group's shared atomics */ \
Expand All @@ -1345,24 +1348,30 @@ bool pl_shader_detect_peak(pl_shader sh, struct pl_color_space csp,
@if (has_subgroups) { \
uint group_sum = subgroupAdd(y_pq); \
uint group_max = subgroupMax(y_pq); \
uvec4 b = subgroupBallot(y_pq == 0u); \
@if (cutoff) \
uvec4 b = subgroupBallot(y_pq == 0u); \
if (subgroupElect()) { \
atomicAdd($wg_sum, group_sum); \
atomicMax($wg_max, group_max); \
atomicAdd($wg_black, subgroupBallotBitCount(b)); \
@if (cutoff) \
atomicAdd($wg_black, subgroupBallotBitCount(b)); \
} \
@} else { \
atomicAdd($wg_sum, y_pq); \
atomicMax($wg_max, y_pq); \
if (y_pq == 0u) \
atomicAdd($wg_black, 1u); \
@if (cutoff) { \
if (y_pq == 0u) \
atomicAdd($wg_black, 1u); \
@} \
@} \
barrier(); \
\
@if (use_histogram) { \
@if (cutoff) { \
if (gl_LocalInvocationIndex == 0u) \
$wg_hist[0] -= $wg_black; \
@} \
/* Update the histogram with a cooperative loop */ \
if (gl_LocalInvocationIndex == 0u) \
$wg_hist[0] -= $wg_black; \
for (uint i = local_idx; i < ${const uint: HIST_BINS}; i += wg_size) \
atomicAdd(frame_hist[hist_base + i], $wg_hist[i]); \
@} \
Expand Down

0 comments on commit d6ae48b

Please sign in to comment.