Skip to main content

teksilo_render/
test_support.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4use std::sync::mpsc;
5
6use crate::Renderer;
7
8pub async fn create_test_renderer(
9    label: &'static str,
10) -> Option<(Renderer, wgpu::Device, wgpu::Queue)> {
11    let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
12    let adapter = instance
13        .request_adapter(&wgpu::RequestAdapterOptions {
14            power_preference: wgpu::PowerPreference::LowPower,
15            compatible_surface: None,
16            force_fallback_adapter: false,
17            ..Default::default()
18        })
19        .await
20        .ok()?;
21
22    let (device, queue) = adapter
23        .request_device(&wgpu::DeviceDescriptor {
24            label: Some(label),
25            required_features: wgpu::Features::empty(),
26            required_limits: wgpu::Limits::downlevel_defaults(),
27            ..Default::default()
28        })
29        .await
30        .ok()?;
31
32    let renderer = Renderer::new(
33        device.clone(),
34        queue.clone(),
35        wgpu::TextureFormat::Rgba8UnormSrgb,
36    );
37
38    Some((renderer, device, queue))
39}
40
41pub fn read_texture_rgba(
42    device: &wgpu::Device,
43    queue: &wgpu::Queue,
44    texture: &wgpu::Texture,
45    width: u32,
46    height: u32,
47) -> Vec<u8> {
48    let bytes_per_pixel = 4u32;
49    let unpadded_bytes_per_row = width * bytes_per_pixel;
50    let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(256) * 256;
51    let buffer_size = padded_bytes_per_row as u64 * height as u64;
52
53    let buffer = device.create_buffer(&wgpu::BufferDescriptor {
54        label: Some("teksilo_render_test_readback"),
55        size: buffer_size,
56        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
57        mapped_at_creation: false,
58    });
59
60    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
61        label: Some("teksilo_render_test_copy"),
62    });
63    encoder.copy_texture_to_buffer(
64        wgpu::TexelCopyTextureInfo {
65            texture,
66            mip_level: 0,
67            origin: wgpu::Origin3d::ZERO,
68            aspect: wgpu::TextureAspect::All,
69        },
70        wgpu::TexelCopyBufferInfo {
71            buffer: &buffer,
72            layout: wgpu::TexelCopyBufferLayout {
73                offset: 0,
74                bytes_per_row: Some(padded_bytes_per_row),
75                rows_per_image: Some(height),
76            },
77        },
78        wgpu::Extent3d {
79            width,
80            height,
81            depth_or_array_layers: 1,
82        },
83    );
84    queue.submit(std::iter::once(encoder.finish()));
85
86    let slice = buffer.slice(..);
87    let (tx, rx) = mpsc::channel();
88    slice.map_async(wgpu::MapMode::Read, move |result| {
89        let _ = tx.send(result);
90    });
91    let _ = device.poll(wgpu::PollType::Wait {
92        submission_index: None,
93        timeout: None,
94    });
95    rx.recv()
96        .expect("map_async callback dropped")
97        .expect("map_async failed");
98
99    let mapped = slice
100        .get_mapped_range()
101        .expect("mapped range unavailable after a successful map_async");
102    let mut pixels = vec![0u8; (width * height * bytes_per_pixel) as usize];
103    for row in 0..height as usize {
104        let src_offset = row * padded_bytes_per_row as usize;
105        let dst_offset = row * unpadded_bytes_per_row as usize;
106        pixels[dst_offset..dst_offset + unpadded_bytes_per_row as usize]
107            .copy_from_slice(&mapped[src_offset..src_offset + unpadded_bytes_per_row as usize]);
108    }
109    drop(mapped);
110    buffer.unmap();
111    pixels
112}