Bug 1628175 - WebGL is drawn into the picture cache and then onto the screen r=gw

Part 1 - support RGB external surfaces for promotion to compositor
surfaces; add new shader permutations to handle all buffer kinds.
Set the promotion flag when the pixel format has no alpha, or when the
texture provider can guarantee all-solid alpha values.

Differential Revision: https://phabricator.services.mozilla.com/D71120
This commit is contained in:
Bert Peers 2020-04-27 19:38:02 +00:00
Родитель 0f8bea4c9f
Коммит b91a3eabe0
15 изменённых файлов: 720 добавлений и 351 удалений

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

@ -79,9 +79,13 @@ enum class TextureFlags : uint32_t {
BLOCKING_READ_LOCK = 1 << 16,
// Keep TextureClient alive when host side is used
WAIT_HOST_USAGE_END = 1 << 17,
// The texture is guaranteed to have alpha 1.0 everywhere; some backends
// have trouble with RGBX/BGRX formats, so we use RGBA/BGRA but set this
// hint when we know alpha is opaque (eg. WebGL)
IS_OPAQUE = 1 << 18,
// OR union of all valid bits
ALL_BITS = (1 << 18) - 1,
ALL_BITS = (1 << 19) - 1,
// the default flags
DEFAULT = NO_FLAGS
};

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

@ -51,6 +51,9 @@ void ShareableCanvasRenderer::Initialize(const CanvasInitializeData& aData) {
mFlags |= TextureFlags::NON_PREMULTIPLIED;
}
if (!aData.mHasAlpha) {
mFlags |= TextureFlags::IS_OPAQUE;
}
UniquePtr<gl::SurfaceFactory> factory =
gl::GLScreenBuffer::CreateFactory(mGLContext, caps, forwarder, mFlags);
if (factory) {

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

@ -22,6 +22,10 @@ bool ClientCanvasRenderer::CreateCompositable() {
flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
}
if (IsOpaque()) {
flags |= TextureFlags::IS_OPAQUE;
}
if (!mIsAlphaPremultiplied) {
flags |= TextureFlags::NON_PREMULTIPLIED;
}

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

@ -396,9 +396,14 @@ void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
if (aPipeline->mUseExternalImage) {
MOZ_ASSERT(aPipeline->mCurrentTexture->AsWebRenderTextureHost());
Range<wr::ImageKey> range_keys(&keys[0], keys.Length());
bool prefer_compositor_surface =
IsOpaque(aPipeline->mCurrentTexture->GetFormat()) ||
bool(aPipeline->mCurrentTexture->GetFlags() &
TextureFlags::IS_OPAQUE);
aPipeline->mCurrentTexture->PushDisplayItems(
builder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect),
aPipeline->mFilter, range_keys, /* aPreferCompositorSurface */ true);
aPipeline->mFilter, range_keys,
/* aPreferCompositorSurface */ prefer_compositor_surface);
HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture);
} else {
MOZ_ASSERT(keys.Length() == 1);

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

@ -40,6 +40,10 @@ bool WebRenderCanvasRendererAsync::CreateCompositable() {
flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
}
if (IsOpaque()) {
flags |= TextureFlags::IS_OPAQUE;
}
if (!mIsAlphaPremultiplied) {
flags |= TextureFlags::NON_PREMULTIPLIED;
}

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

@ -23,6 +23,9 @@ varying vec2 vUv;
#endif
#ifdef WR_VERTEX_SHADER
// CPU side data is in CompositeInstance (gpu_types.rs) and is
// converted to GPU data using desc::COMPOSITE (renderer.rs) by
// filling vaos.composite_vao with VertexArrayKind::Composite.
PER_INSTANCE in vec4 aDeviceRect;
PER_INSTANCE in vec4 aDeviceClipRect;
PER_INSTANCE in vec4 aColor;
@ -108,9 +111,13 @@ void main(void) {
);
#else
// The color is just the texture sample modulated by a supplied color
vec4 texel = textureLod(sColor0, vec3(vUv, vLayer), 0.0);
# if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_2D) || defined(WR_FEATURE_TEXTURE_RECT)
vec4 texel = TEX_SAMPLE(sColor0, vec3(vUv, vLayer));
# else
vec4 texel = textureLod(sColor0, vec3(vUv, vLayer), 0.0);
# endif
vec4 color = vColor * texel;
#endif
write_output(color);
write_output(color);
}
#endif

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

