From b0af541b9adc7ccd000aed1c7d9e7cc3eabef5a5 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 2 May 2017 02:39:51 -0400 Subject: [PATCH] Handle gradients with multiple color stops at 0.0 or 1.0 When a clamped gradient has multiple color stops at 0.0, the last one will fill GradientData[0]. When a clamped gradient has multiple color stops at 1.0, the first one will fill GradientData[N-1]. Both of these conditions will cause the area outside of the gradient to not be filled with the correct clamped color, which is the first or last gradient color stop. Filling the first or last color entry of GradientData with the first or last color stop isn't exactly correct either because the first or last color stop entry is only needed in this case for the range outside of [0, 1]. This commit changes the GradientData so that the first color entry is used for offset < 0, the last color entry is used for offset >= 1, and the color entries in between are for the range in between. The first and last color entries are then filled with the correct colors for those ranges. --- webrender/res/prim_shared.glsl | 17 +++-- webrender/src/prim_store.rs | 64 ++++++++++++++----- .../reftests/gradient/linear-clamp-1-ref.yaml | 8 +++ wrench/reftests/gradient/linear-clamp-1a.yaml | 8 +++ wrench/reftests/gradient/linear-clamp-1b.yaml | 8 +++ .../reftests/gradient/linear-clamp-2-ref.yaml | 8 +++ wrench/reftests/gradient/linear-clamp-2.yaml | 8 +++ wrench/reftests/gradient/reftest.list | 11 +++- 8 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 wrench/reftests/gradient/linear-clamp-1-ref.yaml create mode 100644 wrench/reftests/gradient/linear-clamp-1a.yaml create mode 100644 wrench/reftests/gradient/linear-clamp-1b.yaml create mode 100644 wrench/reftests/gradient/linear-clamp-2-ref.yaml create mode 100644 wrench/reftests/gradient/linear-clamp-2.yaml diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 4c65969f57..80d02e2632 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -850,11 +850,18 @@ vec4 dither(vec4 color) { #endif //WR_FEATURE_DITHERING vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) { - // Either saturate or modulo the offset depending on repeat mode - float x = mix(clamp(offset, 0.0, 1.0), fract(offset), gradient_repeat); - - // Scale to the number of gradient color entries (texture width / 2). - x = x * 0.5 * gradient_size.x; + // Modulo the offset if the gradient repeats. We don't need to clamp non-repeating + // gradients because the gradient data texture is bound with CLAMP_TO_EDGE, and the + // first and last color entries are filled with the first and last stop colors + float x = mix(offset, fract(offset), gradient_repeat); + + // Calculate the color entry index to use for this offset: + // offsets < 0 use the first color entry, 0 + // offsets from [0, 1) use the color entries in the range of [1, N-1) + // offsets >= 1 use the last color entry, N-1 + // so transform the range [0, 1) -> [1, N-1) + float gradient_entries = 0.5 * gradient_size.x; + x = x * (gradient_entries - 2.0) + 1.0; // Calculate the texel to index into the gradient color entries: // floor(x) is the gradient color entry index diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 02710fee90..17188dcf02 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -299,8 +299,20 @@ pub struct RadialGradientPrimitiveCpu { pub cache_dirty: bool, } -// The number of entries in a gradient data table. -pub const GRADIENT_DATA_RESOLUTION: usize = 128; +// The gradient entry index for the first color stop +pub const GRADIENT_DATA_FIRST_STOP: usize = 0; +// The gradient entry index for the last color stop +pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1; + +// The start of the gradient data table +pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1; +// The exclusive bound of the gradient data table +pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP; +// The number of entries in the gradient data table. +pub const GRADIENT_DATA_TABLE_SIZE: usize = 128; + +// The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry +pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2; #[derive(Debug, Clone, Copy)] #[repr(C)] @@ -317,10 +329,12 @@ pub struct GradientDataEntry { // the offset within that entry bucket is used to interpolate between the two colors in that entry. // This layout preserves hard stops, as the end color for a given entry can differ from the start // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8 -// format for texture upload. +// format for texture upload. This table requires the gradient color stops to be normalized to the +// range [0, 1]. The first and last entries hold the first and last color stop colors respectively, +// while the entries in between hold the interpolated color stop values for the range [0, 1]. pub struct GradientData { - pub colors_high: [GradientDataEntry; GRADIENT_DATA_RESOLUTION], - pub colors_low: [GradientDataEntry; GRADIENT_DATA_RESOLUTION], + pub colors_high: [GradientDataEntry; GRADIENT_DATA_SIZE], + pub colors_low: [GradientDataEntry; GRADIENT_DATA_SIZE], } impl Default for GradientData { @@ -342,7 +356,8 @@ impl Clone for GradientData { } impl GradientData { - // Generate a color ramp between the start and end indexes from a start color to an end color. + /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating + /// from start_color to end_color. fn fill_colors(&mut self, start_idx: usize, end_idx: usize, start_color: &ColorF, end_color: &ColorF) { // Calculate the color difference for individual steps in the ramp. let inv_steps = 1.0 / (end_idx - start_idx) as f32; @@ -373,18 +388,18 @@ impl GradientData { } } - // Compute an entry index based on a gradient stop offset. + /// Compute an index into the gradient entry table based on a gradient stop offset. This + /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END]. #[inline] fn get_index(offset: f32) -> usize { - (offset.max(0.0).min(1.0) * GRADIENT_DATA_RESOLUTION as f32).round() as usize + (offset.max(0.0).min(1.0) + * GRADIENT_DATA_TABLE_SIZE as f32 + + GRADIENT_DATA_TABLE_BEGIN as f32).round() as usize } // Build the gradient data from the supplied stops, reversing them if necessary. fn build(&mut self, src_stops: AuxIter, reverse_stops: bool) { - const MAX_IDX: usize = GRADIENT_DATA_RESOLUTION; - const MIN_IDX: usize = 0; - // Preconditions (should be ensured by DisplayListBuilder): // * we have at least two stops // * first stop has offset 0.0 @@ -396,8 +411,13 @@ impl GradientData { debug_assert_eq!(first.offset, 0.0); if reverse_stops { - // If the gradient is reversed, then we invert offsets and draw right-to-left - let mut cur_idx = MAX_IDX; + // Fill in the first entry (for reversed stops) with the first color stop + self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color); + + // Fill in the center of the gradient table, generating a color ramp between each consecutive pair + // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The + // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END). + let mut cur_idx = GRADIENT_DATA_TABLE_END; for next in src_stops { let next_idx = Self::get_index(1.0 - next.offset); if next_idx < cur_idx { @@ -407,9 +427,18 @@ impl GradientData { } cur_color = next.color; } - debug_assert_eq!(cur_idx, MIN_IDX); + debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_BEGIN); + + // Fill in the last entry (for reversed stops) with the last color stop + self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color); } else { - let mut cur_idx = MIN_IDX; + // Fill in the first entry with the first color stop + self.fill_colors(GRADIENT_DATA_FIRST_STOP, GRADIENT_DATA_FIRST_STOP + 1, &cur_color, &cur_color); + + // Fill in the center of the gradient table, generating a color ramp between each consecutive pair + // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The + // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END). + let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN; for next in src_stops { let next_idx = Self::get_index(next.offset); if next_idx > cur_idx { @@ -419,7 +448,10 @@ impl GradientData { } cur_color = next.color; } - debug_assert_eq!(cur_idx, MAX_IDX); + debug_assert_eq!(cur_idx, GRADIENT_DATA_TABLE_END); + + // Fill in the last entry with the last color stop + self.fill_colors(GRADIENT_DATA_LAST_STOP, GRADIENT_DATA_LAST_STOP + 1, &cur_color, &cur_color); } } } diff --git a/wrench/reftests/gradient/linear-clamp-1-ref.yaml b/wrench/reftests/gradient/linear-clamp-1-ref.yaml new file mode 100644 index 0000000000..81c366d858 --- /dev/null +++ b/wrench/reftests/gradient/linear-clamp-1-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 0.5, blue, 0.5, red, 1.0, red] diff --git a/wrench/reftests/gradient/linear-clamp-1a.yaml b/wrench/reftests/gradient/linear-clamp-1a.yaml new file mode 100644 index 0000000000..b83963a37a --- /dev/null +++ b/wrench/reftests/gradient/linear-clamp-1a.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 100 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] diff --git a/wrench/reftests/gradient/linear-clamp-1b.yaml b/wrench/reftests/gradient/linear-clamp-1b.yaml new file mode 100644 index 0000000000..ffe3391999 --- /dev/null +++ b/wrench/reftests/gradient/linear-clamp-1b.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 100 100 + end: 200 100 + stops: [0.0, blue, 0.0, red, 1.0, red] diff --git a/wrench/reftests/gradient/linear-clamp-2-ref.yaml b/wrench/reftests/gradient/linear-clamp-2-ref.yaml new file mode 100644 index 0000000000..8eb475d0a5 --- /dev/null +++ b/wrench/reftests/gradient/linear-clamp-2-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 0.25, blue, 0.25, green, 0.75, green, 0.75, red, 1.0, red] diff --git a/wrench/reftests/gradient/linear-clamp-2.yaml b/wrench/reftests/gradient/linear-clamp-2.yaml new file mode 100644 index 0000000000..48428b974a --- /dev/null +++ b/wrench/reftests/gradient/linear-clamp-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 50 100 + end: 150 100 + stops: [0.0, blue, 0.0, green, 1.0, green, 1.0, red] diff --git a/wrench/reftests/gradient/reftest.list b/wrench/reftests/gradient/reftest.list index 467b67c971..0b90112701 100644 --- a/wrench/reftests/gradient/reftest.list +++ b/wrench/reftests/gradient/reftest.list @@ -6,6 +6,10 @@ == linear-reverse.yaml linear-ref.png fuzzy(1,35000) == linear-stops.yaml linear-stops-ref.png +== linear-clamp-1a.yaml linear-clamp-1-ref.yaml +== linear-clamp-1b.yaml linear-clamp-1-ref.yaml +== linear-clamp-2.yaml linear-clamp-2-ref.yaml + # dithering requires us to fuzz here fuzzy(1,20000) == linear.yaml linear-ref.yaml fuzzy(1,20000) == linear-reverse.yaml linear-ref.yaml @@ -35,14 +39,15 @@ fuzzy(255,1200) == repeat-linear-reverse.yaml repeat-linear-ref.yaml fuzzy(255,2664) == repeat-radial.yaml repeat-radial-ref.yaml fuzzy(255,2664) == repeat-radial-negative.yaml repeat-radial-ref.yaml -== tiling-linear-1.yaml tiling-linear-1-ref.yaml -== tiling-linear-2.yaml tiling-linear-2-ref.yaml +# fuzzy because of thin spaced out column of pixels that are 1 off +fuzzy(1,50) == tiling-linear-1.yaml tiling-linear-1-ref.yaml +fuzzy(1,38) == tiling-linear-2.yaml tiling-linear-2-ref.yaml == tiling-linear-3.yaml tiling-linear-3-ref.yaml fuzzy(1,16) == tiling-radial-1.yaml tiling-radial-1-ref.yaml fuzzy(1,1) == tiling-radial-2.yaml tiling-radial-2-ref.yaml == tiling-radial-3.yaml tiling-radial-3-ref.yaml -fuzzy(1,16) == tiling-radial-4.yaml tiling-radial-4-ref.yaml +fuzzy(1,17) == tiling-radial-4.yaml tiling-radial-4-ref.yaml == radial-zero-size-1.yaml radial-zero-size-ref.yaml == radial-zero-size-2.yaml radial-zero-size-ref.yaml