Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion vello_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ pub mod reexports {
pub use crate::filter_effects;
pub use crate::geometry;
pub use crate::mask::Mask;
pub use crate::paint::{Image, ImageId, ImageSource, PaintType, Tint, TintMode};
pub use crate::paint::{
Image, ImageId, ImageSource, PaintType, TextureRegion, Tint, TintMode,
};
pub use crate::pixmap::{PixelMetadata, Pixels, Pixmap};
pub use crate::render_state::RenderState;
pub use crate::transforms::Transforms;
Expand Down
39 changes: 35 additions & 4 deletions vello_common/src/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,37 @@ impl ImageId {
}
}

/// Region of an externally owned texture used by an image paint.
///
/// Whenever sampling the whole texture instead of a subregion from an atlas, you should **always**
/// prefer using [`TextureRegion::Full`] over [`TextureRegion::Rect`], as it will
/// have better performance.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextureRegion {
/// The entire texture view.
///
/// **It is important that the provided width and height match the actual dimensions of the
/// texture, otherwise, rendering might be corrupted!**
Full {
/// Width of the bound texture view.
width: u16,
/// Height of the bound texture view.
height: u16,
},
Comment on lines +83 to +90

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, we could derive that information ourselves using textureDimensions in the shader. However, I faintly remember this being a performance footgun. Hence why I think it's better to require that information to be passed along.

/// A subrectangle in texel coordinates.
Rect(RectU16),
}

impl TextureRegion {
/// Return the source region in texel coordinates.
pub const fn rect(self) -> RectU16 {
match self {
Self::Full { width, height } => RectU16::new(0, 0, width, height),
Self::Rect(rect) => rect,
}
}
}