@ -19,7 +19,7 @@ use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSourc
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask};
use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibilityFlags};
use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibility, PrimitiveVisibilityFlags};
use crate::prim_store::{VECS_PER_SEGMENT, SpaceMapper};
use crate::prim_store::image::ImageSource;
use crate::render_target::RenderTargetContext;
@ -724,6 +724,73 @@ impl BatchBuilder {
}
}
// If an image is being drawn as a compositor surface, we don't want
// to draw the surface itself into the tile. Instead, we draw a transparent
// rectangle that writes to the z-buffer where this compositor surface is.
// That ensures we 'cut out' the part of the tile that has the compositor
// surface on it, allowing us to draw this tile as an overlay on top of
// the compositor surface.
// TODO(gw): There's a slight performance cost to doing this cutout rectangle
// if we end up not needing to use overlay mode. Consider skipping
// the cutout completely in this path.
fn emit_placeholder(
&mut self,
prim_rect: LayoutRect,
prim_info: &PrimitiveVisibility,
z_id: ZBufferId,
transform_id: TransformPaletteId,
batch_features: BatchFeatures,
ctx: &RenderTargetContext,
gpu_cache: &mut GpuCache,
render_tasks: &RenderTaskGraph,
prim_headers: &mut PrimitiveHeaders,
) {
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Solid,
BatchTextures::no_texture(),
[get_shader_opacity(0.0), 0, 0, 0],
0,
);
let prim_cache_address = gpu_cache.get_address(
&ctx.globals.default_transparent_rect_handle,
);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.combined_local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
batch_params.prim_user_data,
);
let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
let transform_kind = transform_id.transform_kind();
let prim_vis_mask = prim_info.visibility_mask;
self.add_segmented_prim_to_batch(
None,
PrimitiveOpacity::translucent(),
&batch_params,
BlendMode::None,
BlendMode::None,
batch_features,
prim_header_index,
bounding_rect,
transform_kind,
render_tasks,
z_id,
prim_info.clip_task_index,
prim_vis_mask,
ctx,
);
}
// Adds a primitive to a batch.
// It can recursively call itself in some situations, for
// example if it encounters a picture where the items
@ -1923,57 +1990,16 @@ impl BatchBuilder {
);
}
PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, is_compositor_surface, .. } => {
// If this YUV image is being drawn as a compositor surface, we don't want
// to draw the YUV surface itself into the tile. Instead, we draw a transparent
// rectangle that writes to the z-buffer where this compositor surface is.
// That ensures we 'cut out' the part of the tile that has the compositor
// surface on it, allowing us to draw this tile as an overlay on top of
// the compositor surface.
// TODO(gw): There's a slight performance cost to doing this cutout rectangle
// if we end up not needing to use overlay mode. Consider skipping
// the cutout completely in this path.
if is_compositor_surface {
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Solid,
BatchTextures::no_texture(),
[get_shader_opacity(0.0), 0, 0, 0],
0,
);
let prim_cache_address = gpu_cache.get_address(
&ctx.globals.default_transparent_rect_handle,
);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.combined_local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
batch_params.prim_user_data,
);
self.add_segmented_prim_to_batch(
None,
PrimitiveOpacity::translucent(),
&batch_params,
BlendMode::None,
BlendMode::None,
batch_features,
prim_header_index,
bounding_rect,
transform_kind,
render_tasks,
z_id,
prim_info.clip_task_index,
prim_vis_mask,
ctx,
);
self.emit_placeholder(prim_rect,
prim_info,
z_id,
transform_id,
batch_features,
ctx,
gpu_cache,
render_tasks,
prim_headers);
return;
}
@ -2086,7 +2112,19 @@ impl BatchBuilder {
ctx,
);
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
PrimitiveInstanceKind::Image { data_handle, image_instance_index, is_compositor_surface, .. } => {
if is_compositor_surface {
self.emit_placeholder(prim_rect,
prim_info,
z_id,
transform_id,
batch_features,
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];

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

@ -87,6 +87,18 @@ pub struct CompositeTile {
pub z_id: ZBufferId,
}
pub enum ExternalSurfaceDependency {
Yuv {
image_dependencies: [ImageDependency; 3],
color_space: YuvColorSpace,
format: YuvFormat,
rescale: f32,
},
Rgb {
image_dependency: ImageDependency,
},
}
/// Describes information about drawing a primitive as a compositor surface.
/// For now, we support only YUV images as compositor surfaces, but in future
/// this will also support RGBA images.
@ -96,12 +108,9 @@ pub struct ExternalSurfaceDescriptor {
pub device_rect: DeviceRect,
pub local_clip_rect: PictureRect,
pub clip_rect: DeviceRect,
pub image_dependencies: [ImageDependency; 3],
pub image_rendering: ImageRendering,
pub yuv_color_space: YuvColorSpace,
pub yuv_format: YuvFormat,
pub yuv_rescale: f32,
pub z_id: ZBufferId,
pub dependency: ExternalSurfaceDependency,
/// If native compositing is enabled, the native compositor surface handle.
/// Otherwise, this will be None
pub native_surface_id: Option<NativeSurfaceId>,
@ -110,18 +119,19 @@ pub struct ExternalSurfaceDescriptor {
pub update_params: Option<DeviceIntSize>,
}
/// Information about a plane in a YUV surface.
/// Information about a plane in a YUV or RGB surface.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct YuvPlaneDescriptor {
#[derive(Debug, Copy, Clone)]
pub struct ExternalPlaneDescriptor {
pub texture: TextureSource,
pub texture_layer: i32,
pub uv_rect: TexelRect,
}
impl YuvPlaneDescriptor {
impl ExternalPlaneDescriptor {
fn invalid() -> Self {
YuvPlaneDescriptor {
ExternalPlaneDescriptor {
texture: TextureSource::Invalid,
texture_layer: 0,
uv_rect: TexelRect::invalid(),
@ -134,6 +144,23 @@ impl YuvPlaneDescriptor {
#[derive(Debug, Copy, Clone)]
pub struct ResolvedExternalSurfaceIndex(pub usize);
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum ResolvedExternalSurfaceColorData {
Yuv {
// YUV specific information
image_dependencies: [ImageDependency; 3],
planes: [ExternalPlaneDescriptor; 3],
color_space: YuvColorSpace,
format: YuvFormat,
rescale: f32,
},
Rgb {
image_dependency: ImageDependency,
plane: ExternalPlaneDescriptor,
},
}
/// An ExternalSurfaceDescriptor that has had image keys
/// resolved to texture handles. This contains all the
/// information that the compositor step in renderer
@ -141,14 +168,8 @@ pub struct ResolvedExternalSurfaceIndex(pub usize);
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ResolvedExternalSurface {
// YUV specific information
pub image_dependencies: [ImageDependency; 3],
pub yuv_planes: [YuvPlaneDescriptor; 3],
pub yuv_color_space: YuvColorSpace,
pub yuv_format: YuvFormat,
pub yuv_rescale: f32,
pub color_data: ResolvedExternalSurfaceColorData,
pub image_buffer_kind: ImageBufferKind,
// Update information for a native surface if it's dirty
pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>,
}
@ -517,22 +538,40 @@ impl CompositeState {
// For each compositor surface that was promoted, build the
// information required for the compositor to draw it
for external_surface in &tile_cache.external_surfaces {
let mut yuv_planes = [
YuvPlaneDescriptor::invalid(),
YuvPlaneDescriptor::invalid(),
YuvPlaneDescriptor::invalid(),
let mut planes = [
ExternalPlaneDescriptor::invalid(),
ExternalPlaneDescriptor::invalid(),
ExternalPlaneDescriptor::invalid(),
];
// Step through the image keys, and build a yuv plane descriptor for each
let required_plane_count = external_surface.yuv_format.get_plane_num();
// Step through the image keys, and build a plane descriptor for each
let required_plane_count =
match external_surface.dependency {
ExternalSurfaceDependency::Yuv { format, .. } => {
format.get_plane_num()
},
ExternalSurfaceDependency::Rgb { .. } => {
1
}
};
let mut valid_plane_count = 0;
let mut image_dependencies = [ImageDependency::INVALID; 3];
for i in 0 .. required_plane_count {
let key = external_surface.image_dependencies[i].key;
let plane = &mut yuv_planes[i];
let dependency = match external_surface.dependency {
ExternalSurfaceDependency::Yuv { image_dependencies, .. } => {
image_dependencies[i]
},
ExternalSurfaceDependency::Rgb { image_dependency, .. } => {
image_dependency
}
};
image_dependencies[i] = dependency;
let request = ImageRequest {
key,
key: dependency.key,
rendering: external_surface.image_rendering,
tile: None,
};
@ -546,8 +585,8 @@ impl CompositeState {
if cache_item.texture_id != TextureSource::Invalid {
valid_plane_count += 1;
*plane = YuvPlaneDescriptor {
let plane = &mut planes[i];
*plane = ExternalPlaneDescriptor {
texture: cache_item.texture_id,
texture_layer: cache_item.texture_layer,
uv_rect: cache_item.uv_rect.into(),
@ -557,7 +596,7 @@ impl CompositeState {
// Check if there are valid images added for each YUV plane
if valid_plane_count < required_plane_count {
warn!("Warnings: skip a YUV compositor surface, found {}/{} valid images",
warn!("Warnings: skip a YUV/RGB compositor surface, found {}/{} valid images",
valid_plane_count,
required_plane_count,
);
@ -586,15 +625,37 @@ impl CompositeState {
)
});
self.external_surfaces.push(ResolvedExternalSurface {
yuv_color_space: external_surface.yuv_color_space,
yuv_format: external_surface.yuv_format,
yuv_rescale: external_surface.yuv_rescale,
image_buffer_kind: get_buffer_kind(yuv_planes[0].texture),
image_dependencies: external_surface.image_dependencies,
yuv_planes,
update_params,
});
match external_surface.dependency {
ExternalSurfaceDependency::Yuv{ color_space, format, rescale, .. } => {
let image_buffer_kind = get_buffer_kind(planes[0].texture);
self.external_surfaces.push(ResolvedExternalSurface {
color_data: ResolvedExternalSurfaceColorData::Yuv {
image_dependencies,
planes,
color_space,
format,
rescale,
},
image_buffer_kind,
update_params,
});
},
ExternalSurfaceDependency::Rgb{ .. } => {
let image_buffer_kind = get_buffer_kind(planes[0].texture);
self.external_surfaces.push(ResolvedExternalSurface {
color_data: ResolvedExternalSurfaceColorData::Rgb {
image_dependency: image_dependencies[0],
plane: planes[0],
},
image_buffer_kind,
update_params,
});
},
}
let tile = CompositeTile {
surface,
@ -613,7 +674,7 @@ impl CompositeState {
surface_id: external_surface.native_surface_id,
offset: tile.rect.origin,
clip_rect: tile.clip_rect,
image_dependencies: external_surface.image_dependencies,
image_dependencies: image_dependencies,
tile_descriptors: Vec::new(),
}
);

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

@ -236,6 +236,8 @@ impl ResolveInstanceData {
}
/// Vertex format for picture cache composite shader.
/// When editing the members, update desc::COMPOSITE
/// so its list of instance_attributes matches:
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CompositeInstance {
@ -280,6 +282,27 @@ impl CompositeInstance {
}
}
pub fn new_rgb(
rect: DeviceRect,
clip_rect: DeviceRect,
color: PremultipliedColorF,
layer: f32,
z_id: ZBufferId,
uv_rect: TexelRect,
) -> Self {
CompositeInstance {
rect,
clip_rect,
color,
z_id: z_id.0 as f32,
yuv_color_space: 0.0,
yuv_format: 0.0,
yuv_rescale: 0.0,
texture_layers: [layer, 0.0, 0.0],
uv_rects: [uv_rect, uv_rect, TexelRect::invalid()],
}
}
pub fn new_yuv(
rect: DeviceRect,
clip_rect: DeviceRect,

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

@ -97,6 +97,7 @@
use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags};
use api::{ImageRendering, ColorDepth, YuvColorSpace, YuvFormat};
use api::units::*;
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
@ -104,7 +105,7 @@ use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX,
SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
};
use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
use crate::composite::{ExternalSurfaceDescriptor};
use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency};
use crate::debug_colors;
use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D, SideOffsets2D};
use euclid::approxeq::ApproxEq;
@ -2252,7 +2253,7 @@ pub struct NativeSurface {
/// Hash key for an external native compositor surface
#[derive(PartialEq, Eq, Hash)]
pub struct ExternalNativeSurfaceKey {
/// The YUV image keys that are used to draw this surface.
/// The YUV/RGB image keys that are used to draw this surface.
pub image_keys: [ImageKey; 3],
/// The current device size of the surface.
pub size: DeviceIntSize,
@ -2933,6 +2934,236 @@ impl TileCacheInstance {
world_culling_rect
}
fn can_promote_to_surface(
&mut self,
flags: PrimitiveFlags,
prim_clip_chain: &ClipChainInstance,
prim_spatial_node_index: SpatialNodeIndex,
on_picture_surface: bool,
frame_context: &FrameVisibilityContext,
) -> bool {
// Check if this primitive _wants_ to be promoted to a compositor surface.
if !flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
return false;
}
// For now, only support a small (arbitrary) number of compositor surfaces.
if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
return false;
}
// If a complex clip is being applied to this primitive, it can't be
// promoted directly to a compositor surface (we might be able to
// do this in limited cases in future, some native compositors do
// support rounded rect clips, for example)
if prim_clip_chain.needs_mask {
return false;
}
// If not on the same surface as the picture cache, it has some kind of
// complex effect (such as a filter, mix-blend-mode or 3d transform).
if !on_picture_surface {
return false;
}
// If the primitive is not axis-aligned with the root coordinate system,
// it can't be promoted to a native compositor surface (could potentially
// be supported in future on some platforms).
let prim_spatial_node = &frame_context.spatial_tree
.spatial_nodes[prim_spatial_node_index.0 as usize];
if prim_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
return false;
}
// If the transform has scale, we can't currently handle
// it in the native compositor - we can support this in future though.
if !self.map_local_to_surface.get_transform().is_simple_2d_translation() {
return false;
}
return true;
}
fn setup_compositor_surfaces_yuv(
&mut self,
prim_info: &mut PrimitiveDependencyInfo,
prim_rect: PictureRect,
frame_context: &FrameVisibilityContext,
image_dependencies: &[ImageDependency;3],
api_keys: &[ImageKey; 3],
resource_cache: &mut ResourceCache,
composite_state: &mut CompositeState,
image_rendering: ImageRendering,
color_depth: ColorDepth,
color_space: YuvColorSpace,
format: YuvFormat,
) {
self.setup_compositor_surfaces_impl(
prim_info,
prim_rect,
frame_context,
ExternalSurfaceDependency::Yuv {
image_dependencies: *image_dependencies,
color_space,
format,
rescale: color_depth.rescaling_factor(),
},
api_keys,
resource_cache,
composite_state,
image_rendering,
);
}
fn setup_compositor_surfaces_rgb(
&mut self,
prim_info: &mut PrimitiveDependencyInfo,
prim_rect: PictureRect,
frame_context: &FrameVisibilityContext,
image_dependency: ImageDependency,
api_key: ImageKey,
resource_cache: &mut ResourceCache,
composite_state: &mut CompositeState,
image_rendering: ImageRendering,
) {
let mut api_keys = [ImageKey::DUMMY; 3];
api_keys[0] = api_key;
self.setup_compositor_surfaces_impl(
prim_info,
prim_rect,
frame_context,
ExternalSurfaceDependency::Rgb {
image_dependency,
},
&api_keys,
resource_cache,
composite_state,
image_rendering,
);
}
fn setup_compositor_surfaces_impl(
&mut self,
prim_info: &mut PrimitiveDependencyInfo,
prim_rect: PictureRect,
frame_context: &FrameVisibilityContext,
dependency: ExternalSurfaceDependency,
api_keys: &[ImageKey; 3],
resource_cache: &mut ResourceCache,
composite_state: &mut CompositeState,
image_rendering: ImageRendering,
) {
prim_info.is_compositor_surface = true;
let pic_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
let world_rect = pic_to_world_mapper
.map(&prim_rect)
.expect("bug: unable to map the primitive to world space");
let world_clip_rect = pic_to_world_mapper
.map(&prim_info.prim_clip_rect)
.expect("bug: unable to map clip to world space");
let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
if !is_visible {
return;
}
// TODO(gw): Is there any case where if the primitive ends up on a fractional
// boundary we want to _skip_ promoting to a compositor surface and
// draw it as part of the content?
let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
// When using native compositing, we need to find an existing native surface
// handle to use, or allocate a new one. For existing native surfaces, we can
// also determine whether this needs to be updated, depending on whether the
// image generation(s) of the planes have changed since last composite.
let (native_surface_id, update_params) = match composite_state.compositor_kind {
CompositorKind::Draw { .. } => {
(None, None)
}
CompositorKind::Native { .. } => {
let native_surface_size = device_rect.size.round().to_i32();
let key = ExternalNativeSurfaceKey {
image_keys: *api_keys,
size: native_surface_size,
};
let native_surface = self.external_native_surface_cache
.entry(key)
.or_insert_with(|| {
// No existing surface, so allocate a new compositor surface and
// a single compositor tile that covers the entire compositor surface.
let native_surface_id = resource_cache.create_compositor_surface(
DeviceIntPoint::zero(),
native_surface_size,
true,
);
let tile_id = NativeTileId {
surface_id: native_surface_id,
x: 0,
y: 0,
};
resource_cache.create_compositor_tile(tile_id);
ExternalNativeSurface {
used_this_frame: true,
native_surface_id,
image_dependencies: [ImageDependency::INVALID; 3],
}
});
// Mark that the surface is referenced this frame so that the
// backing native surface handle isn't freed.
native_surface.used_this_frame = true;
// If the image dependencies match, there is no need to update
// the backing native surface.
let update_params = match dependency {
ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
if image_dependencies == native_surface.image_dependencies {
None
} else {
Some(native_surface_size)
}
},
ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
if image_dependency == native_surface.image_dependencies[0] {
None
} else {
Some(native_surface_size)
}
},
};
(Some(native_surface.native_surface_id), update_params)
}
};
// Each compositor surface allocates a unique z-id
self.external_surfaces.push(ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_rect,
world_rect,
local_clip_rect: prim_info.prim_clip_rect,
dependency,
image_rendering,
device_rect,
clip_rect,
z_id: composite_state.z_generator.next(),
native_surface_id,
update_params,
});
}
/// Update the dependencies for each tile for a given primitive instance.
pub fn update_prim_dependencies(
&mut self,
@ -3064,6 +3295,7 @@ impl TileCacheInstance {
// then applied below.
let mut backdrop_candidate = None;
// For pictures, we don't (yet) know the valid clip rect, so we can't correctly
// use it to calculate the local bounding rect for the tiles. If we include them
// then we may calculate a bounding rect that is too large, since it won't include
@ -3113,11 +3345,24 @@ impl TileCacheInstance {
prim_info.clip_by_tile = true;
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
let image_data = &data_stores.image[data_handle].kind;
PrimitiveInstanceKind::Image { data_handle, image_instance_index, ref mut is_compositor_surface, .. } => {
let image_key = &data_stores.image[data_handle];
let image_data = &image_key.kind;
let image_instance = &image_instances[image_instance_index];
let opacity_binding_index = image_instance.opacity_binding_index;
let mut promote_to_surface = false;
// If picture caching is disabled, we can't support any compositor surfaces.
if composite_state.picture_caching_is_enabled &&
self.can_promote_to_surface(image_key.common.flags,
prim_clip_chain,
prim_spatial_node_index,
on_picture_surface,
frame_context) {
promote_to_surface = true;
}
*is_compositor_surface = promote_to_surface;
if opacity_binding_index == OpacityBindingIndex::INVALID {
if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
// For an image to be a possible opaque backdrop, it must:
@ -3140,10 +3385,26 @@ impl TileCacheInstance {
}
}
prim_info.images.push(ImageDependency {
key: image_data.key,
generation: resource_cache.get_image_generation(image_data.key),
});
if promote_to_surface {
self.setup_compositor_surfaces_rgb(
&mut prim_info,
prim_rect,
frame_context,
ImageDependency {
key: image_data.key,
generation: resource_cache.get_image_generation(image_data.key),
},
image_data.key,
resource_cache,
composite_state,
image_data.image_rendering,
);
} else {
prim_info.images.push(ImageDependency {
key: image_data.key,
generation: resource_cache.get_image_generation(image_data.key),
});
}
}
PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => {
let prim_data = &data_stores.yuv_image[data_handle];
@ -3152,50 +3413,17 @@ impl TileCacheInstance {
// extract the logic below and support RGBA compositor surfaces too.
let mut promote_to_surface = false;
// If picture caching is disabled, we can't support any compositor surfaces.
if composite_state.picture_caching_is_enabled {
// Check if this primitive _wants_ to be promoted to a compositor surface.
if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
promote_to_surface = true;
promote_to_surface = self.can_promote_to_surface(
prim_data.common.flags,
prim_clip_chain,
prim_spatial_node_index,
on_picture_surface,
frame_context);
// For now, only support a small (arbitrary) number of compositor surfaces.
if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
promote_to_surface = false;
}
// If a complex clip is being applied to this primitive, it can't be
// promoted directly to a compositor surface (we might be able to
// do this in limited cases in future, some native compositors do
// support rounded rect clips, for example)
if prim_clip_chain.needs_mask {
promote_to_surface = false;
}
// If not on the same surface as the picture cache, it has some kind of
// complex effect (such as a filter, mix-blend-mode or 3d transform).
if !on_picture_surface {
promote_to_surface = false;
}
// If the primitive is not axis-aligned with the root coordinate system,
// it can't be promoted to a native compositor surface (could potentially
// be supported in future on some platforms).
let prim_spatial_node = &frame_context.spatial_tree
.spatial_nodes[prim_spatial_node_index.0 as usize];
if prim_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
promote_to_surface = false;
}
// If the transform has scale, we can't currently handle
// it in the native compositor - we can support this in future though.
if !self.map_local_to_surface.get_transform().is_simple_2d_translation() {
promote_to_surface = false;
}
// TODO(gw): When we support RGBA images for external surfaces, we also
// need to check if opaque (YUV images are implicitly opaque).
}
// TODO(gw): When we support RGBA images for external surfaces, we also
// need to check if opaque (YUV images are implicitly opaque).
}
// Store on the YUV primitive instance whether this is a promoted surface.
@ -3209,116 +3437,29 @@ impl TileCacheInstance {
// a promoted surface, since we don't want the tiles to invalidate when the
// video content changes, if it's a compositor surface!
if promote_to_surface {
prim_info.is_compositor_surface = true;
let pic_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
let world_rect = pic_to_world_mapper
.map(&prim_rect)
.expect("bug: unable to map the primitive to world space");
let world_clip_rect = pic_to_world_mapper
.map(&prim_info.prim_clip_rect)
.expect("bug: unable to map clip to world space");
let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
if is_visible {
// TODO(gw): Is there any case where if the primitive ends up on a fractional
// boundary we want to _skip_ promoting to a compositor surface and
// draw it as part of the content?
let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
// Build dependency for each YUV plane, with current image generation for
// later detection of when the composited surface has changed.
let mut image_dependencies = [ImageDependency::INVALID; 3];
for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
*dep = ImageDependency {
key,
generation: resource_cache.get_image_generation(key),
}
// Build dependency for each YUV plane, with current image generation for
// later detection of when the composited surface has changed.
let mut image_dependencies = [ImageDependency::INVALID; 3];
for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
*dep = ImageDependency {
key,
generation: resource_cache.get_image_generation(key),
}
// When using native compositing, we need to find an existing native surface
// handle to use, or allocate a new one. For existing native surfaces, we can
// also determine whether this needs to be updated, depending on whether the
// image generation(s) of the YUV planes have changed since last composite.
let (native_surface_id, update_params) = match composite_state.compositor_kind {
CompositorKind::Draw { .. } => {
(None, None)
}
CompositorKind::Native { .. } => {
let native_surface_size = device_rect.size.round().to_i32();
let key = ExternalNativeSurfaceKey {
image_keys: prim_data.kind.yuv_key,
size: native_surface_size,
};
let native_surface = self.external_native_surface_cache
.entry(key)
.or_insert_with(|| {
// No existing surface, so allocate a new compositor surface and
// a single compositor tile that covers the entire compositor surface.
let native_surface_id = resource_cache.create_compositor_surface(
DeviceIntPoint::zero(),
native_surface_size,
true,
);
let tile_id = NativeTileId {
surface_id: native_surface_id,
x: 0,
y: 0,
};
resource_cache.create_compositor_tile(tile_id);
ExternalNativeSurface {
used_this_frame: true,
native_surface_id,
image_dependencies: [ImageDependency::INVALID; 3],
}
});
// Mark that the surface is referenced this frame so that the
// backing native surface handle isn't freed.
native_surface.used_this_frame = true;
// If the image dependencies match, there is no need to update
// the backing native surface.
let update_params = if image_dependencies == native_surface.image_dependencies {
None
} else {
Some(native_surface_size)
};
(Some(native_surface.native_surface_id), update_params)
}
};
// Each compositor surface allocates a unique z-id
self.external_surfaces.push(ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_rect,
world_rect,
local_clip_rect: prim_info.prim_clip_rect,
image_dependencies,
image_rendering: prim_data.kind.image_rendering,
device_rect,
clip_rect,
yuv_color_space: prim_data.kind.color_space,
yuv_format: prim_data.kind.format,
yuv_rescale: prim_data.kind.color_depth.rescaling_factor(),
z_id: composite_state.z_generator.next(),
native_surface_id,
update_params,
});
}
self.setup_compositor_surfaces_yuv(
&mut prim_info,
prim_rect,
frame_context,
&image_dependencies,
&prim_data.kind.yuv_key,
resource_cache,
composite_state,
prim_data.kind.image_rendering,
prim_data.kind.color_depth,
prim_data.kind.color_space,
prim_data.kind.format,
);
} else {
prim_info.images.extend(
prim_data.kind.yuv_key.iter().map(|key| {

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

@ -332,6 +332,7 @@ impl InternablePrimitive for Image {
PrimitiveInstanceKind::Image {
data_handle,
image_instance_index,
is_compositor_surface: false,
}
}
}

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

@ -1428,6 +1428,7 @@ pub enum PrimitiveInstanceKind {
/// Handle to the common interned data for this primitive.
data_handle: ImageDataHandle,
image_instance_index: ImageInstanceIndex,
is_compositor_surface: bool,
},
LinearGradient {
/// Handle to the common interned data for this primitive.

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

@ -48,7 +48,7 @@ use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures,
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile, ResolvedExternalSurface};
use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat};
use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat, ResolvedExternalSurfaceColorData};
use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation};
use crate::debug_colors;
use crate::debug_render::{DebugItem, DebugRenderer};
@ -4530,54 +4530,90 @@ impl Renderer {
self.device.ortho_far_plane(),
);
// Bind an appropriate YUV shader for the texture format kind
self.shaders
.borrow_mut()
.get_composite_shader(
CompositeSurfaceFormat::Yuv,
surface.image_buffer_kind,
).bind(
&mut self.device,
&projection,
&mut self.renderer_errors
);
let ( textures, instance ) = match surface.color_data {
ResolvedExternalSurfaceColorData::Yuv{
ref planes, color_space, format, rescale, .. } => {
let textures = BatchTextures {
colors: [
surface.yuv_planes[0].texture,
surface.yuv_planes[1].texture,
surface.yuv_planes[2].texture,
],
// Bind an appropriate YUV shader for the texture format kind
self.shaders
.borrow_mut()
.get_composite_shader(
CompositeSurfaceFormat::Yuv,
surface.image_buffer_kind,
).bind(
&mut self.device,
&projection,
&mut self.renderer_errors
);
let textures = BatchTextures {
colors: [
planes[0].texture,
planes[1].texture,
planes[2].texture,
],
};
// When the texture is an external texture, the UV rect is not known when
// the external surface descriptor is created, because external textures
// are not resolved until the lock() callback is invoked at the start of
// the frame render. To handle this, query the texture resolver for the
// UV rect if it's an external texture, otherwise use the default UV rect.
let uv_rects = [
self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect),
];
let instance = CompositeInstance::new_yuv(
surface_rect.to_f32(),
surface_rect.to_f32(),
// z-id is not relevant when updating a native compositor surface.
// TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
ZBufferId(0),
color_space,
format,
rescale,
[
planes[0].texture_layer as f32,
planes[1].texture_layer as f32,
planes[2].texture_layer as f32,
],
uv_rects,
);
( textures, instance )
},
ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => {
self.shaders
.borrow_mut()
.get_composite_shader(
CompositeSurfaceFormat::Rgba,
surface.image_buffer_kind,
).bind(
&mut self.device,
&projection,
&mut self.renderer_errors
);
let textures = BatchTextures::color(plane.texture);
let uv_rect = self.texture_resolver.get_uv_rect(&textures.colors[0], plane.uv_rect);
let instance = CompositeInstance::new_rgb(
surface_rect.to_f32(),
surface_rect.to_f32(),
PremultipliedColorF::WHITE,
plane.texture_layer as f32,
ZBufferId(0),
uv_rect,
);
( textures, instance )
},
};
// When the texture is an external texture, the UV rect is not known when
// the external surface descriptor is created, because external textures
// are not resolved until the lock() callback is invoked at the start of
// the frame render. To handle this, query the texture resolver for the
// UV rect if it's an external texture, otherwise use the default UV rect.
let uv_rects = [
self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
];
let instance = CompositeInstance::new_yuv(
surface_rect.to_f32(),
surface_rect.to_f32(),
// z-id is not relevant when updating a native compositor surface.
// TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
ZBufferId(0),
surface.yuv_color_space,
surface.yuv_format,
surface.yuv_rescale,
[
surface.yuv_planes[0].texture_layer as f32,
surface.yuv_planes[1].texture_layer as f32,
surface.yuv_planes[2].texture_layer as f32,
],
uv_rects,
);
self.draw_instanced_batch(
&[instance],
VertexArrayKind::Composite,
@ -4686,43 +4722,64 @@ impl Renderer {
CompositeTileSurface::ExternalSurface { external_surface_index } => {
let surface = &external_surfaces[external_surface_index.0];
let textures = BatchTextures {
colors: [
surface.yuv_planes[0].texture,
surface.yuv_planes[1].texture,
surface.yuv_planes[2].texture,
],
};
match surface.color_data {
ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, rescale, .. } => {
// When the texture is an external texture, the UV rect is not known when
// the external surface descriptor is created, because external textures
// are not resolved until the lock() callback is invoked at the start of
// the frame render. To handle this, query the texture resolver for the
// UV rect if it's an external texture, otherwise use the default UV rect.
let uv_rects = [
self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
];
let textures = BatchTextures {
colors: [
planes[0].texture,
planes[1].texture,
planes[2].texture,
],
};
(
CompositeInstance::new_yuv(
tile.rect,
clip_rect,
tile.z_id,
surface.yuv_color_space,
surface.yuv_format,
surface.yuv_rescale,
[
surface.yuv_planes[0].texture_layer as f32,
surface.yuv_planes[1].texture_layer as f32,
surface.yuv_planes[2].texture_layer as f32,
],
uv_rects,
),
textures,
(CompositeSurfaceFormat::Yuv, surface.image_buffer_kind),
)
// When the texture is an external texture, the UV rect is not known when
// the external surface descriptor is created, because external textures
// are not resolved until the lock() callback is invoked at the start of
// the frame render. To handle this, query the texture resolver for the
// UV rect if it's an external texture, otherwise use the default UV rect.
let uv_rects = [
self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect),
self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect),
];
(
CompositeInstance::new_yuv(
tile.rect,
clip_rect,
tile.z_id,
color_space,
format,
rescale,
[
planes[0].texture_layer as f32,
planes[1].texture_layer as f32,
planes[2].texture_layer as f32,
],
uv_rects,
),
textures,
(CompositeSurfaceFormat::Yuv, surface.image_buffer_kind),
)
},
ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => {
let uv_rect = self.texture_resolver.get_uv_rect(&plane.texture, plane.uv_rect);
(
CompositeInstance::new_rgb(
tile.rect,
clip_rect,
PremultipliedColorF::WHITE,
plane.texture_layer as f32,
tile.z_id,
uv_rect,
),
BatchTextures::color(plane.texture),
(CompositeSurfaceFormat::Rgba, surface.image_buffer_kind),
)
},
}
}
CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
unreachable!("bug: found native surface in simple composite path");

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

@ -579,7 +579,11 @@ pub struct Shaders {
// cache tiles at a lower level (e.g. in DWM for Windows); in that case we
// directly hand the picture cache surfaces over to the OS Compositor, and
// our own Composite shaders below never run.
pub composite_rgba: LazilyCompiledShader,
// To composite external (RGB) surfaces we need various permutations of
// shaders with WR_FEATURE flags on or off based on the type of image
// buffer we're sourcing from (see IMAGE_BUFFER_KINDS).
pub composite_rgba: Vec<Option<LazilyCompiledShader>>,
// The same set of composite shaders but with WR_FEATURE_YUV added.
pub composite_yuv: Vec<Option<LazilyCompiledShader>>,
}
@ -884,13 +888,16 @@ impl Shaders {
// All yuv_image configuration.
let mut yuv_features = Vec::new();
let mut rgba_features = Vec::new();
let yuv_shader_num = IMAGE_BUFFER_KINDS.len();
let mut brush_yuv_image = Vec::new();
let mut composite_yuv = Vec::new();
let mut composite_rgba = Vec::new();
// PrimitiveShader is not clonable. Use push() to initialize the vec.
for _ in 0 .. yuv_shader_num {
brush_yuv_image.push(None);
composite_yuv.push(None);
composite_rgba.push(None);
}
for image_buffer_kind in &IMAGE_BUFFER_KINDS {
if image_buffer_kind.has_platform_support(&gl_type) {
@ -899,6 +906,7 @@ impl Shaders {
let feature_string = image_buffer_kind.get_feature_string();
if feature_string != "" {
yuv_features.push(feature_string);
rgba_features.push(feature_string);
}
let brush_shader = BrushShader::new(
@ -912,7 +920,7 @@ impl Shaders {
use_pixel_local_storage,
)?;
let composite_shader = LazilyCompiledShader::new(
let composite_yuv_shader = LazilyCompiledShader::new(
ShaderKind::Composite,
"composite",
&yuv_features,
@ -921,13 +929,24 @@ impl Shaders {
&shader_list,
)?;
let index = Self::get_yuv_shader_index(
let composite_rgba_shader = LazilyCompiledShader::new(
ShaderKind::Composite,
"composite",
&rgba_features,
device,
options.precache_flags,
&shader_list,
)?;
let index = Self::get_compositing_shader_index(
*image_buffer_kind,
);
brush_yuv_image[index] = Some(brush_shader);
composite_yuv[index] = Some(composite_shader);
composite_yuv[index] = Some(composite_yuv_shader);
composite_rgba[index] = Some(composite_rgba_shader);
yuv_features.clear();
rgba_features.clear()
}
}
@ -967,15 +986,6 @@ impl Shaders {
&shader_list,
)?;
let composite_rgba = LazilyCompiledShader::new(
ShaderKind::Composite,
"composite",
&[],
device,
options.precache_flags,
&shader_list,
)?;
Ok(Shaders {
cs_blur_a8,
cs_blur_rgba8,
@ -1009,7 +1019,7 @@ impl Shaders {
})
}
fn get_yuv_shader_index(buffer_kind: ImageBufferKind) -> usize {
fn get_compositing_shader_index(buffer_kind: ImageBufferKind) -> usize {
buffer_kind as usize
}
@ -1020,11 +1030,13 @@ impl Shaders {
) -> &mut LazilyCompiledShader {
match format {
CompositeSurfaceFormat::Rgba => {
debug_assert_eq!(buffer_kind, ImageBufferKind::Texture2DArray);
&mut self.composite_rgba
let shader_index = Self::get_compositing_shader_index(buffer_kind);
self.composite_rgba[shader_index]
.as_mut()
.expect("bug: unsupported rgba shader requested")
}
CompositeSurfaceFormat::Yuv => {
let shader_index = Self::get_yuv_shader_index(buffer_kind);
let shader_index = Self::get_compositing_shader_index(buffer_kind);
self.composite_yuv[shader_index]
.as_mut()
.expect("bug: unsupported yuv shader requested")
@ -1072,7 +1084,7 @@ impl Shaders {
}
BrushBatchKind::YuvImage(image_buffer_kind, ..) => {
let shader_index =
Self::get_yuv_shader_index(image_buffer_kind);
Self::get_compositing_shader_index(image_buffer_kind);
self.brush_yuv_image[shader_index]
.as_mut()
.expect("Unsupported YUV shader kind")
@ -1139,7 +1151,11 @@ impl Shaders {
self.cs_line_decoration.deinit(device);
self.cs_border_segment.deinit(device);
self.ps_split_composite.deinit(device);
self.composite_rgba.deinit(device);
for shader in self.composite_rgba {
if let Some(shader) = shader {
shader.deinit(device);
}
}
for shader in self.composite_yuv {
if let Some(shader) = shader {
shader.deinit(device);

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

@ -113,8 +113,12 @@ pub fn get_shader_features(flags: ShaderFeatureFlags) -> ShaderFeatures {
}
shaders.insert("brush_image", image_features);
let mut composite_features: Vec<String> = Vec::new();
for texture_type in &texture_types {
let base = concat_features("", texture_type);
composite_features.push(base.clone());
}
// YUV image brush shaders
let mut composite_features: Vec<String> = vec!["".to_string()];
let mut yuv_features: Vec<String> = Vec::new();
for texture_type in &texture_types {
let base = concat_features("YUV", texture_type);