diff --git a/crates/gpui_wgpu/src/wgpu_atlas.rs b/crates/gpui_wgpu/src/wgpu_atlas.rs index 55f6edee21b9f2da02268c66c665c34d5b52066a..933598cabf316345dc0c9bf3c7d8afb6e775eb8c 100644 --- a/crates/gpui_wgpu/src/wgpu_atlas.rs +++ b/crates/gpui_wgpu/src/wgpu_atlas.rs @@ -31,6 +31,7 @@ struct WgpuAtlasState { device: Arc, queue: Arc, max_texture_size: u32, + color_texture_format: wgpu::TextureFormat, storage: WgpuAtlasStorage, tiles_by_key: FxHashMap, pending_uploads: Vec, @@ -41,12 +42,17 @@ pub struct WgpuTextureInfo { } impl WgpuAtlas { - pub fn new(device: Arc, queue: Arc) -> Self { + pub fn new( + device: Arc, + queue: Arc, + color_texture_format: wgpu::TextureFormat, + ) -> Self { let max_texture_size = device.limits().max_texture_dimension_2d; WgpuAtlas(Mutex::new(WgpuAtlasState { device, queue, max_texture_size, + color_texture_format, storage: WgpuAtlasStorage::default(), tiles_by_key: Default::default(), pending_uploads: Vec::new(), @@ -68,10 +74,16 @@ impl WgpuAtlas { /// Handles device lost by clearing all textures and cached tiles. /// The atlas will lazily recreate textures as needed on subsequent frames. - pub fn handle_device_lost(&self, device: Arc, queue: Arc) { + pub fn handle_device_lost( + &self, + device: Arc, + queue: Arc, + color_texture_format: wgpu::TextureFormat, + ) { let mut lock = self.0.lock(); lock.device = device; lock.queue = queue; + lock.color_texture_format = color_texture_format; lock.storage = WgpuAtlasStorage::default(); lock.tiles_by_key.clear(); lock.pending_uploads.clear(); @@ -167,8 +179,7 @@ impl WgpuAtlasState { let size = min_size.min(&max_atlas_size).max(&DEFAULT_ATLAS_SIZE); let format = match kind { AtlasTextureKind::Monochrome => wgpu::TextureFormat::R8Unorm, - AtlasTextureKind::Subpixel => wgpu::TextureFormat::Bgra8Unorm, - AtlasTextureKind::Polychrome => wgpu::TextureFormat::Bgra8Unorm, + AtlasTextureKind::Subpixel | AtlasTextureKind::Polychrome => self.color_texture_format, }; let texture = self.device.create_texture(&wgpu::TextureDescriptor { @@ -221,11 +232,14 @@ impl WgpuAtlasState { } fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds, bytes: &[u8]) { - self.pending_uploads.push(PendingUpload { - id, - bounds, - data: bytes.to_vec(), - }); + let data = self + .storage + .get(id) + .map(|texture| swizzle_upload_data(bytes, texture.format)) + .unwrap_or_else(|| bytes.to_vec()); + + self.pending_uploads + .push(PendingUpload { id, bounds, data }); } fn flush_uploads(&mut self) { @@ -341,7 +355,7 @@ impl WgpuAtlasTexture { fn bytes_per_pixel(&self) -> u8 { match self.format { wgpu::TextureFormat::R8Unorm => 1, - wgpu::TextureFormat::Bgra8Unorm => 4, + wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm => 4, _ => 4, } } @@ -355,6 +369,19 @@ impl WgpuAtlasTexture { } } +fn swizzle_upload_data(bytes: &[u8], format: wgpu::TextureFormat) -> Vec { + match format { + wgpu::TextureFormat::Rgba8Unorm => { + let mut data = bytes.to_vec(); + for pixel in data.chunks_exact_mut(4) { + pixel.swap(0, 2); + } + data + } + _ => bytes.to_vec(), + } +} + #[cfg(all(test, not(target_family = "wasm")))] mod tests { use super::*; @@ -400,7 +427,7 @@ mod tests { fn before_frame_skips_uploads_for_removed_texture() -> anyhow::Result<()> { let (device, queue) = test_device_and_queue()?; - let atlas = WgpuAtlas::new(device, queue); + let atlas = WgpuAtlas::new(device, queue, wgpu::TextureFormat::Bgra8Unorm); let key = AtlasKey::Image(RenderImageParams { image_id: ImageId(1), frame_index: 0, @@ -417,7 +444,24 @@ mod tests { .expect("tile should be created"); atlas.remove(&key); atlas.before_frame(); - Ok(()) } + + #[test] + fn swizzle_upload_data_preserves_bgra_uploads() { + let input = vec![0x10, 0x20, 0x30, 0x40]; + assert_eq!( + swizzle_upload_data(&input, wgpu::TextureFormat::Bgra8Unorm), + input + ); + } + + #[test] + fn swizzle_upload_data_converts_bgra_to_rgba() { + let input = vec![0x10, 0x20, 0x30, 0x40, 0xAA, 0xBB, 0xCC, 0xDD]; + assert_eq!( + swizzle_upload_data(&input, wgpu::TextureFormat::Rgba8Unorm), + vec![0x30, 0x20, 0x10, 0x40, 0xCC, 0xBB, 0xAA, 0xDD] + ); + } } diff --git a/crates/gpui_wgpu/src/wgpu_renderer.rs b/crates/gpui_wgpu/src/wgpu_renderer.rs index c25cba935447d76f0e112079b7c81a9463109806..2612f7dd2f84ceb0b5fba85022ca556d5a59a0dd 100644 --- a/crates/gpui_wgpu/src/wgpu_renderer.rs +++ b/crates/gpui_wgpu/src/wgpu_renderer.rs @@ -161,6 +161,40 @@ impl WgpuRenderer { .expect("GPU resources not available") } + fn select_color_atlas_texture_format( + adapter: &wgpu::Adapter, + ) -> anyhow::Result { + let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; + let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm); + if bgra_features.allowed_usages.contains(required_usages) { + return Ok(wgpu::TextureFormat::Bgra8Unorm); + } + + let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm); + if rgba_features.allowed_usages.contains(required_usages) { + let info = adapter.get_info(); + warn!( + "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \ + falling back to Rgba8Unorm atlas textures.", + info.name, info.backend, required_usages, + ); + return Ok(wgpu::TextureFormat::Rgba8Unorm); + } + + let info = adapter.get_info(); + Err(anyhow::anyhow!( + "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \ + format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \ + Rgba8Unorm allowed usages: {:?}.", + info.name, + info.backend, + info.device, + required_usages, + bgra_features.allowed_usages, + rgba_features.allowed_usages, + )) + } + /// Creates a new WgpuRenderer from raw window handles. /// /// The `gpu_context` is a shared reference that coordinates GPU context across @@ -217,9 +251,11 @@ impl WgpuRenderer { None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?), }; + let color_atlas_texture_format = Self::select_color_atlas_texture_format(&context.adapter)?; let atlas = Arc::new(WgpuAtlas::new( Arc::clone(&context.device), Arc::clone(&context.queue), + color_atlas_texture_format, )); Self::new_internal( @@ -243,9 +279,11 @@ impl WgpuRenderer { .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?; + let color_atlas_texture_format = Self::select_color_atlas_texture_format(&context.adapter)?; let atlas = Arc::new(WgpuAtlas::new( Arc::clone(&context.device), Arc::clone(&context.queue), + color_atlas_texture_format, )); Self::new_internal(None, context, surface, config, None, atlas) @@ -1805,10 +1843,14 @@ impl WgpuRenderer { let gpu_context = Rc::clone(gpu_context); let ctx_ref = gpu_context.borrow(); let context = ctx_ref.as_ref().expect("context should exist"); + let color_atlas_texture_format = Self::select_color_atlas_texture_format(&context.adapter)?; self.resources = None; - self.atlas - .handle_device_lost(Arc::clone(&context.device), Arc::clone(&context.queue)); + self.atlas.handle_device_lost( + Arc::clone(&context.device), + Arc::clone(&context.queue), + color_atlas_texture_format, + ); *self = Self::new_internal( Some(gpu_context.clone()),