/// Bitmap source used by `Image`.
#[derive(Debug, Clone)]
pub enum ImageSource {
Expand All @@ -88,8 +119,8 @@ pub enum ImageSource {
ExternalTexture {
/// Opaque external texture handle.
id: TextureId,
/// Source region to sample from in texel coordinates.
source_region: RectU16,
/// Source region to sample from.
source_region: TextureRegion,
/// Whether the source region may contain non-opaque pixels.
may_have_transparency: bool,
},
Expand Down Expand Up @@ -123,11 +154,11 @@ impl ImageSource {
/// Panics if `source_region` is empty.
pub fn external_texture(
texture_id: TextureId,
source_region: RectU16,
source_region: TextureRegion,
may_have_transparency: bool,
) -> Self {
assert!(
!source_region.is_empty(),
!source_region.rect().is_empty(),
"external texture source regions must not be empty"
);

Expand Down
1 change: 1 addition & 0 deletions vello_gpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ web-sys = { workspace = true, features = [
"WebGlTexture",
"WebGlFramebuffer",
"WebGlRenderbuffer",
"WebGlSampler",
"WebGlVertexArrayObject",
], optional = true }

Expand Down
136 changes: 111 additions & 25 deletions vello_gpu/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,20 +577,27 @@ impl StripAlphaFillSegmentExt for StripAlphaFillSegment {
mod tests {
use super::{Draw, DrawBuffers, DrawBuilder, DrawState, ExternalTextureRun, OpaqueDraw};
use crate::GpuStrip;
use crate::paint::{EXTERNAL_TEXTURE_SLOT_SHIFT, PaintResolver, TextureSourceId};
use crate::paint::{
EXTERNAL_TEXTURE_SLOT_SHIFT, ExternalSampler, PaintResolver, SamplerAddress, SamplerFilter,
TextureSourceId,
};
use crate::render::common::{GpuEncodedImage, GpuEncodedPaint, encode_native_image_paint};
use crate::scene::{RecordedDraw, RecordedRect};
use crate::target::{
DrawTarget, LayerTextureId, LayerTextureRegion, RootTarget, TextureParity, TextureRegion,
};
use crate::util::VecExt;
use alloc::vec::Vec;
use bytemuck::Zeroable;
use vello_common::TextureId;
use vello_common::encode::{EncodedImage, EncodedPaint};
use vello_common::geometry::RectU16;
use vello_common::image_cache::ImageCache;
use vello_common::kurbo::{Affine, Rect, Vec2};
use vello_common::multi_atlas::{AtlasConfig, AtlasId};
use vello_common::paint::{ImageId, ImageSource, IndexedPaint, Paint, PremulColor};
use vello_common::paint::{
ImageId, ImageSource, IndexedPaint, Paint, PremulColor, TextureRegion as ImageTextureRegion,
};
use vello_common::peniko::color::palette::css::BLUE;
use vello_common::peniko::{Extend, ImageQuality, ImageSampler};
use vello_common::strip_generator::StripStorage;
Expand Down Expand Up @@ -649,7 +656,18 @@ mod tests {
}

fn no_paints() -> PaintResolver<'static> {
PaintResolver::new(&[], &[])
PaintResolver::new(&[], &[], &[])
}

fn gpu_paints(encoded: &[EncodedPaint]) -> Vec<GpuEncodedPaint> {
encoded
.iter()
.map(|paint| match paint {
EncodedPaint::Image(image) => encode_native_image_paint(image)
.unwrap_or_else(|| GpuEncodedPaint::Image(GpuEncodedImage::zeroed())),
_ => unreachable!("draw tests only use image paints"),
})
.collect()
}

fn gpu_strip(x: u16) -> GpuStrip {
Expand Down Expand Up @@ -681,7 +699,18 @@ mod tests {
}

fn external_sources<const N: usize>() -> [TextureSourceId; N] {
texture_ids().map(TextureSourceId::External)
texture_ids().map(external_source)
}

fn external_source(id: TextureId) -> TextureSourceId {
TextureSourceId::External(
id,
ExternalSampler {
filter: SamplerFilter::Nearest,
address_x: SamplerAddress::Pad,
address_y: SamplerAddress::Pad,
},
)
}

fn run_states(
Expand All @@ -696,12 +725,28 @@ mod tests {
}

fn external(texture_id: TextureId) -> EncodedPaint {
external_with_sampler(texture_id, ImageQuality::Low, Extend::Pad, Extend::Pad)
}

fn external_with_sampler(
texture_id: TextureId,
quality: ImageQuality,
x_extend: Extend,
y_extend: Extend,
) -> EncodedPaint {
EncodedPaint::Image(EncodedImage {
source: ImageSource::external_texture(texture_id, RectU16::new(0, 0, 8, 8), true),
source: ImageSource::external_texture(
texture_id,
ImageTextureRegion::Full {
width: 8,
height: 8,
},
true,
),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
x_extend,
y_extend,
quality,
alpha: 1.0,
},
may_have_transparency: true,
Expand Down Expand Up @@ -744,7 +789,8 @@ mod tests {
let [texture_a, texture_b] = texture_ids();
let encoded = [external(texture_a), external(texture_b)];
let offsets = [0, 0];
let resolver = PaintResolver::new(&encoded, &offsets);
let gpu_paints = gpu_paints(&encoded);
let resolver = PaintResolver::new(&encoded, &offsets, &gpu_paints);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 32, 8));
let mut draw = Draw::default();
let mut other = Draw::default();
Expand All @@ -760,9 +806,9 @@ mod tests {
assert_eq!(
run_states(&draw.external_texture_runs),
[
([Some(TextureSourceId::External(texture_a))], 0),
([Some(TextureSourceId::External(texture_b))], 2),
([Some(TextureSourceId::External(texture_a))], 4),
([Some(external_source(texture_a))], 0),
([Some(external_source(texture_b))], 2),
([Some(external_source(texture_a))], 4),
]
);
}
Expand All @@ -772,7 +818,8 @@ mod tests {
let textures: [TextureId; 5] = texture_ids();
let encoded = textures.map(external);
let offsets = [0; 5];
let resolver = PaintResolver::new(&encoded, &offsets);
let gpu_paints = gpu_paints(&encoded);
let resolver = PaintResolver::new(&encoded, &offsets, &gpu_paints);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 32, 8));
let mut draw = Draw::default();

Expand All @@ -788,12 +835,12 @@ mod tests {
assert_eq!(
run_states(&draw.external_texture_runs),
[
([Some(TextureSourceId::External(textures[0]))], 0),
([Some(TextureSourceId::External(textures[1]))], 1),
([Some(TextureSourceId::External(textures[2]))], 2),
([Some(TextureSourceId::External(textures[3]))], 3),
([Some(TextureSourceId::External(textures[4]))], 4),
([Some(TextureSourceId::External(textures[0]))], 5),
([Some(external_source(textures[0]))], 0),
([Some(external_source(textures[1]))], 1),
([Some(external_source(textures[2]))], 2),
([Some(external_source(textures[3]))], 3),
([Some(external_source(textures[4]))], 4),
([Some(external_source(textures[0]))], 5),
]
);
assert_eq!(
Expand All @@ -810,7 +857,8 @@ mod tests {
fn texture_runs_coalesce_distinct_paints_for_same_texture() {
let [texture] = texture_ids();
let encoded = [external(texture), external(texture)];
let resolver = PaintResolver::new(&encoded, &[0, 3]);
let gpu_paints = gpu_paints(&encoded);
let resolver = PaintResolver::new(&encoded, &[0, 3], &gpu_paints);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();

Expand All @@ -819,7 +867,42 @@ mod tests {

assert_eq!(
run_states(&draw.external_texture_runs),
[([Some(TextureSourceId::External(texture))], 0)]
[([Some(external_source(texture))], 0)]
);
}

#[test]
fn texture_runs_separate_sampler_modes_for_same_texture() {
let [texture] = texture_ids();
let encoded = [
external(texture),
external_with_sampler(texture, ImageQuality::Medium, Extend::Repeat, Extend::Pad),
];
let gpu_paints = gpu_paints(&encoded);
let resolver = PaintResolver::new(&encoded, &[0, 3], &gpu_paints);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();

case.rect(&mut draw, rect(0.0), indexed(0), resolver);
case.rect(&mut draw, rect(4.0), indexed(1), resolver);

assert_eq!(draw.external_texture_runs.len(), 2);
assert_eq!(
run_states(&draw.external_texture_runs),
[
([Some(external_source(texture))], 0),
(
[Some(TextureSourceId::External(
texture,
ExternalSampler {
filter: SamplerFilter::Linear,
address_x: SamplerAddress::Repeat,
address_y: SamplerAddress::Pad,
},
))],
1
),
]
);
}

Expand All @@ -829,7 +912,9 @@ mod tests {
let mut image_cache = ImageCache::new_with_config(AtlasConfig::default());
let image_id = image_cache.allocate(1, 1, 0).unwrap();
let encoded = [external(texture), atlas_image(image_id)];
let resolver = PaintResolver::new(&encoded, &[0, 0]).with_image_cache(&image_cache);
let gpu_paints = gpu_paints(&encoded);
let resolver =
PaintResolver::new(&encoded, &[0, 0], &gpu_paints).with_image_cache(&image_cache);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 16, 8));
let mut draw = Draw::default();

Expand All @@ -841,9 +926,9 @@ mod tests {
assert_eq!(
run_states(&draw.external_texture_runs),
[
([Some(TextureSourceId::External(texture))], 0),
([Some(external_source(texture))], 0),
([Some(TextureSourceId::Atlas(AtlasId::new(0)))], 1),
([Some(TextureSourceId::External(texture))], 2),
([Some(external_source(texture))], 2),
]
);
}
Expand Down Expand Up @@ -954,7 +1039,8 @@ mod tests {
let [texture_id] = texture_ids();
let encoded = [external(texture_id)];
let offsets = [0];
let resolver = PaintResolver::new(&encoded, &offsets);
let gpu_paints = gpu_paints(&encoded);
let resolver = PaintResolver::new(&encoded, &offsets, &gpu_paints);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();

Expand Down
Loading
Loading