/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/webgpu/canvas.rs
675 строк
19 KB
Leo Kettmeir
chore: update webgpu (#34319)
08 июн 2026, 23:29
Не верифицирован
08 июн 2026, 23:29
2b313e2
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::cell::RefCell; use std::rc::Rc; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_core::cppgc::Ref; use deno_core::op2; use deno_core::v8; use deno_core::webidl::WebIdlConverter; use deno_error::JsErrorBox; use deno_image::image::DynamicImage; use deno_image::image::GenericImageView; use wgpu_core::resource::TextureDescriptor; use wgpu_types::CompositeAlphaMode; use wgpu_types::Extent3d; use wgpu_types::SurfaceConfiguration; use wgpu_types::SurfaceStatus; use crate::Instance; use crate::device::GPUDevice; use crate::error::GPUError; use crate::texture::GPUTexture; use crate::texture::GPUTextureFormat; use crate::webidl::GPUTextureUsageFlags; pub enum ContextData { Canvas(Rc<RefCell<DynamicImage>>), Surface(Rc<RefCell<SurfaceData>>), } pub struct SurfaceData { pub width: u32, pub height: u32, pub id: wgpu_core::id::SurfaceId, pub instance: Instance, } impl Drop for SurfaceData { fn drop(&mut self) { self.instance.surface_drop(self.id); } } pub enum Descriptor { Texture(TextureDescriptor<'static>), Surface(SurfaceConfiguration<Vec<wgpu_types::TextureFormat>>), } pub struct GPUCanvasContext { canvas: v8::Global<v8::Object>, data: ContextData, pub texture_descriptor: RefCell<Option<Descriptor>>, pub configuration: RefCell<Option<GPUCanvasConfiguration>>, pub configuration_obj: RefCell<Option<v8::Global<v8::Object>>>, pub current_texture: RefCell<Option<v8::Global<v8::Object>>>, } // SAFETY: we're sure this can be GCed unsafe impl GarbageCollected for GPUCanvasContext { fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {} fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCanvasContext" } } #[op2] impl GPUCanvasContext { #[getter] fn canvas(&self) -> v8::Global<v8::Object> { self.canvas.clone() } fn configure<'s>( &self, scope: &mut v8::PinScope<'s, '_>, #[webidl] configuration_value: v8::Local<'s, v8::Value>, ) -> Result<(), JsErrorBox> { let configuration = GPUCanvasConfiguration::convert( scope, configuration_value, "Failed to execute 'configure' on 'GPUCanvasContext'".into(), (|| "Argument 1".into()).into(), &Default::default(), ) .map_err(JsErrorBox::from_err)?; let configuration_obj: v8::Local<v8::Object> = configuration_value .try_into() .map_err(|_| JsErrorBox::type_error("expected object"))?; let configuration_obj = v8::Global::new(scope, configuration_obj); if !matches!( configuration.format, GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm | GPUTextureFormat::Rgba16float ) { return Err(JsErrorBox::type_error(format!( "The format '{}' is not supported", configuration.format.as_str() ))); } let descriptor = self.get_descriptor_for_configuration(&configuration)?; match &descriptor { Descriptor::Texture(_) => {} Descriptor::Surface(surface) => { let ContextData::Surface(surface_data) = &self.data else { unreachable!() }; let err = configuration.device.instance.surface_configure( surface_data.borrow().id, configuration.device.id, surface, ); configuration.device.error_handler.push_error(err); } } self.configuration.replace(Some(configuration)); self.configuration_obj.replace(Some(configuration_obj)); self.texture_descriptor.replace(Some(descriptor)); Ok(()) } #[fast] fn unconfigure(&self, scope: &mut v8::PinScope<'_, '_>) { self.configuration.take(); self.configuration_obj.take(); self.texture_descriptor.take(); self.replace_drawing_buffer(scope); } fn get_configuration<'s>( &self, scope: &mut v8::PinScope<'s, '_>, ) -> Option<v8::Local<'s, v8::Object>> { self .configuration_obj .borrow() .as_ref() .map(|obj| v8::Local::new(scope, obj)) } fn get_current_texture( &self, scope: &mut v8::PinScope<'_, '_>, ) -> Result<v8::Global<v8::Object>, JsErrorBox> { let configuration = self.configuration.borrow(); let configuration = configuration.as_ref().ok_or_else(|| { JsErrorBox::type_error("GPUCanvasContext has not been configured") })?; let texture_descriptor = self.texture_descriptor.borrow(); let texture_descriptor = texture_descriptor.as_ref().unwrap(); let device = &configuration.device; let mut current_texture = self.current_texture.borrow_mut(); if let Some(texture) = current_texture.as_ref() { Ok(texture.clone()) } else { let texture = match texture_descriptor { Descriptor::Texture(texture_descriptor) => { let (id, err) = device.instance.device_create_texture( device.id, texture_descriptor, None, ); device.error_handler.push_error(err); GPUTexture { instance: device.instance.clone(), error_handler: device.error_handler.clone(), id, device_id: device.id, queue_id: device.queue, default_view_id: Default::default(), label: texture_descriptor.label.as_ref().unwrap().to_string(), size: texture_descriptor.size, mip_level_count: texture_descriptor.mip_level_count, sample_count: texture_descriptor.sample_count, dimension: crate::texture::GPUTextureDimension::D2, format: configuration.format.clone(), usage: configuration.usage, } } Descriptor::Surface(surface) => { let ContextData::Surface(surface_data) = &self.data else { unreachable!() }; let output = configuration .device .instance .surface_get_current_texture(surface_data.borrow().id, None) .map_err(|e| JsErrorBox::generic(e.to_string()))?; match output.status { SurfaceStatus::Good | SurfaceStatus::Suboptimal => { let id = output.texture.unwrap(); GPUTexture { instance: configuration.device.instance.clone(), error_handler: configuration.device.error_handler.clone(), id, device_id: configuration.device.id, queue_id: configuration.device.queue, default_view_id: Default::default(), label: "".to_string(), size: Extent3d { width: surface.width, height: surface.height, depth_or_array_layers: 1, }, mip_level_count: 0, sample_count: 0, dimension: crate::texture::GPUTextureDimension::D2, format: configuration.format.clone(), usage: configuration.usage, } } _ => return Err(JsErrorBox::generic("Invalid Surface Status")), } } }; let texture_obj = deno_core::cppgc::make_cppgc_object(scope, texture); let texture_obj = v8::Global::new(scope, texture_obj); *current_texture = Some(texture_obj.clone()); Ok(texture_obj) } } } impl GPUCanvasContext { fn get_descriptor_for_configuration( &self, configuration: &GPUCanvasConfiguration, ) -> Result<Descriptor, JsErrorBox> { let usage = configuration.usage.0; let view_formats = configuration .view_formats .clone() .into_iter() .map(Into::into) .collect(); match &self.data { ContextData::Canvas(image) => { let (width, height) = image.borrow().dimensions(); Ok(Descriptor::Texture(TextureDescriptor { label: Some("GPUCanvasContext".into()), size: Extent3d { width, height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu_types::TextureDimension::D2, format: configuration.format.clone().into(), usage: usage | wgpu_types::TextureUsages::COPY_SRC, view_formats, })) } ContextData::Surface(surface_data) => { let SurfaceData { width, height, .. } = &*surface_data.borrow(); Ok(Descriptor::Surface(SurfaceConfiguration { usage, format: configuration.format.clone().into(), width: *width, height: *height, present_mode: configuration .present_mode .clone() .map(Into::into) .unwrap_or_default(), desired_maximum_frame_latency: 2, alpha_mode: configuration.alpha_mode.clone().into(), view_formats, })) } } } pub fn copy_image_contents_to_canvas_data( &self, scope: &mut v8::PinScope<'_, '_>, ) -> Result<(), JsErrorBox> { let ContextData::Canvas(image) = &self.data else { unreachable!() }; let configuration = self.configuration.borrow(); let Some(GPUCanvasConfiguration { device, .. }) = configuration.as_ref() else { image.replace_with(|image| { let (width, height) = image.dimensions(); let image = deno_image::image::RgbaImage::new(width, height); DynamicImage::from(image) }); return Ok(()); }; let texture_descriptor = self.texture_descriptor.borrow(); if let Some(texture) = self.current_texture.borrow().as_ref() { let Descriptor::Texture(TextureDescriptor { size, .. }) = texture_descriptor.as_ref().unwrap() else { unreachable!() }; let local = v8::Local::new(scope, texture).cast::<v8::Value>(); let underlying_texture = deno_core::cppgc::try_unwrap_cppgc_object::<GPUTexture>(scope, local) .unwrap(); let (command_encoder, err) = device.instance.device_create_command_encoder( device.id, &wgpu_types::CommandEncoderDescriptor { label: Some("GPUCanvasContext".into()), }, None, ); if let Some(err) = err { return Err(JsErrorBox::from_err(GPUError::from(err))); } let data = copy_texture_to_vec( &device.instance, device.id, device.queue, command_encoder, underlying_texture.id, size, )?; image.replace_with(|image| { let (width, height) = image.dimensions(); let image = deno_image::image::RgbaImage::from_raw(width, height, data).unwrap(); DynamicImage::from(image) }); } Ok(()) } fn expire_current_texture(&self, scope: &mut v8::PinScope<'_, '_>) { if let Some(texture) = self.current_texture.borrow().as_ref() { let local = v8::Local::new(scope, texture).cast::<v8::Value>(); let underlying_texture = deno_core::cppgc::try_unwrap_cppgc_object::<GPUTexture>(scope, local) .unwrap(); underlying_texture .instance .texture_destroy(underlying_texture.id); } } fn replace_drawing_buffer(&self, scope: &mut v8::PinScope<'_, '_>) { self.expire_current_texture(scope); self.current_texture.borrow_mut().take(); } pub fn resize(&self, scope: &mut v8::PinScope<'_, '_>) { self.replace_drawing_buffer(scope); if let Some(configuration) = self.configuration.borrow().as_ref() { self.texture_descriptor.replace(Some( self .get_descriptor_for_configuration(configuration) .unwrap(), )); match &self.data { ContextData::Canvas(_) => {} ContextData::Surface(surface_data) => { let texture_descriptor = self.texture_descriptor.borrow(); let Descriptor::Surface(descriptor) = texture_descriptor.as_ref().unwrap() else { unreachable!() }; let err = configuration.device.instance.surface_configure( surface_data.borrow().id, configuration.device.id, descriptor, ); configuration.device.error_handler.push_error(err); } } } } pub fn bitmap_read_hook( &self, scope: &mut v8::PinScope<'_, '_>, ) -> Result<(), JsErrorBox> { self.copy_image_contents_to_canvas_data(scope) } pub fn post_transfer_to_image_bitmap_hook( &self, scope: &mut v8::PinScope<'_, '_>, ) { self.replace_drawing_buffer(scope); } } #[derive(WebIDL)] #[webidl(dictionary)] pub struct GPUCanvasConfiguration { pub device: Ref<GPUDevice>, pub format: GPUTextureFormat, #[webidl(default = GPUTextureUsageFlags(wgpu_types::TextureUsages::RENDER_ATTACHMENT))] usage: GPUTextureUsageFlags, #[webidl(default = vec![])] pub view_formats: Vec<GPUTextureFormat>, pub tone_mapping: GPUCanvasToneMapping, // TODO(@crowlkats): support #[webidl(default = PredefinedColorSpace::Srgb)] pub color_space: PredefinedColorSpace, // TODO(@crowlkats): support #[webidl(default = GPUCanvasAlphaMode::Opaque)] pub alpha_mode: GPUCanvasAlphaMode, // Extended from spec pub present_mode: Option<GPUPresentMode>, } #[derive(WebIDL, Clone)] #[webidl(dictionary)] pub struct GPUCanvasToneMapping { #[webidl(default = GPUCanvasToneMappingMode::Standard)] pub mode: GPUCanvasToneMappingMode, // TODO(@crowlkats): support } #[derive(WebIDL, Clone)] #[webidl(enum)] pub enum GPUCanvasToneMappingMode { Standard, Extended, } #[derive(WebIDL, Clone)] #[webidl(enum)] pub enum PredefinedColorSpace { Srgb, SrgbLinear, DisplayP3, DisplayP3Linear, } #[derive(WebIDL, Clone)] #[webidl(enum)] pub enum GPUCanvasAlphaMode { Opaque, Premultiplied, } impl From<GPUCanvasAlphaMode> for CompositeAlphaMode { fn from(value: GPUCanvasAlphaMode) -> Self { match value { GPUCanvasAlphaMode::Opaque => CompositeAlphaMode::Opaque, GPUCanvasAlphaMode::Premultiplied => CompositeAlphaMode::PreMultiplied, } } } // Extended from spec #[derive(WebIDL, Clone)] #[webidl(enum)] pub enum GPUPresentMode { AutoVsync, AutoNoVsync, Fifo, FifoRelaxed, Immediate, Mailbox, } impl From<GPUPresentMode> for wgpu_types::PresentMode { fn from(value: GPUPresentMode) -> Self { match value { GPUPresentMode::AutoVsync => Self::AutoVsync, GPUPresentMode::AutoNoVsync => Self::AutoNoVsync, GPUPresentMode::Fifo => Self::Fifo, GPUPresentMode::FifoRelaxed => Self::FifoRelaxed, GPUPresentMode::Immediate => Self::Immediate, GPUPresentMode::Mailbox => Self::Mailbox, } } } pub struct PaddedSize { pub padded_bytes_per_row: u32, pub unpadded_bytes_per_row: u32, } pub fn copy_texture_to_vec( instance: &Instance, device: wgpu_core::id::DeviceId, queue: wgpu_core::id::QueueId, command_encoder: wgpu_core::id::CommandEncoderId, texture: wgpu_core::id::TextureId, size: &Extent3d, ) -> Result<Vec<u8>, JsErrorBox> { // We only support the 8 bit per pixel formats with 4 channels // as such a pixel has 4 bytes const BYTES_PER_PIXEL: u32 = 4; let overflow = || JsErrorBox::range_error("Texture dimensions overflow buffer-size math"); let unpadded_bytes_per_row = size .width .checked_mul(BYTES_PER_PIXEL) .ok_or_else(overflow)?; let padded_bytes_per_row_padding = (wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT - (unpadded_bytes_per_row % wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT)) % wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT; let padded_bytes_per_row = unpadded_bytes_per_row .checked_add(padded_bytes_per_row_padding) .ok_or_else(overflow)?; let buffer_size = (padded_bytes_per_row as u64) .checked_mul(size.height as u64) .ok_or_else(overflow)?; let unpadded_total = (unpadded_bytes_per_row as u64) .checked_mul(size.height as u64) .ok_or_else(overflow)?; let (buffer, maybe_err) = instance.device_create_buffer( device, &wgpu_types::BufferDescriptor { label: None, size: buffer_size, usage: wgpu_types::BufferUsages::MAP_READ | wgpu_types::BufferUsages::COPY_DST, mapped_at_creation: false, }, None, ); if let Some(maybe_err) = maybe_err { return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into())); } instance .command_encoder_copy_texture_to_buffer( command_encoder, &wgpu_types::TexelCopyTextureInfo { texture, mip_level: 0, origin: Default::default(), aspect: Default::default(), }, &wgpu_types::TexelCopyBufferInfo { buffer, layout: wgpu_types::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded_bytes_per_row), rows_per_image: None, }, }, size, ) .map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?; let (command_buffer, maybe_err) = instance.command_encoder_finish( command_encoder, &wgpu_types::CommandBufferDescriptor { label: None }, None, ); if let Some((_, maybe_err)) = maybe_err { return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into())); } let maybe_err = instance.queue_submit(queue, &[command_buffer]).err(); if let Some((_, maybe_err)) = maybe_err { return Err(JsErrorBox::from_err::<GPUError>(maybe_err.into())); } let index = instance .buffer_map_async( buffer, 0, None, wgpu_core::resource::BufferMapOperation { host: wgpu_core::device::HostMap::Read, callback: None, }, ) .map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?; instance .device_poll( device, wgpu_types::PollType::Wait { submission_index: Some(index), timeout: None, }, ) .unwrap(); let (slice_pointer, range_size) = instance .buffer_get_mapped_range(buffer, 0, None) .map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?; let data = { // SAFETY: creating a slice from pointer and length provided by wgpu and // then dropping it before unmapping let slice = unsafe { std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize) }; let mut unpadded = Vec::with_capacity(unpadded_total as usize); let padded = padded_bytes_per_row as usize; let unpadded_row = unpadded_bytes_per_row as usize; for i in 0..size.height as usize { let start = i * padded; unpadded.extend_from_slice(&slice[start..start + padded][..unpadded_row]); } unpadded }; instance .buffer_unmap(buffer) .map_err(|e| JsErrorBox::from_err::<GPUError>(e.into()))?; instance.buffer_drop(buffer); Ok(data) } pub const CONTEXT_ID: &str = "webgpu"; pub fn create<'s>( _instance: Option<Instance>, canvas: v8::Global<v8::Object>, data: ContextData, scope: &mut v8::PinScope<'s, '_>, _options: v8::Local<'s, v8::Value>, _prefix: &'static str, _context: &'static str, ) -> Result<v8::Global<v8::Value>, JsErrorBox> { let obj = deno_core::cppgc::make_cppgc_object( scope, GPUCanvasContext { canvas, data, texture_descriptor: RefCell::new(None), configuration: RefCell::new(None), configuration_obj: RefCell::new(None), current_texture: RefCell::new(None), }, ); Ok(v8::Global::new(scope, obj.cast())) }