diff --git a/sample/src/main.rs b/sample/src/main.rs index 914c9b7df2..8a70a53f9f 100644 --- a/sample/src/main.rs +++ b/sample/src/main.rs @@ -9,7 +9,7 @@ use gleam::gl; use std::path::PathBuf; use std::ffi::CStr; use webrender_traits::{AuxiliaryListsBuilder, ColorF, Epoch, GlyphInstance}; -use webrender_traits::{ImageFormat, PipelineId, RendererKind}; +use webrender_traits::{ImageData, ImageFormat, PipelineId, RendererKind}; use std::fs::File; use std::io::Read; use std::env; @@ -135,7 +135,7 @@ fn main() { let clip_region = { let mask = webrender_traits::ImageMask { - image: api.add_image(2, 2, None, ImageFormat::A8, vec![0,80, 180, 255]), + image: api.add_image(2, 2, None, ImageFormat::A8, ImageData::Raw(vec![0,80, 180, 255])), rect: Rect::new(Point2D::new(75.0, 75.0), Size2D::new(100.0, 100.0)), repeat: false, }; diff --git a/webrender/res/cs_text_run.vs.glsl b/webrender/res/cs_text_run.vs.glsl index 3ac5314bce..46703a0d09 100644 --- a/webrender/res/cs_text_run.vs.glsl +++ b/webrender/res/cs_text_run.vs.glsl @@ -13,17 +13,18 @@ void main(void) { TextRun text = fetch_text_run(cpi.specific_prim_index); Glyph glyph = fetch_glyph(cpi.sub_index); PrimitiveGeometry pg = fetch_prim_geometry(cpi.global_prim_index); + ResourceRect res = fetch_resource_rect(cpi.user_data.x); // Glyphs size is already in device-pixels. // The render task origin is in device-pixels. Offset that by // the glyph offset, relative to its primitive bounding rect. - vec2 size = glyph.uv_rect.zw - glyph.uv_rect.xy; + vec2 size = res.uv_rect.zw - res.uv_rect.xy; vec2 origin = task.data0.xy + uDevicePixelRatio * (glyph.offset.xy - pg.local_rect.xy); vec4 local_rect = vec4(origin, size); vec2 texture_size = vec2(textureSize(sColor0, 0)); - vec2 st0 = glyph.uv_rect.xy / texture_size; - vec2 st1 = glyph.uv_rect.zw / texture_size; + vec2 st0 = res.uv_rect.xy / texture_size; + vec2 st1 = res.uv_rect.zw / texture_size; vec2 pos = mix(local_rect.xy, local_rect.xy + local_rect.zw, diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 060216e8d9..0bbcffad99 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -57,6 +57,7 @@ uniform sampler2D sData16; uniform sampler2D sData32; uniform sampler2D sData64; uniform sampler2D sData128; +uniform sampler2D sResourceRects; ivec2 get_fetch_uv(int index, int vecs_per_item) { int items_per_row = WR_MAX_VERTEX_TEXTURE_WIDTH / vecs_per_item; @@ -208,16 +209,14 @@ GradientStop fetch_gradient_stop(int index) { struct Glyph { vec4 offset; - vec4 uv_rect; }; Glyph fetch_glyph(int index) { Glyph glyph; - ivec2 uv = get_fetch_uv_2(index); + ivec2 uv = get_fetch_uv_1(index); - glyph.offset = texelFetchOffset(sData32, uv, 0, ivec2(0, 0)); - glyph.uv_rect = texelFetchOffset(sData32, uv, 0, ivec2(1, 0)); + glyph.offset = texelFetchOffset(sData16, uv, 0, ivec2(0, 0)); return glyph; } @@ -324,19 +323,22 @@ struct CachePrimitiveInstance { int specific_prim_index; int render_task_index; int sub_index; + ivec4 user_data; }; CachePrimitiveInstance fetch_cache_instance(int index) { CachePrimitiveInstance cpi; - int offset = index * 1; + int offset = index * 2; ivec4 data0 = int_data[offset + 0]; + ivec4 data1 = int_data[offset + 1]; cpi.global_prim_index = data0.x; cpi.specific_prim_index = data0.y; cpi.render_task_index = data0.z; cpi.sub_index = data0.w; + cpi.user_data = data1; return cpi; } @@ -536,6 +538,20 @@ TransformVertexInfo write_transform_vertex(vec4 instance_rect, #endif //WR_FEATURE_TRANSFORM +struct ResourceRect { + vec4 uv_rect; +}; + +ResourceRect fetch_resource_rect(int index) { + ResourceRect rect; + + ivec2 uv = get_fetch_uv_1(index); + + rect.uv_rect = texelFetchOffset(sResourceRects, uv, 0, ivec2(0, 0)); + + return rect; +} + struct Rectangle { vec4 color; }; @@ -565,7 +581,6 @@ TextRun fetch_text_run(int index) { } struct Image { - vec4 st_rect; // Location of the image texture in the texture atlas. vec4 stretch_size_and_tile_spacing; // Size of the actual image and amount of space between // tiled instances of this image. }; @@ -573,10 +588,9 @@ struct Image { Image fetch_image(int index) { Image image; - ivec2 uv = get_fetch_uv_2(index); + ivec2 uv = get_fetch_uv_1(index); - image.st_rect = texelFetchOffset(sData32, uv, 0, ivec2(0, 0)); - image.stretch_size_and_tile_spacing = texelFetchOffset(sData32, uv, 0, ivec2(1, 0)); + image.stretch_size_and_tile_spacing = texelFetchOffset(sData16, uv, 0, ivec2(0, 0)); return image; } diff --git a/webrender/res/ps_image.vs.glsl b/webrender/res/ps_image.vs.glsl index 2bcd0b4953..ae22ddd7e8 100644 --- a/webrender/res/ps_image.vs.glsl +++ b/webrender/res/ps_image.vs.glsl @@ -6,6 +6,7 @@ void main(void) { Primitive prim = load_primitive(gl_InstanceID); Image image = fetch_image(prim.prim_index); + ResourceRect res = fetch_resource_rect(prim.user_data.x); #ifdef WR_FEATURE_TRANSFORM TransformVertexInfo vi = write_transform_vertex(prim.local_rect, @@ -26,8 +27,8 @@ void main(void) { // vUv will contain how many times this image has wrapped around the image size. vec2 texture_size = vec2(textureSize(sColor0, 0)); - vec2 st0 = image.st_rect.xy / texture_size; - vec2 st1 = image.st_rect.zw / texture_size; + vec2 st0 = res.uv_rect.xy / texture_size; + vec2 st1 = res.uv_rect.zw / texture_size; vTextureSize = st1 - st0; vTextureOffset = st0; diff --git a/webrender/res/ps_text_run.vs.glsl b/webrender/res/ps_text_run.vs.glsl index 4102b50744..0fb5dd8f26 100644 --- a/webrender/res/ps_text_run.vs.glsl +++ b/webrender/res/ps_text_run.vs.glsl @@ -7,7 +7,9 @@ void main(void) { Primitive prim = load_primitive(gl_InstanceID); TextRun text = fetch_text_run(prim.prim_index); Glyph glyph = fetch_glyph(prim.sub_index); - vec4 local_rect = vec4(glyph.offset.xy, (glyph.uv_rect.zw - glyph.uv_rect.xy) / uDevicePixelRatio); + ResourceRect res = fetch_resource_rect(prim.user_data.x); + + vec4 local_rect = vec4(glyph.offset.xy, (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio); #ifdef WR_FEATURE_TRANSFORM TransformVertexInfo vi = write_transform_vertex(local_rect, @@ -28,8 +30,8 @@ void main(void) { write_clip(vi.global_clamped_pos, prim.clip_area); vec2 texture_size = vec2(textureSize(sColor0, 0)); - vec2 st0 = glyph.uv_rect.xy / texture_size; - vec2 st1 = glyph.uv_rect.zw / texture_size; + vec2 st0 = res.uv_rect.xy / texture_size; + vec2 st1 = res.uv_rect.zw / texture_size; vColor = text.color; vUv = mix(st0, st1, f); diff --git a/webrender/src/device.rs b/webrender/src/device.rs index c7a2e6d3cc..e5e4fb310d 100644 --- a/webrender/src/device.rs +++ b/webrender/src/device.rs @@ -1366,6 +1366,11 @@ impl Device { if u_data128 != -1 { gl::uniform_1i(u_data128, TextureSampler::Data128 as i32); } + + let u_resource_rects = gl::get_uniform_location(program.id, "sResourceRects"); + if u_resource_rects != -1 { + gl::uniform_1i(u_resource_rects, TextureSampler::ResourceRects as i32); + } } } } diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs index 59cb756c11..9bb569b4c7 100644 --- a/webrender/src/internal_types.rs +++ b/webrender/src/internal_types.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use tiling; use webrender_traits::{Epoch, ColorF, PipelineId}; use webrender_traits::{ImageFormat, MixBlendMode, NativeFontHandle}; -use webrender_traits::{ScrollLayerId, WebGLCommand}; +use webrender_traits::{ExternalImageId, ScrollLayerId, WebGLCommand}; // An ID for a texture that is owned by the // texture cache module. This can include atlases @@ -45,9 +45,7 @@ pub enum SourceTexture { Invalid, TextureCache(CacheTextureId), WebGL(u32), // Is actually a gl::GLuint - - // TODO(gw): Implement external image support via callback - //External(i32), + External(ExternalImageId), } pub enum GLContextHandleWrapper { @@ -203,6 +201,7 @@ pub enum TextureSampler { Layers, RenderTasks, Geometry, + ResourceRects, } impl TextureSampler { diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 202c8305f0..d4612e01c0 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -121,4 +121,5 @@ extern crate offscreen_gl_context; extern crate byteorder; extern crate rayon; +pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler}; pub use renderer::{Renderer, RendererOptions}; diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index c4aa89285f..46e4c7c068 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -7,7 +7,7 @@ use euclid::{Point2D, Matrix4D, Rect, Size2D}; use gpu_store::{GpuStore, GpuStoreAddress}; use internal_types::{device_pixel, DeviceRect, DeviceSize, SourceTexture}; use mask_cache::{MaskCacheInfo, MaskCacheKey}; -use resource_cache::ResourceCache; +use resource_cache::{ImageProperties, ResourceCache}; use std::mem; use std::usize; use tiling::RenderTask; @@ -19,6 +19,39 @@ use webrender_traits::{FontKey, FontRenderMode, WebGLContextId}; pub const CLIP_DATA_GPU_SIZE: usize = 5; pub const MASK_DATA_GPU_SIZE: usize = 1; +/// Stores two coordinates in texel space. The coordinates +/// are stored in texel coordinates because the texture atlas +/// may grow. Storing them as texel coords and normalizing +/// the UVs in the vertex shader means nothing needs to be +/// updated on the CPU when the texture size changes. +#[derive(Clone)] +pub struct TexelRect { + pub uv0: Point2D, + pub uv1: Point2D, +} + +impl Default for TexelRect { + fn default() -> TexelRect { + TexelRect { + uv0: Point2D::zero(), + uv1: Point2D::zero(), + } + } +} + +/// For external images, it's not possible to know the +/// UV coords of the image (or the image data itself) +/// until the render thread receives the frame and issues +/// callbacks to the client application. For external +/// images that are visible, a DeferredResolve is created +/// that is stored in the frame. This allows the render +/// thread to iterate this list and update any changed +/// texture data and update the UV rect. +pub struct DeferredResolve { + pub resource_address: GpuStoreAddress, + pub image_properties: ImageProperties, +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct SpecificPrimitiveIndex(pub usize); @@ -98,12 +131,11 @@ pub enum ImagePrimitiveKind { pub struct ImagePrimitiveCpu { pub kind: ImagePrimitiveKind, pub color_texture_id: SourceTexture, + pub resource_address: GpuStoreAddress, } #[derive(Debug, Clone)] pub struct ImagePrimitiveGpu { - pub uv0: Point2D, - pub uv1: Point2D, pub stretch_size: Size2D, pub tile_spacing: Size2D, } @@ -193,14 +225,13 @@ pub struct TextRunPrimitiveCpu { pub color_texture_id: SourceTexture, pub color: ColorF, pub render_mode: FontRenderMode, + pub resource_address: GpuStoreAddress, } #[derive(Debug, Clone)] struct GlyphPrimitive { offset: Point2D, padding: Point2D, - uv0: Point2D, - uv1: Point2D, } #[derive(Debug, Clone)] @@ -346,6 +377,9 @@ pub struct PrimitiveStore { pub gpu_data64: GpuStore, pub gpu_data128: GpuStore, + // Resolved resource rects. + pub gpu_resource_rects: GpuStore, + // General device_pixel_ratio: f32, prims_to_resolve: Vec, @@ -365,6 +399,7 @@ impl PrimitiveStore { gpu_data32: GpuStore::new(), gpu_data64: GpuStore::new(), gpu_data128: GpuStore::new(), + gpu_resource_rects: GpuStore::new(), device_pixel_ratio: device_pixel_ratio, prims_to_resolve: Vec::new(), } @@ -406,9 +441,10 @@ impl PrimitiveStore { metadata } - PrimitiveContainer::TextRun(text_cpu, text_gpu) => { + PrimitiveContainer::TextRun(mut text_cpu, text_gpu) => { let gpu_address = self.gpu_data16.push(text_gpu); - let gpu_glyphs_address = self.gpu_data32.alloc(text_cpu.glyph_range.length); + let gpu_glyphs_address = self.gpu_data16.alloc(text_cpu.glyph_range.length); + text_cpu.resource_address = self.gpu_resource_rects.alloc(text_cpu.glyph_range.length); let metadata = PrimitiveMetadata { is_opaque: false, @@ -425,8 +461,10 @@ impl PrimitiveStore { self.cpu_text_runs.push(text_cpu); metadata } - PrimitiveContainer::Image(image_cpu, image_gpu) => { - let gpu_address = self.gpu_data32.push(image_gpu); + PrimitiveContainer::Image(mut image_cpu, image_gpu) => { + image_cpu.resource_address = self.gpu_resource_rects.alloc(1); + + let gpu_address = self.gpu_data16.push(image_gpu); let metadata = PrimitiveMetadata { is_opaque: false, @@ -540,12 +578,14 @@ impl PrimitiveStore { PrimitiveIndex(prim_index) } - pub fn resolve_primitives(&mut self, resource_cache: &ResourceCache) { + pub fn resolve_primitives(&mut self, resource_cache: &ResourceCache) -> Vec { + let mut deferred_resolves = Vec::new(); + for prim_index in self.prims_to_resolve.drain(..) { let metadata = &mut self.cpu_metadata[prim_index.0]; if let Some(MaskCacheInfo{ key: MaskCacheKey { image: Some(gpu_address), .. }, image: Some(ref mask), .. }) = metadata.clip_cache_info { - let cache_item = resource_cache.get_image(mask.image, ImageRendering::Auto); + let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto); let mask_data = self.gpu_data32.get_slice_mut(gpu_address, MASK_DATA_GPU_SIZE); mask_data[0] = GpuBlock32::from(ImageMaskData { uv_rect: Rect::new(cache_item.uv0, @@ -563,44 +603,64 @@ impl PrimitiveStore { PrimitiveKind::TextRun => { let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; - let dest_glyphs = self.gpu_data32.get_slice_mut(metadata.gpu_data_address, - text.glyph_range.length); + let dest_rects = self.gpu_resource_rects.get_slice_mut(text.resource_address, + text.glyph_range.length); let texture_id = resource_cache.get_glyphs(text.font_key, text.font_size, &text.glyph_indices, text.render_mode, |index, uv0, uv1| { - let dest_glyph = &mut dest_glyphs[index]; - let dest: &mut GlyphPrimitive = unsafe { - mem::transmute(dest_glyph) - }; - dest.uv0 = uv0; - dest.uv1 = uv1; + let dest_rect = &mut dest_rects[index]; + dest_rect.uv0 = uv0; + dest_rect.uv1 = uv1; }); text.color_texture_id = texture_id; } PrimitiveKind::Image => { let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0]; - let image_gpu: &mut ImagePrimitiveGpu = unsafe { - mem::transmute(self.gpu_data32.get_mut(metadata.gpu_prim_index)) - }; - let cache_item = match image_cpu.kind { + let (texture_id, cache_item) = match image_cpu.kind { ImagePrimitiveKind::Image(image_key, image_rendering, _) => { - resource_cache.get_image(image_key, image_rendering) + // Check if an external image that needs to be resolved + // by the render thread. + let image_properties = resource_cache.get_image_properties(image_key); + + match image_properties.external_id { + Some(external_id) => { + // This is an external texture - we will add it to + // the deferred resolves list to be patched by + // the render thread... + deferred_resolves.push(DeferredResolve { + resource_address: image_cpu.resource_address, + image_properties: image_properties, + }); + + (SourceTexture::External(external_id), None) + } + None => { + let cache_item = resource_cache.get_cached_image(image_key, image_rendering); + (cache_item.texture_id, Some(cache_item)) + } + } } ImagePrimitiveKind::WebGL(context_id) => { - resource_cache.get_webgl_texture(&context_id) + let cache_item = resource_cache.get_webgl_texture(&context_id); + (cache_item.texture_id, Some(cache_item)) } }; - image_cpu.color_texture_id = cache_item.texture_id; - image_gpu.uv0 = cache_item.uv0; - image_gpu.uv1 = cache_item.uv1; + if let Some(cache_item) = cache_item { + let resource_rect = self.gpu_resource_rects.get_mut(image_cpu.resource_address); + resource_rect.uv0 = cache_item.uv0; + resource_rect.uv1 = cache_item.uv1; + } + image_cpu.color_texture_id = texture_id; } } } + + deferred_resolves } pub fn get_bounding_rect(&self, index: PrimitiveIndex) -> &Option { @@ -695,7 +755,7 @@ impl PrimitiveStore { debug_assert!(metadata.gpu_data_count == text.glyph_range.length as i32); debug_assert!(text.glyph_indices.is_empty()); let src_glyphs = auxiliary_lists.glyph_instances(&text.glyph_range); - let dest_glyphs = self.gpu_data32.get_slice_mut(metadata.gpu_data_address, + let dest_glyphs = self.gpu_data16.get_slice_mut(metadata.gpu_data_address, text.glyph_range.length); let mut glyph_key = GlyphKey::new(text.font_key, text.font_size, @@ -724,9 +784,7 @@ impl PrimitiveStore { Size2D::new(width, height)); local_rect = local_rect.union(&local_glyph_rect); - dest_glyphs[actual_glyph_count] = GpuBlock32::from(GlyphPrimitive { - uv0: Point2D::zero(), - uv1: Point2D::zero(), + dest_glyphs[actual_glyph_count] = GpuBlock16::from(GlyphPrimitive { padding: Point2D::zero(), offset: local_glyph_rect.origin, }); @@ -866,6 +924,22 @@ impl From for GpuBlock16 { } } +impl From for GpuBlock16 { + fn from(data: ImagePrimitiveGpu) -> GpuBlock16 { + unsafe { + mem::transmute::(data) + } + } +} + +impl From for GpuBlock16 { + fn from(data: GlyphPrimitive) -> GpuBlock16 { + unsafe { + mem::transmute::(data) + } + } +} + #[derive(Clone)] pub struct GpuBlock32 { data: [f32; 8], @@ -895,22 +969,6 @@ impl From for GpuBlock32 { } } -impl From for GpuBlock32 { - fn from(data: GlyphPrimitive) -> GpuBlock32 { - unsafe { - mem::transmute::(data) - } - } -} - -impl From for GpuBlock32 { - fn from(data: ImagePrimitiveGpu) -> GpuBlock32 { - unsafe { - mem::transmute::(data) - } - } -} - impl From for GpuBlock32 { fn from(data: ClipRect) -> GpuBlock32 { unsafe { diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 56fa1ce8b2..475602e880 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -16,7 +16,7 @@ use std::io::{Cursor, Read}; use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use texture_cache::TextureCache; -use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace}; +use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace, ImageData}; use webrender_traits::{FlushNotifier, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId}; use record; use tiling::FrameBuilderConfig; @@ -124,14 +124,16 @@ impl RenderBackend { }; tx.send(glyph_dimensions).unwrap(); } - ApiMsg::AddImage(id, width, height, stride, format, bytes) => { - profile_counters.image_templates.inc(bytes.len()); + ApiMsg::AddImage(id, width, height, stride, format, data) => { + if let ImageData::Raw(ref bytes) = data { + profile_counters.image_templates.inc(bytes.len()); + } self.resource_cache.add_image_template(id, width, height, stride, format, - bytes); + data); } ApiMsg::Flush => { let mut flush_notifier = self.flush_notifier.lock(); diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 35add78802..68a596173a 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -13,7 +13,7 @@ use debug_colors; use debug_render::DebugRenderer; use device::{Device, ProgramId, TextureId, VertexFormat, GpuProfiler}; use device::{TextureFilter, VAOId, VertexUsageHint, FileWatcherHandler, TextureTarget}; -use euclid::{Matrix4D, Size2D}; +use euclid::{Matrix4D, Point2D, Size2D}; use fnv::FnvHasher; use internal_types::{CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp}; use internal_types::{TextureUpdateList, PackedVertex, RenderTargetMode}; @@ -38,7 +38,7 @@ use tiling::{RenderTarget, ClearTile}; use time::precise_time_ns; use util::TransformedRectKind; use webrender_traits::{ColorF, Epoch, FlushNotifier, PipelineId, RenderNotifier, RenderDispatcher}; -use webrender_traits::{ImageFormat, RenderApiSender, RendererKind}; +use webrender_traits::{ExternalImageId, ImageFormat, RenderApiSender, RendererKind}; pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024; @@ -388,6 +388,8 @@ pub struct Renderer { data32_texture: VertexDataTexture, data64_texture: VertexDataTexture, data128_texture: VertexDataTexture, + resource_rects_texture: VertexDataTexture, + pipeline_epoch_map: HashMap>, /// Used to dispatch functions to the main thread's event loop. /// Required to allow GLContext sharing in some implementations like WGL. @@ -400,6 +402,13 @@ pub struct Renderer { /// freed by the backend thread. This saves having to /// use a hashmap, and allows a flat vector for performance. cache_texture_id_map: Vec>, + + /// Optional trait object that allows the client + /// application to provide external buffers for image data. + external_image_handler: Option>, + + /// Map of external image IDs to native textures. + external_images: HashMap>, } impl Renderer { @@ -606,6 +615,7 @@ impl Renderer { let data32_texture = VertexDataTexture::new(&mut device); let data64_texture = VertexDataTexture::new(&mut device); let data128_texture = VertexDataTexture::new(&mut device); + let resource_rects_texture = VertexDataTexture::new(&mut device); let x0 = 0.0; let y0 = 0.0; @@ -721,9 +731,12 @@ impl Renderer { data32_texture: data32_texture, data64_texture: data64_texture, data128_texture: data128_texture, + resource_rects_texture: resource_rects_texture, pipeline_epoch_map: HashMap::with_hasher(Default::default()), main_thread_dispatcher: main_thread_dispatcher, cache_texture_id_map: Vec::new(), + external_image_handler: None, + external_images: HashMap::with_hasher(Default::default()), }; let sender = RenderApiSender::new(api_tx, payload_tx); @@ -800,6 +813,11 @@ impl Renderer { match texture_id { &SourceTexture::Invalid => TextureId::invalid(), &SourceTexture::WebGL(id) => TextureId::new(id), + &SourceTexture::External(ref key) => { + *self.external_images + .get(key) + .expect("BUG: External image should be resolved by now!") + } &SourceTexture::TextureCache(index) => { self.cache_texture_id_map[index.0] .expect("BUG: Texture should exist in texture cache map!") @@ -807,6 +825,11 @@ impl Renderer { } } + /// Set a callback for handling external images. + pub fn set_external_image_handler(&mut self, handler: Box) { + self.external_image_handler = Some(handler); + } + /// Renders the current frame. /// /// A Frame is supplied by calling [set_root_stacking_context()][newframe]. @@ -1321,9 +1344,52 @@ impl Renderer { self.device.set_blend(false); } + fn update_deferred_resolves(&mut self, frame: &mut Frame) { + // The first thing we do is run through any pending deferred + // resolves, and use a callback to get the UV rect for this + // custom item. Then we patch the resource_rects structure + // here before it's uploaded to the GPU. + if !frame.deferred_resolves.is_empty() { + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + + for deferred_resolve in &frame.deferred_resolves { + let props = &deferred_resolve.image_properties; + let external_id = props.external_id + .expect("BUG: Deferred resolves must be external images!"); + let image = handler.get(external_id); + + let texture_id = match image.source { + ExternalImageSource::NativeTexture(texture_id) => TextureId::new(texture_id), + }; + + self.external_images.insert(external_id, texture_id); + let resource_rect_index = deferred_resolve.resource_address.0 as usize; + let resource_rect = &mut frame.gpu_resource_rects[resource_rect_index]; + resource_rect.uv0 = Point2D::new(image.u0, image.v0); + resource_rect.uv1 = Point2D::new(image.u1, image.v1); + } + } + } + + fn release_external_textures(&mut self) { + if !self.external_images.is_empty() { + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + + for (external_id, _) in self.external_images.drain() { + handler.release(external_id); + } + } + } + fn draw_tile_frame(&mut self, frame: &mut Frame, framebuffer_size: &Size2D) { + self.update_deferred_resolves(frame); + // Some tests use a restricted viewport smaller than the main screen size. // Ensure we clear the framebuffer in these tests. // TODO(gw): Find a better solution for this? @@ -1382,6 +1448,7 @@ impl Renderer { self.data64_texture.init(&mut self.device, &mut frame.gpu_data64); self.data128_texture.init(&mut self.device, &mut frame.gpu_data128); self.prim_geom_texture.init(&mut self.device, &mut frame.gpu_geometry); + self.resource_rects_texture.init(&mut self.device, &mut frame.gpu_resource_rects); self.device.bind_texture(TextureSampler::Layers, self.layer_texture.id); self.device.bind_texture(TextureSampler::RenderTasks, self.render_task_texture.id); @@ -1390,6 +1457,7 @@ impl Renderer { self.device.bind_texture(TextureSampler::Data32, self.data32_texture.id); self.device.bind_texture(TextureSampler::Data64, self.data64_texture.id); self.device.bind_texture(TextureSampler::Data128, self.data128_texture.id); + self.device.bind_texture(TextureSampler::ResourceRects, self.resource_rects_texture.id); let mut src_id = None; @@ -1432,6 +1500,8 @@ impl Renderer { max_prim_items, &projection); } + + self.release_external_textures(); } pub fn debug_renderer<'a>(&'a mut self) -> &'a mut DebugRenderer { @@ -1447,6 +1517,36 @@ impl Renderer { } } +pub enum ExternalImageSource { + // TODO(gw): Work out the API for raw buffers. + //RawData(*const u8, usize), + NativeTexture(u32), // Is a gl::GLuint texture handle +} + +/// The data that an external client should provide about +/// an external image. The timestamp is used to test if +/// the renderer should upload new texture data this +/// frame. For instance, if providing video frames, the +/// application could call wr.render() whenever a new +/// video frame is ready. If the callback increments +/// the returned timestamp for a given image, the renderer +/// will know to re-upload the image data to the GPU. +/// Note that the UV coords are supplied in texel-space! +pub struct ExternalImage { + pub u0: f32, + pub v0: f32, + pub u1: f32, + pub v1: f32, + pub source: ExternalImageSource, +} + +/// Interface that an application can implement +/// to support providing external image buffers. +pub trait ExternalImageHandler { + fn get(&mut self, key: ExternalImageId) -> ExternalImage; + fn release(&mut self, key: ExternalImageId); +} + #[derive(Clone, Debug)] pub struct RendererOptions { pub device_pixel_ratio: f32, diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 2de2bf5b2a..0e7a56eff4 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -18,7 +18,8 @@ use std::hash::BuildHasherDefault; use std::hash::Hash; use texture_cache::{TextureCache, TextureCacheItemId}; use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering}; -use webrender_traits::{FontRenderMode, GlyphDimensions, WebGLContextId}; +use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId}; +use webrender_traits::ExternalImageId; thread_local!(pub static FONT_CONTEXT: RefCell = RefCell::new(FontContext::new())); @@ -58,6 +59,10 @@ impl RenderedGlyphKey { pub struct ImageProperties { pub format: ImageFormat, pub is_opaque: bool, + pub external_id: Option, + pub width: u32, + pub height: u32, + pub stride: Option, } #[derive(Debug, Copy, Clone, PartialEq)] @@ -68,7 +73,7 @@ enum State { } struct ImageResource { - bytes: Vec, + data: ImageData, width: u32, height: u32, stride: Option, @@ -218,14 +223,18 @@ impl ResourceCache { height: u32, stride: Option, format: ImageFormat, - bytes: Vec) { + data: ImageData) { + let is_opaque = match data { + ImageData::Raw(ref bytes) => is_image_opaque(format, bytes), + ImageData::External(..) => false, // TODO: Allow providing this through API. + }; let resource = ImageResource { - is_opaque: is_image_opaque(format, &bytes), + is_opaque: is_opaque, width: width, height: height, stride: stride, format: format, - bytes: bytes, + data: data, epoch: Epoch(0), }; @@ -254,7 +263,7 @@ impl ResourceCache { height: height, stride: None, format: format, - bytes: bytes, + data: ImageData::Raw(bytes), epoch: next_epoch, }; @@ -397,10 +406,11 @@ impl ResourceCache { } #[inline] - pub fn get_image(&self, - image_key: ImageKey, - image_rendering: ImageRendering) -> CacheItem { + pub fn get_cached_image(&self, + image_key: ImageKey, + image_rendering: ImageRendering) -> CacheItem { debug_assert!(self.state == State::QueryResources); + let image_info = &self.cached_images.get(&(image_key, image_rendering), self.current_frame_id); let item = self.texture_cache.get(image_info.texture_cache_id); @@ -416,9 +426,18 @@ impl ResourceCache { pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties { let image_template = &self.image_templates[&image_key]; + let external_id = match image_template.data { + ImageData::External(id) => Some(id), + ImageData::Raw(..) => None, + }; + ImageProperties { format: image_template.format, is_opaque: image_template.is_opaque, + external_id: external_id, + width: image_template.width, + height: image_template.height, + stride: image_template.stride, } } @@ -453,47 +472,54 @@ impl ResourceCache { let cached_images = &mut self.cached_images; let image_template = &self.image_templates[&key]; - match cached_images.entry((key, rendering), self.current_frame_id) { - Occupied(entry) => { - let image_id = entry.get().texture_cache_id; - - if entry.get().epoch != image_template.epoch { - // TODO: Can we avoid the clone of the bytes here? - self.texture_cache.update(image_id, - image_template.width, - image_template.height, - image_template.stride, - image_template.format, - image_template.bytes.clone()); - - // Update the cached epoch - *entry.into_mut() = CachedImageInfo { - texture_cache_id: image_id, - epoch: image_template.epoch, - }; + match image_template.data { + ImageData::Raw(ref bytes) => { + match cached_images.entry((key, rendering), self.current_frame_id) { + Occupied(entry) => { + let image_id = entry.get().texture_cache_id; + + if entry.get().epoch != image_template.epoch { + // TODO: Can we avoid the clone of the bytes here? + self.texture_cache.update(image_id, + image_template.width, + image_template.height, + image_template.stride, + image_template.format, + bytes.clone()); + + // Update the cached epoch + *entry.into_mut() = CachedImageInfo { + texture_cache_id: image_id, + epoch: image_template.epoch, + }; + } + } + Vacant(entry) => { + let image_id = self.texture_cache.new_item_id(); + + let filter = match rendering { + ImageRendering::Pixelated => TextureFilter::Nearest, + ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear, + }; + + // TODO: Can we avoid the clone of the bytes here? + self.texture_cache.insert(image_id, + image_template.width, + image_template.height, + image_template.stride, + image_template.format, + filter, + bytes.clone()); + + entry.insert(CachedImageInfo { + texture_cache_id: image_id, + epoch: image_template.epoch, + }); + } } } - Vacant(entry) => { - let image_id = self.texture_cache.new_item_id(); - - let filter = match rendering { - ImageRendering::Pixelated => TextureFilter::Nearest, - ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear, - }; - - // TODO: Can we avoid the clone of the bytes here? - self.texture_cache.insert(image_id, - image_template.width, - image_template.height, - image_template.stride, - image_template.format, - filter, - image_template.bytes.clone()); - - entry.insert(CachedImageInfo { - texture_cache_id: image_id, - epoch: image_template.epoch, - }); + ImageData::External(..) => { + // External images don't get added to the texture cache! } } } diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index 2b94f1ca22..bf93d6ca47 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -16,8 +16,8 @@ use mask_cache::{MaskCacheKey, MaskCacheInfo}; use prim_store::{PrimitiveGeometry, RectanglePrimitive, PrimitiveContainer}; use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu}; use prim_store::{ImagePrimitiveCpu, ImagePrimitiveGpu, ImagePrimitiveKind}; -use prim_store::{PrimitiveKind, PrimitiveIndex, PrimitiveMetadata}; -use prim_store::{CLIP_DATA_GPU_SIZE, PrimitiveClipSource}; +use prim_store::{PrimitiveKind, PrimitiveIndex, PrimitiveMetadata, TexelRect}; +use prim_store::{CLIP_DATA_GPU_SIZE, DeferredResolve, PrimitiveClipSource}; use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, GradientType}; use prim_store::{PrimitiveCacheKey, TextRunPrimitiveGpu, TextRunPrimitiveCpu}; use prim_store::{PrimitiveStore, GpuBlock16, GpuBlock32, GpuBlock64, GpuBlock128}; @@ -211,6 +211,8 @@ impl AlphaBatchHelpers for PrimitiveStore { }); } &mut PrimitiveBatchData::TextRun(ref mut data) => { + let text_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0]; + for glyph_index in 0..metadata.gpu_data_count { data.push(PrimitiveInstance { task_index: task_index, @@ -219,11 +221,13 @@ impl AlphaBatchHelpers for PrimitiveStore { global_prim_id: global_prim_id, prim_address: prim_address, sub_index: metadata.gpu_data_address.0 + glyph_index, - user_data: [ 0, 0 ], + user_data: [ text_cpu.resource_address.0 + glyph_index, 0 ], }); } } &mut PrimitiveBatchData::Image(ref mut data) => { + let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0]; + data.push(PrimitiveInstance { task_index: task_index, clip_task_index: clip_task_index, @@ -231,7 +235,7 @@ impl AlphaBatchHelpers for PrimitiveStore { global_prim_id: global_prim_id, prim_address: prim_address, sub_index: 0, - user_data: [ 0, 0 ], + user_data: [ image_cpu.resource_address.0, 0 ], }); } &mut PrimitiveBatchData::Borders(ref mut data) => { @@ -623,7 +627,7 @@ impl ClipBatcher { } })); if let (Some(address), Some(mask_key)) = (key.image, task_info.image) { - let cache_item = resource_cache.get_image(mask_key, ImageRendering::Auto); + let cache_item = resource_cache.get_cached_image(mask_key, ImageRendering::Auto); self.images.entry(cache_item.texture_id) .or_insert(Vec::new()) .push(CacheClipInstance { @@ -744,6 +748,7 @@ impl RenderTarget { global_prim_id: prim_index.0 as i32, prim_address: prim_metadata.gpu_prim_index, sub_index: 0, + user_data: [0; 4], }); } PrimitiveKind::TextRun => { @@ -769,6 +774,7 @@ impl RenderTarget { global_prim_id: prim_index.0 as i32, prim_address: prim_metadata.gpu_prim_index, sub_index: prim_metadata.gpu_data_address.0 + glyph_index, + user_data: [ text.resource_address.0 + glyph_index, 0, 0, 0 ], }); } } @@ -1259,6 +1265,7 @@ pub struct CachePrimitiveInstance { prim_address: GpuStoreAddress, task_id: i32, sub_index: i32, + user_data: [i32; 4], } /// A clipping primitive drawn into the clipping mask. @@ -1552,6 +1559,13 @@ pub struct Frame { pub gpu_data64: Vec, pub gpu_data128: Vec, pub gpu_geometry: Vec, + pub gpu_resource_rects: Vec, + + // List of textures that we don't know about yet + // from the backend thread. The render thread + // will use a callback to resolve these and + // patch the data structures. + pub deferred_resolves: Vec, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -2065,6 +2079,7 @@ impl FrameBuilder { color_texture_id: SourceTexture::Invalid, color: *color, render_mode: render_mode, + resource_address: GpuStoreAddress(0), }; let prim_gpu = TextRunPrimitiveGpu { @@ -2149,11 +2164,10 @@ impl FrameBuilder { let prim_cpu = ImagePrimitiveCpu { kind: ImagePrimitiveKind::WebGL(context_id), color_texture_id: SourceTexture::Invalid, + resource_address: GpuStoreAddress(0), }; let prim_gpu = ImagePrimitiveGpu { - uv0: Point2D::zero(), - uv1: Point2D::zero(), stretch_size: rect.size, tile_spacing: Size2D::zero(), }; @@ -2175,11 +2189,10 @@ impl FrameBuilder { image_rendering, *tile_spacing), color_texture_id: SourceTexture::Invalid, + resource_address: GpuStoreAddress(0), }; let prim_gpu = ImagePrimitiveGpu { - uv0: Point2D::zero(), - uv1: Point2D::zero(), stretch_size: *stretch_size, tile_spacing: *tile_spacing, }; @@ -2562,7 +2575,7 @@ impl FrameBuilder { resource_cache.block_until_all_resources_added(); - self.prim_store.resolve_primitives(resource_cache); + let deferred_resolves = self.prim_store.resolve_primitives(resource_cache); let mut passes = Vec::new(); @@ -2609,6 +2622,8 @@ impl FrameBuilder { gpu_data64: self.prim_store.gpu_data64.build(), gpu_data128: self.prim_store.gpu_data128.build(), gpu_geometry: self.prim_store.gpu_geometry.build(), + gpu_resource_rects: self.prim_store.gpu_resource_rects.build(), + deferred_resolves: deferred_resolves, } } } diff --git a/webrender_traits/src/api.rs b/webrender_traits/src/api.rs index 134169637e..e1affd00dd 100644 --- a/webrender_traits/src/api.rs +++ b/webrender_traits/src/api.rs @@ -9,8 +9,8 @@ use offscreen_gl_context::{GLContextAttributes, GLLimits}; use std::cell::Cell; use {ApiMsg, AuxiliaryLists, BuiltDisplayList, ColorF, Epoch}; use {FontKey, IdNamespace, ImageFormat, ImageKey, NativeFontHandle, PipelineId}; -use {RenderApiSender, ResourceId, ScrollEventPhase, ScrollLayerState, WebGLContextId, WebGLCommand}; -use {GlyphKey, GlyphDimensions}; +use {RenderApiSender, ResourceId, ScrollEventPhase, ScrollLayerState}; +use {GlyphKey, GlyphDimensions, ImageData, WebGLContextId, WebGLCommand}; impl RenderApiSender { pub fn new(api_sender: IpcSender, @@ -92,10 +92,10 @@ impl RenderApi { height: u32, stride: Option, format: ImageFormat, - bytes: Vec) -> ImageKey { + data: ImageData) -> ImageKey { let new_id = self.next_unique_id(); let key = ImageKey::new(new_id.0, new_id.1); - let msg = ApiMsg::AddImage(key, width, height, stride, format, bytes); + let msg = ApiMsg::AddImage(key, width, height, stride, format, data); self.api_sender.send(msg).unwrap(); key } diff --git a/webrender_traits/src/types.rs b/webrender_traits/src/types.rs index 61248d33aa..834d5d3b8b 100644 --- a/webrender_traits/src/types.rs +++ b/webrender_traits/src/types.rs @@ -28,7 +28,7 @@ pub enum ApiMsg { /// Gets the glyph dimensions GetGlyphDimensions(Vec, IpcSender>>), /// Adds an image from the resource cache. - AddImage(ImageKey, u32, u32, Option, ImageFormat, Vec), + AddImage(ImageKey, u32, u32, Option, ImageFormat, ImageData), /// Updates the the resource cache with the new image data. UpdateImage(ImageKey, u32, u32, ImageFormat, Vec), /// Drops an image from the resource cache. @@ -316,6 +316,18 @@ pub enum ImageFormat { RGBAF32, } +/// An arbitrary identifier for an external image provided by the +/// application. It must be a unique identifier for each external +/// image. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct ExternalImageId(pub u64); + +#[derive(Serialize, Deserialize)] +pub enum ImageData { + Raw(Vec), + External(ExternalImageId), +} + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct ImageKey(u32, u32);