From 96ade8668f8317a44a539e21c959454f6e1aadc4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 14 Sep 2021 16:48:44 +0200 Subject: [PATCH] Start on image rendering --- Cargo.lock | 65 +++++++- gpui/Cargo.toml | 1 + gpui/src/elements.rs | 24 +-- gpui/src/elements/image.rs | 65 ++++++++ gpui/src/image_data.rs | 31 ++++ gpui/src/lib.rs | 2 + gpui/src/platform/mac/atlas.rs | 84 ++++++++-- gpui/src/platform/mac/renderer.rs | 173 +++++++++++++++++--- gpui/src/platform/mac/shaders/shaders.h | 51 ++++-- gpui/src/platform/mac/shaders/shaders.metal | 33 ++++ gpui/src/scene.rs | 22 ++- zed/Cargo.toml | 1 + zed/src/workspace.rs | 33 ++-- 13 files changed, 510 insertions(+), 75 deletions(-) create mode 100644 gpui/src/elements/image.rs create mode 100644 gpui/src/image_data.rs diff --git a/Cargo.lock b/Cargo.lock index 562da86feec01872e2eb71718d775b41f454dde7..c749b35f2dc5e329fea6b282a3cab690f7156e6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -814,7 +814,7 @@ dependencies = [ "error-chain", "glob 0.2.11", "icns", - "image", + "image 0.12.4", "libflate", "md5", "msi", @@ -2102,6 +2102,16 @@ dependencies = [ "lzw", ] +[[package]] +name = "gif" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.23.0" @@ -2167,6 +2177,7 @@ dependencies = [ "font-kit", "foreign-types", "gpui_macros", + "image 0.23.14", "lazy_static", "log", "metal", @@ -2462,15 +2473,34 @@ checksum = "d95816db758249fe16f23a4e23f1a3a817fe11892dbfd1c5836f625324702158" dependencies = [ "byteorder", "enum_primitive", - "gif", + "gif 0.9.2", "jpeg-decoder", "num-iter", - "num-rational", + "num-rational 0.1.42", "num-traits 0.1.43", "png 0.6.2", "scoped_threadpool", ] +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif 0.11.2", + "jpeg-decoder", + "num-iter", + "num-rational 0.3.2", + "num-traits 0.2.14", + "png 0.16.8", + "scoped_threadpool", + "tiff", +] + [[package]] name = "indexmap" version = "1.6.2" @@ -3014,6 +3044,17 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits 0.2.14", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -5129,6 +5170,17 @@ dependencies = [ "tide", ] +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + [[package]] name = "time" version = "0.1.44" @@ -5694,6 +5746,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "weezl" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" + [[package]] name = "wepoll-sys" version = "3.0.1" @@ -5836,6 +5894,7 @@ dependencies = [ "gpui", "http-auth-basic", "ignore", + "image 0.23.14", "lazy_static", "libc", "log", diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 11a855cd40c087c4aed6fc278adfbdb7ded92952..7cc202e0b642dd6a22caa58b6008b8f9dee7b03c 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -11,6 +11,7 @@ backtrace = "0.3" ctor = "0.1" etagere = "0.2" gpui_macros = { path = "../gpui_macros" } +image = "0.23" lazy_static = "1.4.0" log = "0.4" num_cpus = "1.13" diff --git a/gpui/src/elements.rs b/gpui/src/elements.rs index 6d7429222c7c1bf74d259932cbc5b3b614892ce3..252cebd7334f57874a8fd6164da15ad0ba3c2986 100644 --- a/gpui/src/elements.rs +++ b/gpui/src/elements.rs @@ -6,6 +6,7 @@ mod empty; mod event_handler; mod flex; mod hook; +mod image; mod label; mod line_box; mod list; @@ -16,25 +17,12 @@ mod svg; mod text; mod uniform_list; +pub use self::{ + align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*, + hook::*, image::*, label::*, line_box::*, list::*, mouse_event_handler::*, overlay::*, + stack::*, svg::*, text::*, uniform_list::*, +}; pub use crate::presenter::ChildView; -pub use align::*; -pub use canvas::*; -pub use constrained_box::*; -pub use container::*; -pub use empty::*; -pub use event_handler::*; -pub use flex::*; -pub use hook::*; -pub use label::*; -pub use line_box::*; -pub use list::*; -pub use mouse_event_handler::*; -pub use overlay::*; -pub use stack::*; -pub use svg::*; -pub use text::*; -pub use uniform_list::*; - use crate::{ geometry::{rect::RectF, vector::Vector2F}, json, DebugContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, diff --git a/gpui/src/elements/image.rs b/gpui/src/elements/image.rs new file mode 100644 index 0000000000000000000000000000000000000000..44f39d8a3078173ac64f946f6c6a18cf4b4d5684 --- /dev/null +++ b/gpui/src/elements/image.rs @@ -0,0 +1,65 @@ +use crate::{ + geometry::{rect::RectF, vector::Vector2F}, + json::{json, ToJson}, + scene, DebugContext, Element, Event, EventContext, ImageData, LayoutContext, PaintContext, + SizeConstraint, +}; +use std::sync::Arc; + +pub struct Image(Arc); + +impl Image { + pub fn new(data: Arc) -> Self { + Self(data) + } +} + +impl Element for Image { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + _: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + (constraint.max, ()) + } + + fn paint( + &mut self, + bounds: RectF, + _: RectF, + _: &mut Self::LayoutState, + cx: &mut PaintContext, + ) -> Self::PaintState { + cx.scene.push_image(scene::Image { + bounds, + data: self.0.clone(), + }); + } + + fn dispatch_event( + &mut self, + _: &Event, + _: RectF, + _: &mut Self::LayoutState, + _: &mut Self::PaintState, + _: &mut EventContext, + ) -> bool { + false + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &DebugContext, + ) -> serde_json::Value { + json!({ + "type": "Image", + "bounds": bounds.to_json(), + }) + } +} diff --git a/gpui/src/image_data.rs b/gpui/src/image_data.rs new file mode 100644 index 0000000000000000000000000000000000000000..352393e3b57328f8373134c23edcb9b9ed5790ae --- /dev/null +++ b/gpui/src/image_data.rs @@ -0,0 +1,31 @@ +use crate::geometry::vector::{vec2i, Vector2I}; +use image::{Bgra, ImageBuffer}; +use std::sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, +}; + +pub struct ImageData { + pub id: usize, + data: ImageBuffer, Vec>, +} + +impl ImageData { + pub fn new(data: ImageBuffer, Vec>) -> Arc { + static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + + Arc::new(Self { + id: NEXT_ID.fetch_add(1, SeqCst), + data, + }) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + pub fn size(&self) -> Vector2I { + let (width, height) = self.data.dimensions(); + vec2i(width as i32, height as i32) + } +} diff --git a/gpui/src/lib.rs b/gpui/src/lib.rs index 6cb1c6f39d7d5caee1b94516461a371335924231..4b4d5f25d55060585df09f267c9bad55a258ced2 100644 --- a/gpui/src/lib.rs +++ b/gpui/src/lib.rs @@ -7,6 +7,8 @@ mod test; pub use assets::*; pub mod elements; pub mod font_cache; +mod image_data; +pub use crate::image_data::ImageData; pub mod views; pub use font_cache::FontCache; mod clipboard; diff --git a/gpui/src/platform/mac/atlas.rs b/gpui/src/platform/mac/atlas.rs index c9d910a586d686b3691f8710fa49c85923b67886..e23a045d47e4ea5f28bda6e1c951a2724a9f0258 100644 --- a/gpui/src/platform/mac/atlas.rs +++ b/gpui/src/platform/mac/atlas.rs @@ -1,4 +1,7 @@ -use crate::geometry::vector::{vec2i, Vector2I}; +use crate::geometry::{ + rect::RectI, + vector::{vec2i, Vector2I}, +}; use etagere::BucketedAtlasAllocator; use foreign_types::ForeignType; use metal::{self, Device, TextureDescriptor}; @@ -11,6 +14,12 @@ pub struct AtlasAllocator { free_atlases: Vec, } +#[derive(Copy, Clone)] +pub struct AllocId { + pub atlas_id: usize, + alloc_id: etagere::AllocId, +} + impl AtlasAllocator { pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self { let mut me = Self { @@ -31,20 +40,40 @@ impl AtlasAllocator { ) } - pub fn allocate(&mut self, requested_size: Vector2I) -> anyhow::Result<(usize, Vector2I)> { - let origin = self + pub fn allocate(&mut self, requested_size: Vector2I) -> (AllocId, Vector2I) { + let (alloc_id, origin) = self .atlases .last_mut() .unwrap() .allocate(requested_size) .unwrap_or_else(|| { let mut atlas = self.new_atlas(requested_size); - let origin = atlas.allocate(requested_size).unwrap(); + let (id, origin) = atlas.allocate(requested_size).unwrap(); self.atlases.push(atlas); - origin + (id, origin) }); - Ok((self.atlases.len() - 1, origin)) + let id = AllocId { + atlas_id: self.atlases.len() - 1, + alloc_id, + }; + (id, origin) + } + + pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> (AllocId, RectI) { + let (alloc_id, origin) = self.allocate(size); + let bounds = RectI::new(origin, size); + self.atlases[alloc_id.atlas_id].upload(bounds, bytes); + (alloc_id, bounds) + } + + pub fn deallocate(&mut self, id: AllocId) { + if let Some(atlas) = self.atlases.get_mut(id.atlas_id) { + atlas.deallocate(id.alloc_id); + if atlas.is_empty() { + self.free_atlases.push(self.atlases.remove(id.atlas_id)); + } + } } pub fn clear(&mut self) { @@ -102,13 +131,44 @@ impl Atlas { vec2i(size.width, size.height) } - fn allocate(&mut self, size: Vector2I) -> Option { - let origin = self + fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> { + let alloc = self .allocator - .allocate(etagere::Size::new(size.x(), size.y()))? - .rectangle - .min; - Some(vec2i(origin.x, origin.y)) + .allocate(etagere::Size::new(size.x(), size.y()))?; + let origin = alloc.rectangle.min; + Some((alloc.id, vec2i(origin.x, origin.y))) + } + + fn upload(&mut self, bounds: RectI, bytes: &[u8]) { + let region = metal::MTLRegion::new_2d( + bounds.origin().x() as u64, + bounds.origin().y() as u64, + bounds.size().x() as u64, + bounds.size().y() as u64, + ); + self.texture.replace_region( + region, + 0, + bytes.as_ptr() as *const _, + (bounds.size().x() * self.bytes_per_pixel() as i32) as u64, + ); + } + + fn bytes_per_pixel(&self) -> u8 { + use metal::MTLPixelFormat::*; + match self.texture.pixel_format() { + A8Unorm | R8Unorm => 1, + RGBA8Unorm | BGRA8Unorm => 4, + _ => unimplemented!(), + } + } + + fn deallocate(&mut self, id: etagere::AllocId) { + self.allocator.deallocate(id); + } + + fn is_empty(&self) -> bool { + self.allocator.is_empty() } fn clear(&mut self) { diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index e12a52d6134dbd2c5d110a570f52920c449a287d..215863d881c9f2bce84e6b6cbb20fe16b0481447 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -1,13 +1,15 @@ -use super::{atlas::AtlasAllocator, sprite_cache::SpriteCache}; +use super::{ + atlas::{self, AtlasAllocator}, + sprite_cache::SpriteCache, +}; use crate::{ color::Color, geometry::{ - rect::RectF, + rect::{RectF, RectI}, vector::{vec2f, vec2i, Vector2F}, }, platform, - scene::{Glyph, Icon, Layer, Quad, Shadow}, - Scene, + scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow}, }; use cocoa::foundation::NSUInteger; use metal::{MTLPixelFormat, MTLResourceOptions, NSRange}; @@ -21,9 +23,13 @@ const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decisio pub struct Renderer { sprite_cache: SpriteCache, path_atlases: AtlasAllocator, + image_atlases: AtlasAllocator, + prev_rendered_images: HashMap, + curr_rendered_images: HashMap, quad_pipeline_state: metal::RenderPipelineState, shadow_pipeline_state: metal::RenderPipelineState, sprite_pipeline_state: metal::RenderPipelineState, + image_pipeline_state: metal::RenderPipelineState, path_atlas_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, instances: metal::Buffer, @@ -64,7 +70,10 @@ impl Renderer { ); let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), fonts); - let path_atlases = build_path_atlas_allocator(MTLPixelFormat::R8Unorm, &device); + let path_atlases = + AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor()); + let image_atlases = + AtlasAllocator::new(device.clone(), build_image_atlas_texture_descriptor()); let quad_pipeline_state = build_pipeline_state( &device, &library, @@ -89,6 +98,14 @@ impl Renderer { "sprite_fragment", pixel_format, ); + let image_pipeline_state = build_pipeline_state( + &device, + &library, + "image", + "image_vertex", + "image_fragment", + pixel_format, + ); let path_atlas_pipeline_state = build_path_atlas_pipeline_state( &device, &library, @@ -100,9 +117,13 @@ impl Renderer { Self { sprite_cache, path_atlases, + image_atlases, + prev_rendered_images: Default::default(), + curr_rendered_images: Default::default(), quad_pipeline_state, shadow_pipeline_state, sprite_pipeline_state, + image_pipeline_state, path_atlas_pipeline_state, unit_vertices, instances, @@ -117,6 +138,12 @@ impl Renderer { output: &metal::TextureRef, ) { let mut offset = 0; + + mem::swap( + &mut self.curr_rendered_images, + &mut self.prev_rendered_images, + ); + let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer); self.render_layers( scene, @@ -130,6 +157,11 @@ impl Renderer { location: 0, length: offset as NSUInteger, }); + + for (id, _) in self.prev_rendered_images.values() { + self.image_atlases.deallocate(*id); + } + self.prev_rendered_images.clear(); } fn render_path_atlases( @@ -146,11 +178,11 @@ impl Renderer { for path in layer.paths() { let origin = path.bounds.origin() * scene.scale_factor(); let size = (path.bounds.size() * scene.scale_factor()).ceil(); - let (atlas_id, atlas_origin) = self.path_atlases.allocate(size.to_i32()).unwrap(); + let (alloc_id, atlas_origin) = self.path_atlases.allocate(size.to_i32()); let atlas_origin = atlas_origin.to_f32(); sprites.push(PathSprite { layer_id, - atlas_id, + atlas_id: alloc_id.atlas_id, shader_data: shaders::GPUISprite { origin: origin.floor().to_float2(), target_size: size.to_float2(), @@ -162,7 +194,7 @@ impl Renderer { }); if let Some(current_atlas_id) = current_atlas_id { - if atlas_id != current_atlas_id { + if alloc_id.atlas_id != current_atlas_id { self.render_paths_to_atlas( offset, &vertices, @@ -173,7 +205,7 @@ impl Renderer { } } - current_atlas_id = Some(atlas_id); + current_atlas_id = Some(alloc_id.atlas_id); for vertex in &path.vertices { let xy_position = @@ -316,6 +348,13 @@ impl Renderer { drawable_size, command_encoder, ); + self.render_images( + layer.images(), + scale_factor, + offset, + drawable_size, + command_encoder, + ); self.render_quads( layer.underlines(), scale_factor, @@ -602,6 +641,97 @@ impl Renderer { } } + fn render_images( + &mut self, + images: &[Image], + scale_factor: f32, + offset: &mut usize, + drawable_size: Vector2F, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if images.is_empty() { + return; + } + + let mut images_by_atlas = HashMap::new(); + for image in images { + let origin = image.bounds.origin() * scale_factor; + let target_size = image.bounds.size() * scale_factor; + let (alloc_id, atlas_bounds) = self + .prev_rendered_images + .remove(&image.data.id) + .or_else(|| self.curr_rendered_images.get(&image.data.id).copied()) + .unwrap_or_else(|| { + self.image_atlases + .upload(image.data.size(), image.data.as_bytes()) + }); + self.curr_rendered_images + .insert(image.data.id, (alloc_id, atlas_bounds)); + images_by_atlas + .entry(alloc_id.atlas_id) + .or_insert_with(Vec::new) + .push(shaders::GPUIImage { + origin: origin.to_float2(), + target_size: target_size.to_float2(), + source_size: atlas_bounds.size().to_float2(), + atlas_origin: atlas_bounds.origin().to_float2(), + }); + } + + command_encoder.set_render_pipeline_state(&self.image_pipeline_state); + command_encoder.set_vertex_buffer( + shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexVertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_bytes( + shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexViewportSize as u64, + mem::size_of::() as u64, + [drawable_size.to_float2()].as_ptr() as *const c_void, + ); + + for (atlas_id, images) in images_by_atlas { + align_offset(offset); + let next_offset = *offset + images.len() * mem::size_of::(); + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + let texture = self.image_atlases.texture(atlas_id).unwrap(); + command_encoder.set_vertex_buffer( + shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexImages as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_vertex_bytes( + shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexAtlasSize as u64, + mem::size_of::() as u64, + [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr() + as *const c_void, + ); + command_encoder.set_fragment_texture( + shaders::GPUIImageFragmentInputIndex_GPUIImageFragmentInputIndexAtlas as u64, + Some(texture), + ); + + unsafe { + let buffer_contents = (self.instances.contents() as *mut u8) + .offset(*offset as isize) + as *mut shaders::GPUIImage; + std::ptr::copy_nonoverlapping(images.as_ptr(), buffer_contents, images.len()); + } + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + images.len() as u64, + ); + *offset = next_offset; + } + } + fn render_path_sprites( &mut self, layer_id: usize, @@ -708,19 +838,23 @@ impl Renderer { } } -fn build_path_atlas_allocator( - pixel_format: MTLPixelFormat, - device: &metal::Device, -) -> AtlasAllocator { +fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor { let texture_descriptor = metal::TextureDescriptor::new(); texture_descriptor.set_width(2048); texture_descriptor.set_height(2048); - texture_descriptor.set_pixel_format(pixel_format); + texture_descriptor.set_pixel_format(MTLPixelFormat::R8Unorm); texture_descriptor .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead); texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private); - let path_atlases = AtlasAllocator::new(device.clone(), texture_descriptor); - path_atlases + texture_descriptor +} + +fn build_image_atlas_texture_descriptor() -> metal::TextureDescriptor { + let texture_descriptor = metal::TextureDescriptor::new(); + texture_descriptor.set_width(2048); + texture_descriptor.set_height(2048); + texture_descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm); + texture_descriptor } fn align_offset(offset: &mut usize) { @@ -803,9 +937,10 @@ mod shaders { #![allow(non_camel_case_types)] #![allow(non_snake_case)] - use pathfinder_geometry::vector::Vector2I; - - use crate::{color::Color, geometry::vector::Vector2F}; + use crate::{ + color::Color, + geometry::vector::{Vector2F, Vector2I}, + }; use std::mem; include!(concat!(env!("OUT_DIR"), "/shaders.rs")); diff --git a/gpui/src/platform/mac/shaders/shaders.h b/gpui/src/platform/mac/shaders/shaders.h index 5f49bfca64004ec67abc9c58c641b631cfd58375..b06e2e565fbb88ec7b2c4e3b580fd6d222b9cb1b 100644 --- a/gpui/src/platform/mac/shaders/shaders.h +++ b/gpui/src/platform/mac/shaders/shaders.h @@ -1,16 +1,19 @@ #include -typedef struct { +typedef struct +{ vector_float2 viewport_size; } GPUIUniforms; -typedef enum { +typedef enum +{ GPUIQuadInputIndexVertices = 0, GPUIQuadInputIndexQuads = 1, GPUIQuadInputIndexUniforms = 2, } GPUIQuadInputIndex; -typedef struct { +typedef struct +{ vector_float2 origin; vector_float2 size; vector_uchar4 background_color; @@ -22,13 +25,15 @@ typedef struct { float corner_radius; } GPUIQuad; -typedef enum { +typedef enum +{ GPUIShadowInputIndexVertices = 0, GPUIShadowInputIndexShadows = 1, GPUIShadowInputIndexUniforms = 2, } GPUIShadowInputIndex; -typedef struct { +typedef struct +{ vector_float2 origin; vector_float2 size; float corner_radius; @@ -36,18 +41,21 @@ typedef struct { vector_uchar4 color; } GPUIShadow; -typedef enum { +typedef enum +{ GPUISpriteVertexInputIndexVertices = 0, GPUISpriteVertexInputIndexSprites = 1, GPUISpriteVertexInputIndexViewportSize = 2, GPUISpriteVertexInputIndexAtlasSize = 3, } GPUISpriteVertexInputIndex; -typedef enum { +typedef enum +{ GPUISpriteFragmentInputIndexAtlas = 0, } GPUISpriteFragmentInputIndex; -typedef struct { +typedef struct +{ vector_float2 origin; vector_float2 target_size; vector_float2 source_size; @@ -56,14 +64,37 @@ typedef struct { uint8_t compute_winding; } GPUISprite; -typedef enum { +typedef enum +{ GPUIPathAtlasVertexInputIndexVertices = 0, GPUIPathAtlasVertexInputIndexAtlasSize = 1, } GPUIPathAtlasVertexInputIndex; -typedef struct { +typedef struct +{ vector_float2 xy_position; vector_float2 st_position; vector_float2 clip_rect_origin; vector_float2 clip_rect_size; } GPUIPathVertex; + +typedef enum +{ + GPUIImageVertexInputIndexVertices = 0, + GPUIImageVertexInputIndexImages = 1, + GPUIImageVertexInputIndexViewportSize = 2, + GPUIImageVertexInputIndexAtlasSize = 3, +} GPUIImageVertexInputIndex; + +typedef enum +{ + GPUIImageFragmentInputIndexAtlas = 0, +} GPUIImageFragmentInputIndex; + +typedef struct +{ + vector_float2 origin; + vector_float2 target_size; + vector_float2 source_size; + vector_float2 atlas_origin; +} GPUIImage; diff --git a/gpui/src/platform/mac/shaders/shaders.metal b/gpui/src/platform/mac/shaders/shaders.metal index 91e5ea129577d9443ee0f395c0e01df72f37b702..c83bac43ad59e3cb0d1acde7347aa55cab0962db 100644 --- a/gpui/src/platform/mac/shaders/shaders.metal +++ b/gpui/src/platform/mac/shaders/shaders.metal @@ -217,6 +217,39 @@ fragment float4 sprite_fragment( return color; } +struct ImageFragmentInput { + float4 position [[position]]; + float2 atlas_position; +}; + +vertex ImageFragmentInput image_vertex( + uint unit_vertex_id [[vertex_id]], + uint image_id [[instance_id]], + constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]], + constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]], + constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]], + constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]] +) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + GPUIImage image = images[image_id]; + float2 position = unit_vertex * image.target_size + image.origin; + float4 device_position = to_device_position(position, *viewport_size); + float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size; + + return ImageFragmentInput { + device_position, + atlas_position, + }; +} + +fragment float4 image_fragment( + ImageFragmentInput input [[stage_in]], + texture2d atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]] +) { + constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear); + return atlas.sample(atlas_sampler, input.atlas_position); +} + struct PathAtlasVertexOutput { float4 position [[position]]; float2 st_position; diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs index 401918c5fe014426a98496d7f4e2d31b903bb274..ab6ca71a06465d9c1ef2a02260f543730734349c 100644 --- a/gpui/src/scene.rs +++ b/gpui/src/scene.rs @@ -1,12 +1,13 @@ use serde::Deserialize; use serde_json::json; -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; use crate::{ color::Color, fonts::{FontId, GlyphId}, geometry::{rect::RectF, vector::Vector2F}, json::ToJson, + ImageData, }; pub struct Scene { @@ -25,6 +26,7 @@ pub struct Layer { clip_bounds: Option, quads: Vec, underlines: Vec, + images: Vec, shadows: Vec, glyphs: Vec, icons: Vec, @@ -124,6 +126,11 @@ pub struct PathVertex { pub st_position: Vector2F, } +pub struct Image { + pub bounds: RectF, + pub data: Arc, +} + impl Scene { pub fn new(scale_factor: f32) -> Self { let stacking_context = StackingContext::new(None); @@ -166,6 +173,10 @@ impl Scene { self.active_layer().push_quad(quad) } + pub fn push_image(&mut self, image: Image) { + self.active_layer().push_image(image) + } + pub fn push_underline(&mut self, underline: Quad) { self.active_layer().push_underline(underline) } @@ -240,6 +251,7 @@ impl Layer { clip_bounds, quads: Vec::new(), underlines: Vec::new(), + images: Vec::new(), shadows: Vec::new(), glyphs: Vec::new(), icons: Vec::new(), @@ -267,6 +279,14 @@ impl Layer { self.underlines.as_slice() } + fn push_image(&mut self, image: Image) { + self.images.push(image); + } + + pub fn images(&self) -> &[Image] { + self.images.as_slice() + } + fn push_shadow(&mut self, shadow: Shadow) { self.shadows.push(shadow); } diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 985901c50cf8a14d4331ed73f89a98fd1323d28d..2b36db9ba86152b040fa6edcf74da5c6bc68a0d2 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -30,6 +30,7 @@ futures = "0.3" gpui = { path = "../gpui" } http-auth-basic = "0.1.3" ignore = "0.4" +image = "0.23" lazy_static = "1.4.0" libc = "0.2" log = "0.4" diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index df10aef54d641b316c8fc567963ee249f1f4abd6..92e05d7a8e75ac9c2d52dc2834b731127a257e6c 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -21,7 +21,7 @@ use gpui::{ json::to_string_pretty, keymap::Binding, platform::WindowOptions, - AnyViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, + AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; @@ -354,10 +354,19 @@ pub struct Workspace { (usize, Arc), postage::watch::Receiver, Arc>>>, >, + image: Arc, } impl Workspace { pub fn new(app_state: &AppState, cx: &mut ViewContext) -> Self { + let image_bytes = crate::assets::Assets::get("images/as-cii.jpeg").unwrap(); + let image = image::io::Reader::new(std::io::Cursor::new(&*image_bytes.data)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap() + .into_bgra8(); + let pane = cx.add_view(|_| Pane::new(app_state.settings.clone())); let pane_id = pane.id(); cx.subscribe(&pane, move |me, _, event, cx| { @@ -401,6 +410,7 @@ impl Workspace { worktrees: Default::default(), items: Default::default(), loading_items: Default::default(), + image: ImageData::new(image), } } @@ -954,17 +964,16 @@ impl View for Workspace { Flex::column() .with_child( ConstrainedBox::new( - Container::new( - Align::new( - Label::new( - "zed".into(), - theme.workspace.titlebar.label.clone() - ).boxed() - ) - .boxed() - ) - .with_style(&theme.workspace.titlebar.container) - .boxed(), + Image::new(self.image.clone()).boxed() + // Container::new( + // Align::new( + // Label::new("zed".into(), theme.workspace.titlebar.label.clone()) + // .boxed(), + // ) + // .boxed(), + // ) + // .with_style(&theme.workspace.titlebar.container) + // .boxed(), ) .with_height(32.) .named("titlebar"),