Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
use api::{AlphaType, ClipMode, ImageBufferKind};
use api::{FontInstanceFlags, YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF};
use api::units::*;
use crate::clip::{ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore};
use crate::command_buffer::PrimitiveCommand;
use crate::composite::CompositorSurfaceKind;
use crate::pattern::PatternKind;
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSystemId};
use glyph_rasterizer::{GlyphFormat, SubpixelDirection};
use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress};
use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
use crate::gpu_types::SplitCompositeInstance;
use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
use crate::gpu_types::{ImageBrushData, get_shader_opacity, BoxShadowData, MaskInstance};
use crate::gpu_types::{ClipMaskInstanceCommon, ClipMaskInstanceRect, ClipMaskInstanceBoxShadow};
use crate::internal_types::{FastHashMap, Filter, FrameAllocator, FrameMemory, FrameVec, Swizzle, TextureSource};
use crate::picture::{Picture3DContext, PictureCompositeMode, calculate_screen_uv};
use crate::prim_store::{PrimitiveInstanceKind, ClipData};
use crate::prim_store::{PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
use crate::prim_store::VECS_PER_SEGMENT;
use crate::quad;
use crate::render_target::RenderTargetContext;
use crate::render_task_graph::{RenderTaskId, RenderTaskGraph};
use crate::render_task::{RenderTaskAddress, RenderTaskKind, SubPass};
use crate::renderer::{BlendMode, GpuBufferBuilder, ShaderColorMode};
use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH;
use crate::resource_cache::{GlyphFetchResult, ImageProperties};
use crate::space::SpaceMapper;
use crate::visibility::{PrimitiveVisibilityFlags, VisibilityState};
use smallvec::SmallVec;
use std::{f32, i32, usize};
use crate::util::{project_rect, MaxRect, TransformedRectKind, ScaleOffset};
use crate::segment::EdgeAaSegmentMask;
// Special sentinel value recognized by the shader. It is considered to be
// a dummy task that doesn't mask out anything.
const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fffffff);
/// Used to signal there are no segments provided with this primitive.
pub const INVALID_SEGMENT_INDEX: i32 = 0xffff;
/// Size in device pixels for tiles that clip masks are drawn in.
const CLIP_RECTANGLE_TILE_SIZE: i32 = 128;
/// The minimum size of a clip mask before trying to draw in tiles.
const CLIP_RECTANGLE_AREA_THRESHOLD: f32 = (CLIP_RECTANGLE_TILE_SIZE * CLIP_RECTANGLE_TILE_SIZE * 4) as f32;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum BrushBatchKind {
Solid,
Image(ImageBufferKind),
Blend,
MixBlend {
task_id: RenderTaskId,
backdrop_id: RenderTaskId,
},
YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
LinearGradient,
Opacity,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum BatchKind {
SplitComposite,
TextRun(GlyphFormat),
Brush(BrushBatchKind),
Quad(PatternKind),
}
/// Input textures for a primitive, without consideration of clip mask
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TextureSet {
pub colors: [TextureSource; 3],
}
impl TextureSet {
const UNTEXTURED: TextureSet = TextureSet {
colors: [
TextureSource::Invalid,
TextureSource::Invalid,
TextureSource::Invalid,
],
};
/// A textured primitive
fn prim_textured(
color: TextureSource,
) -> Self {
TextureSet {
colors: [
color,
TextureSource::Invalid,
TextureSource::Invalid,
],
}
}
fn is_compatible_with(&self, other: &TextureSet) -> bool {
self.colors[0].is_compatible(&other.colors[0]) &&
self.colors[1].is_compatible(&other.colors[1]) &&
self.colors[2].is_compatible(&other.colors[2])
}
}
impl TextureSource {
fn combine(&self, other: TextureSource) -> TextureSource {
if other == TextureSource::Invalid {
*self
} else {
other
}
}
}
/// Optional textures that can be used as a source in the shaders.
/// Textures that are not used by the batch are equal to TextureId::invalid().
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BatchTextures {
pub input: TextureSet,
pub clip_mask: TextureSource,
}
impl BatchTextures {
/// An empty batch textures (no binding slots set)
pub fn empty() -> BatchTextures {
BatchTextures {
input: TextureSet::UNTEXTURED,
clip_mask: TextureSource::Invalid,
}
}
/// A textured primitive with optional clip mask
pub fn prim_textured(
color: TextureSource,
clip_mask: TextureSource,
) -> BatchTextures {
BatchTextures {
input: TextureSet::prim_textured(color),
clip_mask,
}
}
/// An untextured primitive with optional clip mask
pub fn prim_untextured(
clip_mask: TextureSource,
) -> BatchTextures {
BatchTextures {
input: TextureSet::UNTEXTURED,
clip_mask,
}
}
/// A composite style effect with single input texture
pub fn composite_rgb(
texture: TextureSource,
) -> BatchTextures {
BatchTextures {
input: TextureSet {
colors: [
texture,
TextureSource::Invalid,
TextureSource::Invalid,
],
},
clip_mask: TextureSource::Invalid,
}
}
/// A composite style effect with up to 3 input textures
pub fn composite_yuv(
color0: TextureSource,
color1: TextureSource,
color2: TextureSource,
) -> BatchTextures {
BatchTextures {
input: TextureSet {
colors: [color0, color1, color2],
},
clip_mask: TextureSource::Invalid,
}
}
pub fn is_compatible_with(&self, other: &BatchTextures) -> bool {
if !self.clip_mask.is_compatible(&other.clip_mask) {
return false;
}
self.input.is_compatible_with(&other.input)
}
pub fn combine_textures(&self, other: BatchTextures) -> Option<BatchTextures> {
if !self.is_compatible_with(&other) {
return None;
}
let mut new_textures = BatchTextures::empty();
new_textures.clip_mask = self.clip_mask.combine(other.clip_mask);
for i in 0 .. 3 {
new_textures.input.colors[i] = self.input.colors[i].combine(other.input.colors[i]);
}
Some(new_textures)
}
fn merge(&mut self, other: &BatchTextures) {
self.clip_mask = self.clip_mask.combine(other.clip_mask);
for (s, o) in self.input.colors.iter_mut().zip(other.input.colors.iter()) {
*s = s.combine(*o);
}
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BatchKey {
pub kind: BatchKind,
pub blend_mode: BlendMode,
pub textures: BatchTextures,
}
impl BatchKey {
pub fn new(kind: BatchKind, blend_mode: BlendMode, textures: BatchTextures) -> Self {
BatchKey {
kind,
blend_mode,
textures,
}
}
pub fn is_compatible_with(&self, other: &BatchKey) -> bool {
self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures)
}
}
pub struct BatchRects {
/// Union of all of the batch's item rects.
///
/// Very often we can skip iterating over item rects by testing against
/// this one first.
batch: PictureRect,
/// When the batch rectangle above isn't a good enough approximation, we
/// store per item rects.
items: Option<FrameVec<PictureRect>>,
// TODO: batch rects don't need to be part of the frame but they currently
// are. It may be cleaner to remove them from the frame's final data structure
// and not use the frame's allocator.
allocator: FrameAllocator,
}
impl BatchRects {
fn new(allocator: FrameAllocator) -> Self {
BatchRects {
batch: PictureRect::zero(),
items: None,
allocator,
}
}
#[inline]
fn add_rect(&mut self, rect: &PictureRect) {
let union = self.batch.union(rect);
// If we have already started storing per-item rects, continue doing so.
// Otherwise, check whether only storing the batch rect is a good enough
// approximation.
if let Some(items) = &mut self.items {
items.push(*rect);
} else if self.batch.area() + rect.area() < union.area() {
let mut items = self.allocator.clone().new_vec_with_capacity(16);
items.push(self.batch);
items.push(*rect);
self.items = Some(items);
}
self.batch = union;
}
#[inline]
fn intersects(&mut self, rect: &PictureRect) -> bool {
if !self.batch.intersects(rect) {
return false;
}
if let Some(items) = &self.items {
items.iter().any(|item| item.intersects(rect))
} else {
// If we don't have per-item rects it means the batch rect is a good
// enough approximation and we didn't bother storing per-rect items.
true
}
}
}
pub struct AlphaBatchList {
pub batches: FrameVec<PrimitiveBatch>,
pub batch_rects: FrameVec<BatchRects>,
current_batch_index: usize,
current_z_id: ZBufferId,
break_advanced_blend_batches: bool,
}
impl AlphaBatchList {
fn new(break_advanced_blend_batches: bool, preallocate: usize, memory: &FrameMemory) -> Self {
AlphaBatchList {
batches: memory.new_vec_with_capacity(preallocate),
batch_rects: memory.new_vec_with_capacity(preallocate),
current_z_id: ZBufferId::invalid(),
current_batch_index: usize::MAX,
break_advanced_blend_batches,
}
}
/// Clear all current batches in this list. This is typically used
/// when a primitive is encountered that occludes all previous
/// content in this batch list.
fn clear(&mut self) {
self.current_batch_index = usize::MAX;
self.current_z_id = ZBufferId::invalid();
self.batches.clear();
self.batch_rects.clear();
}
pub fn set_params_and_get_batch(
&mut self,
key: BatchKey,
features: BatchFeatures,
// The bounding box of everything at this Z plane. We expect potentially
// multiple primitive segments coming with the same `z_id`.
z_bounding_rect: &PictureRect,
z_id: ZBufferId,
) -> &mut FrameVec<PrimitiveInstanceData> {
if z_id != self.current_z_id ||
self.current_batch_index == usize::MAX ||
!self.batches[self.current_batch_index].key.is_compatible_with(&key)
{
let mut selected_batch_index = None;
match key.blend_mode {
BlendMode::Advanced(_) if self.break_advanced_blend_batches => {
// don't try to find a batch
}
_ => {
for (batch_index, batch) in self.batches.iter().enumerate().rev() {
// For normal batches, we only need to check for overlaps for batches
// other than the first batch we consider. If the first batch
// is compatible, then we know there isn't any potential overlap
// issues to worry about.
if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(batch_index);
break;
}
// check for intersections
if self.batch_rects[batch_index].intersects(z_bounding_rect) {
break;
}
}
}
}
if selected_batch_index.is_none() {
// Text runs tend to have a lot of instances per batch, causing a lot of reallocation
// churn as items are added one by one, so we give it a head start. Ideally we'd start
// with a larger number, closer to 1k but in some bad cases with lots of batch break
// we would be wasting a lot of memory.
// Generally it is safe to preallocate small-ish values for other batch kinds because
// the items are small and there are no zero-sized batches so there will always be
// at least one allocation.
let prealloc = match key.kind {
BatchKind::TextRun(..) => 128,
_ => 16,
};
let mut new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
new_batch.instances.reserve(prealloc);
selected_batch_index = Some(self.batches.len());
self.batches.push(new_batch);
self.batch_rects.push(BatchRects::new(self.batches.allocator().clone()));
}
self.current_batch_index = selected_batch_index.unwrap();
self.batch_rects[self.current_batch_index].add_rect(z_bounding_rect);
self.current_z_id = z_id;
}
let batch = &mut self.batches[self.current_batch_index];
batch.features |= features;
batch.key.textures.merge(&key.textures);
&mut batch.instances
}
}
pub struct OpaqueBatchList {
pub pixel_area_threshold_for_new_batch: f32,
pub batches: FrameVec<PrimitiveBatch>,
pub current_batch_index: usize,
lookback_count: usize,
}
impl OpaqueBatchList {
fn new(pixel_area_threshold_for_new_batch: f32, lookback_count: usize, memory: &FrameMemory) -> Self {
OpaqueBatchList {
batches: memory.new_vec(),
pixel_area_threshold_for_new_batch,
current_batch_index: usize::MAX,
lookback_count,
}
}
/// Clear all current batches in this list. This is typically used
/// when a primitive is encountered that occludes all previous
/// content in this batch list.
fn clear(&mut self) {
self.current_batch_index = usize::MAX;
self.batches.clear();
}
pub fn set_params_and_get_batch(
&mut self,
key: BatchKey,
features: BatchFeatures,
// The bounding box of everything at the current Z, whatever it is. We expect potentially
// multiple primitive segments produced by a primitive, which we allow to check
// `current_batch_index` instead of iterating the batches.
z_bounding_rect: &PictureRect,
) -> &mut FrameVec<PrimitiveInstanceData> {
// If the area of this primitive is larger than the given threshold,
// then it is large enough to warrant breaking a batch for. In this
// case we just see if it can be added to the existing batch or
// create a new one.
let is_large_occluder = z_bounding_rect.area() > self.pixel_area_threshold_for_new_batch;
// Since primitives of the same kind tend to come in succession, we keep track
// of the current batch index to skip the search in some cases. We ignore the
// current batch index in the case of large occluders to make sure they get added
// at the top of the bach list.
if is_large_occluder || self.current_batch_index == usize::MAX ||
!self.batches[self.current_batch_index].key.is_compatible_with(&key) {
let mut selected_batch_index = None;
if is_large_occluder {
if let Some(batch) = self.batches.last() {
if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(self.batches.len() - 1);
}
}
} else {
// Otherwise, look back through a reasonable number of batches.
for (batch_index, batch) in self.batches.iter().enumerate().rev().take(self.lookback_count) {
if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(batch_index);
break;
}
}
}
if selected_batch_index.is_none() {
let new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
selected_batch_index = Some(self.batches.len());
self.batches.push(new_batch);
}
self.current_batch_index = selected_batch_index.unwrap();
}
let batch = &mut self.batches[self.current_batch_index];
batch.features |= features;
batch.key.textures.merge(&key.textures);
&mut batch.instances
}
fn finalize(&mut self) {
// Reverse the instance arrays in the opaque batches
// to get maximum z-buffer efficiency by drawing
// front-to-back.
// TODO(gw): Maybe we can change the batch code to
// build these in reverse and avoid having
// to reverse the instance array here.
for batch in &mut self.batches {
batch.instances.reverse();
}
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveBatch {
pub key: BatchKey,
pub instances: FrameVec<PrimitiveInstanceData>,
pub features: BatchFeatures,
}
bitflags! {
/// Features of the batch that, if not requested, may allow a fast-path.
///
/// Rather than breaking batches when primitives request different features,
/// we always request the minimum amount of features to satisfy all items in
/// the batch.
/// The goal is to let the renderer be optionally select more specialized
/// versions of a shader if the batch doesn't require code certain code paths.
/// Not all shaders necessarily implement all of these features.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct BatchFeatures: u8 {
const ALPHA_PASS = 1 << 0;
const ANTIALIASING = 1 << 1;
const REPETITION = 1 << 2;
/// Indicates a primitive in this batch may use a clip mask.
const CLIP_MASK = 1 << 3;
}
}
impl PrimitiveBatch {
fn new(key: BatchKey, allocator: FrameAllocator) -> PrimitiveBatch {
PrimitiveBatch {
key,
instances: FrameVec::new_in(allocator),
features: BatchFeatures::empty(),
}
}
fn merge(&mut self, other: PrimitiveBatch) {
self.instances.extend(other.instances);
self.features |= other.features;
self.key.textures.merge(&other.key.textures);
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct AlphaBatchContainer {
pub opaque_batches: FrameVec<PrimitiveBatch>,
pub alpha_batches: FrameVec<PrimitiveBatch>,
/// The overall scissor rect for this render task, if one
/// is required.
pub task_scissor_rect: Option<DeviceIntRect>,
/// The rectangle of the owning render target that this
/// set of batches affects.
pub task_rect: DeviceIntRect,
}
impl AlphaBatchContainer {
pub fn new(
task_scissor_rect: Option<DeviceIntRect>,
memory: &FrameMemory,
) -> AlphaBatchContainer {
AlphaBatchContainer {
opaque_batches: memory.new_vec(),
alpha_batches: memory.new_vec(),
task_scissor_rect,
task_rect: DeviceIntRect::zero(),
}
}
pub fn is_empty(&self) -> bool {
self.opaque_batches.is_empty() &&
self.alpha_batches.is_empty()
}
fn merge(&mut self, builder: AlphaBatchBuilder, task_rect: &DeviceIntRect) {
self.task_rect = self.task_rect.union(task_rect);
for other_batch in builder.opaque_batch_list.batches {
let batch_index = self.opaque_batches.iter().position(|batch| {
batch.key.is_compatible_with(&other_batch.key)
});
match batch_index {
Some(batch_index) => {
self.opaque_batches[batch_index].merge(other_batch);
}
None => {
self.opaque_batches.push(other_batch);
}
}
}
let mut min_batch_index = 0;
for other_batch in builder.alpha_batch_list.batches {
let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| {
batch.key.is_compatible_with(&other_batch.key)
});
match batch_index {
Some(batch_index) => {
let index = batch_index + min_batch_index;
self.alpha_batches[index].merge(other_batch);
min_batch_index = index;
}
None => {
self.alpha_batches.push(other_batch);
min_batch_index = self.alpha_batches.len();
}
}
}
}
}
/// Each segment can optionally specify a per-segment
/// texture set and one user data field.
#[derive(Debug, Copy, Clone)]
struct SegmentInstanceData {
textures: TextureSet,
specific_resource_address: i32,
}
/// Encapsulates the logic of building batches for items that are blended.
pub struct AlphaBatchBuilder {
pub alpha_batch_list: AlphaBatchList,
pub opaque_batch_list: OpaqueBatchList,
pub render_task_id: RenderTaskId,
render_task_address: RenderTaskAddress,
}
impl AlphaBatchBuilder {
pub fn new(
screen_size: DeviceIntSize,
break_advanced_blend_batches: bool,
lookback_count: usize,
render_task_id: RenderTaskId,
render_task_address: RenderTaskAddress,
memory: &FrameMemory,
) -> Self {
// The threshold for creating a new batch is
// one quarter the screen size.
let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
AlphaBatchBuilder {
alpha_batch_list: AlphaBatchList::new(break_advanced_blend_batches, 128, memory),
opaque_batch_list: OpaqueBatchList::new(batch_area_threshold, lookback_count, memory),
render_task_id,
render_task_address,
}
}
/// Clear all current batches in this builder. This is typically used
/// when a primitive is encountered that occludes all previous
/// content in this batch list.
fn clear(&mut self) {
self.alpha_batch_list.clear();
self.opaque_batch_list.clear();
}
pub fn build(
mut self,
batch_containers: &mut FrameVec<AlphaBatchContainer>,
merged_batches: &mut AlphaBatchContainer,
task_rect: DeviceIntRect,
task_scissor_rect: Option<DeviceIntRect>,
) {
self.opaque_batch_list.finalize();
if task_scissor_rect.is_none() {
merged_batches.merge(self, &task_rect);
} else {
batch_containers.push(AlphaBatchContainer {
alpha_batches: self.alpha_batch_list.batches,
opaque_batches: self.opaque_batch_list.batches,
task_scissor_rect,
task_rect,
});
}
}
pub fn push_single_instance(
&mut self,
key: BatchKey,
features: BatchFeatures,
bounding_rect: &PictureRect,
z_id: ZBufferId,
instance: PrimitiveInstanceData,
) {
self.set_params_and_get_batch(key, features, bounding_rect, z_id)
.push(instance);
}
pub fn set_params_and_get_batch(
&mut self,
key: BatchKey,
features: BatchFeatures,
bounding_rect: &PictureRect,
z_id: ZBufferId,
) -> &mut FrameVec<PrimitiveInstanceData> {
match key.blend_mode {
BlendMode::None => {
self.opaque_batch_list
.set_params_and_get_batch(key, features, bounding_rect)
}
BlendMode::Alpha |
BlendMode::PremultipliedAlpha |
BlendMode::PremultipliedDestOut |
BlendMode::SubpixelDualSource |
BlendMode::Advanced(_) |
BlendMode::MultiplyDualSource |
BlendMode::Screen |
BlendMode::Exclusion |
BlendMode::PlusLighter => {
self.alpha_batch_list
.set_params_and_get_batch(key, features, bounding_rect, z_id)
}
}
}
}
/// Supports (recursively) adding a list of primitives and pictures to an alpha batch
/// builder. In future, it will support multiple dirty regions / slices, allowing the
/// contents of a picture to be spliced into multiple batch builders.
pub struct BatchBuilder {
/// A temporary buffer that is used during glyph fetching, stored here
/// to reduce memory allocations.
glyph_fetch_buffer: Vec<GlyphFetchResult>,
batcher: AlphaBatchBuilder,
}
impl BatchBuilder {
pub fn new(batcher: AlphaBatchBuilder) -> Self {
BatchBuilder {
glyph_fetch_buffer: Vec::new(),
batcher,
}
}
pub fn finalize(self) -> AlphaBatchBuilder {
self.batcher
}
fn add_brush_instance_to_batches(
&mut self,
batch_key: BatchKey,
features: BatchFeatures,
bounding_rect: &PictureRect,
z_id: ZBufferId,
segment_index: i32,
edge_flags: EdgeAaSegmentMask,
clip_task_address: RenderTaskAddress,
brush_flags: BrushFlags,
prim_header_index: PrimitiveHeaderIndex,
resource_address: i32,
) {
let instance = BrushInstance {
segment_index,
edge_flags,
clip_task_address,
brush_flags,
prim_header_index,
resource_address,
};
self.batcher.push_single_instance(
batch_key,
features,
bounding_rect,
z_id,
PrimitiveInstanceData::from(instance),
);
}
fn add_split_composite_instance_to_batches(
&mut self,
batch_key: BatchKey,
features: BatchFeatures,
bounding_rect: &PictureRect,
z_id: ZBufferId,
prim_header_index: PrimitiveHeaderIndex,
polygons_address: i32,
) {
let render_task_address = self.batcher.render_task_address;
self.batcher.push_single_instance(
batch_key,
features,
bounding_rect,
z_id,
PrimitiveInstanceData::from(SplitCompositeInstance {
prim_header_index,
render_task_address,
polygons_address,
z: z_id,
}),
);
}
/// Clear all current batchers. This is typically used when a primitive
/// is encountered that occludes all previous content in this batch list.
fn clear_batches(&mut self) {
self.batcher.clear();
}
// Adds a primitive to a batch.
// It can recursively call itself in some situations, for
// example if it encounters a picture where the items
// in that picture are being drawn into the same target.
pub fn add_prim_to_batch(
&mut self,
cmd: &PrimitiveCommand,
prim_spatial_node_index: SpatialNodeIndex,
ctx: &RenderTargetContext,
gpu_cache: &mut GpuCache,
render_tasks: &RenderTaskGraph,
prim_headers: &mut PrimitiveHeaders,
transforms: &mut TransformPalette,
root_spatial_node_index: SpatialNodeIndex,
surface_spatial_node_index: SpatialNodeIndex,
z_generator: &mut ZBufferIdGenerator,
prim_instances: &[PrimitiveInstance],
gpu_buffer_builder: &mut GpuBufferBuilder,
segments: &[RenderTaskId],
) {
let (prim_instance_index, extra_prim_gpu_address) = match cmd {
PrimitiveCommand::Simple { prim_instance_index } => {
(prim_instance_index, None)
}
PrimitiveCommand::Complex { prim_instance_index, gpu_address } => {
(prim_instance_index, Some(gpu_address.as_int()))
}
PrimitiveCommand::Instance { prim_instance_index, gpu_buffer_address } => {
(prim_instance_index, Some(gpu_buffer_address.as_int()))
}
PrimitiveCommand::Quad { pattern, pattern_input, prim_instance_index, gpu_buffer_address, quad_flags, edge_flags, transform_id, src_color_task_id } => {
let prim_instance = &prim_instances[prim_instance_index.0 as usize];
let prim_info = &prim_instance.vis;
let bounding_rect = &prim_info.clip_chain.pic_coverage_rect;
let render_task_address = self.batcher.render_task_address;
if segments.is_empty() {
let z_id = z_generator.next();
quad::add_to_batch(
*pattern,
*pattern_input,
render_task_address,
*transform_id,
*gpu_buffer_address,
*quad_flags,
*edge_flags,
INVALID_SEGMENT_INDEX as u8,
*src_color_task_id,
z_id,
render_tasks,
gpu_buffer_builder,
|key, instance| {
let batch = self.batcher.set_params_and_get_batch(
key,
BatchFeatures::empty(),
bounding_rect,
z_id,
);
batch.push(instance);
},
);
} else {
for (i, task_id) in segments.iter().enumerate() {
// TODO(gw): edge_flags should be per-segment, when used for more than composites
debug_assert!(edge_flags.is_empty());
let z_id = z_generator.next();
quad::add_to_batch(
*pattern,
*pattern_input,
render_task_address,
*transform_id,
*gpu_buffer_address,
*quad_flags,
*edge_flags,
i as u8,
*task_id,
z_id,
render_tasks,
gpu_buffer_builder,
|key, instance| {
let batch = self.batcher.set_params_and_get_batch(
key,
BatchFeatures::empty(),
bounding_rect,
z_id,
);
batch.push(instance);
},
);
}
}
return;
}
};
let prim_instance = &prim_instances[prim_instance_index.0 as usize];
let is_anti_aliased = ctx.data_stores.prim_has_anti_aliasing(prim_instance);
let brush_flags = if is_anti_aliased {
BrushFlags::FORCE_AA
} else {
BrushFlags::empty()
};
let vis_flags = match prim_instance.vis.state {
VisibilityState::Culled => {
return;
}
VisibilityState::PassThrough |
VisibilityState::Unset => {
panic!("bug: invalid visibility state");
}
VisibilityState::Visible { vis_flags, .. } => {
vis_flags
}
};
// If this primitive is a backdrop, that means that it is known to cover
// the entire picture cache background. In that case, the renderer will
// use the backdrop color as a clear color, and so we can drop this
// primitive and any prior primitives from the batch lists for this
// picture cache slice.
if vis_flags.contains(PrimitiveVisibilityFlags::IS_BACKDROP) {
self.clear_batches();
return;
}
let transform_id = transforms
.get_id(
prim_spatial_node_index,
root_spatial_node_index,
ctx.spatial_tree,
);
// TODO(gw): Calculating this for every primitive is a bit
// wasteful. We should probably cache this in
// the scroll node...
let transform_kind = transform_id.transform_kind();
let prim_info = &prim_instance.vis;
let bounding_rect = &prim_info.clip_chain.pic_coverage_rect;
let z_id = z_generator.next();
let prim_rect = ctx.data_stores.get_local_prim_rect(
prim_instance,
&ctx.prim_store.pictures,
ctx.surfaces,
);
let mut batch_features = BatchFeatures::empty();
if ctx.data_stores.prim_may_need_repetition(prim_instance) {
batch_features |= BatchFeatures::REPETITION;
}
if transform_kind != TransformedRectKind::AxisAligned || is_anti_aliased {
batch_features |= BatchFeatures::ANTIALIASING;
}
// Check if the primitive might require a clip mask.
if prim_info.clip_task_index != ClipTaskIndex::INVALID {
batch_features |= BatchFeatures::CLIP_MASK;
}
if !bounding_rect.is_empty() {
debug_assert_eq!(prim_info.clip_chain.pic_spatial_node_index, surface_spatial_node_index,
"The primitive's bounding box is specified in a different coordinate system from the current batch!");
}
match prim_instance.kind {
PrimitiveInstanceKind::BoxShadow { .. } => {
unreachable!("BUG: Should not hit box-shadow here as they are handled by quad infra");
}
PrimitiveInstanceKind::Clear { data_handle } => {
let prim_data = &ctx.data_stores.prim[data_handle];
let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// TODO(gw): We can abstract some of the common code below into
// helper methods, as we port more primitives to make
// use of interning.
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[get_shader_opacity(1.0), 0, 0, 0],
);
let batch_key = BatchKey {
blend_mode: BlendMode::PremultipliedDestOut,
kind: BatchKind::Brush(BrushBatchKind::Solid),
textures: BatchTextures::prim_untextured(clip_mask_texture_id),
};
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
prim_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
0,
);
}
PrimitiveInstanceKind::NormalBorder { data_handle, ref render_task_ids, .. } => {
let prim_data = &ctx.data_stores.normal_border[data_handle];
let common_data = &prim_data.common;
let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
let task_ids = &ctx.scratch.border_cache_handles[*render_task_ids];
let specified_blend_mode = BlendMode::PremultipliedAlpha;
let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new();
// Collect the segment instance data from each render
// task for each valid edge / corner of the border.
for task_id in task_ids {
if let Some((uv_rect_address, texture)) = render_tasks.resolve_location(*task_id, gpu_cache) {
segment_data.push(
SegmentInstanceData {
textures: TextureSet::prim_textured(texture),
specific_resource_address: uv_rect_address.as_int(),
}
);
}
}
// TODO: it would be less error-prone to get this info from the texture cache.
let image_buffer_kind = ImageBufferKind::Texture2D;
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
specified_blend_mode
} else {
BlendMode::None
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let batch_params = BrushBatchParameters::instanced(
BrushBatchKind::Image(image_buffer_kind),
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode(),
segment_data,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
let border_data = &prim_data.kind;
self.add_segmented_prim_to_batch(
Some(border_data.brush_segments.as_slice()),
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
common_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => {
let run = &ctx.prim_store.text_runs[run_index];
let subpx_dir = run.used_font.get_subpx_dir();
// The GPU cache data is stored in the template and reused across
// frames and display lists.
let prim_data = &ctx.data_stores.text_run[data_handle];
let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
// The local prim rect is only informative for text primitives, as
// thus is not directly necessary for any drawing of the text run.
// However the glyph offsets are relative to the prim rect origin
// less the unsnapped reference frame offset. We also want the
// the snapped reference frame offset, because cannot recalculate
// it as it ignores the animated components for the transform. As
// such, we adjust the prim rect origin here, and replace the size
// with the unsnapped and snapped offsets respectively. This has
// the added bonus of avoiding quantization effects when storing
// floats in the extra header integers.
let prim_header = PrimitiveHeader {
local_rect: LayoutRect {
min: prim_rect.min - run.reference_frame_relative_offset,
max: run.snapped_reference_frame_relative_offset.to_point(),
},
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
(run.raster_scale * 65535.0).round() as i32,
0,
0,
0,
],
);
let base_instance = GlyphInstance::new(
prim_header_index,
);
let batcher = &mut self.batcher;
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// The run.used_font.clone() is here instead of instead of inline in the `fetch_glyph`
// function call to work around a miscompilation.
let font = run.used_font.clone();
ctx.resource_cache.fetch_glyphs(
font,
&glyph_keys,
&mut self.glyph_fetch_buffer,
gpu_cache,
|texture_id, glyph_format, glyphs| {
debug_assert_ne!(texture_id, TextureSource::Invalid);
let subpx_dir = subpx_dir.limit_by(glyph_format);
let textures = BatchTextures::prim_textured(
texture_id,
clip_mask_texture_id,
);
let kind = BatchKind::TextRun(glyph_format);
let (blend_mode, color_mode) = match glyph_format {
GlyphFormat::Subpixel |
GlyphFormat::TransformedSubpixel => {
debug_assert!(ctx.use_dual_source_blending);
(
BlendMode::SubpixelDualSource,
ShaderColorMode::SubpixelDualSource,
)
}
GlyphFormat::Alpha |
GlyphFormat::TransformedAlpha |
GlyphFormat::Bitmap => {
(
BlendMode::PremultipliedAlpha,
ShaderColorMode::Alpha,
)
}
GlyphFormat::ColorBitmap => {
(
BlendMode::PremultipliedAlpha,
if run.shadow {
// Ignore color and only sample alpha when shadowing.
ShaderColorMode::BitmapShadow
} else {
ShaderColorMode::ColorBitmap
},
)
}
};
// Calculate a tighter bounding rect of just the glyphs passed to this
// callback from request_glyphs(), rather than using the bounds of the
// entire text run. This improves batching when glyphs are fragmented
// over multiple textures in the texture cache.
// This code is taken from the ps_text_run shader.
let tight_bounding_rect = {
let snap_bias = match subpx_dir {
SubpixelDirection::None => DeviceVector2D::new(0.5, 0.5),
SubpixelDirection::Horizontal => DeviceVector2D::new(0.125, 0.5),
SubpixelDirection::Vertical => DeviceVector2D::new(0.5, 0.125),
SubpixelDirection::Mixed => DeviceVector2D::new(0.125, 0.125),
};
let text_offset = prim_header.local_rect.max.to_vector();
let pic_bounding_rect = if run.used_font.flags.contains(FontInstanceFlags::TRANSFORM_GLYPHS) {
let mut device_bounding_rect = DeviceRect::default();
let glyph_transform = ctx.spatial_tree.get_relative_transform(
prim_spatial_node_index,
root_spatial_node_index,
).into_transform()
.with_destination::<WorldPixel>()
.then(&euclid::Transform3D::from_scale(ctx.global_device_pixel_scale));
let glyph_translation = DeviceVector2D::new(glyph_transform.m41, glyph_transform.m42);
let mut use_tight_bounding_rect = true;
for glyph in glyphs {
let glyph_offset = prim_data.glyphs[glyph.index_in_text_run as usize].point + prim_header.local_rect.min.to_vector();
let transformed_offset = match glyph_transform.transform_point2d(glyph_offset) {
Some(transformed_offset) => transformed_offset,
None => {
use_tight_bounding_rect = false;
break;
}
};
let raster_glyph_offset = (transformed_offset + snap_bias).floor();
let raster_text_offset = (
glyph_transform.transform_vector2d(text_offset) +
glyph_translation +
DeviceVector2D::new(0.5, 0.5)
).floor() - glyph_translation;
let device_glyph_rect = DeviceRect::from_origin_and_size(
glyph.offset + raster_glyph_offset.to_vector() + raster_text_offset,
glyph.size.to_f32(),
);
device_bounding_rect = device_bounding_rect.union(&device_glyph_rect);
}
if use_tight_bounding_rect {
let map_device_to_surface: SpaceMapper<PicturePixel, DevicePixel> = SpaceMapper::new_with_target(
root_spatial_node_index,
surface_spatial_node_index,
device_bounding_rect,
ctx.spatial_tree,
);
match map_device_to_surface.unmap(&device_bounding_rect) {
Some(r) => r.intersection(bounding_rect),
None => Some(*bounding_rect),
}
} else {
Some(*bounding_rect)
}
} else {
let mut local_bounding_rect = LayoutRect::default();
let glyph_raster_scale = run.raster_scale * ctx.global_device_pixel_scale.get();
for glyph in glyphs {
let glyph_offset = prim_data.glyphs[glyph.index_in_text_run as usize].point + prim_header.local_rect.min.to_vector();
let glyph_scale = LayoutToDeviceScale::new(glyph_raster_scale / glyph.scale);
let raster_glyph_offset = (glyph_offset * LayoutToDeviceScale::new(glyph_raster_scale) + snap_bias).floor() / glyph.scale;
let local_glyph_rect = LayoutRect::from_origin_and_size(
(glyph.offset + raster_glyph_offset.to_vector()) / glyph_scale + text_offset,
glyph.size.to_f32() / glyph_scale,
);
local_bounding_rect = local_bounding_rect.union(&local_glyph_rect);
}
let map_prim_to_surface: SpaceMapper<LayoutPixel, PicturePixel> = SpaceMapper::new_with_target(
surface_spatial_node_index,
prim_spatial_node_index,
*bounding_rect,
ctx.spatial_tree,
);
map_prim_to_surface.map(&local_bounding_rect)
};
let intersected = match pic_bounding_rect {
// The text run may have been clipped, for example if part of it is offscreen.
// So intersect our result with the original bounding rect.
Some(rect) => rect.intersection(bounding_rect).unwrap_or_else(PictureRect::zero),
// If space mapping went off the rails, fall back to the old behavior.
//TODO: consider skipping the glyph run completely in this case.
None => *bounding_rect,
};
intersected
};
let key = BatchKey::new(kind, blend_mode, textures);
let batch = batcher.alpha_batch_list.set_params_and_get_batch(
key,
batch_features,
&tight_bounding_rect,
z_id,
);
batch.reserve(glyphs.len());
for glyph in glyphs {
batch.push(base_instance.build(
clip_task_address,
subpx_dir,
glyph.index_in_text_run,
glyph.uv_rect_address,
color_mode,
));
}
},
);
}
PrimitiveInstanceKind::LineDecoration { data_handle, ref render_task, .. } => {
// The GPU cache data is stored in the template and reused across
// frames and display lists.
let common_data = &ctx.data_stores.line_decoration[data_handle].common;
let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let (batch_kind, textures, prim_user_data, specific_resource_address) = match render_task {
Some(task_id) => {
let (uv_rect_address, texture) = render_tasks.resolve_location(*task_id, gpu_cache).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
(
BrushBatchKind::Image(texture.image_buffer_kind()),
textures,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode(),
uv_rect_address.as_int(),
)
}
None => {
(
BrushBatchKind::Solid,
BatchTextures::prim_untextured(clip_mask_texture_id),
[get_shader_opacity(1.0), 0, 0, 0],
0,
)
}
};
// TODO(gw): We can abstract some of the common code below into
// helper methods, as we port more primitives to make
// use of interning.
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
prim_user_data,
);
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(batch_kind),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
common_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
specific_resource_address,
);
}
PrimitiveInstanceKind::Picture { pic_index, .. } => {
let picture = &ctx.prim_store.pictures[pic_index.0];
let blend_mode = BlendMode::PremultipliedAlpha;
let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
match picture.raster_config {
Some(ref raster_config) => {
// If the child picture was rendered in local space, we can safely
// interpolate the UV coordinates with perspective correction.
let brush_flags = brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION;
let surface = &ctx.surfaces[raster_config.surface_index.0];
let mut local_clip_rect = prim_info.clip_chain.local_clip_rect;
// If we are drawing with snapping enabled, form a simple transform that just applies
// the scale / translation from the raster transform. Otherwise, in edge cases where the
// intermediate surface has a non-identity but axis-aligned transform (e.g. a 180 degree
// rotation) it can be applied twice.
let transform_id = if surface.surface_spatial_node_index == surface.raster_spatial_node_index {
transform_id
} else {
let map_local_to_raster = SpaceMapper::new_with_target(
root_spatial_node_index,
surface.surface_spatial_node_index,
LayoutRect::max_rect(),
ctx.spatial_tree,
);
let raster_rect = map_local_to_raster
.map(&prim_rect)
.unwrap();
let sx = (raster_rect.max.x - raster_rect.min.x) / (prim_rect.max.x - prim_rect.min.x);
let sy = (raster_rect.max.y - raster_rect.min.y) / (prim_rect.max.y - prim_rect.min.y);
let tx = raster_rect.min.x - sx * prim_rect.min.x;
let ty = raster_rect.min.y - sy * prim_rect.min.y;
let transform = ScaleOffset::new(sx, sy, tx, ty);
let raster_clip_rect = map_local_to_raster
.map(&prim_info.clip_chain.local_clip_rect)
.unwrap();
local_clip_rect = transform.unmap_rect(&raster_clip_rect);
transforms.get_custom(transform.to_transform())
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let mut is_opaque = prim_info.clip_task_index == ClipTaskIndex::INVALID
&& surface.is_opaque
&& transform_kind == TransformedRectKind::AxisAligned
&& !is_anti_aliased;
let pic_task_id = picture.primary_render_task_id.unwrap();
let pic_task = &render_tasks[pic_task_id];
match pic_task.sub_pass {
Some(SubPass::Masks { .. }) => {
is_opaque = false;
}
None => {}
}
match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. } => {
// TODO(gw): For now, TileCache is still a composite mode, even though
// it will only exist as a top level primitive and never
// be encountered during batching. Consider making TileCache
// a standalone type, not a picture.
}
PictureCompositeMode::IntermediateSurface { .. } => {
// TODO(gw): As an optimization, support making this a pass-through
// and/or drawing directly from here when possible
// (e.g. if not wrapped by filters / different spatial node).
}
PictureCompositeMode::Filter(ref filter) => {
assert!(filter.is_visible());
match filter {
Filter::Blur { .. } => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
);
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
kind,
blend_mode,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_address.as_int(),
);
}
Filter::DropShadows(shadows) => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// Draw an instance per shadow first, following by the content.
// The shadows and the content get drawn as a brush image.
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D),
);
// Gets the saved render task ID of the content, which is
// deeper in the render task graph than the direct child.
let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
let content_source = {
let secondary_task = &render_tasks[secondary_id];
let texture_id = secondary_task.get_target_texture();
TextureSource::TextureCache(
texture_id,
Swizzle::default(),
)
};
// Retrieve the UV rect addresses for shadow/content.
let (shadow_uv_rect_address, shadow_texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let shadow_textures = BatchTextures::prim_textured(
shadow_texture,
clip_mask_texture_id,
);
let content_uv_rect_address = render_tasks[secondary_id]
.get_texture_address(gpu_cache)
.as_int();
// Build BatchTextures for shadow/content
let content_textures = BatchTextures::prim_textured(
content_source,
clip_mask_texture_id,
);
// Build batch keys for shadow/content
let shadow_key = BatchKey::new(kind, blend_mode, shadow_textures);
let content_key = BatchKey::new(kind, blend_mode, content_textures);
for (shadow, shadow_gpu_data) in shadows.iter().zip(picture.extra_gpu_data_handles.iter()) {
// Get the GPU cache address of the extra data handle.
let shadow_prim_address = gpu_cache.get_address(shadow_gpu_data);
let shadow_rect = prim_header.local_rect.translate(shadow.offset);
let shadow_prim_header = PrimitiveHeader {
local_rect: shadow_rect,
specific_prim_address: shadow_prim_address,
..prim_header
};
let shadow_prim_header_index = prim_headers.push(
&shadow_prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Alpha,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
shadow_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
shadow_prim_header_index,
shadow_uv_rect_address.as_int(),
);
}
let z_id_content = z_generator.next();
let content_prim_header_index = prim_headers.push(
&prim_header,
z_id_content,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
content_key,
batch_features,
bounding_rect,
z_id_content,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
content_prim_header_index,
content_uv_rect_address,
);
}
Filter::Opacity(_, amount) => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let amount = (amount * 65536.0) as i32;
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Opacity),
BlendMode::PremultipliedAlpha,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
uv_rect_address.as_int(),
amount,
0,
0,
]
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
0,
);
}
_ => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// Must be kept in sync with brush_blend.glsl
let filter_mode = filter.as_int();
let user_data = match filter {
Filter::Identity => 0x10000i32, // matches `Contrast(1)`
Filter::Contrast(amount) |
Filter::Grayscale(amount) |
Filter::Invert(amount) |
Filter::Saturate(amount) |
Filter::Sepia(amount) |
Filter::Brightness(amount) => {
(amount * 65536.0) as i32
}
Filter::SrgbToLinear | Filter::LinearToSrgb => 0,
Filter::HueRotate(angle) => {
(0.01745329251 * angle * 65536.0) as i32
}
Filter::ColorMatrix(_) => {
picture.extra_gpu_data_handles[0].as_int(gpu_cache)
}
Filter::Flood(_) => {
picture.extra_gpu_data_handles[0].as_int(gpu_cache)
}
// These filters are handled via different paths.
Filter::ComponentTransfer |
Filter::Blur { .. } |
Filter::DropShadows(..) |
Filter::Opacity(..) |
Filter::SVGGraphNode(..) => unreachable!(),
};
// Other filters that may introduce opacity are handled via different
// paths.
if let Filter::ColorMatrix(..) = filter {
is_opaque = false;
}
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let blend_mode = if is_opaque {
BlendMode::None
} else {
BlendMode::PremultipliedAlpha
};
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Blend),
blend_mode,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
uv_rect_address.as_int(),
filter_mode,
user_data,
0,
]
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
0,
);
}
}
}
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 filter_data = &ctx.data_stores.filter_data[handle];
let filter_mode : i32 = Filter::ComponentTransfer.as_int() |
((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 (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Blend),
BlendMode::PremultipliedAlpha,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
uv_rect_address.as_int(),
filter_mode,
user_data,
0,
]
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
0,
);
}
PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
mode,
ctx.use_advanced_blending,
!ctx.break_advanced_blend_batches,
ctx.use_dual_source_blending,
).is_some() => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D),
),
BlendMode::from_mix_blend_mode(
mode,
ctx.use_advanced_blending,
!ctx.break_advanced_blend_batches,
ctx.use_dual_source_blending,
).unwrap(),
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: match key.blend_mode {
BlendMode::MultiplyDualSource => ShaderColorMode::MultiplyDualSource,
_ => ShaderColorMode::Image,
},
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_address.as_int(),
);
}
PictureCompositeMode::MixBlend(mode) => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
let color0 = render_tasks[backdrop_id].get_target_texture();
let color1 = render_tasks[pic_task_id].get_target_texture();
// Create a separate brush instance for each batcher. For most cases,
// there is only one batcher. However, in the case of drawing onto
// a picture cache, there is one batcher per tile. Although not
// currently used, the implementation of mix-blend-mode now supports
// doing partial readbacks per-tile. In future, this will be enabled
// and allow mix-blends to operate on picture cache surfaces without
// a separate isolated intermediate surface.
let batch_key = BatchKey::new(
BatchKind::Brush(
BrushBatchKind::MixBlend {
task_id: self.batcher.render_task_id,
backdrop_id,
},
),
BlendMode::PremultipliedAlpha,
BatchTextures {
input: TextureSet {
colors: [
TextureSource::TextureCache(
color0,
Swizzle::default(),
),
TextureSource::TextureCache(
color1,
Swizzle::default(),
),
TextureSource::Invalid,
],
},
clip_mask: clip_mask_texture_id,
},
);
let src_uv_address = render_tasks[pic_task_id].get_texture_address(gpu_cache);
let readback_uv_address = render_tasks[backdrop_id].get_texture_address(gpu_cache);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
mode as u32 as i32,
readback_uv_address.as_int(),
src_uv_address.as_int(),
0,
]
);
let instance = BrushInstance {
segment_index: INVALID_SEGMENT_INDEX,
edge_flags: EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
resource_address: 0,
};
self.batcher.push_single_instance(
batch_key,
batch_features,
bounding_rect,
z_id,
PrimitiveInstanceData::from(instance),
);
}
PictureCompositeMode::Blit(_) => {
match picture.context_3d {
Picture3DContext::In { root_data: Some(_), .. } => {
unreachable!("bug: should not have a raster_config");
}
Picture3DContext::In { root_data: None, .. } => {
// TODO(gw): Store this inside the split picture so that we
// don't need to pass in extra_prim_gpu_address for
// every prim instance.
// TODO(gw): Ideally we'd skip adding 3d child prims to batches
// without gpu cache address but it's currently
// used by the prepare pass. Refactor this!
let extra_prim_gpu_address = match extra_prim_gpu_address {
Some(prim_address) => prim_address,
None => return,
};
// Get clip task, if set, for the picture primitive.
let (child_clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: GpuCacheAddress::INVALID,
transform_id: transforms
.get_id(
prim_spatial_node_index,
root_spatial_node_index,
ctx.spatial_tree,
),
};
let child_pic_task_id = picture
.primary_render_task_id
.unwrap();
let (uv_rect_address, texture) = render_tasks.resolve_location(
child_pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
// Need a new z-id for each child preserve-3d context added
// by this inner loop.
let z_id = z_generator.next();
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[
uv_rect_address.as_int(),
BrushFlags::PERSPECTIVE_INTERPOLATION.bits() as i32,
0,
child_clip_task_address.0 as i32,
]
);
let key = BatchKey::new(
BatchKind::SplitComposite,
BlendMode::PremultipliedAlpha,
textures,
);
self.add_split_composite_instance_to_batches(
key,
BatchFeatures::CLIP_MASK,
&prim_info.clip_chain.pic_coverage_rect,
z_id,
prim_header_index,
extra_prim_gpu_address,
);
}
Picture3DContext::Out { .. } => {
let uv_rect_address = render_tasks[pic_task_id]
.get_texture_address(gpu_cache)
.as_int();
let cache_render_task = &render_tasks[pic_task_id];
let texture_id = cache_render_task.get_target_texture();
let textures = TextureSet {
colors: [
TextureSource::TextureCache(
texture_id,
Swizzle::default(),
),
TextureSource::Invalid,
TextureSource::Invalid,
],
};
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Image(ImageBufferKind::Texture2D),
textures,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
uv_rect_address,
);
let prim_header = PrimitiveHeader {
specific_prim_address: prim_cache_address,
..prim_header
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
let (opacity, blend_mode) = if is_opaque {
(PrimitiveOpacity::opaque(), BlendMode::None)
} else {
(PrimitiveOpacity::translucent(), BlendMode::PremultipliedAlpha)
};
self.add_segmented_prim_to_batch(
None,
opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
EdgeAaSegmentMask::all(),
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
}
}
PictureCompositeMode::SvgFilter(..) => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
);
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
kind,
blend_mode,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_address.as_int(),
);
}
PictureCompositeMode::SVGFEGraph(..) => {
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
);
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
kind,
blend_mode,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
None => {
unreachable!();
}
}
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let prim_data = &ctx.data_stores.image_border[data_handle];
let common_data = &prim_data.common;
let border_data = &prim_data.kind;
let (uv_rect_address, texture) = match render_tasks.resolve_location(border_data.src_color, gpu_cache) {
Some(src) => src,
None => {
return;
}
};
let textures = TextureSet::prim_textured(texture);
let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Image(texture.image_buffer_kind()),
textures,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode(),
uv_rect_address.as_int(),
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
Some(border_data.brush_segments.as_slice()),
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
common_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, use_legacy_path, .. } => {
debug_assert!(use_legacy_path);
let prim_data = &ctx.data_stores.prim[data_handle];
let blend_mode = if !prim_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Solid,
TextureSet::UNTEXTURED,
[get_shader_opacity(1.0), 0, 0, 0],
0,
);
let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED {
(gpu_cache.get_address(&prim_data.gpu_cache_handle), None)
} else {
let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
(gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
segments,
prim_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
prim_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, compositor_surface_kind, .. } => {
if compositor_surface_kind.needs_cutout() {
self.add_compositor_surface_cutout(
prim_rect,
prim_info.clip_chain.local_clip_rect,
prim_info.clip_task_index,
transform_id,
z_id,
bounding_rect,
ctx,
gpu_cache,
render_tasks,
prim_headers,
);
return;
}
let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind;
let mut textures = TextureSet::UNTEXTURED;
let mut uv_rect_addresses = [0; 3];
//yuv channel
let channel_count = yuv_image_data.format.get_plane_num();
debug_assert!(channel_count <= 3);
for channel in 0 .. channel_count {
let src_channel = render_tasks.resolve_location(yuv_image_data.src_yuv[channel], gpu_cache);
let (uv_rect_address, texture_source) = match src_channel {
Some(src) => src,
None => {
warn!("Warnings: skip a PrimitiveKind::YuvImage");
return;
}
};
textures.colors[channel] = texture_source;
uv_rect_addresses[channel] = uv_rect_address.as_int();
}
// All yuv textures should be the same type.
let buffer_kind = textures.colors[0].image_buffer_kind();
assert!(
textures.colors[1 .. yuv_image_data.format.get_plane_num()]
.iter()
.all(|&tid| buffer_kind == tid.image_buffer_kind())
);
let kind = BrushBatchKind::YuvImage(
buffer_kind,
yuv_image_data.format,
yuv_image_data.color_depth,
yuv_image_data.color_space,
yuv_image_data.color_range,
);
let batch_params = BrushBatchParameters::shared(
kind,
textures,
[
uv_rect_addresses[0],
uv_rect_addresses[1],
uv_rect_addresses[2],
0,
],
0,
);
let prim_common_data = ctx.data_stores.as_common_data(&prim_instance);
let blend_mode = if !prim_common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
debug_assert_ne!(segment_instance_index, SegmentInstanceIndex::INVALID);
let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED {
(gpu_cache.get_address(&prim_common_data.gpu_cache_handle), None)
} else {
let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
(gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
};
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
segments,
prim_common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
prim_common_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, compositor_surface_kind, .. } => {
if compositor_surface_kind.needs_cutout() {
self.add_compositor_surface_cutout(
prim_rect,
prim_info.clip_chain.local_clip_rect,
prim_info.clip_task_index,
transform_id,
z_id,
bounding_rect,
ctx,
gpu_cache,
render_tasks,
prim_headers,
);
return;
}
let image_data = &ctx.data_stores.image[data_handle].kind;
let common_data = &ctx.data_stores.image[data_handle].common;
let image_instance = &ctx.prim_store.images[image_instance_index];
let prim_user_data = ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: image_data.alpha_type,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode();
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
match image_data.alpha_type {
AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
AlphaType::Alpha => BlendMode::Alpha,
}
} else {
BlendMode::None
};
if image_instance.visible_tiles.is_empty() {
if cfg!(debug_assertions) {
match ctx.resource_cache.get_image_properties(image_data.key) {
Some(ImageProperties { tiling: None, .. }) | None => (),
other => panic!("Non-tiled image with no visible images detected! Properties {:?}", other),
}
}
let src_color = render_tasks.resolve_location(image_instance.src_color, gpu_cache);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => {
return;
}
};
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Image(texture_source.image_buffer_kind()),
TextureSet::prim_textured(texture_source),
prim_user_data,
uv_rect_address.as_int(),
);
debug_assert_ne!(image_instance.segment_instance_index, SegmentInstanceIndex::INVALID);
let (prim_cache_address, segments) = if image_instance.segment_instance_index == SegmentInstanceIndex::UNUSED {
(gpu_cache.get_address(&common_data.gpu_cache_handle), None)
} else {
let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
(gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
};
let local_rect = image_instance.adjustment.map_local_rect(&prim_rect);
let local_clip_rect = image_instance.tight_local_clip_rect
.intersection_unchecked(&local_rect);
let prim_header = PrimitiveHeader {
local_rect,
local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
let brush_flags = match image_instance.normalized_uvs {
true => brush_flags | BrushFlags::NORMALIZED_UVS,
false => brush_flags,
};
self.add_segmented_prim_to_batch(
segments,
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
common_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
} else {
const VECS_PER_SPECIFIC_BRUSH: usize = 3;
let max_tiles_per_header = (MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_SPECIFIC_BRUSH) / VECS_PER_SEGMENT;
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// use temporary block storage since we don't know the number of visible tiles beforehand
let mut gpu_blocks = Vec::<GpuBlockData>::with_capacity(3 + max_tiles_per_header * 2);
for chunk in image_instance.visible_tiles.chunks(max_tiles_per_header) {
gpu_blocks.clear();
gpu_blocks.push(image_data.color.premultiplied().into()); //color
gpu_blocks.push(PremultipliedColorF::WHITE.into()); //bg color
gpu_blocks.push([-1.0, 0.0, 0.0, 0.0].into()); //stretch size
// negative first value makes the shader code ignore it and use the local size instead
for tile in chunk {
let tile_rect = tile.local_rect.translate(-prim_rect.min.to_vector());
gpu_blocks.push(tile_rect.into());
gpu_blocks.push(GpuBlockData::EMPTY);
}
let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: image_instance.tight_local_clip_rect,
specific_prim_address: gpu_cache.get_address(&gpu_handle),
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
prim_user_data,
);
for (i, tile) in chunk.iter().enumerate() {
let (uv_rect_address, texture) = match render_tasks.resolve_location(tile.src_color, gpu_cache) {
Some(result) => result,
None => {
return;
}
};
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(BrushBatchKind::Image(texture.image_buffer_kind())),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
i as i32,
tile.edge_flags,
clip_task_address,
brush_flags | BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
}
PrimitiveInstanceKind::LinearGradient { data_handle, ref visible_tiles_range, .. } => {
let prim_data = &ctx.data_stores.linear_grad[data_handle];
let mut prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: GpuCacheAddress::INVALID,
transform_id,
};
let blend_mode = if !prim_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let user_data = [extra_prim_gpu_address.unwrap(), 0, 0, 0];
if visible_tiles_range.is_empty() {
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::LinearGradient,
TextureSet::UNTEXTURED,
user_data,
0,
);
prim_header.specific_prim_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
user_data,
);
let segments = if prim_data.brush_segments.is_empty() {
None
} else {
Some(prim_data.brush_segments.as_slice())
};
self.add_segmented_prim_to_batch(
segments,
prim_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
prim_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
} else {
let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let key = BatchKey {
blend_mode,
kind: BatchKind::Brush(BrushBatchKind::LinearGradient),
textures: BatchTextures::prim_untextured(clip_mask_texture_id),
};
for tile in visible_tiles {
let tile_prim_header = PrimitiveHeader {
specific_prim_address: gpu_cache.get_address(&tile.handle),
local_rect: tile.local_rect,
local_clip_rect: tile.local_clip_rect,
..prim_header
};
let prim_header_index = prim_headers.push(
&tile_prim_header,
z_id,
self.batcher.render_task_address,
user_data,
);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
prim_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
0,
);
}
}
}
PrimitiveInstanceKind::CachedLinearGradient { data_handle, ref visible_tiles_range, .. } => {
let prim_data = &ctx.data_stores.linear_grad[data_handle];
let common_data = &prim_data.common;
let src_color = render_tasks.resolve_location(prim_data.src_color, gpu_cache);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => {
return;
}
};
let textures = TextureSet::prim_textured(texture_source);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: gpu_cache.get_address(&common_data.gpu_cache_handle),
transform_id,
};
let prim_user_data = ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode();
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let batch_kind = BrushBatchKind::Image(texture_source.image_buffer_kind());
if visible_tiles_range.is_empty() {
let batch_params = BrushBatchParameters::shared(
batch_kind,
textures,
prim_user_data,
uv_rect_address.as_int(),
);
let segments = if prim_data.brush_segments.is_empty() {
None
} else {
Some(&prim_data.brush_segments[..])
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
segments,
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
common_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
} else {
let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(batch_kind),
textures: BatchTextures {
input: textures,
clip_mask,
},
};
for tile in visible_tiles {
let tile_prim_header = PrimitiveHeader {
local_rect: tile.local_rect,
local_clip_rect: tile.local_clip_rect,
..prim_header
};
let prim_header_index = prim_headers.push(
&tile_prim_header,
z_id,
self.batcher.render_task_address,
prim_user_data,
);
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
prim_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
let prim_data = &ctx.data_stores.radial_grad[data_handle];
let common_data = &prim_data.common;
let src_color = render_tasks.resolve_location(prim_data.src_color, gpu_cache);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => {
return;
}
};
let textures = TextureSet::prim_textured(texture_source);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: gpu_cache.get_address(&common_data.gpu_cache_handle),
transform_id,
};
let prim_user_data = ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode();
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let batch_kind = BrushBatchKind::Image(texture_source.image_buffer_kind());
if visible_tiles_range.is_empty() {
let batch_params = BrushBatchParameters::shared(
batch_kind,
textures,
prim_user_data,
uv_rect_address.as_int(),
);
let segments = if prim_data.brush_segments.is_empty() {
None
} else {
Some(&prim_data.brush_segments[..])
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
segments,
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
prim_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
} else {
let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(batch_kind),
textures: BatchTextures {
input: textures,
clip_mask,
},
};
for tile in visible_tiles {
let tile_prim_header = PrimitiveHeader {
local_rect: tile.local_rect,
local_clip_rect: tile.local_clip_rect,
..prim_header
};
let prim_header_index = prim_headers.push(
&tile_prim_header,
z_id,
self.batcher.render_task_address,
prim_user_data,
);
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
prim_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
PrimitiveInstanceKind::ConicGradient { data_handle, ref visible_tiles_range, .. } => {
let prim_data = &ctx.data_stores.conic_grad[data_handle];
let common_data = &prim_data.common;
let src_color = render_tasks.resolve_location(prim_data.src_color, gpu_cache);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => {
return;
}
};
let textures = TextureSet::prim_textured(texture_source);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: gpu_cache.get_address(&common_data.gpu_cache_handle),
transform_id,
};
let prim_user_data = ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode();
let blend_mode = if !common_data.opacity.is_opaque ||
prim_info.clip_task_index != ClipTaskIndex::INVALID ||
transform_kind == TransformedRectKind::Complex ||
is_anti_aliased
{
BlendMode::PremultipliedAlpha
} else {
BlendMode::None
};
let batch_kind = BrushBatchKind::Image(texture_source.image_buffer_kind());
if visible_tiles_range.is_empty() {
let batch_params = BrushBatchParameters::shared(
batch_kind,
textures,
prim_user_data,
uv_rect_address.as_int(),
);
let segments = if prim_data.brush_segments.is_empty() {
None
} else {
Some(&prim_data.brush_segments[..])
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
segments,
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
prim_data.edge_aa_mask,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
} else {
let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(batch_kind),
textures: BatchTextures {
input: textures,
clip_mask,
},
};
for tile in visible_tiles {
let tile_prim_header = PrimitiveHeader {
local_rect: tile.local_rect,
local_clip_rect: tile.local_clip_rect,
..prim_header
};
let prim_header_index = prim_headers.push(
&tile_prim_header,
z_id,
self.batcher.render_task_address,
prim_user_data,
);
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
prim_data.edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
PrimitiveInstanceKind::BackdropCapture { .. } => {}
PrimitiveInstanceKind::BackdropRender { pic_index, .. } => {
let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
let blend_mode = BlendMode::PremultipliedAlpha;
let pic_task_id = ctx.prim_store.pictures[pic_index.0].primary_render_task_id;
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
);
let (_, texture) = render_tasks.resolve_location(
pic_task_id,
gpu_cache,
).unwrap();
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let key = BatchKey::new(
kind,
blend_mode,
textures,
);
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
ImageBrushData {
color_mode: ShaderColorMode::Image,
alpha_type: AlphaType::PremultipliedAlpha,
raster_space: RasterizationSpace::Screen,
opacity: 1.0,
}.encode(),
);
let pic_task = &render_tasks[pic_task_id.unwrap()];
let pic_info = match pic_task.kind {
RenderTaskKind::Picture(ref info) => info,
_ => panic!("bug: not a picture"),
};
let target_rect = pic_task.get_target_rect();
let backdrop_rect = DeviceRect::from_origin_and_size(
pic_info.content_origin,
target_rect.size().to_f32(),
);
let map_prim_to_backdrop = SpaceMapper::new_with_target(
pic_info.surface_spatial_node_index,
prim_spatial_node_index,
WorldRect::max_rect(),
ctx.spatial_tree,
);
let points = [
map_prim_to_backdrop.map_point(prim_rect.top_left()),
map_prim_to_backdrop.map_point(prim_rect.top_right()),
map_prim_to_backdrop.map_point(prim_rect.bottom_left()),
map_prim_to_backdrop.map_point(prim_rect.bottom_right()),
];
if points.iter().any(|p| p.is_none()) {
return;
}
let uvs = [
calculate_screen_uv(points[0].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
calculate_screen_uv(points[1].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
calculate_screen_uv(points[2].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
calculate_screen_uv(points[3].unwrap() * pic_info.device_pixel_scale, backdrop_rect),
];
// TODO (gw): This is a hack that provides the GPU cache blocks for an
// ImageSource. We should update the GPU cache interfaces to
// allow pushing per-frame blocks via a request interface.
let gpu_blocks = &[
GpuBlockData::from([
target_rect.min.x as f32,
target_rect.min.y as f32,
target_rect.max.x as f32,
target_rect.max.y as f32,
]),
GpuBlockData::from([0.0; 4]),
GpuBlockData::from(uvs[0]),
GpuBlockData::from(uvs[1]),
GpuBlockData::from(uvs[2]),
GpuBlockData::from(uvs[3]),
];
let uv_rect_handle = gpu_cache.push_per_frame_blocks(gpu_blocks);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::all(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_handle.as_int(gpu_cache),
);
}
}
}
/// Draw a (potentially masked) alpha cutout so that a video underlay will be blended
/// through by the compositor
fn add_compositor_surface_cutout(
&mut self,
prim_rect: LayoutRect,
local_clip_rect: LayoutRect,
clip_task_index: ClipTaskIndex,
transform_id: TransformPaletteId,
z_id: ZBufferId,
bounding_rect: &PictureRect,
ctx: &RenderTargetContext,
gpu_cache: &mut GpuCache,
render_tasks: &RenderTaskGraph,
prim_headers: &mut PrimitiveHeaders,
) {
let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_black_rect_handle);
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
clip_task_index,
render_tasks,
).unwrap();
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
self.batcher.render_task_address,
[get_shader_opacity(1.0), 0, 0, 0],
);
let batch_key = BatchKey {
blend_mode: BlendMode::PremultipliedDestOut,
kind: BatchKind::Brush(BrushBatchKind::Solid),
textures: BatchTextures::prim_untextured(clip_mask_texture_id),
};
self.add_brush_instance_to_batches(
batch_key,
BatchFeatures::ALPHA_PASS | BatchFeatures::CLIP_MASK,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::empty(),
clip_task_address,
BrushFlags::empty(),
prim_header_index,
0,
);
}
/// Add a single segment instance to a batch.
///
/// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only
/// if the segments enable it.
/// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will
/// be anti-aliased, only that they could be.
fn add_segment_to_batch(
&mut self,
segment: &BrushSegment,
segment_data: &SegmentInstanceData,
segment_index: i32,
batch_kind: BrushBatchKind,
prim_header_index: PrimitiveHeaderIndex,
alpha_blend_mode: BlendMode,
features: BatchFeatures,
brush_flags: BrushFlags,
edge_aa_mask: EdgeAaSegmentMask,
bounding_rect: &PictureRect,
transform_kind: TransformedRectKind,
z_id: ZBufferId,
prim_opacity: PrimitiveOpacity,
clip_task_index: ClipTaskIndex,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
) {
debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
// Get GPU address of clip task for this segment, or None if
// the entire segment is clipped out.
if let Some((clip_task_address, clip_mask)) = ctx.get_clip_task_and_texture(
clip_task_index,
segment_index,
render_tasks,
) {
// If a got a valid (or OPAQUE) clip task address, add the segment.
let is_inner = segment.edge_flags.is_empty();
let needs_blending = !prim_opacity.is_opaque ||
clip_task_address != OPAQUE_TASK_ADDRESS ||
(!is_inner && transform_kind == TransformedRectKind::Complex) ||
brush_flags.contains(BrushFlags::FORCE_AA);
let textures = BatchTextures {
input: segment_data.textures,
clip_mask,
};
let batch_key = BatchKey {
blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
kind: BatchKind::Brush(batch_kind),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
features,
bounding_rect,
z_id,
segment_index,
segment.edge_flags & edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
prim_header_index,
segment_data.specific_resource_address,
);
}
}
/// Add any segment(s) from a brush to batches.
///
/// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only
/// if the segments enable it.
/// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will
/// be anti-aliased, only that they could be.
fn add_segmented_prim_to_batch(
&mut self,
brush_segments: Option<&[BrushSegment]>,
prim_opacity: PrimitiveOpacity,
params: &BrushBatchParameters,
blend_mode: BlendMode,
features: BatchFeatures,
brush_flags: BrushFlags,
edge_aa_mask: EdgeAaSegmentMask,
prim_header_index: PrimitiveHeaderIndex,
bounding_rect: &PictureRect,
transform_kind: TransformedRectKind,
z_id: ZBufferId,
clip_task_index: ClipTaskIndex,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
) {
match (brush_segments, ¶ms.segment_data) {
(Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => {
// In this case, we have both a list of segments, and a list of
// per-segment instance data. Zip them together to build batches.
debug_assert_eq!(brush_segments.len(), segment_data.len());
for (segment_index, (segment, segment_data)) in brush_segments
.iter()
.zip(segment_data.iter())
.enumerate()
{
self.add_segment_to_batch(
segment,
segment_data,
segment_index as i32,
params.batch_kind,
prim_header_index,
blend_mode,
features,
brush_flags,
edge_aa_mask,
bounding_rect,
transform_kind,
z_id,
prim_opacity,
clip_task_index,
ctx,
render_tasks,
);
}
}
(Some(ref brush_segments), SegmentDataKind::Shared(ref segment_data)) => {
// A list of segments, but the per-segment data is common
// between all segments.
for (segment_index, segment) in brush_segments
.iter()
.enumerate()
{
self.add_segment_to_batch(
segment,
segment_data,
segment_index as i32,
params.batch_kind,
prim_header_index,
blend_mode,
features,
brush_flags,
edge_aa_mask,
bounding_rect,
transform_kind,
z_id,
prim_opacity,
clip_task_index,
ctx,
render_tasks,
);
}
}
(None, SegmentDataKind::Shared(ref segment_data)) => {
// No segments, and thus no per-segment instance data.
// Note: the blend mode already takes opacity into account
let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
clip_task_index,
render_tasks,
).unwrap();
let textures = BatchTextures {
input: segment_data.textures,
clip_mask,
};
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(params.batch_kind),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
segment_data.specific_resource_address,
);
}
(None, SegmentDataKind::Instanced(..)) => {
// We should never hit the case where there are no segments,
// but a list of segment instance data.
unreachable!();
}
}
}
}
/// Either a single texture / user data for all segments,
/// or a list of one per segment.
enum SegmentDataKind {
Shared(SegmentInstanceData),
Instanced(SmallVec<[SegmentInstanceData; 8]>),
}
/// The parameters that are specific to a kind of brush,
/// used by the common method to add a brush to batches.
struct BrushBatchParameters {
batch_kind: BrushBatchKind,
prim_user_data: [i32; 4],
segment_data: SegmentDataKind,
}
impl BrushBatchParameters {
/// This brush instance has a list of per-segment
/// instance data.
fn instanced(
batch_kind: BrushBatchKind,
prim_user_data: [i32; 4],
segment_data: SmallVec<[SegmentInstanceData; 8]>,
) -> Self {
BrushBatchParameters {
batch_kind,
prim_user_data,
segment_data: SegmentDataKind::Instanced(segment_data),
}
}
/// This brush instance shares the per-segment data
/// across all segments.
fn shared(
batch_kind: BrushBatchKind,
textures: TextureSet,
prim_user_data: [i32; 4],
specific_resource_address: i32,
) -> Self {
BrushBatchParameters {
batch_kind,
prim_user_data,
segment_data: SegmentDataKind::Shared(
SegmentInstanceData {
textures,
specific_resource_address,
}
),
}
}
}
/// A list of clip instances to be drawn into a target.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipMaskInstanceList {
pub mask_instances_fast: FrameVec<MaskInstance>,
pub mask_instances_slow: FrameVec<MaskInstance>,
pub mask_instances_fast_with_scissor: FastHashMap<DeviceIntRect, FrameVec<MaskInstance>>,
pub mask_instances_slow_with_scissor: FastHashMap<DeviceIntRect, FrameVec<MaskInstance>>,
pub image_mask_instances: FastHashMap<TextureSource, FrameVec<PrimitiveInstanceData>>,
pub image_mask_instances_with_scissor: FastHashMap<(DeviceIntRect, TextureSource), FrameVec<PrimitiveInstanceData>>,
}
impl ClipMaskInstanceList {
pub fn new(memory: &FrameMemory) -> Self {
ClipMaskInstanceList {
mask_instances_fast: memory.new_vec(),
mask_instances_slow: memory.new_vec(),
mask_instances_fast_with_scissor: FastHashMap::default(),
mask_instances_slow_with_scissor: FastHashMap::default(),
image_mask_instances: FastHashMap::default(),
image_mask_instances_with_scissor: FastHashMap::default(),
}
}
}
/// A list of clip instances to be drawn into a target.
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipBatchList {
/// Rectangle draws fill up the rectangles with rounded corners.
pub slow_rectangles: FrameVec<ClipMaskInstanceRect>,
pub fast_rectangles: FrameVec<ClipMaskInstanceRect>,
pub box_shadows: FastHashMap<TextureSource, FrameVec<ClipMaskInstanceBoxShadow>>,
}
impl ClipBatchList {
fn new(memory: &FrameMemory) -> Self {
ClipBatchList {
slow_rectangles: memory.new_vec(),
fast_rectangles: memory.new_vec(),
box_shadows: FastHashMap::default(),
}
}
pub fn is_empty(&self) -> bool {
self.slow_rectangles.is_empty()
&& self.fast_rectangles.is_empty()
&& self.box_shadows.is_empty()
}
}
/// Batcher managing draw calls into the clip mask (in the RT cache).
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipBatcher {
/// The first clip in each clip task. This will overwrite all pixels
/// in the clip region, so we can skip doing a clear and write with
/// blending disabled, which is a big performance win on Intel GPUs.
pub primary_clips: ClipBatchList,
/// Any subsequent clip masks (rare) for a clip task get drawn in
/// a second pass with multiplicative blending enabled.
pub secondary_clips: ClipBatchList,
gpu_supports_fast_clears: bool,
}
impl ClipBatcher {
pub fn new(
gpu_supports_fast_clears: bool,
memory: &FrameMemory,
) -> Self {
ClipBatcher {
primary_clips: ClipBatchList::new(memory),
secondary_clips: ClipBatchList::new(memory),
gpu_supports_fast_clears,
}
}
pub fn add_clip_region(
&mut self,
local_pos: LayoutPoint,
sub_rect: DeviceRect,
clip_data: ClipData,
task_origin: DevicePoint,
screen_origin: DevicePoint,
device_pixel_scale: f32,
) {
let instance = ClipMaskInstanceRect {
common: ClipMaskInstanceCommon {
clip_transform_id: TransformPaletteId::IDENTITY,
prim_transform_id: TransformPaletteId::IDENTITY,
sub_rect,
task_origin,
screen_origin,
device_pixel_scale,
},
local_pos,
clip_data,
};
self.primary_clips.slow_rectangles.push(instance);
}
/// Where appropriate, draw a clip rectangle as a small series of tiles,
/// instead of one large rectangle.
fn add_tiled_clip_mask(
&mut self,
mask_screen_rect: DeviceRect,
local_clip_rect: LayoutRect,
clip_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
world_rect: &WorldRect,
global_device_pixel_scale: DevicePixelScale,
common: &ClipMaskInstanceCommon,
is_first_clip: bool,
) -> bool {
// Only try to draw in tiles if the clip mark is big enough.
if mask_screen_rect.area() < CLIP_RECTANGLE_AREA_THRESHOLD {
return false;
}
let mask_screen_rect_size = mask_screen_rect.size().to_i32();
let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index);
// Only support clips that are axis-aligned to the root coordinate space,
// for now, to simplify the logic below. This handles the vast majority
// of real world cases, but could be expanded in future if needed.
if clip_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
return false;
}
// Get the world rect of the clip rectangle. If we can't transform it due
// to the matrix, just fall back to drawing the entire clip mask.
let transform = spatial_tree.get_world_transform(
clip_spatial_node_index,
);
let world_clip_rect = match project_rect(
&transform.into_transform(),
&local_clip_rect,
&world_rect,
) {
Some(rect) => rect,
None => return false,
};
// Work out how many tiles to draw this clip mask in, stretched across the
// device rect of the primitive clip mask.
let world_device_rect = world_clip_rect * global_device_pixel_scale;
let x_tiles = (mask_screen_rect_size.width + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
let y_tiles = (mask_screen_rect_size.height + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
// Because we only run this code path for axis-aligned rects (the root coord system check above),
// and only for rectangles (not rounded etc), the world_device_rect is not conservative - we know
// that there is no inner_rect, and the world_device_rect should be the real, axis-aligned clip rect.
let mask_origin = mask_screen_rect.min.to_vector();
let clip_list = self.get_batch_list(is_first_clip);
for y in 0 .. y_tiles {
for x in 0 .. x_tiles {
let p0 = DeviceIntPoint::new(
x * CLIP_RECTANGLE_TILE_SIZE,
y * CLIP_RECTANGLE_TILE_SIZE,
);
let p1 = DeviceIntPoint::new(
(p0.x + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.width),
(p0.y + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.height),
);
let normalized_sub_rect = DeviceIntRect {
min: p0,
max: p1,
}.to_f32();
let world_sub_rect = normalized_sub_rect.translate(mask_origin);
// If the clip rect completely contains this tile rect, then drawing
// these pixels would be redundant - since this clip can't possibly
// affect the pixels in this tile, skip them!
if !world_device_rect.contains_box(&world_sub_rect) {
clip_list.slow_rectangles.push(ClipMaskInstanceRect {
common: ClipMaskInstanceCommon {
sub_rect: normalized_sub_rect,
..*common
},
local_pos: local_clip_rect.min,
clip_data: ClipData::uniform(local_clip_rect.size(), 0.0, ClipMode::Clip),
});
}
}
}
true
}
/// Retrieve the correct clip batch list to append to, depending
/// on whether this is the first clip mask for a clip task.
fn get_batch_list(
&mut self,
is_first_clip: bool,
) -> &mut ClipBatchList {
if is_first_clip && !self.gpu_supports_fast_clears {
&mut self.primary_clips
} else {
&mut self.secondary_clips
}
}
pub fn add(
&mut self,
clip_node_range: ClipNodeRange,
root_spatial_node_index: SpatialNodeIndex,
render_tasks: &RenderTaskGraph,
gpu_cache: &GpuCache,
clip_store: &ClipStore,
transforms: &mut TransformPalette,
actual_rect: DeviceRect,
surface_device_pixel_scale: DevicePixelScale,
task_origin: DevicePoint,
screen_origin: DevicePoint,
ctx: &RenderTargetContext,
) -> bool {
let mut is_first_clip = true;
let mut clear_to_one = false;
for i in 0 .. clip_node_range.count {
let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
let clip_node = &ctx.data_stores.clip[clip_instance.handle];
let clip_transform_id = transforms.get_id(
clip_node.item.spatial_node_index,
ctx.root_spatial_node_index,
ctx.spatial_tree,
);
let prim_transform_id = transforms.get_id(
root_spatial_node_index,
ctx.root_spatial_node_index,
ctx.spatial_tree,
);
let common = ClipMaskInstanceCommon {
sub_rect: DeviceRect::from_size(actual_rect.size()),
task_origin,
screen_origin,
device_pixel_scale: surface_device_pixel_scale.0,
clip_transform_id,
prim_transform_id,
};
let added_clip = match clip_node.item.kind {
ClipItemKind::Image { .. } => {
unreachable!();
}
ClipItemKind::BoxShadow { ref source } => {
let task_id = source
.render_task
.expect("bug: render task handle not allocated");
let (uv_rect_address, texture) = render_tasks.resolve_location(task_id, gpu_cache).unwrap();
self.get_batch_list(is_first_clip)
.box_shadows
.entry(texture)
.or_insert_with(|| ctx.frame_memory.new_vec())
.push(ClipMaskInstanceBoxShadow {
common,
resource_address: uv_rect_address,
shadow_data: BoxShadowData {
src_rect_size: source.original_alloc_size,
clip_mode: source.clip_mode as i32,
stretch_mode_x: source.stretch_mode_x as i32,
stretch_mode_y: source.stretch_mode_y as i32,
dest_rect: source.prim_shadow_rect,
},
});
true
}
ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => {
self.get_batch_list(is_first_clip)
.slow_rectangles
.push(ClipMaskInstanceRect {
common,
local_pos: rect.min,
clip_data: ClipData::uniform(rect.size(), 0.0, ClipMode::ClipOut),
});
true
}
ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => {
if clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) {
false
} else {
if self.add_tiled_clip_mask(
actual_rect,
rect,
clip_node.item.spatial_node_index,
ctx.spatial_tree,
&ctx.screen_world_rect,
ctx.global_device_pixel_scale,
&common,
is_first_clip,
) {
clear_to_one |= is_first_clip;
} else {
self.get_batch_list(is_first_clip)
.slow_rectangles
.push(ClipMaskInstanceRect {
common,
local_pos: rect.min,
clip_data: ClipData::uniform(rect.size(), 0.0, ClipMode::Clip),
});
}
true
}
}
ClipItemKind::RoundedRectangle { rect, ref radius, mode, .. } => {
let batch_list = self.get_batch_list(is_first_clip);
let instance = ClipMaskInstanceRect {
common,
local_pos: rect.min,
clip_data: ClipData::rounded_rect(rect.size(), radius, mode),
};
if clip_instance.flags.contains(ClipNodeFlags::USE_FAST_PATH) {
batch_list.fast_rectangles.push(instance);
} else {
batch_list.slow_rectangles.push(instance);
}
true
}
};
is_first_clip &= !added_clip;
}
clear_to_one
}
}
impl<'a, 'rc> RenderTargetContext<'a, 'rc> {
/// Retrieve the GPU task address for a given clip task instance.
/// Returns None if the segment was completely clipped out.
/// Returns Some(OPAQUE_TASK_ADDRESS) if no clip mask is needed.
/// Returns Some(task_address) if there was a valid clip mask.
fn get_clip_task_and_texture(
&self,
clip_task_index: ClipTaskIndex,
offset: i32,
render_tasks: &RenderTaskGraph,
) -> Option<(RenderTaskAddress, TextureSource)> {
match self.scratch.clip_mask_instances[clip_task_index.0 as usize + offset as usize] {
ClipMaskKind::Mask(task_id) => {
Some((
task_id.into(),
TextureSource::TextureCache(
render_tasks[task_id].get_target_texture(),
Swizzle::default(),
)
))
}
ClipMaskKind::None => {
Some((OPAQUE_TASK_ADDRESS, TextureSource::Invalid))
}
ClipMaskKind::Clipped => {
None
}
}
}
/// Helper function to get the clip task address for a
/// non-segmented primitive.
fn get_prim_clip_task_and_texture(
&self,
clip_task_index: ClipTaskIndex,
render_tasks: &RenderTaskGraph,
) -> Option<(RenderTaskAddress, TextureSource)> {
self.get_clip_task_and_texture(
clip_task_index,
0,
render_tasks,
)
}
}
impl CompositorSurfaceKind {
/// Returns true if the type of compositor surface needs an alpha cutout rendered
fn needs_cutout(&self) -> bool {
match self {
CompositorSurfaceKind::Underlay => true,
CompositorSurfaceKind::Overlay | CompositorSurfaceKind::Blit => false,
}
}
}