/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{ AlphaType, ColorDepth, ColorF, ColorU, ExternalImageType, ImageKey as ApiImageKey, ImageBufferKind, ImageRendering, PremultipliedColorF, RasterSpace, Shadow, YuvColorSpace, ColorRange, YuvFormat, }; use api::units::*; use euclid::point2; use crate::clip::{ClipChainInstance, ClipIntern}; use crate::command_buffer::CommandBufferIndex; use crate::composite::CompositorSurfaceKind; use crate::gpu_types::{ImageBrushPrimitiveData, YuvPrimitive}; use crate::pattern::image::ImagePattern; use crate::quad::QuadTransformState; use crate::renderer::{GpuBufferBuilderF, GpuBufferWriterF}; use crate::scene_building::{CreateShadow, IsVisible}; use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext}; use crate::intern::{DataStore, Handle as InternHandle, InternDebug, Internable}; use crate::internal_types::LayoutPrimitiveInfo; use crate::prim_store::{ EdgeMask, InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData, PrimitiveInstanceIndex, PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveScratchBuffer, PrimitiveStore, SegmentInstanceIndex, SizeKey }; use crate::render_target::RenderTargetKind; use crate::render_task_graph::RenderTaskId; use crate::render_task::RenderTask; use crate::render_task_cache::{ RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent }; use crate::resource_cache::{ImageRequest, ImageProperties, ResourceCache}; use crate::visibility::{PrimitiveVisibility, compute_conservative_visible_rect}; use crate::spatial_tree::SpatialNodeIndex; use crate::{image_tiling, quad}; #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct VisibleImageTile { pub src_color: RenderTaskId, pub edge_flags: EdgeMask, pub local_rect: LayoutRect, pub local_clip_rect: LayoutRect, } // Key that identifies a unique (partial) image that is being // stored in the render task cache. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ImageCacheKey { pub request: ImageRequest, pub texel_rect: Option, } /// Instance specific fields for an image primitive. These are /// currently stored in a separate array to avoid bloating the /// size of PrimitiveInstance. In the future, we should be able /// to remove this and store the information inline, by: /// (a) Removing opacity collapse / binding support completely. /// Once we have general picture caching, we don't need this. /// (b) Change visible_tiles to use Storage in the primitive /// scratch buffer. This will reduce the size of the /// visible_tiles field here, and save memory allocation /// when image tiling is used. I've left it as a Vec for /// now to reduce the number of changes, and because image /// tiling is very rare on real pages. #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] pub struct ImageInstance { pub segment_instance_index: SegmentInstanceIndex, pub tight_local_clip_rect: LayoutRect, pub visible_tiles: Vec, pub src_color: Option, pub normalized_uvs: bool, pub adjustment: AdjustedImageSource, } #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf, Hash)] pub struct Image { pub key: ApiImageKey, pub stretch_size: SizeKey, pub tile_spacing: SizeKey, pub color: ColorU, pub image_rendering: ImageRendering, pub alpha_type: AlphaType, } pub type ImageKey = PrimKey; impl ImageKey { pub fn new( info: &LayoutPrimitiveInfo, image: Image, ) -> Self { ImageKey { common: info.into(), kind: image, } } } impl InternDebug for ImageKey {} #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, MallocSizeOf)] pub struct ImageData { pub key: ApiImageKey, pub stretch_size: LayoutSize, pub tile_spacing: LayoutSize, pub color: ColorF, pub image_rendering: ImageRendering, pub alpha_type: AlphaType, } impl From for ImageData { fn from(image: Image) -> Self { ImageData { key: image.key, color: image.color.into(), stretch_size: image.stretch_size.into(), tile_spacing: image.tile_spacing.into(), image_rendering: image.image_rendering, alpha_type: image.alpha_type, } } } impl ImageData { /// Update the GPU cache for a given primitive template. This may be called multiple /// times per frame, by each primitive reference that refers to this interned /// template. The initial request call to the GPU cache ensures that work is only /// done if the cache entry is invalid (due to first use or eviction). pub fn update( &mut self, common: &mut PrimTemplateCommonData, image_instance: &mut ImageInstance, prim_spatial_node_index: SpatialNodeIndex, frame_state: &mut FrameBuildingState, frame_context: &FrameBuildingContext, visibility: &mut PrimitiveVisibility, prim_origin: LayoutPoint, ) { let image_properties = frame_state .resource_cache .get_image_properties(self.key); common.opacity = match &image_properties { Some(properties) => { if properties.descriptor.is_opaque() { PrimitiveOpacity::from_alpha(self.color.a) } else { PrimitiveOpacity::translucent() } } None => PrimitiveOpacity::opaque(), }; if self.stretch_size.width >= common.prim_size.width && self.stretch_size.height >= common.prim_size.height { common.may_need_repetition = false; } let request = ImageRequest { key: self.key, rendering: self.image_rendering, tile: None, }; // Tighten the clip rect because decomposing the repeated image can // produce primitives that are partially covering the original image // rect and we want to clip these extra parts out. // We also rely on having a tight clip rect in some cases other than // tiled/repeated images, for example when rendering a snapshot image // where the snapshot area is tighter than the rasterized area. let prim_rect = LayoutRect::from_origin_and_size(prim_origin, common.prim_size); let tight_clip_rect = visibility .clip_chain .local_clip_rect .intersection(&prim_rect).unwrap(); image_instance.tight_local_clip_rect = tight_clip_rect; image_instance.adjustment = AdjustedImageSource::new(); match image_properties { // Non-tiled (most common) path. Some(ImageProperties { tiling: None, ref descriptor, ref external_image, adjustment, .. }) => { image_instance.adjustment = adjustment; let mut size = frame_state.resource_cache.request_image( request, &mut frame_state.frame_gpu_data.f32, ); let mut task_id = frame_state.rg_builder.add().init( RenderTask::new_image(size, request, false) ); if let Some(external_image) = external_image { // On some devices we cannot render from an ImageBufferKind::TextureExternal // source using most shaders, so must peform a copy to a regular texture first. let requires_copy = frame_context.fb_config.external_images_require_copy && external_image.image_type == ExternalImageType::TextureHandle(ImageBufferKind::TextureExternal); if requires_copy { let target_kind = if descriptor.format.bytes_per_pixel() == 1 { RenderTargetKind::Alpha } else { RenderTargetKind::Color }; task_id = RenderTask::new_scaling( task_id, frame_state.rg_builder, target_kind, size ); frame_state.surface_builder.add_child_render_task( task_id, frame_state.rg_builder, ); } // Ensure the instance is rendered using normalized_uvs if the external image // requires so. If we inserted a scale above this is not required as the // instance is rendered from a render task rather than the external image. if !requires_copy { image_instance.normalized_uvs = external_image.normalized_uvs; } } // Every frame, for cached items, we need to request the render // task cache item. The closure will be invoked on the first // time through, and any time the render task output has been // evicted from the texture cache. if self.tile_spacing == LayoutSize::zero() { // Most common case. image_instance.src_color = Some(task_id); } else { let padding = DeviceIntSideOffsets::new( 0, (self.tile_spacing.width * size.width as f32 / self.stretch_size.width) as i32, (self.tile_spacing.height * size.height as f32 / self.stretch_size.height) as i32, 0, ); size.width += padding.horizontal(); size.height += padding.vertical(); if padding != DeviceIntSideOffsets::zero() { common.opacity = PrimitiveOpacity::translucent(); } let image_cache_key = ImageCacheKey { request, texel_rect: None, }; let target_kind = if descriptor.format.bytes_per_pixel() == 1 { RenderTargetKind::Alpha } else { RenderTargetKind::Color }; // Request a pre-rendered image task. let cached_task_handle = frame_state.resource_cache.request_render_task( Some(RenderTaskCacheKey { origin: DeviceIntPoint::zero(), size, kind: RenderTaskCacheKeyKind::Image(image_cache_key), }), descriptor.is_opaque(), RenderTaskParent::Surface, &mut frame_state.frame_gpu_data.f32, frame_state.rg_builder, &mut frame_state.surface_builder, &mut |rg_builder, _| { // Create a task to blit from the texture cache to // a normal transient render task surface. // TODO: figure out if/when we can do a blit instead. let cache_to_target_task_id = RenderTask::new_scaling_with_padding( task_id, rg_builder, target_kind, size, padding, ); // Create a task to blit the rect from the child render // task above back into the right spot in the persistent // render target cache. RenderTask::new_blit( size, cache_to_target_task_id, size.into(), rg_builder, ) } ); image_instance.src_color = Some(cached_task_handle); } } // Tiled image path. Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => { // we'll have a source handle per visible tile instead. image_instance.src_color = None; image_instance.visible_tiles.clear(); // TODO: rename the blob's visible_rect into something that doesn't conflict // with the terminology we use during culling since it's not really the same // thing. let active_rect = visible_rect; let visible_rect = compute_conservative_visible_rect( &visibility.clip_chain, frame_state.current_dirty_region().combined, frame_state.current_dirty_region().visibility_spatial_node, prim_spatial_node_index, frame_context.spatial_tree, ); let base_edge_flags = edge_flags_for_tile_spacing(&self.tile_spacing); let stride = self.stretch_size + self.tile_spacing; // We are performing the decomposition on the CPU here, no need to // have it in the shader. common.may_need_repetition = false; let prim_rect = LayoutRect::from_origin_and_size(prim_origin, common.prim_size); let repetitions = image_tiling::repetitions( &prim_rect, &visible_rect, stride, ); for image_tiling::Repetition { origin, edge_flags } in repetitions { let edge_flags = base_edge_flags | edge_flags; let layout_image_rect = LayoutRect::from_origin_and_size( origin, self.stretch_size, ); let tiles = image_tiling::tiles( &layout_image_rect, &visible_rect, &active_rect, tile_size as i32, ); for tile in tiles { let request = request.with_tile(tile.offset); let size = frame_state.resource_cache.request_image( request, &mut frame_state.frame_gpu_data.f32, ); let task_id = frame_state.rg_builder.add().init( RenderTask::new_image(size, request, false) ); image_instance.visible_tiles.push(VisibleImageTile { src_color: task_id, edge_flags: tile.edge_flags & edge_flags, local_rect: tile.rect, local_clip_rect: tight_clip_rect, }); } } if image_instance.visible_tiles.is_empty() { // Mark as invisible visibility.reset(); } } None => { image_instance.src_color = None; } } if let Some(task_id) = frame_state.image_dependencies.get(&self.key) { frame_state.surface_builder.add_child_render_task( *task_id, frame_state.rg_builder ); } let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3); self.write_prim_gpu_blocks(&image_instance.adjustment, &mut writer); common.gpu_buffer_address = writer.finish(); } pub fn write_prim_gpu_blocks(&self, adjustment: &AdjustedImageSource, writer: &mut GpuBufferWriterF) { let stretch_size = adjustment.map_stretch_size(self.stretch_size) + self.tile_spacing; writer.push(&ImageBrushPrimitiveData { color: self.color.premultiplied(), background_color: PremultipliedColorF::WHITE, stretch_size, }); } } pub fn can_use_quad_shaders( _image_data: &ImageData, _resource_cache: &ResourceCache, ) -> bool { // TODO(Bug 2026629): Temporarily disabled due to some visiual regressions. // let image_properties = resource_cache.get_image_properties(image_data.key); // match &image_properties { // Some(ImageProperties { tiling: None, external_image: None, adjustment, .. }) => { // return adjustment.x0 == 0.0 // && adjustment.y0 == 0.0 // && adjustment.x1 == 0.0 // && adjustment.y1 == 0.0 // && image_data.alpha_type == AlphaType::PremultipliedAlpha // // See the comment in ps_quad_textured about ignoring the base color // // due to a driver issue. // && image_data.color == ColorF::WHITE; // } // _ => { // return false; // } // } false } pub fn prepare_image_quads( prim_rect: &LayoutRect, common_data: &PrimTemplateCommonData, image_data: &ImageData, clip_chain: &ClipChainInstance, prim_instance_index: PrimitiveInstanceIndex, quad_transform: &mut QuadTransformState, frame_context: &FrameBuildingContext, pic_context: &PictureContext, targets: &[CommandBufferIndex], interned_clips: &DataStore, frame_state: &mut FrameBuildingState, scratch: &mut PrimitiveScratchBuffer, ) { let image_properties = frame_state .resource_cache .get_image_properties(image_data.key); match image_properties { // Non-tiled (most common) path. Some(ImageProperties { tiling: None, ref descriptor, .. }) => { let request = ImageRequest { key: image_data.key, rendering: image_data.image_rendering, tile: None, }; let size = frame_state.resource_cache.request_image( request, &mut frame_state.frame_gpu_data.f32, ); let is_opaque = if descriptor.is_opaque() { image_data.color.a >= 0.9999 } else { false }; let task_id = frame_state.rg_builder.add().init( RenderTask::new_image(size, request, false) ); prepare_non_tiled_image_quad( task_id, is_opaque, prim_rect, common_data, image_data, clip_chain, prim_instance_index, quad_transform, frame_context, pic_context, targets, interned_clips, frame_state, scratch ); } _ => { unimplemented!(); } } } pub fn prepare_non_tiled_image_quad( image_task: RenderTaskId, is_opaque: bool, prim_rect: &LayoutRect, common_data: &PrimTemplateCommonData, image_data: &ImageData, clip_chain: &ClipChainInstance, prim_instance_index: PrimitiveInstanceIndex, quad_transform: &mut QuadTransformState, frame_context: &FrameBuildingContext, pic_context: &PictureContext, targets: &[CommandBufferIndex], interned_clips: &DataStore, frame_state: &mut FrameBuildingState, scratch: &mut PrimitiveScratchBuffer, ) { let pattern_builder = ImagePattern { src_task_id: image_task, src_is_opaque: common_data.opacity.is_opaque && is_opaque, }; quad::prepare_repeatable_quad( &pattern_builder, prim_rect, image_data.stretch_size, image_data.tile_spacing, common_data.aligned_aa_edges, common_data.transformed_aa_edges, prim_instance_index, &None, clip_chain, quad_transform, frame_context, pic_context, targets, interned_clips, frame_state, scratch, ); } fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeMask { let mut flags = EdgeMask::empty(); if tile_spacing.width > 0.0 { flags |= EdgeMask::LEFT | EdgeMask::RIGHT; } if tile_spacing.height > 0.0 { flags |= EdgeMask::TOP | EdgeMask::BOTTOM; } flags } pub type ImageTemplate = PrimTemplate; impl From for ImageTemplate { fn from(image: ImageKey) -> Self { let common = PrimTemplateCommonData::with_key_common(image.common); ImageTemplate { common, kind: image.kind.into(), } } } pub type ImageDataHandle = InternHandle; impl Internable for Image { type Key = ImageKey; type StoreData = ImageTemplate; type InternData = (); const PROFILE_COUNTER: usize = crate::profiler::INTERNED_IMAGES; } impl InternablePrimitive for Image { fn into_key( self, info: &LayoutPrimitiveInfo, ) -> ImageKey { ImageKey::new(info, self) } fn make_instance_kind( _key: ImageKey, data_handle: ImageDataHandle, prim_store: &mut PrimitiveStore, ) -> PrimitiveInstanceKind { // TODO(gw): Refactor this to not need a separate image // instance (see ImageInstance struct). let image_instance_index = prim_store.images.push(ImageInstance { segment_instance_index: SegmentInstanceIndex::INVALID, tight_local_clip_rect: LayoutRect::zero(), visible_tiles: Vec::new(), src_color: None, normalized_uvs: false, adjustment: AdjustedImageSource::new(), }); PrimitiveInstanceKind::Image { data_handle, image_instance_index, compositor_surface_kind: CompositorSurfaceKind::Blit, } } } impl CreateShadow for Image { fn create_shadow( &self, shadow: &Shadow, _: bool, _: RasterSpace, ) -> Self { Image { tile_spacing: self.tile_spacing, stretch_size: self.stretch_size, key: self.key, image_rendering: self.image_rendering, alpha_type: self.alpha_type, color: shadow.color.into(), } } } impl IsVisible for Image { fn is_visible(&self) -> bool { true } } /// Represents an adjustment to apply to an image primitive. /// This can be used to compensate for a difference between the bounds of /// the images expected by the primitive and the bounds that were actually /// drawn in the texture cache. /// /// This happens when rendering snapshot images: A picture is marked so that /// a specific reference area in layout space can be rendered as an image. /// However, the bounds of the rasterized area of the picture typically differ /// from that reference area. /// /// The adjustment is stored as 4 floats (x0, y0, x1, y1) that represent a /// transformation of the primitve's local rect such that: /// /// ```ignore /// adjusted_rect.min = prim_rect.min + prim_rect.size() * (x0, y0); /// adjusted_rect.max = prim_rect.max + prim_rect.size() * (x1, y1); /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct AdjustedImageSource { x0: f32, y0: f32, x1: f32, y1: f32, } impl AdjustedImageSource { /// The "identity" adjustment. pub fn new() -> Self { AdjustedImageSource { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0, } } /// An adjustment to render an image item defined in function of the `reference` /// rect whereas the `actual` rect was cached instead. pub fn from_rects(reference: &LayoutRect, actual: &LayoutRect) -> Self { let ref_size = reference.size(); let min_offset = reference.min.to_vector(); let max_offset = reference.max.to_vector(); AdjustedImageSource { x0: (actual.min.x - min_offset.x) / ref_size.width, y0: (actual.min.y - min_offset.y) / ref_size.height, x1: (actual.max.x - max_offset.x) / ref_size.width, y1: (actual.max.y - max_offset.y) / ref_size.height, } } /// Adjust the primitive's local rect. pub fn map_local_rect(&self, rect: &LayoutRect) -> LayoutRect { let w = rect.width(); let h = rect.height(); LayoutRect { min: point2( rect.min.x + w * self.x0, rect.min.y + h * self.y0, ), max: point2( rect.max.x + w * self.x1, rect.max.y + h * self.y1, ), } } /// The stretch size has to be adjusted as well because it is defined /// using the snapshot area as reference but will stretch the rasterized /// area instead. /// /// It has to be scaled by a factor of (adjusted.size() / prim_rect.size()). /// We derive the formula in function of the adjustment factors: /// /// ```ignore /// factor = (adjusted.max - adjusted.min) / (w, h) /// = (rect.max + (w, h) * (x1, y1) - (rect.min + (w, h) * (x0, y0))) / (w, h) /// = ((w, h) + (w, h) * (x1, y1) - (w, h) * (x0, y0)) / (w, h) /// = (1.0, 1.0) + (x1, y1) - (x0, y0) /// ``` pub fn map_stretch_size(&self, size: LayoutSize) -> LayoutSize { LayoutSize::new( size.width * (1.0 + self.x1 - self.x0), size.height * (1.0 + self.y1 - self.y0), ) } } //////////////////////////////////////////////////////////////////////////////// #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] pub struct YuvImage { pub color_depth: ColorDepth, pub yuv_key: [ApiImageKey; 3], pub format: YuvFormat, pub color_space: YuvColorSpace, pub color_range: ColorRange, pub image_rendering: ImageRendering, } pub type YuvImageKey = PrimKey; impl YuvImageKey { pub fn new( info: &LayoutPrimitiveInfo, yuv_image: YuvImage, ) -> Self { YuvImageKey { common: info.into(), kind: yuv_image, } } } impl InternDebug for YuvImageKey {} #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pub struct YuvImageData { pub color_depth: ColorDepth, pub yuv_key: [ApiImageKey; 3], pub src_yuv: [Option; 3], pub format: YuvFormat, pub color_space: YuvColorSpace, pub color_range: ColorRange, pub image_rendering: ImageRendering, } impl From for YuvImageData { fn from(image: YuvImage) -> Self { YuvImageData { color_depth: image.color_depth, yuv_key: image.yuv_key, src_yuv: [None, None, None], format: image.format, color_space: image.color_space, color_range: image.color_range, image_rendering: image.image_rendering, } } } impl YuvImageData { /// Update the GPU cache for a given primitive template. This may be called multiple /// times per frame, by each primitive reference that refers to this interned /// template. The initial request call to the GPU cache ensures that work is only /// done if the cache entry is invalid (due to first use or eviction). pub fn update( &mut self, common: &mut PrimTemplateCommonData, is_composited: bool, frame_state: &mut FrameBuildingState, ) { self.src_yuv = [ None, None, None ]; let channel_num = self.format.get_plane_num(); debug_assert!(channel_num <= 3); for channel in 0 .. channel_num { let request = ImageRequest { key: self.yuv_key[channel], rendering: self.image_rendering, tile: None, }; let size = frame_state.resource_cache.request_image( request, &mut frame_state.frame_gpu_data.f32, ); let task_id = frame_state.rg_builder.add().init( RenderTask::new_image( size, request, is_composited, ) ); self.src_yuv[channel] = Some(task_id); } let mut writer = frame_state.frame_gpu_data.f32.write_blocks(1); self.write_prim_gpu_blocks(&mut writer); common.gpu_buffer_address = writer.finish(); // YUV images never have transparency common.opacity = PrimitiveOpacity::opaque(); } pub fn request_resources( &mut self, resource_cache: &mut ResourceCache, gpu_buffer: &mut GpuBufferBuilderF, ) { let channel_num = self.format.get_plane_num(); debug_assert!(channel_num <= 3); for channel in 0 .. channel_num { resource_cache.request_image( ImageRequest { key: self.yuv_key[channel], rendering: self.image_rendering, tile: None, }, gpu_buffer, ); } } pub fn write_prim_gpu_blocks(&self, writer: &mut GpuBufferWriterF) { writer.push(&YuvPrimitive { channel_bit_depth: self.color_depth.bit_depth(), color_space: self.color_space.with_range(self.color_range), yuv_format: self.format, }); } } pub type YuvImageTemplate = PrimTemplate; impl From for YuvImageTemplate { fn from(image: YuvImageKey) -> Self { let common = PrimTemplateCommonData::with_key_common(image.common); YuvImageTemplate { common, kind: image.kind.into(), } } } pub type YuvImageDataHandle = InternHandle; impl Internable for YuvImage { type Key = YuvImageKey; type StoreData = YuvImageTemplate; type InternData = (); const PROFILE_COUNTER: usize = crate::profiler::INTERNED_YUV_IMAGES; } impl InternablePrimitive for YuvImage { fn into_key( self, info: &LayoutPrimitiveInfo, ) -> YuvImageKey { YuvImageKey::new(info, self) } fn make_instance_kind( _key: YuvImageKey, data_handle: YuvImageDataHandle, _prim_store: &mut PrimitiveStore, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index: SegmentInstanceIndex::INVALID, compositor_surface_kind: CompositorSurfaceKind::Blit, } } } impl IsVisible for YuvImage { fn is_visible(&self) -> bool { true } } #[test] #[cfg(target_pointer_width = "64")] fn test_struct_sizes() { use std::mem; // The sizes of these structures are critical for performance on a number of // talos stress tests. If you get a failure here on CI, there's two possibilities: // (a) You made a structure smaller than it currently is. Great work! Update the // test expectations and move on. // (b) You made a structure larger. This is not necessarily a problem, but should only // be done with care, and after checking if talos performance regresses badly. assert_eq!(mem::size_of::(), 32, "Image size changed"); assert_eq!(mem::size_of::(), 64, "ImageTemplate size changed"); assert_eq!(mem::size_of::(), 44, "ImageKey size changed"); assert_eq!(mem::size_of::(), 32, "YuvImage size changed"); assert_eq!(mem::size_of::(), 76, "YuvImageTemplate size changed"); assert_eq!(mem::size_of::(), 44, "YuvImageKey size changed"); }