Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ordered dithering to gradient-like effects #966

Merged
merged 3 commits into from Mar 24, 2017
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Add ordered dithering to gradient-like effects

  • Loading branch information
squarewave committed Mar 23, 2017
commit 0775efee0f46318598c7cec251098bfb9a01beb9
@@ -36,5 +36,5 @@ void main(void) {
// Unpremultiply the alpha.
color.rgb /= color.a;

oFragColor = color;
oFragColor = dither(color);
}
@@ -144,5 +144,5 @@ void main(void) {
float value = color(pos, p0Rect, p1Rect, radii, sigma);

value = max(value, 0.0);
oFragColor = vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value);
oFragColor = dither(vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value));
}
@@ -758,4 +758,13 @@ float do_clip() {
return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
all(inside) ? textureLod(sCacheA8, vClipMaskUv, 0.0).r : 0.0;
}

vec4 dither(vec4 color) {
const int matrix_mask = 7;

ivec2 pos = ivec2(gl_FragCoord) & ivec2(matrix_mask);

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

I think it would be safer to use gl_FragCoord.xy here, given that without it you'd be asking for 2 conversions done: float to int and vec4 to vec2, which some GLSL compilers may not appreciate.

float noise_factor = 4.0 / 255.0;
float noise = texelFetch(sDither, pos, 0).r * noise_factor;
return color + vec4(noise, noise, noise, 0);
}
#endif //WR_FRAGMENT_SHADER
@@ -11,11 +11,10 @@ void main(void) {
// gradient color entries (texture width / 2).
float x = mix(clamp(vOffset, 0.0, 1.0), fract(vOffset), vGradientRepeat) * 0.5 * texture_size.x;

// Start at the center of first color in the nearest 2-color entry, then offset with the
// fractional remainder to interpolate between the colors. Rely on texture clamping when
// outside of valid range.
x = 2.0 * floor(x) + 0.5 + fract(x);
float y = vGradientIndex + 1.0 / 256.0;

// Normalize the texture coordates so we can use texture() for bilinear filtering.
oFragColor = texture(sGradients, vec2(x, vGradientIndex) / texture_size);
// Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
// bits (vGradientIndex)
oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
}
@@ -27,7 +27,7 @@ void main(void) {
vOffset = dot(vi.local_pos - start_point, dir) / dot(dir, dir);

// V coordinate of gradient row in lookup texture.
vGradientIndex = float(prim.sub_index) + 0.5;
vGradientIndex = float(prim.sub_index) * 2.0 + 0.5;

// Whether to repeat the gradient instead of clamping.
vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
@@ -16,5 +16,5 @@ void main(void) {
uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);

// Modulate the box shadow by the color.
oFragColor = vColor * texture(sCacheRGBA8, vec3(uv, vUv.z));
oFragColor = dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
}
@@ -12,5 +12,5 @@ void main(void) {
#endif

alpha = min(alpha, do_clip());
oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
}
@@ -50,11 +50,10 @@ void main(void) {
// gradient color entries (texture width / 2).
x = mix(clamp(x, 0.0, 1.0), fract(x), vGradientRepeat) * 0.5 * texture_size.x;

// Start at the center of first color in the nearest 2-color entry, then offset with the
// fractional remainder to interpolate between the colors. Rely on texture clamping when
// outside of valid range.
x = 2.0 * floor(x) + 0.5 + fract(x);

// Normalize the texture coordates so we can use texture() for bilinear filtering.
oFragColor = texture(sGradients, vec2(x, vGradientIndex) / texture_size);
// Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
// bits (vGradientIndex)
float y = vGradientIndex + 1.0 / 256.0;
oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
}
@@ -27,7 +27,7 @@ void main(void) {
vEndRadius = gradient.start_end_radius_extend_mode.y;

// V coordinate of gradient row in lookup texture.
vGradientIndex = float(prim.sub_index) + 0.5;
vGradientIndex = float(prim.sub_index) * 2.0 + 0.5;

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

I think we are overloading the vGradientIndex semantic a bit here, and the logic gets split between VS and PS too much. Perhaps, let's just do vGradientIndex = prim.sub_idex in the VS, and then do vGradientIndex * 2.0 + 0.5 + 1.0/256.0 in PS? It would make no difference in terms of performance, but would be cleaner to read.


// Whether to repeat the gradient instead of clamping.
vGradientRepeat = float(int(gradient.start_end_radius_extend_mode.z) == EXTEND_MODE_REPEAT);
@@ -36,6 +36,7 @@
uniform sampler2D sColor0;
uniform sampler2D sColor1;
uniform sampler2D sColor2;
uniform sampler2D sDither;
uniform sampler2D sMask;

//======================================================================================
@@ -1547,6 +1547,10 @@ impl Device {
if u_color_2 != -1 {
self.gl.uniform_1i(u_color_2, TextureSampler::Color2 as i32);
}
let u_noise = self.gl.get_uniform_location(program.id, "sDither");
if u_noise != -1 {
self.gl.uniform_1i(u_noise, TextureSampler::Dither as i32);
}
let u_mask = self.gl.get_uniform_location(program.id, "sMask");
if u_mask != -1 {
self.gl.uniform_1i(u_mask, TextureSampler::Mask as i32);
@@ -1713,7 +1717,7 @@ impl Device {
}
ImageFormat::RGB8 => (gl::RGB, 3, data),
ImageFormat::RGBA8 => (get_gl_format_bgra(self.gl()), 4, data),
ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
ImageFormat::Invalid | ImageFormat::RGBA16 | ImageFormat::RGBAF32 => unreachable!(),

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

Let's remove RGBA16 as not needed now?

};

let row_length = match stride {
@@ -2099,6 +2103,7 @@ fn gl_texture_formats_for_image_format(gl: &gl::Gl, format: ImageFormat) -> (gl:
}
}
}
ImageFormat::RGBA16 => (gl::RGBA16UI as gl::GLint, gl::RGBA_INTEGER),

This comment has been minimized.

@kvark

kvark Mar 20, 2017

Member

We should bring some consistency with names. Having RGBA8 meaning one thing, RGBA16 another, and RGBAF32on top of it is a bit of a mess. MaybeRGBA8, RGBA16U. RGBA32F`?

ImageFormat::RGBAF32 => (gl::RGBA32F as gl::GLint, gl::RGBA),
ImageFormat::Invalid => unreachable!(),
}
@@ -2107,6 +2112,7 @@ fn gl_texture_formats_for_image_format(gl: &gl::Gl, format: ImageFormat) -> (gl:
fn gl_type_for_texture_format(format: ImageFormat) -> gl::GLuint {
match format {
ImageFormat::RGBAF32 => gl::FLOAT,
ImageFormat::RGBA16 => gl::UNSIGNED_SHORT,
_ => gl::UNSIGNED_BYTE,
}
}
@@ -30,6 +30,7 @@ pub trait GpuStoreLayout {
fn texel_size() -> usize {
match Self::image_format() {
ImageFormat::RGBA8 => 4,
ImageFormat::RGBA16 => 8,
ImageFormat::RGBAF32 => 16,
_ => unreachable!(),
}
@@ -45,6 +46,10 @@ pub trait GpuStoreLayout {
fn items_per_row<T>() -> usize {
Self::texture_width::<T>() / Self::texels_per_item::<T>()
}

fn rows_per_item<T>() -> usize {
Self::texels_per_item::<T>() / Self::texture_width::<T>()
}
}

/// A CPU-side buffer storing content to be uploaded to the GPU.
@@ -81,8 +86,10 @@ impl<T: Clone + Default, L: GpuStoreLayout> GpuStore<T, L> {
// Extend the data array to be a multiple of the row size.
// This ensures memory safety when the array is passed to
// OpenGL to upload to the GPU.
while items.len() % items_per_row != 0 {
items.push(T::default());
if items_per_row != 0 {
while items_per_row != 0 && items.len() % items_per_row != 0 {

This comment has been minimized.

@kvark

kvark Mar 24, 2017

Member

this first part of the condition is not redundant, but I don't see it as a show stopper :)

items.push(T::default());
}
}

items
@@ -170,6 +170,7 @@ impl GLContextWrapper {
}

const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
const COLOR_FLOAT_TO_FIXED_WIDE: f32 = 65535.0;
pub const ANGLE_FLOAT_TO_FIXED: f32 = 65535.0;

pub const ORTHO_NEAR_PLANE: f32 = -1000000.0;
@@ -198,6 +199,7 @@ pub enum TextureSampler {
Geometry,
ResourceRects,
Gradients,
Dither,
}

impl TextureSampler {
@@ -313,14 +315,23 @@ pub struct PackedTexel {
}

impl PackedTexel {
pub fn from_color(color: &ColorF) -> PackedTexel {
pub fn high_bytes(color: &ColorF) -> PackedTexel {
PackedTexel {
b: (0.5 + color.b * COLOR_FLOAT_TO_FIXED).floor() as u8,
g: (0.5 + color.g * COLOR_FLOAT_TO_FIXED).floor() as u8,
r: (0.5 + color.r * COLOR_FLOAT_TO_FIXED).floor() as u8,
a: (0.5 + color.a * COLOR_FLOAT_TO_FIXED).floor() as u8,
}
}

pub fn low_bytes(color: &ColorF) -> PackedTexel {
PackedTexel {
b: ((0.5 + color.b * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u16 & 0xff) as u8,

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

you could technically have a helper method here, which would accept COLOR_FLOAT_TO_FIXED_WIDE or COLOR_FLOAT_TO_FIXED as an extra argument

g: ((0.5 + color.g * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u16 & 0xff) as u8,
r: ((0.5 + color.r * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u16 & 0xff) as u8,
a: ((0.5 + color.a * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u16 & 0xff) as u8,
}
}
}

#[derive(Debug, Clone, Copy)]
@@ -277,24 +277,27 @@ pub struct GradientDataEntry {
// first the entry index is calculated to determine which two colors to interpolate between, then
// 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
// color for the following entry, despite them being adjacent. Colors are stored within in RGBA16

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

comment needs to be fixed

// format for texture upload.
pub struct GradientData {
pub colors: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
pub colors_high: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
pub colors_low: [GradientDataEntry; GRADIENT_DATA_RESOLUTION],
}

impl Default for GradientData {
fn default() -> GradientData {
GradientData {
colors: unsafe { mem::uninitialized() }
colors_high: unsafe { mem::uninitialized() },
colors_low: unsafe { mem::uninitialized() }
}
}
}

impl Clone for GradientData {
fn clone(&self) -> GradientData {
GradientData {
colors: self.colors,
colors_high: self.colors_high,
colors_low: self.colors_low,
}
}
}
@@ -314,18 +317,24 @@ impl GradientData {
let step_a = (end_color.a - start_color.a) * inv_steps;

let mut cur_color = *start_color;
let mut cur_packed_color = PackedTexel::from_color(&cur_color);
let mut cur_color_high = PackedTexel::high_bytes(&cur_color);
let mut cur_color_low = PackedTexel::low_bytes(&cur_color);

// Walk the ramp writing start and end colors for each entry.
for entry in &mut self.colors[start_idx..end_idx] {
entry.start_color = cur_packed_color;
for index in start_idx..end_idx {
let high_byte_entry = &mut self.colors_high[index];
let low_byte_entry = &mut self.colors_low[index];

high_byte_entry.start_color = cur_color_high;
low_byte_entry.start_color = cur_color_low;
cur_color.r += step_r;
cur_color.g += step_g;
cur_color.b += step_b;
cur_color.a += step_a;
cur_packed_color = PackedTexel::from_color(&cur_color);
entry.end_color = cur_packed_color;
cur_color_high = PackedTexel::high_bytes(&cur_color);
cur_color_low = PackedTexel::low_bytes(&cur_color);
high_byte_entry.end_color = cur_color_high;
low_byte_entry.end_color = cur_color_low;
}

end_idx
@@ -154,15 +154,22 @@ impl<L: GpuStoreLayout> GpuDataTexture<L> {
}

let items_per_row = L::items_per_row::<T>();
let rows_per_item = L::rows_per_item::<T>();

// Extend the data array to be a multiple of the row size.
// This ensures memory safety when the array is passed to
// OpenGL to upload to the GPU.
while data.len() % items_per_row != 0 {
data.push(T::default());
if items_per_row != 0 {
while data.len() % items_per_row != 0 {
data.push(T::default());
}
}

let height = data.len() / items_per_row;
let height = if items_per_row != 0 {
data.len() / items_per_row
} else {
data.len() * rows_per_item

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

should this be division instead?

This comment has been minimized.

@squarewave

squarewave Mar 23, 2017

Author Contributor

Multiplication is correct. We want the height to be the number of items divided by the number of items per row (which in our case would be 0.5), but the existing items_per_row is an int. I was debating between this style (adding another integer rows_per_item) or just turning items_per_row into a float. They're both ugly, since items_per_row is primarily used by design as an int in order to ensure that the texture is a full rectangle, so I'd need to cast it to an int there and it all just felt nasty. I'm open to going with that option though.

This comment has been minimized.

@kvark

kvark Mar 23, 2017

Member

I see, thanks for the explanation!

};

device.init_texture(self.id,
L::texture_width::<T>() as u32,
@@ -201,7 +208,7 @@ impl GpuStoreLayout for GradientDataTextureLayout {
}

fn texture_width<T>() -> usize {
mem::size_of::<GradientData>() / Self::texel_size()
mem::size_of::<GradientData>() / Self::texel_size() / 2
}

fn texture_filter() -> TextureFilter {
@@ -513,6 +520,8 @@ pub struct Renderer {
/// when no target is yet provided as a cache texture input.
dummy_cache_texture_id: TextureId,

dither_matrix_texture_id: TextureId,

/// Optional trait object that allows the client
/// application to provide external buffers for image data.
external_image_handler: Option<Box<ExternalImageHandler>>,
@@ -749,6 +758,18 @@ impl Renderer {
0xff, 0xff,
0xff, 0xff,
];

let dither_matrix: [u8; 64] = [
00, 48, 12, 60, 03, 51, 15, 63,
32, 16, 44, 28, 35, 19, 47, 31,
08, 56, 04, 52, 11, 59, 07, 55,
40, 24, 36, 20, 43, 27, 39, 23,
02, 50, 14, 62, 01, 49, 13, 61,
34, 18, 46, 30, 33, 17, 45, 29,
10, 58, 06, 54, 09, 57, 05, 53,
42, 26, 38, 22, 41, 25, 37, 21
];

// TODO: Ensure that the white texture can never get evicted when the cache supports LRU eviction!
let white_image_id = texture_cache.new_item_id();
texture_cache.insert(white_image_id,
@@ -773,6 +794,15 @@ impl Renderer {
RenderTargetMode::LayerRenderTarget(1),
None);

let dither_matrix_texture_id = device.create_texture_ids(1, TextureTarget::Default)[0];
device.init_texture(dither_matrix_texture_id,
8,
8,
ImageFormat::A8,
TextureFilter::Nearest,
RenderTargetMode::None,
Some(&dither_matrix));

let debug_renderer = DebugRenderer::new(&mut device);

let gpu_data_textures = [
@@ -914,6 +944,7 @@ impl Renderer {
main_thread_dispatcher: main_thread_dispatcher,
cache_texture_id_map: Vec::new(),
dummy_cache_texture_id: dummy_cache_texture_id,
dither_matrix_texture_id: dither_matrix_texture_id,
external_image_handler: None,
external_images: HashMap::with_hasher(Default::default()),
vr_compositor_handler: vr_compositor,
@@ -1254,6 +1285,9 @@ impl Renderer {
self.device.bind_texture(TextureSampler::color(i), texture_id);
}

// TODO: this probably isn't the best place for this.
self.device.bind_texture(TextureSampler::Dither, self.dither_matrix_texture_id);

self.device.update_vao_instances(vao, data, VertexUsageHint::Stream);
self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
self.profile_counters.vertices.add(6 * data.len());
@@ -607,7 +607,7 @@ impl TextureCache {
ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
ImageFormat::RGBA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
ImageFormat::Invalid | ImageFormat::RGBA16 | ImageFormat::RGBAF32 => unreachable!(),
};

// TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
@@ -28,6 +28,7 @@ pub enum ImageFormat {
RGB8 = 2,
RGBA8 = 3,
RGBAF32 = 4,
RGBA16 = 5,
}

impl ImageFormat {
@@ -36,6 +37,7 @@ impl ImageFormat {
ImageFormat::A8 => Some(1),
ImageFormat::RGB8 => Some(3),
ImageFormat::RGBA8 => Some(4),
ImageFormat::RGBA16 => Some(8),
ImageFormat::RGBAF32 => Some(16),
ImageFormat::Invalid => None,
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.