@@ -31,6 +31,7 @@ struct WgpuAtlasState {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
max_texture_size: u32,
+ color_texture_format: wgpu::TextureFormat,
storage: WgpuAtlasStorage,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
pending_uploads: Vec<PendingUpload>,
@@ -41,12 +42,17 @@ pub struct WgpuTextureInfo {
}
impl WgpuAtlas {
- pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
+ pub fn new(
+ device: Arc<wgpu::Device>,
+ queue: Arc<wgpu::Queue>,
+ 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<wgpu::Device>, queue: Arc<wgpu::Queue>) {
+ pub fn handle_device_lost(
+ &self,
+ device: Arc<wgpu::Device>,
+ queue: Arc<wgpu::Queue>,
+ 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<DevicePixels>, 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<u8> {
+ 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]
+ );
+ }
}
@@ -161,6 +161,40 @@ impl WgpuRenderer {
.expect("GPU resources not available")
}
+ fn select_color_atlas_texture_format(
+ adapter: &wgpu::Adapter,
+ ) -> anyhow::Result<wgpu::TextureFormat> {
+ 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()),