diff --git a/Cargo.lock b/Cargo.lock index 8f219b8c..668fa40a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1707,6 +1707,7 @@ dependencies = [ name = "pathfinder_gpu" version = "0.1.0" dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "half 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.4 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_color 0.1.0", diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 9c4b5b2f..712bbe86 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -16,7 +16,7 @@ use pathfinder_content::effects::{BlendMode, CompositeOp, Effects, Filter}; use pathfinder_content::fill::FillRule; use pathfinder_content::gradient::Gradient; use pathfinder_content::outline::{ArcDirection, Contour, Outline}; -use pathfinder_content::pattern::{Pattern, RenderTargetId}; +use pathfinder_content::pattern::{Pattern, PatternFlags, RenderTargetId}; use pathfinder_content::stroke::{LineCap, LineJoin as StrokeLineJoin}; use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle}; use pathfinder_geometry::line_segment::LineSegment2F; @@ -326,6 +326,28 @@ impl CanvasRenderingContext2D { self.current_state.global_composite_operation = new_composite_operation; } + // Image smoothing + + #[inline] + pub fn image_smoothing_enabled(&self) -> bool { + self.current_state.image_smoothing_enabled + } + + #[inline] + pub fn set_image_smoothing_enabled(&mut self, enabled: bool) { + self.current_state.image_smoothing_enabled = enabled + } + + #[inline] + pub fn image_smoothing_quality(&self) -> ImageSmoothingQuality { + self.current_state.image_smoothing_quality + } + + #[inline] + pub fn set_image_smoothing_quality(&mut self, new_quality: ImageSmoothingQuality) { + self.current_state.image_smoothing_quality = new_quality + } + // The canvas state #[inline] @@ -357,6 +379,8 @@ struct State { shadow_paint: Paint, shadow_offset: Vector2F, text_align: TextAlign, + image_smoothing_enabled: bool, + image_smoothing_quality: ImageSmoothingQuality, global_alpha: f32, global_composite_operation: CompositeOperation, clip_path: Option, @@ -379,6 +403,8 @@ impl State { shadow_paint: Paint::transparent_black(), shadow_offset: Vector2F::default(), text_align: TextAlign::Left, + image_smoothing_enabled: true, + image_smoothing_quality: ImageSmoothingQuality::Low, global_alpha: 1.0, global_composite_operation: CompositeOperation::SourceOver, clip_path: None, @@ -389,9 +415,18 @@ impl State { if self.transform.is_identity() { return Cow::Borrowed(paint); } + if let Paint::Pattern(ref pattern) = *paint { + if !self.image_smoothing_enabled == + pattern.flags.contains(PatternFlags::NO_SMOOTHING) { + return Cow::Borrowed(paint) + } + } let mut paint = (*paint).clone(); paint.apply_transform(&self.transform); + if let Paint::Pattern(ref mut pattern) = paint { + pattern.flags.set(PatternFlags::NO_SMOOTHING, !self.image_smoothing_enabled); + } Cow::Owned(paint) } @@ -644,3 +679,10 @@ impl CompositeOperation { } } } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ImageSmoothingQuality { + Low, + Medium, + High, +} diff --git a/content/src/pattern.rs b/content/src/pattern.rs index 75108e97..4a42e913 100644 --- a/content/src/pattern.rs +++ b/content/src/pattern.rs @@ -21,7 +21,7 @@ use image::RgbaImage; #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct Pattern { pub source: PatternSource, - pub repeat: Repeat, + pub flags: PatternFlags, } #[derive(Clone, PartialEq, Eq, Hash, Debug)] @@ -44,16 +44,17 @@ pub struct Image { } bitflags! { - pub struct Repeat: u8 { - const X = 0x01; - const Y = 0x02; + pub struct PatternFlags: u8 { + const REPEAT_X = 0x01; + const REPEAT_Y = 0x02; + const NO_SMOOTHING = 0x04; } } impl Pattern { #[inline] - pub fn new(source: PatternSource, repeat: Repeat) -> Pattern { - Pattern { source, repeat } + pub fn new(source: PatternSource, flags: PatternFlags) -> Pattern { + Pattern { source, flags } } } diff --git a/gl/src/lib.rs b/gl/src/lib.rs index b1ef4178..26681a9c 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -22,7 +22,8 @@ use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::{BlendFactor, BlendOp, BufferData, BufferTarget, BufferUploadMode, ClearOps}; use pathfinder_gpu::{DepthFunc, Device, Primitive, RenderOptions, RenderState, RenderTarget}; use pathfinder_gpu::{ShaderKind, StencilFunc, TextureData, TextureDataRef, TextureFormat}; -use pathfinder_gpu::{UniformData, VertexAttrClass, VertexAttrDescriptor, VertexAttrType}; +use pathfinder_gpu::{TextureSamplingFlags, UniformData, VertexAttrClass}; +use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType}; use pathfinder_simd::default::F32x4; use std::ffi::CString; use std::mem; @@ -48,20 +49,6 @@ impl GLDevice { self.default_framebuffer = framebuffer; } - fn set_texture_parameters(&self, texture: &GLTexture) { - self.bind_texture(texture, 0); - unsafe { - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); ck(); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); ck(); - gl::TexParameteri(gl::TEXTURE_2D, - gl::TEXTURE_WRAP_S, - gl::CLAMP_TO_EDGE as GLint); ck(); - gl::TexParameteri(gl::TEXTURE_2D, - gl::TEXTURE_WRAP_T, - gl::CLAMP_TO_EDGE as GLint); ck(); - } - } - fn set_render_state(&self, render_state: &RenderState) { self.bind_render_target(render_state.target); @@ -236,7 +223,7 @@ impl Device for GLDevice { ptr::null()); ck(); } - self.set_texture_parameters(&texture); + self.set_texture_sampling_mode(&texture, TextureSamplingFlags::empty()); texture } @@ -258,7 +245,7 @@ impl Device for GLDevice { data_ptr) } - self.set_texture_parameters(&texture); + self.set_texture_sampling_mode(&texture, TextureSamplingFlags::empty()); texture } @@ -473,6 +460,40 @@ impl Device for GLDevice { texture.size } + fn set_texture_sampling_mode(&self, texture: &Self::Texture, flags: TextureSamplingFlags) { + self.bind_texture(texture, 0); + unsafe { + gl::TexParameteri(gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + if flags.contains(TextureSamplingFlags::NEAREST_MIN) { + gl::NEAREST as GLint + } else { + gl::LINEAR as GLint + }); ck(); + gl::TexParameteri(gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + if flags.contains(TextureSamplingFlags::NEAREST_MAG) { + gl::NEAREST as GLint + } else { + gl::LINEAR as GLint + }); ck(); + gl::TexParameteri(gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + if flags.contains(TextureSamplingFlags::REPEAT_U) { + gl::REPEAT as GLint + } else { + gl::CLAMP_TO_EDGE as GLint + }); ck(); + gl::TexParameteri(gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + if flags.contains(TextureSamplingFlags::REPEAT_V) { + gl::REPEAT as GLint + } else { + gl::CLAMP_TO_EDGE as GLint + }); ck(); + } + } + fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef) { let data_ptr = data.check_and_extract_data_ptr(rect.size(), texture.format); @@ -506,7 +527,7 @@ impl Device for GLDevice { } } - self.set_texture_parameters(texture); + self.set_texture_sampling_mode(texture, TextureSamplingFlags::empty()); } fn read_pixels(&self, render_target: &RenderTarget, viewport: RectI) diff --git a/gpu/Cargo.toml b/gpu/Cargo.toml index 432e2c22..ed6eeea1 100644 --- a/gpu/Cargo.toml +++ b/gpu/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Patrick Walton "] edition = "2018" [dependencies] +bitflags = "1.0" half = "1.4" [dependencies.image] diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index 25475cab..8f3f2064 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -10,6 +10,9 @@ //! Minimal abstractions over GPU device capabilities. +#[macro_use] +extern crate bitflags; + use crate::resources::ResourceLoader; use half::f16; use image::ImageFormat; @@ -73,6 +76,7 @@ pub trait Device: Sized { fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture; fn texture_format(&self, texture: &Self::Texture) -> TextureFormat; fn texture_size(&self, texture: &Self::Texture) -> Vector2I; + fn set_texture_sampling_mode(&self, texture: &Self::Texture, flags: TextureSamplingFlags); fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef); fn read_pixels(&self, target: &RenderTarget, viewport: RectI) -> Self::TextureDataReceiver; @@ -395,6 +399,15 @@ impl Default for BlendState { } } +bitflags! { + pub struct TextureSamplingFlags: u8 { + const REPEAT_U = 0x01; + const REPEAT_V = 0x02; + const NEAREST_MIN = 0x04; + const NEAREST_MAG = 0x08; + } +} + impl<'a> TextureDataRef<'a> { #[doc(hidden)] pub fn check_and_extract_data_ptr(self, minimum_size: Vector2I, format: TextureFormat) diff --git a/metal/src/lib.rs b/metal/src/lib.rs index 1bf06dfa..3cf60ee8 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -46,8 +46,8 @@ use pathfinder_geometry::vector::Vector2I; use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::{BlendFactor, BlendOp, BufferData, BufferTarget, BufferUploadMode, DepthFunc}; use pathfinder_gpu::{Device, Primitive, RenderState, RenderTarget, ShaderKind, StencilFunc}; -use pathfinder_gpu::{TextureData, TextureDataRef, TextureFormat, UniformData, VertexAttrClass}; -use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType}; +use pathfinder_gpu::{TextureData, TextureDataRef, TextureFormat, TextureSamplingFlags}; +use pathfinder_gpu::{UniformData, VertexAttrClass, VertexAttrDescriptor, VertexAttrType}; use pathfinder_simd::default::{F32x2, F32x4}; use std::cell::{Cell, RefCell}; use std::mem; @@ -66,7 +66,7 @@ pub struct MetalDevice { main_depth_stencil_texture: Texture, command_queue: CommandQueue, command_buffers: RefCell>, - sampler: SamplerState, + samplers: Vec, shared_event: SharedEvent, shared_event_listener: SharedEventListener, next_timer_query_event_value: Cell, @@ -90,14 +90,37 @@ impl MetalDevice { let drawable = layer.next_drawable().unwrap().retain(); let command_queue = device.new_command_queue(); - let sampler_descriptor = SamplerDescriptor::new(); - sampler_descriptor.set_support_argument_buffers(true); - sampler_descriptor.set_normalized_coordinates(true); - sampler_descriptor.set_min_filter(MTLSamplerMinMagFilter::Linear); - sampler_descriptor.set_mag_filter(MTLSamplerMinMagFilter::Linear); - sampler_descriptor.set_address_mode_s(MTLSamplerAddressMode::ClampToEdge); - sampler_descriptor.set_address_mode_t(MTLSamplerAddressMode::ClampToEdge); - let sampler = device.new_sampler(&sampler_descriptor); + let samplers = (0..16).map(|sampling_flags_value| { + let sampling_flags = TextureSamplingFlags::from_bits(sampling_flags_value).unwrap(); + let sampler_descriptor = SamplerDescriptor::new(); + sampler_descriptor.set_support_argument_buffers(true); + sampler_descriptor.set_normalized_coordinates(true); + sampler_descriptor.set_min_filter( + if sampling_flags.contains(TextureSamplingFlags::NEAREST_MIN) { + MTLSamplerMinMagFilter::Nearest + } else { + MTLSamplerMinMagFilter::Linear + }); + sampler_descriptor.set_mag_filter( + if sampling_flags.contains(TextureSamplingFlags::NEAREST_MAG) { + MTLSamplerMinMagFilter::Nearest + } else { + MTLSamplerMinMagFilter::Linear + }); + sampler_descriptor.set_address_mode_s( + if sampling_flags.contains(TextureSamplingFlags::REPEAT_U) { + MTLSamplerAddressMode::Repeat + } else { + MTLSamplerAddressMode::ClampToEdge + }); + sampler_descriptor.set_address_mode_t( + if sampling_flags.contains(TextureSamplingFlags::REPEAT_V) { + MTLSamplerAddressMode::Repeat + } else { + MTLSamplerAddressMode::ClampToEdge + }); + device.new_sampler(&sampler_descriptor) + }).collect(); let main_color_texture = drawable.texture(); let framebuffer_size = Vector2I::new(main_color_texture.width() as i32, @@ -113,7 +136,7 @@ impl MetalDevice { main_depth_stencil_texture, command_queue, command_buffers: RefCell::new(vec![]), - sampler, + samplers, shared_event, shared_event_listener: SharedEventListener::new(), next_timer_query_event_value: Cell::new(1), @@ -145,6 +168,7 @@ enum ShaderUniforms { pub struct MetalTexture { texture: Texture, + sampling_flags: Cell, dirty: Cell, } @@ -229,7 +253,11 @@ impl Device for MetalDevice { descriptor.set_height(size.y() as u64); descriptor.set_storage_mode(MTLStorageMode::Managed); descriptor.set_usage(MTLTextureUsage::Unknown); - MetalTexture { texture: self.device.new_texture(&descriptor), dirty: Cell::new(false) } + MetalTexture { + texture: self.device.new_texture(&descriptor), + sampling_flags: Cell::new(TextureSamplingFlags::empty()), + dirty: Cell::new(false), + } } fn create_texture_from_data(&self, format: TextureFormat, size: Vector2I, data: TextureDataRef) @@ -461,6 +489,10 @@ impl Device for MetalDevice { Vector2I::new(texture.texture.width() as i32, texture.texture.height() as i32) } + fn set_texture_sampling_mode(&self, texture: &MetalTexture, flags: TextureSamplingFlags) { + texture.sampling_flags.set(flags) + } + fn upload_to_texture(&self, texture: &MetalTexture, rect: RectI, data: TextureDataRef) { let texture_size = self.texture_size(texture); assert!(rect.size().x() >= 0); @@ -987,7 +1019,8 @@ impl MetalDevice { argument_encoder.set_texture(&texture.texture, argument_index.main); let mut resource_usage = MTLResourceUsage::Read; if let Some(sampler_index) = argument_index.sampler { - argument_encoder.set_sampler_state(&self.sampler, sampler_index); + let sampler = &self.samplers[texture.sampling_flags.get().bits() as usize]; + argument_encoder.set_sampler_state(sampler, sampler_index); resource_usage |= MTLResourceUsage::Sample; } render_command_encoder.use_resource(&texture.texture, resource_usage); diff --git a/renderer/src/allocator.rs b/renderer/src/allocator.rs index 9764ce36..78bae777 100644 --- a/renderer/src/allocator.rs +++ b/renderer/src/allocator.rs @@ -53,15 +53,22 @@ enum TreeNode { Parent([Box; 4]), } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum AllocationMode { + Atlas, + OwnPage, +} + impl TextureAllocator { #[inline] pub fn new() -> TextureAllocator { TextureAllocator { pages: vec![] } } - pub fn allocate(&mut self, requested_size: Vector2I) -> TextureLocation { - // If too big, the image gets its own page. - if requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 || + pub fn allocate(&mut self, requested_size: Vector2I, mode: AllocationMode) -> TextureLocation { + // If requested, or if the image is too big, use a separate page. + if mode == AllocationMode::OwnPage || + requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 || requested_size.y() > ATLAS_TEXTURE_LENGTH as i32 { return self.allocate_image(requested_size); } @@ -105,7 +112,7 @@ impl TextureAllocator { pub fn page_size(&self, page_index: PaintPageId) -> Vector2I { match self.pages[page_index.0 as usize] { TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32), - TexturePageAllocator::Image { size } | + TexturePageAllocator::Image { size, .. } | TexturePageAllocator::RenderTarget { size, .. } => size, } } diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index e3882b7f..16482de7 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -12,8 +12,9 @@ use crate::concurrent::executor::Executor; use crate::gpu::renderer::{BlendModeProgram, MASK_TILES_ACROSS}; -use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex}; -use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileBatch, TileObjectPrimitive}; +use crate::gpu_data::{AlphaTile, AlphaTileBatch, AlphaTileVertex, FillBatchPrimitive, MaskTile}; +use crate::gpu_data::{MaskTileVertex, PaintPageId, RenderCommand}; +use crate::gpu_data::{SolidTileBatch, TileObjectPrimitive}; use crate::options::{PreparedBuildOptions, RenderCommandListener}; use crate::paint::{PaintInfo, PaintMetadata}; use crate::scene::{DisplayItem, Scene}; @@ -27,6 +28,7 @@ use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmen use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::util; +use pathfinder_gpu::TextureSamplingFlags; use pathfinder_simd::default::{F32x4, I32x4}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Instant; @@ -53,6 +55,7 @@ pub(crate) struct ObjectBuilder { struct BuiltDrawPath { path: BuiltPath, blend_mode: BlendMode, + sampling_flags: TextureSamplingFlags, paint_page: PaintPageId, } @@ -192,6 +195,7 @@ impl<'a> SceneBuilder<'a> { path: tiler.object_builder.built_path, blend_mode: path_object.blend_mode(), paint_page: paint_metadata.tex_page, + sampling_flags: paint_metadata.sampling_flags, } } @@ -265,29 +269,33 @@ impl<'a> SceneBuilder<'a> { // TODO(pcwalton): If we really wanted to, we could use tile maps to avoid batch // breaks in some cases… match culled_tiles.display_list.last() { - Some(&CulledDisplayItem::DrawAlphaTiles { + Some(&CulledDisplayItem::DrawAlphaTiles(AlphaTileBatch { tiles: _, paint_page, - blend_mode - }) if paint_page == built_draw_path.paint_page && + blend_mode, + sampling_flags + })) if paint_page == built_draw_path.paint_page && blend_mode == built_draw_path.blend_mode && + sampling_flags == built_draw_path.sampling_flags && !BlendModeProgram::from_blend_mode( blend_mode).needs_readable_framebuffer() => {} _ => { - culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles { + culled_tiles.display_list + .push(CulledDisplayItem::DrawAlphaTiles(AlphaTileBatch { tiles: vec![], paint_page: built_draw_path.paint_page, blend_mode: built_draw_path.blend_mode, - }) + sampling_flags: built_draw_path.sampling_flags, + })) } } // Fetch the destination alpha tiles buffer. let culled_alpha_tiles = match *culled_tiles.display_list.last_mut().unwrap() { - CulledDisplayItem::DrawAlphaTiles { + CulledDisplayItem::DrawAlphaTiles(AlphaTileBatch { tiles: ref mut culled_alpha_tiles, .. - } => culled_alpha_tiles, + }) => culled_alpha_tiles, _ => unreachable!(), }; @@ -358,12 +366,8 @@ impl<'a> SceneBuilder<'a> { CulledDisplayItem::DrawSolidTiles(batch) => { self.listener.send(RenderCommand::DrawSolidTiles(batch)) } - CulledDisplayItem::DrawAlphaTiles { tiles, paint_page, blend_mode } => { - self.listener.send(RenderCommand::DrawAlphaTiles { - tiles, - paint_page, - blend_mode, - }) + CulledDisplayItem::DrawAlphaTiles(batch) => { + self.listener.send(RenderCommand::DrawAlphaTiles(batch)) } CulledDisplayItem::DrawRenderTarget { render_target, effects } => { self.listener.send(RenderCommand::DrawRenderTarget { render_target, effects }) @@ -444,7 +448,7 @@ struct CulledTiles { enum CulledDisplayItem { DrawSolidTiles(SolidTileBatch), - DrawAlphaTiles { tiles: Vec, paint_page: PaintPageId, blend_mode: BlendMode }, + DrawAlphaTiles(AlphaTileBatch), DrawRenderTarget { render_target: RenderTargetId, effects: Effects }, PushRenderTarget(RenderTargetId), PopRenderTarget, @@ -733,14 +737,13 @@ impl AlphaTileVertex { paint_metadata: &PaintMetadata) -> AlphaTileVertex { let tile_position = tile_origin + tile_offset; - let color_uv = paint_metadata.calculate_tex_coords(tile_position).scale(65535.0).to_i32(); + let color_uv = paint_metadata.calculate_tex_coords(tile_position); let mask_uv = calculate_mask_uv(tile_index, tile_offset); - AlphaTileVertex { tile_x: tile_position.x() as i16, tile_y: tile_position.y() as i16, - color_u: color_uv.x() as u16, - color_v: color_uv.y() as u16, + color_u: color_uv.x(), + color_v: color_uv.y(), mask_u: mask_uv.x() as u16, mask_v: mask_uv.y() as u16, object_index, diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 8c5250c7..49e664dd 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -33,7 +33,7 @@ use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::{BlendFactor, BlendOp, BlendState, BufferData, BufferTarget, BufferUploadMode}; use pathfinder_gpu::{ClearOps, DepthFunc, DepthState, Device, Primitive, RenderOptions}; use pathfinder_gpu::{RenderState, RenderTarget, StencilFunc, StencilState, TextureDataRef}; -use pathfinder_gpu::{TextureFormat, UniformData}; +use pathfinder_gpu::{TextureFormat, TextureSamplingFlags, UniformData}; use pathfinder_simd::default::{F32x2, F32x4}; use std::cmp; use std::collections::VecDeque; @@ -418,13 +418,16 @@ where let count = batch.vertices.len() / 4; self.stats.solid_tile_count += count; self.upload_solid_tiles(&batch.vertices); - self.draw_solid_tiles(count as u32, batch.paint_page); + self.draw_solid_tiles(count as u32, batch.paint_page, batch.sampling_flags); } - RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, paint_page, blend_mode } => { - let count = alpha_tiles.len(); + RenderCommand::DrawAlphaTiles(ref batch) => { + let count = batch.tiles.len(); self.stats.alpha_tile_count += count; - self.upload_alpha_tiles(alpha_tiles); - self.draw_alpha_tiles(count as u32, paint_page, blend_mode); + self.upload_alpha_tiles(&batch.tiles); + self.draw_alpha_tiles(count as u32, + batch.paint_page, + batch.sampling_flags, + batch.blend_mode) } RenderCommand::Finish { .. } => {} } @@ -764,6 +767,7 @@ where fn draw_alpha_tiles(&mut self, tile_count: u32, paint_page: PaintPageId, + sampling_flags: TextureSamplingFlags, blend_mode: BlendMode) { let blend_mode_program = BlendModeProgram::from_blend_mode(blend_mode); if blend_mode_program.needs_readable_framebuffer() { @@ -824,6 +828,8 @@ where _ => self.paint_texture(paint_page), }; + self.device.set_texture_sampling_mode(paint_texture, sampling_flags); + textures.push(paint_texture); uniforms.push((&self.alpha_tile_program.paint_texture_uniform, UniformData::TextureUnit(1))); @@ -983,7 +989,10 @@ where }); } - fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: PaintPageId) { + fn draw_solid_tiles(&mut self, + tile_count: u32, + paint_page: PaintPageId, + sampling_flags: TextureSamplingFlags) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![]; @@ -995,6 +1004,7 @@ where ]; let paint_texture = self.paint_texture(paint_page); + self.device.set_texture_sampling_mode(paint_texture, sampling_flags); textures.push(paint_texture); uniforms.push((&self.solid_tile_program.paint_texture_uniform, UniformData::TextureUnit(0))); diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 2a0c8bbd..dbe7457c 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -16,8 +16,8 @@ use pathfinder_gpu::resources::ResourceLoader; // TODO(pcwalton): Replace with `mem::size_of` calls? const FILL_INSTANCE_SIZE: usize = 8; -const SOLID_TILE_VERTEX_SIZE: usize = 12; -const ALPHA_TILE_VERTEX_SIZE: usize = 16; +const SOLID_TILE_VERTEX_SIZE: usize = 16; +const ALPHA_TILE_VERTEX_SIZE: usize = 20; const MASK_TILE_VERTEX_SIZE: usize = 12; pub const MAX_FILLS_PER_BATCH: usize = 0x4000; @@ -216,8 +216,8 @@ impl AlphaTileVertexArray where D: Device { }); device.configure_vertex_attr(&vertex_array, &color_tex_coord_attr, &VertexAttrDescriptor { size: 2, - class: VertexAttrClass::FloatNorm, - attr_type: VertexAttrType::U16, + class: VertexAttrClass::Float, + attr_type: VertexAttrType::F32, stride: ALPHA_TILE_VERTEX_SIZE, offset: 8, divisor: 0, @@ -228,7 +228,7 @@ impl AlphaTileVertexArray where D: Device { class: VertexAttrClass::FloatNorm, attr_type: VertexAttrType::U8, stride: ALPHA_TILE_VERTEX_SIZE, - offset: 14, + offset: 18, divisor: 0, buffer_index: 0, }); @@ -278,8 +278,8 @@ where &color_tex_coord_attr, &VertexAttrDescriptor { size: 2, - class: VertexAttrClass::FloatNorm, - attr_type: VertexAttrType::U16, + class: VertexAttrClass::Float, + attr_type: VertexAttrType::F32, stride: SOLID_TILE_VERTEX_SIZE, offset: 4, divisor: 0, diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index c54c3853..61770453 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -17,6 +17,7 @@ use pathfinder_content::fill::FillRule; use pathfinder_content::pattern::RenderTargetId; use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::Vector2I; +use pathfinder_gpu::TextureSamplingFlags; use std::fmt::{Debug, Formatter, Result as DebugResult}; use std::time::Duration; @@ -56,7 +57,7 @@ pub enum RenderCommand { PopRenderTarget, // Draws a batch of alpha tiles to the render target on top of the stack. - DrawAlphaTiles { tiles: Vec, paint_page: PaintPageId, blend_mode: BlendMode }, + DrawAlphaTiles(AlphaTileBatch), // Draws a batch of solid tiles to the render target on top of the stack. DrawSolidTiles(SolidTileBatch), @@ -92,10 +93,19 @@ pub enum PaintPageContents { RenderTarget(RenderTargetId), } +#[derive(Clone, Debug)] +pub struct AlphaTileBatch { + pub tiles: Vec, + pub paint_page: PaintPageId, + pub blend_mode: BlendMode, + pub sampling_flags: TextureSamplingFlags, +} + #[derive(Clone, Debug)] pub struct SolidTileBatch { pub vertices: Vec, pub paint_page: PaintPageId, + pub sampling_flags: TextureSamplingFlags, } #[derive(Clone, Copy, Debug)] @@ -128,8 +138,8 @@ pub struct FillBatchPrimitive { pub struct SolidTileVertex { pub tile_x: i16, pub tile_y: i16, - pub color_u: u16, - pub color_v: u16, + pub color_u: f32, + pub color_v: f32, pub object_index: u16, pub pad: u16, } @@ -170,8 +180,8 @@ pub struct AlphaTileVertex { pub tile_y: i16, pub mask_u: u16, pub mask_v: u16, - pub color_u: u16, - pub color_v: u16, + pub color_u: f32, + pub color_v: f32, pub object_index: u16, pub opacity: u8, pub pad: u8, @@ -196,18 +206,20 @@ impl Debug for RenderCommand { RenderCommand::DrawRenderTarget { render_target, .. } => { write!(formatter, "DrawRenderTarget({:?})", render_target) } - RenderCommand::DrawAlphaTiles { ref tiles, paint_page, blend_mode } => { + RenderCommand::DrawAlphaTiles(ref batch) => { write!(formatter, - "DrawAlphaTiles(x{}, {:?}, {:?})", - tiles.len(), - paint_page, - blend_mode) + "DrawAlphaTiles(x{}, {:?}, {:?}, {:?})", + batch.tiles.len(), + batch.paint_page, + batch.blend_mode, + batch.sampling_flags) } RenderCommand::DrawSolidTiles(ref batch) => { write!(formatter, - "DrawSolidTiles(x{}, {:?})", + "DrawSolidTiles(x{}, {:?}, {:?})", batch.vertices.len(), - batch.paint_page) + batch.paint_page, + batch.sampling_flags) } RenderCommand::Finish { .. } => write!(formatter, "Finish"), } diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 084f1849..a66b70b3 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -8,18 +8,19 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::allocator::{TextureAllocator, TextureLocation}; +use crate::allocator::{AllocationMode, TextureAllocator, TextureLocation}; use crate::gpu_data::{PaintData, PaintPageContents, PaintPageData, PaintPageId}; use crate::scene::RenderTarget; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use hashbrown::HashMap; use pathfinder_color::ColorU; use pathfinder_content::gradient::{Gradient, GradientGeometry}; -use pathfinder_content::pattern::{Image, Pattern, PatternSource, RenderTargetId}; +use pathfinder_content::pattern::{Image, Pattern, PatternFlags, PatternSource, RenderTargetId}; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F}; use pathfinder_geometry::util; use pathfinder_geometry::vector::{Vector2F, Vector2I}; +use pathfinder_gpu::TextureSamplingFlags; use pathfinder_simd::default::F32x4; use std::fmt::{self, Debug, Formatter}; @@ -168,6 +169,8 @@ pub struct PaintMetadata { pub tex_rect: RectI, /// The transform to apply to screen coordinates to translate them into UVs. pub tex_transform: Transform2F, + /// The sampling mode for the texture. + pub sampling_flags: TextureSamplingFlags, /// True if this paint is fully opaque. pub is_opaque: bool, } @@ -206,24 +209,50 @@ impl Palette { // Assign paint locations. let mut solid_color_tile_builder = SolidColorTileBuilder::new(); for paint in &self.paints { - let tex_location = match paint { - Paint::Color(_) => solid_color_tile_builder.allocate(&mut allocator), + let (tex_location, mut sampling_flags); + match paint { + Paint::Color(_) => { + tex_location = solid_color_tile_builder.allocate(&mut allocator); + sampling_flags = TextureSamplingFlags::empty(); + } Paint::Gradient(_) => { // TODO(pcwalton): Optimize this: // 1. Use repeating/clamp on the sides. // 2. Choose an optimal size for the gradient that minimizes memory usage while // retaining quality. - allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32)) + tex_location = allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32), + AllocationMode::Atlas); + sampling_flags = TextureSamplingFlags::empty(); } Paint::Pattern(ref pattern) => { match pattern.source { PatternSource::RenderTarget(render_target_id) => { - render_target_locations[render_target_id.0 as usize] + tex_location = render_target_locations[render_target_id.0 as usize]; } PatternSource::Image(ref image) => { - allocator.allocate(image.size()) + // TODO(pcwalton): We should be able to use tile cleverness to repeat + // inside the atlas in some cases. + let allocation_mode = if pattern.flags == PatternFlags::empty() { + AllocationMode::Atlas + } else { + AllocationMode::OwnPage + }; + + tex_location = allocator.allocate(image.size(), allocation_mode); } } + + sampling_flags = TextureSamplingFlags::empty(); + if pattern.flags.contains(PatternFlags::REPEAT_X) { + sampling_flags.insert(TextureSamplingFlags::REPEAT_U); + } + if pattern.flags.contains(PatternFlags::REPEAT_Y) { + sampling_flags.insert(TextureSamplingFlags::REPEAT_V); + } + if pattern.flags.contains(PatternFlags::NO_SMOOTHING) { + sampling_flags.insert(TextureSamplingFlags::NEAREST_MIN | + TextureSamplingFlags::NEAREST_MAG); + } } }; @@ -231,6 +260,7 @@ impl Palette { tex_page: tex_location.page, tex_rect: tex_location.rect, tex_transform: Transform2F::default(), + sampling_flags, is_opaque: paint.is_opaque(), }); } @@ -301,11 +331,11 @@ impl Palette { } Paint::Gradient(ref gradient) => { self.render_gradient(gradient, - metadata.tex_rect, - &metadata.tex_transform, - texels, - page_size, - page_scale); + metadata.tex_rect, + &metadata.tex_transform, + texels, + page_size, + page_scale); } Paint::Pattern(ref pattern) => { match pattern.source { @@ -440,7 +470,8 @@ impl SolidColorTileBuilder { if self.0.is_none() { // TODO(pcwalton): Handle allocation failure gracefully! self.0 = Some(SolidColorTileBuilderData { - tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32)), + tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32), + AllocationMode::Atlas), next_index: 0, }); } diff --git a/renderer/src/z_buffer.rs b/renderer/src/z_buffer.rs index 66168dd9..3eeef34c 100644 --- a/renderer/src/z_buffer.rs +++ b/renderer/src/z_buffer.rs @@ -69,11 +69,13 @@ impl ZBuffer { // Create a batch if necessary. match solid_tiles.batches.last() { - Some(ref batch) if batch.paint_page == paint_metadata.tex_page => {} + Some(ref batch) if batch.paint_page == paint_metadata.tex_page && + batch.sampling_flags == paint_metadata.sampling_flags => {} _ => { // Batch break. solid_tiles.batches.push(SolidTileBatch { paint_page: paint_metadata.tex_page, + sampling_flags: paint_metadata.sampling_flags, vertices: vec![], }); } @@ -101,13 +103,13 @@ impl ZBuffer { impl SolidTileVertex { fn new(tile_position: Vector2I, object_index: u16, paint_metadata: &PaintMetadata) -> SolidTileVertex { - let color_uv = paint_metadata.calculate_tex_coords(tile_position).scale(65535.0).to_i32(); + let color_uv = paint_metadata.calculate_tex_coords(tile_position); SolidTileVertex { tile_x: tile_position.x() as i16, tile_y: tile_position.y() as i16, object_index: object_index, - color_u: color_uv.x() as u16, - color_v: color_uv.y() as u16, + color_u: color_uv.x(), + color_v: color_uv.y(), pad: 0, } }