Skip to content

vello_hybrid: Add inline Pixmap rendering#1459

Open
grebmeg wants to merge 1 commit intogemberg/glyph-cache-hybrid-supportfrom
gemberg/glyph-cache-pixmap-hybrid
Open

vello_hybrid: Add inline Pixmap rendering#1459
grebmeg wants to merge 1 commit intogemberg/glyph-cache-hybrid-supportfrom
gemberg/glyph-cache-pixmap-hybrid

Conversation

@grebmeg
Copy link
Collaborator

@grebmeg grebmeg commented Feb 19, 2026

This PR adds support for rendering Arc<Pixmap> images directly via ImageSource::Pixmap in vello_hybrid, without requiring callers to manually pre-upload them. The renderer automatically deduplicates pixmaps by pointer identity across frames and lazily uploads them to the GPU atlas on demand, with periodic eviction of stale entries.

@grebmeg grebmeg force-pushed the gemberg/glyph-cache-pixmap-hybrid branch from f494fe8 to f4f8830 Compare February 19, 2026 23:52
Comment on lines +511 to +514
Err(e) => {
log::warn!("Failed to allocate pixmap in atlas: {e:?}");
None
}
Copy link
Contributor

@taj-p taj-p Feb 20, 2026

Choose a reason for hiding this comment

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

Should this be a RenderError?

ImageSource::Pixmap(pixmap) => match self.pixmap_register.get(pixmap) {
Some(id) => Some(id),
None => {
match self.image_cache.allocate(pixmap.width(), pixmap.height()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How are these sorts of pixmaps de-allocated from the texture?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhh, I see via maintain().

Copy link
Contributor

@taj-p taj-p left a comment

Choose a reason for hiding this comment

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

Code looks good! Just asking some questions for my understanding

Comment on lines +570 to +571
/// to the GPU atlas on demand. The same `Arc<Pixmap>` is used for two fills in
/// different locations, verifying that deduplication works (only one atlas upload).
Copy link
Contributor

Choose a reason for hiding this comment

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

"verifying that deduplication works" - is that tested here?

Comment on lines +461 to +480
ImageSource::Pixmap(pixmap) => match self.pixmap_register.get(pixmap) {
Some(id) => Some(id),
None => {
match self.image_cache.allocate(pixmap.width(), pixmap.height()) {
Ok(id) => {
self.pixmap_register.insert(pixmap, id);
self.pending_uploads.push(PendingImageUpload {
image_id: id,
pixmap: pixmap.clone(),
});
Some(id)
}
Err(e) => {
log::warn!("Failed to allocate pixmap in atlas: {e:?}");
None
}
}
}
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a way to do this without doing 2 lookups on the hash map?

/// Upload any pixmaps that were allocated during [`Self::prepare_gpu_encoded_paints`]
/// but haven't been written to the GPU atlas yet.
fn upload_pending_images(&mut self) {
let uploads: Vec<_> = self.pending_uploads.drain(..).collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to collect here or can we drain without collecting?
I.e, does this work?

        let mut uploads = core::mem::take(&mut self.pending_uploads);
        for upload in uploads.drain(..) {
            self.write_to_atlas(
                device,
                queue,
                encoder,
                upload.image_id,
                &upload.pixmap,
                None,
            );
        }
        self.pending_uploads = uploads;

Comment on lines +557 to +558
self.programs
.maybe_resize_atlas_texture_array(&self.gl, self.image_cache.atlas_count() as u32);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
self.programs
.maybe_resize_atlas_texture_array(&self.gl, self.image_cache.atlas_count() as u32);

I think this is called in write_to_atlas

Comment on lines +524 to +530
Programs::maybe_resize_atlas_texture_array(
device,
encoder,
&mut self.programs.resources,
&self.programs.atlas_bind_group_layout,
self.image_cache.atlas_count() as u32,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Programs::maybe_resize_atlas_texture_array(
device,
encoder,
&mut self.programs.resources,
&self.programs.atlas_bind_group_layout,
self.image_cache.atlas_count() as u32,
);

I think this is called in write_to_atlas

Comment on lines +522 to +539
let uploads: Vec<_> = self.pending_uploads.drain(..).collect();
for upload in uploads {
Programs::maybe_resize_atlas_texture_array(
device,
encoder,
&mut self.programs.resources,
&self.programs.atlas_bind_group_layout,
self.image_cache.atlas_count() as u32,
);
self.write_to_atlas(
device,
queue,
encoder,
upload.image_id,
&upload.pixmap,
None,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let uploads: Vec<_> = self.pending_uploads.drain(..).collect();
for upload in uploads {
Programs::maybe_resize_atlas_texture_array(
device,
encoder,
&mut self.programs.resources,
&self.programs.atlas_bind_group_layout,
self.image_cache.atlas_count() as u32,
);
self.write_to_atlas(
device,
queue,
encoder,
upload.image_id,
&upload.pixmap,
None,
);
}
let mut uploads = core::mem::take(&mut self.pending_uploads);
for upload in uploads.drain(..) {
self.write_to_atlas(
device,
queue,
encoder,
upload.image_id,
&upload.pixmap,
None,
);
}
self.pending_uploads = uploads;

Copy link
Contributor

Choose a reason for hiding this comment

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

For what types of use cases would we use this over asking the caller to manage the lifetime of pixmaps?

I'm a little wary of using this API because it means the caller needs to keep the pixmap in memory - is that right? The caller can't deallocate the pixmap if they pass it as an ImageSource::Pixmap because the next time they use it, they need to pass the Arc<Pixmap>? Or is there a certain use case for glyph caching that this is used?

@LaurenzV
Copy link
Collaborator

Echoing Taj's comment, I would like to understand the motivation further before looking at the code more deeply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments