Bug 1505871. Implement putting the required data in the gpu cache for component transfer. r=gw

For table/discrete we just create a lookup table for all 256 possible input values. We should probably switch to just computing the value in the shader, unless the list of value is really long.
This commit is contained in:
Timothy Nikkel 2019-02-26 00:16:37 -06:00
Родитель c0ef28ed8e
Коммит 487492593e
7 изменённых файлов: 314 добавлений и 9 удалений

Просмотреть файл

@ -1388,6 +1388,7 @@ impl AlphaBatchBuilder {
FilterOp::ColorMatrix(..) => 10,
FilterOp::SrgbToLinear => 11,
FilterOp::LinearToSrgb => 12,
FilterOp::ComponentTransfer => unreachable!(),
};
let user_data = match filter {
@ -1413,6 +1414,7 @@ impl AlphaBatchBuilder {
FilterOp::ColorMatrix(_) => {
picture.extra_gpu_data_handle.as_int(gpu_cache)
}
FilterOp::ComponentTransfer => unreachable!(),
};
let (uv_rect_address, textures) = surface
@ -1452,6 +1454,60 @@ impl AlphaBatchBuilder {
}
}
}
PictureCompositeMode::ComponentTransferFilter(handle) => {
// This is basically the same as the general filter case above
// except we store a little more data in the filter mode and
// a gpu cache handle in the user data.
let surface = ctx.surfaces[raster_config.surface_index.0]
.surface
.as_ref()
.expect("bug: surface must be allocated by now");
let filter_data = &ctx.data_stores.filterdata[handle];
let filter_mode : i32 = 13 |
((filter_data.data.r_func.to_int() << 28 |
filter_data.data.g_func.to_int() << 24 |
filter_data.data.b_func.to_int() << 20 |
filter_data.data.a_func.to_int() << 16) as i32);
let user_data = filter_data.gpu_cache_handle.as_int(gpu_cache);
let (uv_rect_address, textures) = surface
.resolve(
render_tasks,
ctx.resource_cache,
gpu_cache,
);
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Blend),
BlendMode::PremultipliedAlpha,
textures,
);
let prim_header_index = prim_headers.push(&prim_header, z_id, [
uv_rect_address.as_int(),
filter_mode,
user_data,
]);
let instance = BrushInstance {
prim_header_index,
clip_task_address,
segment_index: INVALID_SEGMENT_INDEX,
edge_flags: EdgeAaSegmentMask::empty(),
brush_flags,
user_data: 0,
};
self.current_batch_list().push_single_instance(
key,
bounding_rect,
z_id,
PrimitiveInstanceData::from(instance),
);
}
PictureCompositeMode::Puppet { master: Some(source) } if ctx.is_picture_surface_visible(source) => return,
PictureCompositeMode::MixBlend { mode, backdrop } if ctx.is_picture_surface_visible(backdrop) => {
let backdrop_picture = &ctx.prim_store.pictures[backdrop.0];

Просмотреть файл

@ -43,6 +43,7 @@ use std::collections::vec_deque::VecDeque;
use std::sync::Arc;
use tiling::{CompositeOps};
use util::{MaxRect, VecHelper};
use ::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
#[derive(Debug, Copy, Clone)]
struct ClipNode {
@ -608,6 +609,7 @@ impl<'a> DisplayListFlattener<'a> {
let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
CompositeOps::new(
stacking_context.filter_ops_for_compositing(display_list, filters),
stacking_context.filter_datas_for_compositing(display_list, filter_datas),
stacking_context.mix_blend_mode_for_compositing(),
)
};
@ -1425,7 +1427,7 @@ impl<'a> DisplayListFlattener<'a> {
// perspective present, and skip the plane splitting
// completely when that is not the case.
Picture3DContext::In { ancestor_index, .. } => {
assert_ne!(leaf_composite_mode, None);
assert!(!leaf_composite_mode.is_none());
Picture3DContext::In { root_data: None, ancestor_index }
}
Picture3DContext::Out => Picture3DContext::Out,
@ -1511,9 +1513,41 @@ impl<'a> DisplayListFlattener<'a> {
}
// For each filter, create a new image with that composite mode.
let mut current_filter_data_index = 0;
for filter in &stacking_context.composite_ops.filters {
let filter = filter.sanitize();
let composite_mode = Some(PictureCompositeMode::Filter(filter));
let composite_mode = Some(match filter {
FilterOp::ComponentTransfer => {
let filter_data =
&stacking_context.composite_ops.filter_datas[current_filter_data_index];
let filter_data = filter_data.sanitize();
current_filter_data_index = current_filter_data_index + 1;
if filter_data.is_identity() {
continue
} else {
let filter_data_key = SFilterDataKey {
data:
SFilterData {
r_func: SFilterDataComponent::from_functype_values(
filter_data.func_r_type, &filter_data.r_values),
g_func: SFilterDataComponent::from_functype_values(
filter_data.func_g_type, &filter_data.g_values),
b_func: SFilterDataComponent::from_functype_values(
filter_data.func_b_type, &filter_data.b_values),
a_func: SFilterDataComponent::from_functype_values(
filter_data.func_a_type, &filter_data.a_values),
},
};
let handle = self.interners
.filterdata
.intern(&filter_data_key, || ());
PictureCompositeMode::ComponentTransferFilter(handle)
}
}
_ => PictureCompositeMode::Filter(filter),
});
let filter_pic_index = PictureIndex(self.prim_store.pictures
.alloc()

Просмотреть файл

@ -60,6 +60,31 @@ impl hash::Hash for SFilterDataComponent {
}
}
impl SFilterDataComponent {
pub fn to_int(&self) -> u32 {
match self {
SFilterDataComponent::Identity => 0,
SFilterDataComponent::Table(_) => 1,
SFilterDataComponent::Discrete(_) => 2,
SFilterDataComponent::Linear(_, _) => 3,
SFilterDataComponent::Gamma(_, _, _) => 4,
}
}
pub fn from_functype_values(
func_type: ComponentTransferFuncType,
values: &[f32],
) -> SFilterDataComponent {
match func_type {
ComponentTransferFuncType::Identity => SFilterDataComponent::Identity,
ComponentTransferFuncType::Table => SFilterDataComponent::Table(values.to_vec()),
ComponentTransferFuncType::Discrete => SFilterDataComponent::Discrete(values.to_vec()),
ComponentTransferFuncType::Linear => SFilterDataComponent::Linear(values[0], values[1]),
ComponentTransferFuncType::Gamma => SFilterDataComponent::Gamma(values[0], values[1], values[2]),
}
}
}
#[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
@ -96,3 +121,78 @@ impl From<SFilterDataKey> for SFilterDataTemplate {
}
}
}
impl SFilterDataTemplate {
/// Update the GPU cache for a given filter data template. This may be called multiple
/// times per frame, by each primitive reference that refers to this interned
/// template. The initial request call to the GPU cache ensures that work is only
/// done if the cache entry is invalid (due to first use or eviction).
pub fn update(
&mut self,
frame_state: &mut FrameBuildingState,
) {
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
push_component_transfer_data(&self.data.r_func, &mut request);
push_component_transfer_data(&self.data.g_func, &mut request);
push_component_transfer_data(&self.data.b_func, &mut request);
push_component_transfer_data(&self.data.a_func, &mut request);
assert!(self.data.r_func != SFilterDataComponent::Identity
|| self.data.g_func != SFilterDataComponent::Identity
|| self.data.b_func != SFilterDataComponent::Identity
|| self.data.a_func != SFilterDataComponent::Identity);
}
}
}
fn push_component_transfer_data(
func_comp: &SFilterDataComponent,
request: &mut GpuDataRequest,
) {
match func_comp {
SFilterDataComponent::Identity => { return; }
SFilterDataComponent::Table(values) |
SFilterDataComponent::Discrete(values) => {
// Push a 256 entry lookup table.
assert!(values.len() > 0);
for i in 0 .. 64 {
let mut arr = [0.0 ; 4];
for j in 0 .. 4 {
if (values.len() == 1) || (i == 63 && j == 3) {
arr[j] = values[values.len()-1];
} else {
let c = ((4*i + j) as f32)/255.0;
match func_comp {
SFilterDataComponent::Table(_) => {
let n = (values.len()-1) as f32;
let k = (n * c).floor() as u32;
let ku = k as usize;
assert!(ku < values.len()-1);
arr[j] = values[ku] + (c*n - (k as f32)) * (values[ku+1] - values[ku]);
}
SFilterDataComponent::Discrete(_) => {
let n = values.len() as f32;
let k = (n * c).floor() as usize;
assert!(k < values.len());
arr[j] = values[k];
}
SFilterDataComponent::Identity |
SFilterDataComponent::Linear(_,_) |
SFilterDataComponent::Gamma(_,_,_) => {
unreachable!();
}
}
}
}
request.push(arr);
}
}
SFilterDataComponent::Linear(a, b) => {
request.push([*a, *b, 0.0, 0.0]);
}
SFilterDataComponent::Gamma(a, b, c) => {
request.push([*a, *b, *c, 0.0]);
}
}
}

Просмотреть файл

@ -37,6 +37,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use texture_cache::TextureCacheHandle;
use tiling::RenderTargetKind;
use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect, scale_factors};
use ::filterdata::{FilterDataHandle};
/*
A picture represents a dynamically rendered image. It consists of:
@ -1882,7 +1883,7 @@ bitflags! {
/// Specifies how this Picture should be composited
/// onto the target it belongs to.
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum PictureCompositeMode {
/// Don't composite this picture in a standard way,
@ -1899,8 +1900,10 @@ pub enum PictureCompositeMode {
mode: MixBlendMode,
backdrop: PictureIndex,
},
/// Apply a CSS filter.
/// Apply a CSS filter (except component transfer).
Filter(FilterOp),
/// Apply a component transfer filter.
ComponentTransferFilter(FilterDataHandle),
/// Draw to intermediate surface, copy straight across. This
/// is used for CSS isolation, and plane splitting.
Blit(BlitReason),
@ -3110,6 +3113,33 @@ impl PicturePrimitive {
frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::ComponentTransferFilter(handle) => {
let filter_data = &mut data_stores.filterdata[handle];
filter_data.update(frame_state);
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
true,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
device_pixel_scale,
);
let render_task_id = frame_state.render_tasks.add(picture_task);
frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::Puppet { .. } |
PictureCompositeMode::MixBlend { .. } |
PictureCompositeMode::Blit(_) => {

Просмотреть файл

@ -6,6 +6,7 @@ use api::{
ColorU, FilterOp, LayoutSize, LayoutPrimitiveInfo, MixBlendMode,
PropertyBinding, PropertyBindingId, LayoutVector2D,
};
use intern::ItemUid;
use app_units::Au;
use display_list_flattener::{AsInstanceKind, IsVisible};
use intern::{Internable, InternDebug};
@ -40,6 +41,7 @@ pub enum PictureCompositeKey {
ColorMatrix([Au; 20]),
SrgbToLinear,
LinearToSrgb,
ComponentTransfer(ItemUid),
// MixBlendMode
Multiply,
@ -115,8 +117,12 @@ impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
}
PictureCompositeKey::ColorMatrix(quantized_values)
}
FilterOp::ComponentTransfer => unreachable!(),
}
}
Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
PictureCompositeKey::ComponentTransfer(handle.uid())
}
Some(PictureCompositeMode::Puppet { .. }) |
Some(PictureCompositeMode::Blit(_)) |
Some(PictureCompositeMode::TileCache { .. }) |
@ -225,7 +231,7 @@ fn test_struct_sizes() {
// test expectations and move on.
// (b) You made a structure larger. This is not necessarily a problem, but should only
// be done with care, and after checking if talos performance regresses badly.
assert_eq!(mem::size_of::<Picture>(), 84, "Picture size changed");
assert_eq!(mem::size_of::<Picture>(), 88, "Picture size changed");
assert_eq!(mem::size_of::<PictureTemplate>(), 20, "PictureTemplate size changed");
assert_eq!(mem::size_of::<PictureKey>(), 96, "PictureKey size changed");
assert_eq!(mem::size_of::<PictureKey>(), 104, "PictureKey size changed");
}

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
use api::{DocumentLayer, FilterOp, ImageFormat, DevicePoint};
use api::{DocumentLayer, FilterOp, FilterData, ImageFormat, DevicePoint};
use api::{MixBlendMode, PipelineId, DeviceRect, LayoutSize, WorldRect};
use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
use clip::ClipStore;
@ -1105,21 +1105,25 @@ impl RenderPass {
pub struct CompositeOps {
// Requires only a single texture as input (e.g. most filters)
pub filters: Vec<FilterOp>,
pub filter_datas: Vec<FilterData>,
// Requires two source textures (e.g. mix-blend-mode)
pub mix_blend_mode: Option<MixBlendMode>,
}
impl CompositeOps {
pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> Self {
pub fn new(filters: Vec<FilterOp>,
filter_datas: Vec<FilterData>,
mix_blend_mode: Option<MixBlendMode>) -> Self {
CompositeOps {
filters,
filter_datas,
mix_blend_mode,
}
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty() && self.mix_blend_mode.is_none()
self.filters.is_empty() && self.filter_datas.is_empty() && self.mix_blend_mode.is_none()
}
}

Просмотреть файл

@ -687,6 +687,81 @@ pub struct FilterData {
pub a_values: Vec<f32>,
}
fn sanitize_func_type(
func_type: ComponentTransferFuncType,
values: &[f32],
) -> ComponentTransferFuncType {
if values.is_empty() {
return ComponentTransferFuncType::Identity;
}
if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
return ComponentTransferFuncType::Identity;
}
if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
return ComponentTransferFuncType::Identity;
}
func_type
}
fn sanitize_values(
func_type: ComponentTransferFuncType,
values: &[f32],
) -> bool {
if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
return false;
}
if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
return false;
}
true
}
impl FilterData {
/// Ensure that the number of values matches up with the function type.
pub fn sanitize(&self) -> FilterData {
FilterData {
func_r_type: sanitize_func_type(self.func_r_type, &self.r_values),
r_values:
if sanitize_values(self.func_r_type, &self.r_values) {
self.r_values.clone()
} else {
Vec::new()
},
func_g_type: sanitize_func_type(self.func_g_type, &self.g_values),
g_values:
if sanitize_values(self.func_g_type, &self.g_values) {
self.g_values.clone()
} else {
Vec::new()
},
func_b_type: sanitize_func_type(self.func_b_type, &self.b_values),
b_values:
if sanitize_values(self.func_b_type, &self.b_values) {
self.b_values.clone()
} else {
Vec::new()
},
func_a_type: sanitize_func_type(self.func_a_type, &self.a_values),
a_values:
if sanitize_values(self.func_a_type, &self.a_values) {
self.a_values.clone()
} else {
Vec::new()
},
}
}
pub fn is_identity(&self) -> bool {
self.func_r_type == ComponentTransferFuncType::Identity &&
self.func_g_type == ComponentTransferFuncType::Identity &&
self.func_b_type == ComponentTransferFuncType::Identity &&
self.func_a_type == ComponentTransferFuncType::Identity
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct IframeDisplayItem {
pub pipeline_id: PipelineId,