diff --git a/vendor/zed/PATCHES.md b/vendor/zed/PATCHES.md index 14f3909..5bee8cb 100644 --- a/vendor/zed/PATCHES.md +++ b/vendor/zed/PATCHES.md @@ -5,3 +5,4 @@ Each local patch to this tree is a commit on the integrating branch, never on `z | patch | commit | files | |---|---|---| | `assets/fonts` | this commit | `vendor/zed/assets/fonts/` | +| `video-frames` | this commit | `vendor/zed/crates/gpui/src/platform.rs`, `vendor/zed/crates/gpui/src/scene.rs`, `vendor/zed/crates/gpui/src/window.rs`, `vendor/zed/crates/gpui_wgpu/src/wgpu_renderer.rs`, `vendor/zed/crates/gpui_wgpu/src/shaders.wgsl`, `vendor/zed/crates/gpui_wgpu/src/shaders_storage.wgsl`, `vendor/zed/crates/gpui_wgpu/src/shaders_webgl.wgsl` | diff --git a/vendor/zed/crates/gpui/src/platform.rs b/vendor/zed/crates/gpui/src/platform.rs index c08d964..02eaa85 100644 --- a/vendor/zed/crates/gpui/src/platform.rs +++ b/vendor/zed/crates/gpui/src/platform.rs @@ -1739,6 +1739,28 @@ impl From for AtlasKey { } } +/// A handle to a standalone GPU texture holding one video frame. +/// +/// Video frames are full-frame images that arrive every frame, so they are +/// not packed into the sprite atlas. The id is renderer-local and must be +/// freed with [`Window::free_video_texture`](crate::Window::free_video_texture) +/// once the frame is no longer painted. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct VideoTextureId(pub(crate) u64); + +impl VideoTextureId { + /// Build an id from the raw value stored on a painted frame. + pub fn new(id: u64) -> Self { + Self(id) + } + + /// The raw value, for matching a painted frame back to its texture. + pub fn as_u64(self) -> u64 { + self.0 + } +} + #[expect(missing_docs)] pub trait PlatformAtlas { /// The builder runs with the atlas locked and must not re-enter the same atlas. diff --git a/vendor/zed/crates/gpui/src/scene.rs b/vendor/zed/crates/gpui/src/scene.rs index be37813..60646d3 100644 --- a/vendor/zed/crates/gpui/src/scene.rs +++ b/vendor/zed/crates/gpui/src/scene.rs @@ -49,6 +49,13 @@ pub struct Scene { pub monochrome_sprites: Vec, pub subpixel_sprites: Vec, pub polychrome_sprites: Vec, + pub video_frames: Vec, + /// The pixels behind [`Self::video_frames`], keyed by texture id. + /// + /// A frame is painted from a standalone texture the renderer owns. This map + /// carries the pixels the renderer has not uploaded yet, so a texture that + /// is not painted this frame is simply absent and keeps its GPU copy. + pub video_frame_pixels: std::collections::HashMap, pub surfaces: Vec, } @@ -65,6 +72,8 @@ impl Scene { self.monochrome_sprites.clear(); self.subpixel_sprites.clear(); self.polychrome_sprites.clear(); + self.video_frames.clear(); + self.video_frame_pixels.clear(); self.surfaces.clear(); } @@ -129,6 +138,10 @@ impl Scene { sprite.order = order; self.polychrome_sprites.push(*sprite); } + Primitive::VideoFrame(video_frame) => { + video_frame.order = order; + self.video_frames.push(*video_frame); + } Primitive::Surface(surface) => { surface.order = order; self.surfaces.push(surface.clone()); @@ -159,6 +172,7 @@ impl Scene { .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id)); self.polychrome_sprites .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id)); + self.video_frames.sort_by_key(|frame| frame.order); self.surfaces.sort_by_key(|surface| surface.order); } @@ -185,6 +199,8 @@ impl Scene { subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(), polychrome_sprites_start: 0, polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), + video_frames_start: 0, + video_frames_iter: self.video_frames.iter().peekable(), surfaces_start: 0, surfaces_iter: self.surfaces.iter().peekable(), } @@ -208,6 +224,7 @@ pub(crate) enum PrimitiveKind { MonochromeSprite, SubpixelSprite, PolychromeSprite, + VideoFrame, Surface, } @@ -227,6 +244,7 @@ pub enum Primitive { MonochromeSprite(MonochromeSprite), SubpixelSprite(SubpixelSprite), PolychromeSprite(PolychromeSprite), + VideoFrame(VideoFrame), Surface(PaintSurface), } @@ -241,6 +259,7 @@ impl Primitive { Primitive::MonochromeSprite(sprite) => &sprite.bounds, Primitive::SubpixelSprite(sprite) => &sprite.bounds, Primitive::PolychromeSprite(sprite) => &sprite.bounds, + Primitive::VideoFrame(video_frame) => &video_frame.bounds, Primitive::Surface(surface) => &surface.bounds, } } @@ -254,6 +273,7 @@ impl Primitive { Primitive::MonochromeSprite(sprite) => &sprite.content_mask, Primitive::SubpixelSprite(sprite) => &sprite.content_mask, Primitive::PolychromeSprite(sprite) => &sprite.content_mask, + Primitive::VideoFrame(video_frame) => &video_frame.content_mask, Primitive::Surface(surface) => &surface.content_mask, } } @@ -281,6 +301,8 @@ struct BatchIterator<'a> { subpixel_sprites_iter: Peekable>, polychrome_sprites_start: usize, polychrome_sprites_iter: Peekable>, + video_frames_start: usize, + video_frames_iter: Peekable>, surfaces_start: usize, surfaces_iter: Peekable>, } @@ -312,6 +334,10 @@ impl<'a> Iterator for BatchIterator<'a> { self.polychrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::PolychromeSprite, ), + ( + self.video_frames_iter.peek().map(|s| s.order), + PrimitiveKind::VideoFrame, + ), ( self.surfaces_iter.peek().map(|s| s.order), PrimitiveKind::Surface, @@ -447,6 +473,20 @@ impl<'a> Iterator for BatchIterator<'a> { range: sprites_start..sprites_end, }) } + PrimitiveKind::VideoFrame => { + let frames_start = self.video_frames_start; + let mut frames_end = frames_start + 1; + self.video_frames_iter.next(); + while self + .video_frames_iter + .next_if(|frame| (frame.order, batch_kind) < max_order_and_kind) + .is_some() + { + frames_end += 1; + } + self.video_frames_start = frames_end; + Some(PrimitiveBatch::VideoFrames(frames_start..frames_end)) + } PrimitiveKind::Surface => { let surfaces_start = self.surfaces_start; let mut surfaces_end = surfaces_start + 1; @@ -492,6 +532,7 @@ pub enum PrimitiveBatch { texture_id: AtlasTextureId, range: Range, }, + VideoFrames(Range), Surfaces(Range), } @@ -524,6 +565,7 @@ impl PrimitiveBatch { texture_id.index ) } + Self::VideoFrames(range) => format!("video frames ({})", range.len()), Self::Surfaces(range) => format!("surfaces ({})", range.len()), } } @@ -763,6 +805,42 @@ impl From for Primitive { } } +/// The pixels of one video texture, in tightly packed BGRA8. +#[derive(Clone, Debug)] +#[expect(missing_docs)] +pub struct VideoFramePixels { + pub width: u32, + pub height: u32, + pub pixels: std::sync::Arc<[u8]>, + /// Bumped on every upload, so the renderer re-uploads only when it changes. + pub generation: u64, +} + +/// A video frame painted from a standalone GPU texture. +/// +/// Unlike [`PolychromeSprite`], the texture is not an atlas tile: the whole +/// texture is sampled, clipped by [`Self::content_mask`] and rounded by +/// [`Self::corner_radii`]. The texture itself lives in the renderer, keyed by +/// [`Self::texture_id`]. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +#[expect(missing_docs)] +pub struct VideoFrame { + pub order: DrawOrder, + pub bounds: Bounds, + pub content_mask: ContentMask, + pub corner_radii: Corners, + pub texture_id: u64, + pub opacity: f32, + pub pad: u32, +} + +impl From for Primitive { + fn from(video_frame: VideoFrame) -> Self { + Primitive::VideoFrame(video_frame) + } +} + #[derive(Clone, Debug)] #[allow(missing_docs)] pub struct PaintSurface { diff --git a/vendor/zed/crates/gpui/src/window.rs b/vendor/zed/crates/gpui/src/window.rs index 1214b03..47a799e 100644 --- a/vendor/zed/crates/gpui/src/window.rs +++ b/vendor/zed/crates/gpui/src/window.rs @@ -20,9 +20,10 @@ use crate::{ StrikethroughStyle, Style, SubpixelSprite, SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap, TaffyLayoutEngine, Task, TextInputConfiguration, TextInputStateChange, TextRenderingMode, TextStyle, TextStyleRefinement, ThermalState, - TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance, - WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, - WindowVisibility, point, prelude::*, px, rems, size, transparent_black, + TransformationMatrix, Underline, UnderlineStyle, VideoTextureId, WindowAppearance, + WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowOptions, + WindowParams, WindowTextSystem, WindowVisibility, point, prelude::*, px, rems, size, + transparent_black, }; use crate::gestures::{GestureTuning, RecognizedTouchGesture, TouchGestureRecognizer}; @@ -1223,6 +1224,13 @@ pub struct Window { is_resizable: bool, is_minimizable: bool, sprite_atlas: Arc, + /// Standalone GPU textures for video frames, keyed by [`VideoTextureId`]. + /// + /// Each entry is the latest BGRA8 frame the renderer should upload. Entries + /// are removed only by [`Self::free_video_texture`], so a texture that is + /// not painted this frame keeps its pixels for the next one. + video_textures: Rc>>, + next_video_texture_id: Rc>, text_system: Arc, text_rendering_mode: Rc>, rem_size: Pixels, @@ -1384,6 +1392,14 @@ impl<'a> PendingInputStatus<'a> { } } +/// One video frame held until the renderer uploads it. +struct VideoTexture { + width: u32, + height: u32, + pixels: std::sync::Arc<[u8]>, + generation: u64, +} + /// The timeout state for pending input. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PendingInputTimeoutStatus { @@ -2131,6 +2147,8 @@ impl Window { is_resizable, is_minimizable, sprite_atlas, + video_textures: Rc::new(RefCell::new(FxHashMap::default())), + next_video_texture_id: Rc::new(Cell::new(1)), text_system, text_rendering_mode: cx.text_rendering_mode.clone(), rem_size: px(16.), @@ -5171,6 +5189,109 @@ impl Window { }); } + /// Allocate a standalone GPU texture for a video frame. + /// + /// The texture is not part of the sprite atlas: video frames are full-frame + /// images replaced every frame, and packing them into the atlas would evict + /// glyphs and images on every frame. Call [`Self::upload_video_frame`] to + /// replace its pixels and [`Self::paint_video_frame`] to draw it. Free it + /// with [`Self::free_video_texture`] when the frame is no longer shown. + pub fn alloc_video_texture(&mut self, width: u32, height: u32) -> VideoTextureId { + let id = VideoTextureId(post_inc(&mut self.next_video_texture_id.get())); + self.video_textures.borrow_mut().insert( + id, + VideoTexture { + width, + height, + pixels: std::sync::Arc::from([]), + generation: 0, + }, + ); + id + } + + /// Replace the pixels of a video texture. + /// + /// `pixels` is tightly packed BGRA8, so its length must be `width * height * + /// 4` of the size passed to [`Self::alloc_video_texture`]. The upload is + /// applied on the next frame; this does not paint anything by itself. + pub fn upload_video_frame(&mut self, id: VideoTextureId, pixels: Vec) { + let mut textures = self.video_textures.borrow_mut(); + let Some(texture) = textures.get_mut(&id) else { + return; + }; + let expected = texture.width as usize * texture.height as usize * 4; + if pixels.len() != expected { + log::warn!( + "video frame upload of {} bytes does not match texture {}x{} ({} bytes); dropped", + pixels.len(), + texture.width, + texture.height, + expected, + ); + return; + } + texture.pixels = pixels.into(); + texture.generation += 1; + } + + /// Paint a video texture into the current frame. + /// + /// The whole texture is sampled into `bounds`, clipped by the current + /// content mask and rounded by `corner_radii`. A texture that was freed, + /// or that has not had a frame uploaded yet, paints nothing. + pub fn paint_video_frame( + &mut self, + bounds: Bounds, + corner_radii: Corners, + id: VideoTextureId, + ) { + use crate::VideoFrame; + + self.invalidator.debug_assert_paint(); + + if !self + .video_textures + .borrow() + .get(&id) + .is_some_and(|texture| !texture.pixels.is_empty()) + { + return; + } + + self.next_frame.scene.insert_primitive(VideoFrame { + order: 0, + bounds: self.snap_bounds(bounds), + content_mask: self.snapped_content_mask(), + corner_radii: corner_radii.scale(self.scale_factor()), + texture_id: id.0, + opacity: self.element_opacity(), + pad: 0, + }); + if let Some(texture) = self.video_textures.borrow().get(&id) { + use crate::VideoFramePixels; + + self.next_frame.scene.video_frame_pixels.insert( + id.0, + VideoFramePixels { + width: texture.width, + height: texture.height, + pixels: texture.pixels.clone(), + generation: texture.generation, + }, + ); + } + self.request_animation_frame(); + } + + /// Drop a video texture allocated with [`Self::alloc_video_texture`]. + /// + /// The renderer releases the GPU texture on its next frame. Painting the id + /// afterwards does nothing. + pub fn free_video_texture(&mut self, id: VideoTextureId) { + self.video_textures.borrow_mut().remove(&id); + } + /// Removes an image from the sprite atlas. pub fn drop_image(&mut self, data: Arc) -> Result<()> { for frame_index in 0..data.frame_count() { diff --git a/vendor/zed/crates/gpui_wgpu/src/shaders.wgsl b/vendor/zed/crates/gpui_wgpu/src/shaders.wgsl index 718cb75..deb4246 100644 --- a/vendor/zed/crates/gpui_wgpu/src/shaders.wgsl +++ b/vendor/zed/crates/gpui_wgpu/src/shaders.wgsl @@ -1310,6 +1310,51 @@ fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4 { return blend_color(color, sprite.opacity * saturate(0.5 - distance)); } +// --- video frames --- // + +struct VideoFrame { + order: u32, + bounds: Bounds, + content_mask: ContentMask, + corner_radii: Corners, + texture_id: u32, + texture_id_hi: u32, + opacity: f32, + pad: u32, +} + +struct VideoFrameVarying { + @builtin(position) position: vec4, + @location(0) texture_position: vec2, + @location(1) @interpolate(flat) frame_id: u32, + @location(3) clip_distances: vec4, +} + +@vertex +fn vs_video_frame(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> VideoFrameVarying { + let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); + let frame = load_video_frame(instance_id); + + var out = VideoFrameVarying(); + out.position = to_device_position(unit_vertex, frame.bounds); + out.texture_position = unit_vertex; + out.frame_id = instance_id; + out.clip_distances = distance_from_clip_rect(unit_vertex, frame.bounds, frame.content_mask); + return out; +} + +@fragment +fn fs_video_frame(input: VideoFrameVarying) -> @location(0) vec4 { + let sample = textureSample(t_sprite, s_sprite, input.texture_position); + if (any(input.clip_distances < vec4(0.0))) { + return vec4(0.0); + } + + let frame = load_video_frame(input.frame_id); + let distance = quad_sdf(input.position.xy, frame.bounds, frame.corner_radii); + return blend_color(sample, frame.opacity * saturate(0.5 - distance)); +} + // --- surfaces --- // struct SurfaceParams { diff --git a/vendor/zed/crates/gpui_wgpu/src/shaders_storage.wgsl b/vendor/zed/crates/gpui_wgpu/src/shaders_storage.wgsl index 55b8080..7ebc4b9 100644 --- a/vendor/zed/crates/gpui_wgpu/src/shaders_storage.wgsl +++ b/vendor/zed/crates/gpui_wgpu/src/shaders_storage.wgsl @@ -12,6 +12,7 @@ @group(1) @binding(0) var b_underlines: array; @group(1) @binding(0) var b_mono_sprites: array; @group(1) @binding(0) var b_poly_sprites: array; +@group(1) @binding(0) var b_video_frames: array; fn load_quad(instance_id: u32) -> Quad { return b_quads[instance_id]; @@ -40,3 +41,7 @@ fn load_mono_sprite(instance_id: u32) -> MonochromeSprite { fn load_poly_sprite(instance_id: u32) -> PolychromeSprite { return b_poly_sprites[instance_id]; } + +fn load_video_frame(instance_id: u32) -> VideoFrame { + return b_video_frames[instance_id]; +} diff --git a/vendor/zed/crates/gpui_wgpu/src/shaders_webgl.wgsl b/vendor/zed/crates/gpui_wgpu/src/shaders_webgl.wgsl index bf5f629..6409716 100644 --- a/vendor/zed/crates/gpui_wgpu/src/shaders_webgl.wgsl +++ b/vendor/zed/crates/gpui_wgpu/src/shaders_webgl.wgsl @@ -260,3 +260,17 @@ fn load_poly_sprite(instance_id: u32) -> PolychromeSprite { read_atlas_tile(&cursor), ); } + +fn load_video_frame(instance_id: u32) -> VideoFrame { + var cursor = instance_cursor(instance_id * 22u); + return VideoFrame( + read_word(&cursor), + read_bounds(&cursor), + read_bounds(&cursor), + read_corners(&cursor), + read_word(&cursor), + read_word(&cursor), + read_f32(&cursor), + read_word(&cursor), + ); +} diff --git a/vendor/zed/crates/gpui_wgpu/src/wgpu_renderer.rs b/vendor/zed/crates/gpui_wgpu/src/wgpu_renderer.rs index 8387528..f220882 100644 --- a/vendor/zed/crates/gpui_wgpu/src/wgpu_renderer.rs +++ b/vendor/zed/crates/gpui_wgpu/src/wgpu_renderer.rs @@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable}; use collections::FxHashMap; use gpui::{ AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch, - ScaledPixels, Scene, Size, get_gamma_correction_ratios, + ScaledPixels, Scene, Size, VideoTextureId, get_gamma_correction_ratios, }; use log::warn; #[cfg(not(target_family = "wasm"))] @@ -132,6 +132,7 @@ struct WgpuPipelines { mono_sprites: wgpu::RenderPipeline, subpixel_sprites: Option, poly_sprites: wgpu::RenderPipeline, + video_frames: wgpu::RenderPipeline, #[allow(dead_code)] surfaces: wgpu::RenderPipeline, } @@ -154,6 +155,7 @@ struct InstanceBindings { monochrome_sprites: InstanceBinding, subpixel_sprites: InstanceBinding, polychrome_sprites: InstanceBinding, + video_frames: InstanceBinding, } struct WgpuBindGroupLayouts { @@ -186,6 +188,7 @@ struct WgpuResources { bind_group_layouts: WgpuBindGroupLayouts, atlas_sampler: wgpu::Sampler, atlas_texture_bind_groups: FxHashMap, + video_textures: FxHashMap, globals_buffer: wgpu::Buffer, globals_bind_group: wgpu::BindGroup, path_globals_bind_group: wgpu::BindGroup, @@ -201,6 +204,14 @@ struct CachedTextureBindGroup { bind_group: wgpu::BindGroup, } +/// A standalone video-frame texture and the generation of pixels it holds. +struct VideoTextureGpu { + generation: u64, + width: u32, + height: u32, + bind_group: wgpu::BindGroup, +} + impl WgpuResources { fn invalidate_intermediate_textures(&mut self) { self.path_intermediate_texture = None; @@ -917,6 +928,19 @@ impl WgpuRendererCore { &shader_module, ); + let video_frames = create_pipeline( + "video_frames", + "vs_video_frame", + "fs_video_frame", + &layouts.globals, + &layouts.instances, + Some(&layouts.texture), + wgpu::PrimitiveTopology::TriangleStrip, + &[Some(color_target.clone())], + 1, + &shader_module, + ); + let surfaces = create_pipeline( "surfaces", "vs_surface", @@ -939,6 +963,7 @@ impl WgpuRendererCore { mono_sprites, subpixel_sprites, poly_sprites, + video_frames, surfaces, } } @@ -1345,6 +1370,7 @@ impl WgpuRendererCore { bind_group_layouts, atlas_sampler, atlas_texture_bind_groups: FxHashMap::default(), + video_textures: FxHashMap::default(), globals_buffer, globals_bind_group, path_globals_bind_group, @@ -1609,6 +1635,14 @@ impl WgpuRendererCore { &mut pass, )?; } + PrimitiveBatch::VideoFrames(range) => { + self.draw_video_frames( + &instance_bindings.video_frames, + scene, + range, + &mut pass, + )?; + } // Surfaces are macOS-only for video playback and are not // implemented by the WGPU renderer. PrimitiveBatch::Surfaces(_surfaces) => {} @@ -1659,6 +1693,11 @@ impl WgpuRendererCore { instance_offset, &scene.polychrome_sprites, )?, + video_frames: self.write_instance_binding( + "video_frames_bind_group", + instance_offset, + &scene.video_frames, + )?, }) } @@ -1780,6 +1819,153 @@ impl WgpuRendererCore { Ok(()) } + /// Draw video frames, each from its own standalone texture. + /// + /// Frames whose texture was freed, or that have not had pixels uploaded + /// yet, are skipped. The instance data is still written for the whole + /// batch, so a draw covers only the contiguous run of frames that share + /// one live texture. + fn draw_video_frames( + &mut self, + instances: &InstanceBinding, + scene: &Scene, + range: Range, + pass: &mut wgpu::RenderPass<'_>, + ) -> Result<()> { + self.sync_video_textures(scene); + let frames = &scene.video_frames[range.clone()]; + let mut run_start = None; + let mut run_texture = None; + for (offset, frame) in frames.iter().enumerate() { + let texture_id = VideoTextureId::new(frame.texture_id); + let live = self.resources().video_textures.contains_key(&texture_id); + match (run_start, live) { + (None, true) => { + run_start = Some(offset); + run_texture = Some(texture_id); + } + (Some(start), true) if run_texture == Some(texture_id) => { + let _ = start; + } + (Some(start), _) => { + self.draw_video_frame_run( + instances, + run_texture.unwrap(), + range.start + start..range.start + offset, + pass, + ); + run_start = live.then_some(offset); + run_texture = live.then_some(texture_id); + } + (None, false) => {} + } + } + if let Some(start) = run_start { + self.draw_video_frame_run( + instances, + run_texture.unwrap(), + range.start + start..range.end, + pass, + ); + } + Ok(()) + } + + /// Upload video-frame pixels that the renderer has not seen yet, and drop + /// textures that no painted frame still references. + fn sync_video_textures(&mut self, scene: &Scene) { + let mut live = FxHashMap::default(); + for frame in &scene.video_frames { + live.insert(frame.texture_id, ()); + let Some(pixels) = scene.video_frame_pixels.get(&frame.texture_id) else { + continue; + }; + let texture_id = VideoTextureId::new(frame.texture_id); + let current = self.resources().video_textures.get(&texture_id); + if current.is_some_and(|texture| { + texture.generation == pixels.generation + && texture.width == pixels.width + && texture.height == pixels.height + }) { + continue; + } + if pixels.width == 0 || pixels.height == 0 || pixels.pixels.is_empty() { + continue; + } + let texture = self.create_video_texture(pixels); + self.resources_mut() + .video_textures + .insert(texture_id, texture); + } + self.resources_mut() + .video_textures + .retain(|id, _| live.contains_key(&id.as_u64())); + } + + fn create_video_texture(&self, pixels: &gpui::VideoFramePixels) -> VideoTextureGpu { + let resources = self.resources(); + let texture = resources.device.create_texture(&wgpu::TextureDescriptor { + label: Some("video_frame"), + size: wgpu::Extent3d { + width: pixels.width, + height: pixels.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + resources.queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &pixels.pixels, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(pixels.width * 4), + rows_per_image: Some(pixels.height), + }, + wgpu::Extent3d { + width: pixels.width, + height: pixels.height, + depth_or_array_layers: 1, + }, + ); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + VideoTextureGpu { + generation: pixels.generation, + width: pixels.width, + height: pixels.height, + bind_group: self.create_texture_bind_group("video_frame_texture_bind_group", &view), + } + } + + fn draw_video_frame_run( + &self, + instances: &InstanceBinding, + texture_id: VideoTextureId, + range: Range, + pass: &mut wgpu::RenderPass<'_>, + ) { + let resources = self.resources(); + let Some(texture) = resources.video_textures.get(&texture_id) else { + return; + }; + pass.set_pipeline(&resources.pipelines.video_frames); + pass.set_bind_group(0, &resources.globals_bind_group, &[]); + pass.set_bind_group(1, &instances.bind_group, &[]); + pass.set_bind_group(2, &texture.bind_group, &[]); + let start = instances.first_instance + range.start as u32; + let end = instances.first_instance + range.end as u32; + pass.draw(0..4, start..end); + } + unsafe fn instance_bytes(instances: &[T]) -> &[u8] { unsafe { std::slice::from_raw_parts(