use std::borrow::Cow;
use std::cell::RefCell;
use arrayvec::ArrayVec;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use ipc_channel::ipc;
use script_layout_interface::HTMLCanvasDataSource;
use webgpu::swapchain::WebGPUContextId;
use webgpu::wgc::id;
use webgpu::{
ContextConfiguration, WebGPU, WebGPURequest, WebGPUTexture, PRESENTATION_BUFFER_COUNT,
};
use webrender_api::units::DeviceIntSize;
use webrender_api::ImageKey;
use super::gpuconvert::convert_texture_descriptor;
use super::gputexture::GPUTexture;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUCanvasContextMethods, GPUDeviceMethods,
GPUExtent3D, GPUExtent3DDict, GPUObjectDescriptorBase, GPUTextureDescriptor,
GPUTextureDimension, GPUTextureFormat, GPUTextureUsageConstants,
};
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::USVString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers};
use crate::dom::node::{document_from_node, Node, NodeDamage};
impl HTMLCanvasElementOrOffscreenCanvas {
fn size(&self) -> Size2D<u64> {
match self {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
canvas.get_size().cast()
},
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
}
}
}
fn supported_context_format(format: GPUTextureFormat) -> bool {
matches!(
format,
GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm
)
}
#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)]
struct DrawingBuffer {
#[no_trace]
size: DeviceIntSize,
cleared: bool,
#[ignore_malloc_size_of = "Defined in wgpu"]
#[no_trace]
config: Option<ContextConfiguration>,
}
#[dom_struct]
pub struct GPUCanvasContext {
reflector_: Reflector,
#[ignore_malloc_size_of = "channels are hard"]
#[no_trace]
channel: WebGPU,
canvas: HTMLCanvasElementOrOffscreenCanvas,
#[ignore_malloc_size_of = "Defined in webrender"]
#[no_trace]
webrender_image: ImageKey,
#[no_trace]
context_id: WebGPUContextId,
#[ignore_malloc_size_of = "manual writing is hard"]
configuration: RefCell<Option<GPUCanvasConfiguration>>,
texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
drawing_buffer: RefCell<DrawingBuffer>,
current_texture: MutNullableDom<GPUTexture>,
}
impl GPUCanvasContext {
fn new_inherited(
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
channel: WebGPU,
) -> Self {
let (sender, receiver) = ipc::channel().unwrap();
let size = canvas.size().cast().cast_unit();
let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
for _ in 0..PRESENTATION_BUFFER_COUNT {
buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
}
if let Err(e) = channel.0.send(WebGPURequest::CreateContext {
buffer_ids,
size,
sender,
}) {
warn!("Failed to send CreateContext ({:?})", e);
}
let (external_id, webrender_image) = receiver.recv().unwrap();
Self {
reflector_: Reflector::new(),
channel,
canvas,
webrender_image,
context_id: WebGPUContextId(external_id.0),
drawing_buffer: RefCell::new(DrawingBuffer {
size,
cleared: true,
..Default::default()
}),
configuration: RefCell::new(None),
texture_descriptor: RefCell::new(None),
current_texture: MutNullableDom::default(),
}
}
pub fn new(global: &GlobalScope, canvas: &HTMLCanvasElement, channel: WebGPU) -> DomRoot<Self> {
reflect_dom_object(
Box::new(GPUCanvasContext::new_inherited(
global,
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(canvas)),
channel,
)),
global,
)
}
}
impl GPUCanvasContext {
fn texture_descriptor_for_canvas(
&self,
configuration: &GPUCanvasConfiguration,
) -> GPUTextureDescriptor {
let size = self.size();
GPUTextureDescriptor {
format: configuration.format,
usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
width: size.width as u32,
height: size.height as u32,
depthOrArrayLayers: 1,
}),
viewFormats: configuration.viewFormats.clone(),
mipLevelCount: 1,
sampleCount: 1,
parent: GPUObjectDescriptorBase {
label: USVString::default(),
},
dimension: GPUTextureDimension::_2d,
}
}
fn expire_current_texture(&self) {
if let Some(current_texture) = self.current_texture.take() {
self.send_swap_chain_present(current_texture.id());
current_texture.Destroy()
}
}
fn replace_drawing_buffer(&self) {
self.expire_current_texture();
let configuration = self.configuration.borrow();
let mut drawing_buffer = self.drawing_buffer.borrow_mut();
drawing_buffer.size = self.size().cast().cast_unit();
drawing_buffer.cleared = true;
if let Some(configuration) = configuration.as_ref() {
drawing_buffer.config = Some(ContextConfiguration {
device_id: configuration.device.id().0,
queue_id: configuration.device.queue_id().0,
format: configuration.format.into(),
is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
});
} else {
drawing_buffer.config.take();
};
self.channel
.0
.send(WebGPURequest::UpdateContext {
context_id: self.context_id,
size: drawing_buffer.size,
configuration: drawing_buffer.config,
})
.expect("Failed to update webgpu context");
}
}
impl GPUCanvasContext {
fn layout_handle(&self) -> HTMLCanvasDataSource {
if self.drawing_buffer.borrow().cleared {
HTMLCanvasDataSource::Empty
} else {
HTMLCanvasDataSource::WebGPU(self.webrender_image)
}
}
fn send_swap_chain_present(&self, texture_id: WebGPUTexture) {
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 {
context_id: self.context_id,
texture_id: texture_id.0,
encoder_id,
}) {
warn!(
"Failed to send UpdateWebrenderData({:?}) ({})",
self.context_id, e
);
}
}
fn size(&self) -> Size2D<u64> {
self.canvas.size()
}
}
impl GPUCanvasContext {
pub(crate) fn context_id(&self) -> WebGPUContextId {
self.context_id
}
pub(crate) fn mark_as_dirty(&self) {
if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = &self.canvas {
canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
let document = document_from_node(&**canvas);
document.add_dirty_webgpu_canvas(self);
}
}
pub(crate) fn update_rendering_of_webgpu_canvas(&self) {
self.expire_current_texture();
}
pub(crate) fn resize(&self) {
self.replace_drawing_buffer();
let configuration = self.configuration.borrow();
if let Some(configuration) = configuration.as_ref() {
self.texture_descriptor
.replace(Some(self.texture_descriptor_for_canvas(configuration)));
}
}
}
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> {
fn canvas_data_source(self) -> HTMLCanvasDataSource {
(*self.unsafe_get()).layout_handle()
}
}
impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
fn Canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas {
self.canvas.clone()
}
fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
let device = &configuration.device;
let descriptor = self.texture_descriptor_for_canvas(configuration);
let (mut desc, _) = convert_texture_descriptor(&descriptor, device)?;
desc.label = Some(Cow::Borrowed(
"dummy texture for texture descriptor validation",
));
if !supported_context_format(configuration.format) {
return Err(Error::Type(format!(
"Unsupported context format: {:?}",
configuration.format
)));
}
self.configuration.replace(Some(configuration.clone()));
self.texture_descriptor.replace(Some(descriptor));
self.replace_drawing_buffer();
let texture_id = self.global().wgpu_id_hub().create_texture_id();
self.channel
.0
.send(WebGPURequest::ValidateTextureDescriptor {
device_id: device.id().0,
texture_id,
descriptor: desc,
})
.expect("Failed to create WebGPU SwapChain");
Ok(())
}
fn Unconfigure(&self) {
self.configuration.take();
self.current_texture.take();
self.replace_drawing_buffer();
}
fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
let configuration = self.configuration.borrow();
let Some(configuration) = configuration.as_ref() else {
return Err(Error::InvalidState);
};
let texture_descriptor = self.texture_descriptor.borrow();
let texture_descriptor = texture_descriptor.as_ref().unwrap();
let current_texture = if let Some(current_texture) = self.current_texture.get() {
current_texture
} else {
self.replace_drawing_buffer();
let current_texture = configuration.device.CreateTexture(texture_descriptor)?;
self.current_texture.set(Some(¤t_texture));
current_texture
};
self.mark_as_dirty();
Ok(current_texture)
}
}
impl Drop for GPUCanvasContext {
fn drop(&mut self) {
if let Err(e) = self.channel.0.send(WebGPURequest::DestroyContext {
context_id: self.context_id,
}) {
warn!(
"Failed to send DestroySwapChain-ImageKey({:?}) ({})",
self.webrender_image, e
);
}
}
}