compositor: Allow canvas to upload rendered contents asynchronously (#37776)

Adds epoch to each WR image op command that is sent to compositor. The
renderer now has a `FrameDelayer` data structure that is responsible for
tracking when a frame is ready to be displayed. When asking canvases to
update their rendering, they are given an optional `Epoch` which denotes
the `Document`'s canvas epoch. When all image updates for that `Epoch`
are seen in the renderer, the frame can be displayed.

Testing: Existing WPT tests
Fixes: #35733

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Sam 2025-08-29 12:04:41 +02:00 committed by GitHub
parent 4700149fcb
commit 8beef6c21f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 452 additions and 100 deletions

View file

@ -6,6 +6,7 @@ use std::borrow::Cow;
use std::cell::RefCell;
use arrayvec::ArrayVec;
use base::Epoch;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self};
use pixels::Snapshot;
@ -183,19 +184,29 @@ impl GPUCanvasContext {
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture>
fn expire_current_texture(&self) {
if let Some(current_texture) = self.current_texture.take() {
// Make copy of texture content
self.send_swap_chain_present(current_texture.id());
// Step 1
current_texture.Destroy()
}
fn expire_current_texture(&self, canvas_epoch: Option<Epoch>) -> bool {
// Step 1: If context.[[currentTexture]] is not null:
let Some(current_texture) = self.current_texture.take() else {
return false;
};
// Make copy of texture content
let did_swap = self.send_swap_chain_present(current_texture.id(), canvas_epoch);
// Step 1.1: Call context.currentTexture.destroy() (without destroying
// context.drawingBuffer) to terminate write access to the image.
current_texture.Destroy();
// Step 1.2: Set context.[[currentTexture]] to null.
// This is handled by the call to `.take()` above.
did_swap
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-replace-the-drawing-buffer>
fn replace_drawing_buffer(&self) {
// Step 1
self.expire_current_texture();
self.expire_current_texture(None);
// Step 2
let configuration = self.configuration.borrow();
// Step 3
@ -234,19 +245,28 @@ impl GPUCanvasContext {
}
}
fn send_swap_chain_present(&self, texture_id: WebGPUTexture) {
fn send_swap_chain_present(
&self,
texture_id: WebGPUTexture,
canvas_epoch: Option<Epoch>,
) -> bool {
self.drawing_buffer.borrow_mut().cleared = false;
let encoder_id = self.global().wgpu_id_hub().create_command_encoder_id();
if let Err(e) = self.channel.0.send(WebGPURequest::SwapChainPresent {
let send_result = self.channel.0.send(WebGPURequest::SwapChainPresent {
context_id: self.context_id,
texture_id: texture_id.0,
encoder_id,
}) {
canvas_epoch,
});
if let Err(error) = &send_result {
warn!(
"Failed to send UpdateWebrenderData({:?}) ({})",
self.context_id, e
"Failed to send UpdateWebrenderData({:?}) ({error})",
self.context_id,
);
}
send_result.is_ok()
}
}
@ -258,9 +278,19 @@ impl CanvasContext for GPUCanvasContext {
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas>
fn update_rendering(&self) {
// Step 1
self.expire_current_texture();
fn update_rendering(&self, canvas_epoch: Option<Epoch>) -> bool {
if !self.onscreen() {
return false;
}
// Step 1: Expire the current texture of context.
self.expire_current_texture(canvas_epoch)
// Step 2: Set context.[[lastPresentedImage]] to context.[[drawingBuffer]].
// TODO: Implement this.
}
fn image_key(&self) -> Option<ImageKey> {
Some(self.webrender_image)
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-update-the-canvas-size>