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

Tiled image support #897

Merged
merged 6 commits into from Feb 24, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -105,14 +105,9 @@ fn main() {
let vector_img = api.generate_image_key();
api.add_image(
vector_img,
ImageDescriptor {
format: ImageFormat::RGBA8,
width: 100,
height: 100,
stride: None,
is_opaque: true,
},
ImageDescriptor::new(100, 100, ImageFormat::RGBA8, true),
ImageData::new_blob_image(Vec::new()),
None,
);

let pipeline_id = PipelineId(0, 0);
@@ -148,14 +143,9 @@ fn main() {
let mask_image = api.generate_image_key();
api.add_image(
mask_image,
ImageDescriptor {
width: 2,
height: 2,
stride: None,
format: ImageFormat::A8,
is_opaque: true,
},
ImageData::new(vec![0, 80, 180, 255])
ImageDescriptor::new(2, 2, ImageFormat::A8, true),
ImageData::new(vec![0, 80, 180, 255]),
None,
);
let mask = webrender_traits::ImageMask {
image: mask_image,
@@ -1631,7 +1631,12 @@ impl Device {
None => width,
};

assert!(data.len() as u32 == bpp * row_length * height);
// Take the stride into account for all rows, except the last one.
let len = bpp * row_length * (height - 1)
+ width * bpp;

assert!(data.len() as u32 >= len);
let data = &data[0..len as usize];

if let Some(..) = stride {
gl::pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
@@ -16,10 +16,10 @@ use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
use webrender_traits::{AuxiliaryLists, ClipRegion, ColorF, DisplayItem, Epoch, FilterOp};
use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform};
use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform, TileOffset};
use webrender_traits::{MixBlendMode, PipelineId, ScrollEventPhase, ScrollLayerId, ScrollLayerState};
use webrender_traits::{ScrollLocation, ScrollPolicy, ServoScrollRootId, SpecificDisplayItem};
use webrender_traits::{StackingContext, WorldPoint};
use webrender_traits::{StackingContext, WorldPoint, ImageDisplayItem, DeviceUintSize};

#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct FrameId(pub u32);
@@ -29,6 +29,7 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6
struct FlattenContext<'a> {
scene: &'a Scene,
builder: &'a mut FrameBuilder,
resource_cache: &'a mut ResourceCache,
}

// TODO: doc
@@ -226,7 +227,7 @@ impl Frame {
self.clip_scroll_tree.discard_frame_state_for_pipeline(pipeline_id);
}

pub fn create(&mut self, scene: &Scene) {
pub fn create(&mut self, scene: &Scene, resource_cache: &mut ResourceCache) {
let root_pipeline_id = match scene.root_pipeline_id {
Some(root_pipeline_id) => root_pipeline_id,
None => return,
@@ -276,6 +277,7 @@ impl Frame {
let mut context = FlattenContext {
scene: scene,
builder: &mut frame_builder,
resource_cache: resource_cache
};

let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
@@ -547,13 +549,22 @@ impl Frame {
&item.clip, info.context_id);
}
SpecificDisplayItem::Image(ref info) => {
context.builder.add_image(item.rect,
&item.clip,
&info.stretch_size,
&info.tile_spacing,
None,
info.image_key,
info.image_rendering);
let image = context.resource_cache.get_image_properties(info.image_key);
if let Some(tile_size) = image.tiling {
// The image resource is tiled. We have to generate an image primitive
// for each tile.
let image_size = DeviceUintSize::new(image.descriptor.width, image.descriptor.height);
self.decompose_tiled_image(context, &item, info, image_size, tile_size as u32);
} else {
context.builder.add_image(item.rect,
&item.clip,
&info.stretch_size,
&info.tile_spacing,
None,
info.image_key,
info.image_rendering,
None);
}
}
SpecificDisplayItem::YuvImage(ref info) => {
context.builder.add_yuv_image(item.rect,
@@ -646,6 +657,192 @@ impl Frame {
}
}

fn decompose_tiled_image(&mut self,
context: &mut FlattenContext,
item: &DisplayItem,
info: &ImageDisplayItem,
image_size: DeviceUintSize,
tile_size: u32) {
// The image resource is tiled. We have to generate an image primitive
// for each tile.
// We need to do this because the image is broken up into smaller tiles in the texture
// cache and the image shader is not able to work with this type of sparse representation.

// The tiling logic works as follows:
//
// ###################-+ -+
// # | | |//# | | image size
// # | | |//# | |
// #----+----+----+--#-+ | -+
// # | | |//# | | | regular tile size
// # | | |//# | | |
// #----+----+----+--#-+ | -+-+
// #////|////|////|//# | | | "leftover" height
// ################### | -+ ---+
// #----+----+----+----+
//
// In the ascii diagram above, a large image is plit into tiles of almost regular size.
// The tiles on the right and bottom edges (hatched in the diagram) are smaller than
// the regular tiles and are handled separately in the code see leftover_width/height.
// each generated image primitive corresponds to a tile in the texture cache, with the
// assumption that the smaller tiles with leftover sizes are sized to fit their own
// irregular size in the texture cache.

// TODO(nical) supporting tiled repeated images isn't implemented yet.
// One way to implement this is to have another level of decomposition on top of this one,
// and generate a set of image primitive per repetition just like we have a primitive
// per tile here.
//
// For the case where we don't tile along an axis, we can still perform the repetition in
// the shader (for this particular axis), and it is worth special-casing for this to avoid
// generating many primitives.
// This can happen with very tall and thin images used as a repeating background.
// Apparently web authors do that...

let mut stretch_size = info.stretch_size;

let mut repeat_x = false;
let mut repeat_y = false;

if stretch_size.width < item.rect.size.width {
if image_size.width < tile_size {
// we don't actually tile in this dimmension so repeating can be done in the shader.
repeat_x = true;
} else {
println!("Unimplemented! repeating a tiled image (x axis)");
stretch_size.width = item.rect.size.width;
}
}

if stretch_size.height < item.rect.size.height {
// we don't actually tile in this dimmension so repeating can be done in the shader.
if image_size.height < tile_size {
repeat_y = true;
} else {
println!("Unimplemented! repeating a tiled image (y axis)");
stretch_size.height = item.rect.size.height;
}
}

let tile_size_f32 = tile_size as f32;

// Note: this rounds down so it excludes the partially filled tiles on the right and
// bottom edges (we handle them separately below).
let num_tiles_x = (image_size.width / tile_size) as u16;
let num_tiles_y = (image_size.height / tile_size) as u16;

// Ratio between (image space) tile size and image size.
let img_dw = tile_size_f32 / (image_size.width as f32);
let img_dh = tile_size_f32 / (image_size.height as f32);

// Strected size of the tile in layout space.
let stretched_tile_size = LayerSize::new(
img_dw * stretch_size.width,
img_dh * stretch_size.height
);

// The size in pixels of the tiles on the right and bottom edges, smaller
// than the regular tile size if the image is not a multiple of the tile size.
// Zero means the image size is a multiple of the tile size.
let leftover = DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);

for ty in 0..num_tiles_y {
for tx in 0..num_tiles_x {
self.add_tile_primitive(context, item, info,
TileOffset::new(tx, ty),
stretched_tile_size,
1.0, 1.0,
repeat_x, repeat_y);
}
if leftover.width != 0 {
// Tiles on the right edge that are smaller than the tile size.
self.add_tile_primitive(context, item, info,
TileOffset::new(num_tiles_x, ty),
stretched_tile_size,
(leftover.width as f32) / tile_size_f32,
1.0,
repeat_x, repeat_y);
}
}

if leftover.height != 0 {
for tx in 0..num_tiles_x {
// Tiles on the bottom edge that are smaller than the tile size.
self.add_tile_primitive(context, item, info,
TileOffset::new(tx, num_tiles_y),
stretched_tile_size,
1.0,
(leftover.height as f32) / tile_size_f32,
repeat_x, repeat_y);
}

if leftover.width != 0 {
// Finally, the bottom-right tile with a "leftover" size.
self.add_tile_primitive(context, item, info,
TileOffset::new(num_tiles_x, num_tiles_y),
stretched_tile_size,
(leftover.width as f32) / tile_size_f32,
(leftover.height as f32) / tile_size_f32,
repeat_x, repeat_y);
}
}
}

fn add_tile_primitive(&mut self,
context: &mut FlattenContext,
item: &DisplayItem,
info: &ImageDisplayItem,
tile_offset: TileOffset,
stretched_tile_size: LayerSize,
tile_ratio_width: f32,
tile_ratio_height: f32,
repeat_x: bool,
repeat_y: bool) {
// If the the image is tiled along a given axis, we can't have the shader compute
// the image repetition pattern. In this case we base the primitive's rectangle size
// on the stretched tile size which effectively cancels the repetion (and repetition
// has to be emulated by generating more primitives).
// If the image is not tiling along this axis, we can perform the repetition in the
// shader. in this case we use the item's size in the primitive (on that particular
// axis).
// See the repeat_x/y code below.

let stretched_size = LayerSize::new(
stretched_tile_size.width * tile_ratio_width,
stretched_tile_size.height * tile_ratio_height,
);

let mut prim_rect = LayerRect::new(
item.rect.origin + LayerPoint::new(
tile_offset.x as f32 * stretched_tile_size.width,
tile_offset.y as f32 * stretched_tile_size.height,
),
stretched_size,
);

if repeat_x {
assert_eq!(tile_offset.x, 0);
prim_rect.size.width = item.rect.size.width;
}

if repeat_y {
assert_eq!(tile_offset.y, 0);
prim_rect.size.height = item.rect.size.height;
}

// Fix up the primitive's rect if it overflows the original item rect.
if let Some(prim_rect) = prim_rect.intersection(&item.rect) {
context.builder.add_image(prim_rect,
&item.clip,
&stretched_size,
&info.tile_spacing,
None,
info.image_key,
info.image_rendering,
Some(tile_offset));
}
}

pub fn build(&mut self,
resource_cache: &mut ResourceCache,
auxiliary_lists_map: &AuxiliaryListsMap,
@@ -28,7 +28,7 @@ use util::{self, pack_as_float, rect_from_points_f, subtract_rect, TransformedRe
use util::{RectHelpers, TransformedRectKind};
use webrender_traits::{as_scroll_parent_rect, BorderDetails, BorderDisplayItem, BorderSide, BorderStyle};
use webrender_traits::{BoxShadowClipMode, ClipRegion, ColorF, device_length, DeviceIntPoint};
use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintSize, ExtendMode, FontKey};
use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintSize, ExtendMode, FontKey, TileOffset};
use webrender_traits::{FontRenderMode, GlyphOptions, ImageKey, ImageRendering, ItemRange};
use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
use webrender_traits::{RepeatMode, ScrollLayerId, ScrollLayerPixel, WebGLContextId, YuvColorSpace};
@@ -417,7 +417,8 @@ impl FrameBuilder {
&segment.tile_spacing,
Some(segment.sub_rect),
border.image_key,
ImageRendering::Auto);
ImageRendering::Auto,
None);
}
}
BorderDetails::Normal(ref border) => {
@@ -833,10 +834,12 @@ impl FrameBuilder {
tile_spacing: &LayerSize,
sub_rect: Option<TexelRect>,
image_key: ImageKey,
image_rendering: ImageRendering) {
image_rendering: ImageRendering,
tile: Option<TileOffset>) {
let prim_cpu = ImagePrimitiveCpu {
kind: ImagePrimitiveKind::Image(image_key,
image_rendering,
tile,
*tile_spacing),
color_texture_id: SourceTexture::Invalid,
resource_address: GpuStoreAddress(0),
@@ -1361,7 +1364,7 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> {

if let Some(mask) = scroll_layer.clip_source.image_mask() {
// We don't add the image mask for resolution, because layer masks are resolved later.
self.resource_cache.request_image(mask.image, ImageRendering::Auto);
self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
}
}

@@ -387,6 +387,7 @@ pub enum TextureUpdateOp {
height: u32,
data: Arc<Vec<u8>>,
stride: Option<u32>,
offset: u32,
},
UpdateForExternalBuffer {
rect: DeviceUintRect,
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.