#![deny(unsafe_code)]
use std::cell::{Cell, RefCell, RefMut};
use std::ffi::c_void;
use std::num::NonZeroU32;
use std::rc::Rc;
use dpi::PhysicalSize;
use euclid::default::{Rect, Size2D as UntypedSize2D};
use euclid::{Point2D, Size2D};
use gleam::gl::{self, Gl};
use glow::NativeFramebuffer;
use image::RgbaImage;
use log::{debug, trace, warn};
use raw_window_handle::{DisplayHandle, WindowHandle};
use surfman::chains::{PreserveBuffer, SwapChain};
pub use surfman::Error;
use surfman::{
Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
NativeContext, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture, SurfaceType,
};
use webrender_api::units::{DeviceIntRect, DevicePixel};
pub trait RenderingContext {
fn prepare_for_rendering(&self) {}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage>;
fn size(&self) -> PhysicalSize<u32>;
fn size2d(&self) -> Size2D<u32, DevicePixel> {
let size = self.size();
Size2D::new(size.width, size.height)
}
fn resize(&self, size: PhysicalSize<u32>);
fn present(&self);
fn make_current(&self) -> Result<(), Error>;
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
fn create_texture(
&self,
_surface: Surface,
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
None
}
fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> {
None
}
fn connection(&self) -> Option<Connection> {
None
}
}
struct SurfmanRenderingContext {
gl: Rc<dyn Gl>,
device: RefCell<Device>,
context: RefCell<Context>,
}
impl Drop for SurfmanRenderingContext {
fn drop(&mut self) {
let device = &mut self.device.borrow_mut();
let context = &mut self.context.borrow_mut();
let _ = device.destroy_context(context);
}
}
impl SurfmanRenderingContext {
fn new(connection: &Connection, adapter: &Adapter) -> Result<Self, Error> {
let mut device = connection.create_device(adapter)?;
let flags = ContextAttributeFlags::ALPHA |
ContextAttributeFlags::DEPTH |
ContextAttributeFlags::STENCIL;
let gl_api = connection.gl_api();
let version = match &gl_api {
GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 },
GLApi::GL => surfman::GLVersion { major: 3, minor: 2 },
};
let context_descriptor =
device.create_context_descriptor(&ContextAttributes { flags, version })?;
let context = device.create_context(&context_descriptor, None)?;
#[allow(unsafe_code)]
let gl = {
match gl_api {
GLApi::GL => unsafe {
gl::GlFns::load_with(|func_name| device.get_proc_address(&context, func_name))
},
GLApi::GLES => unsafe {
gl::GlesFns::load_with(|func_name| device.get_proc_address(&context, func_name))
},
}
};
Ok(SurfmanRenderingContext {
gl,
device: RefCell::new(device),
context: RefCell::new(context),
})
}
fn create_surface(&self, surface_type: SurfaceType<NativeWidget>) -> Result<Surface, Error> {
let device = &mut self.device.borrow_mut();
let context = &self.context.borrow();
device.create_surface(context, SurfaceAccess::GPUOnly, surface_type)
}
fn bind_surface(&self, surface: Surface) -> Result<(), Error> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})?;
Ok(())
}
fn create_attached_swap_chain(&self) -> Result<SwapChain<Device>, Error> {
let device = &mut self.device.borrow_mut();
let context = &mut self.context.borrow_mut();
SwapChain::create_attached(device, context, SurfaceAccess::GPUOnly)
}
fn resize_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> {
let size = Size2D::new(size.width as i32, size.height as i32);
let device = &mut self.device.borrow_mut();
let context = &mut self.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
device.resize_surface(context, &mut surface, size)?;
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})
}
fn present_bound_surface(&self) -> Result<(), Error> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
device.present_surface(context, &mut surface)?;
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})
}
#[allow(dead_code)]
fn native_context(&self) -> NativeContext {
let device = &self.device.borrow();
let context = &self.context.borrow();
device.native_context(context)
}
fn framebuffer(&self) -> Option<NativeFramebuffer> {
let device = &self.device.borrow();
let context = &self.context.borrow();
device
.context_surface_info(context)
.unwrap_or(None)
.and_then(|info| info.framebuffer_object)
}
fn prepare_for_rendering(&self) {
let framebuffer_id = self
.framebuffer()
.map_or(0, |framebuffer| framebuffer.0.into());
self.gl
.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_id);
}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
let framebuffer_id = self
.framebuffer()
.map_or(0, |framebuffer| framebuffer.0.into());
Framebuffer::read_framebuffer_to_image(&self.gl, framebuffer_id, source_rectangle)
}
fn make_current(&self) -> Result<(), Error> {
let device = &self.device.borrow();
let context = &mut self.context.borrow();
device.make_context_current(context)
}
fn create_texture(
&self,
surface: Surface,
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
let SurfaceInfo {
id: front_buffer_id,
size,
..
} = device.surface_info(&surface);
debug!("... getting texture for surface {:?}", front_buffer_id);
let surface_texture = device.create_surface_texture(context, surface).unwrap();
let gl_texture = device
.surface_texture_object(&surface_texture)
.map(|tex| tex.0.get())
.unwrap_or(0);
Some((surface_texture, gl_texture, size))
}
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
device
.destroy_surface_texture(context, surface_texture)
.map_err(|(error, _)| error)
.ok()
}
fn connection(&self) -> Option<Connection> {
Some(self.device.borrow().connection())
}
}
pub struct SoftwareRenderingContext {
size: Cell<PhysicalSize<u32>>,
surfman_rendering_info: SurfmanRenderingContext,
swap_chain: SwapChain<Device>,
}
impl SoftwareRenderingContext {
pub fn new(size: PhysicalSize<u32>) -> Result<Self, Error> {
let connection = Connection::new()?;
let adapter = connection.create_software_adapter()?;
let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter)?;
let surfman_size = Size2D::new(size.width as i32, size.height as i32);
let surface =
surfman_rendering_info.create_surface(SurfaceType::Generic { size: surfman_size })?;
surfman_rendering_info.bind_surface(surface)?;
surfman_rendering_info.make_current()?;
let swap_chain = surfman_rendering_info.create_attached_swap_chain()?;
Ok(SoftwareRenderingContext {
size: Cell::new(size),
surfman_rendering_info,
swap_chain,
})
}
}
impl Drop for SoftwareRenderingContext {
fn drop(&mut self) {
let device = &mut self.surfman_rendering_info.device.borrow_mut();
let context = &mut self.surfman_rendering_info.context.borrow_mut();
let _ = self.swap_chain.destroy(device, context);
}
}
impl RenderingContext for SoftwareRenderingContext {
fn prepare_for_rendering(&self) {
self.surfman_rendering_info.prepare_for_rendering();
}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
self.surfman_rendering_info.read_to_image(source_rectangle)
}
fn size(&self) -> PhysicalSize<u32> {
self.size.get()
}
fn resize(&self, size: PhysicalSize<u32>) {
if self.size.get() == size {
return;
}
self.size.set(size);
let device = &mut self.surfman_rendering_info.device.borrow_mut();
let context = &mut self.surfman_rendering_info.context.borrow_mut();
let size = Size2D::new(size.width as i32, size.height as i32);
let _ = self.swap_chain.resize(device, context, size);
}
fn present(&self) {
let device = &mut self.surfman_rendering_info.device.borrow_mut();
let context = &mut self.surfman_rendering_info.context.borrow_mut();
let _ = self
.swap_chain
.swap_buffers(device, context, PreserveBuffer::No);
}
fn make_current(&self) -> Result<(), Error> {
self.surfman_rendering_info.make_current()
}
#[allow(unsafe_code)]
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
self.surfman_rendering_info.gl.clone()
}
fn create_texture(
&self,
surface: Surface,
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
self.surfman_rendering_info.create_texture(surface)
}
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
self.surfman_rendering_info.destroy_texture(surface_texture)
}
fn connection(&self) -> Option<Connection> {
self.surfman_rendering_info.connection()
}
}
pub struct WindowRenderingContext {
size: Cell<PhysicalSize<u32>>,
surfman_context: SurfmanRenderingContext,
}
impl WindowRenderingContext {
pub fn new(
display_handle: DisplayHandle,
window_handle: WindowHandle,
size: PhysicalSize<u32>,
) -> Result<Self, Error> {
let connection = Connection::from_display_handle(display_handle)?;
let adapter = connection.create_adapter()?;
let surfman_context = SurfmanRenderingContext::new(&connection, &adapter)?;
let native_widget = connection
.create_native_widget_from_window_handle(
window_handle,
Size2D::new(size.width as i32, size.height as i32),
)
.expect("Failed to create native widget");
let surface = surfman_context.create_surface(SurfaceType::Widget { native_widget })?;
surfman_context.bind_surface(surface)?;
surfman_context.make_current()?;
Ok(Self {
size: Cell::new(size),
surfman_context,
})
}
pub fn offscreen_context(
self: &Rc<Self>,
size: PhysicalSize<u32>,
) -> OffscreenRenderingContext {
OffscreenRenderingContext::new(self.clone(), size)
}
pub fn get_proc_address(&self, name: &str) -> *const c_void {
let device = &self.surfman_context.device.borrow();
let context = &self.surfman_context.context.borrow();
device.get_proc_address(context, name)
}
pub fn take_window(&self) -> Result<(), Error> {
let device = self.surfman_context.device.borrow_mut();
let mut context = self.surfman_context.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap();
device.destroy_surface(&mut context, &mut surface)?;
Ok(())
}
pub fn set_window(
&self,
window_handle: WindowHandle,
size: PhysicalSize<u32>,
) -> Result<(), Error> {
let mut device = self.surfman_context.device.borrow_mut();
let mut context = self.surfman_context.context.borrow_mut();
let native_widget = device
.connection()
.create_native_widget_from_window_handle(
window_handle,
Size2D::new(size.width as i32, size.height as i32),
)
.expect("Failed to create native widget");
let surface_access = SurfaceAccess::GPUOnly;
let surface_type = SurfaceType::Widget { native_widget };
let surface = device.create_surface(&context, surface_access, surface_type)?;
device
.bind_surface_to_context(&mut context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(&mut context, &mut surface);
err
})?;
device.make_context_current(&context)?;
Ok(())
}
pub fn surfman_details(&self) -> (RefMut<Device>, RefMut<Context>) {
(
self.surfman_context.device.borrow_mut(),
self.surfman_context.context.borrow_mut(),
)
}
}
impl RenderingContext for WindowRenderingContext {
fn prepare_for_rendering(&self) {
self.surfman_context.prepare_for_rendering();
}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
self.surfman_context.read_to_image(source_rectangle)
}
fn size(&self) -> PhysicalSize<u32> {
self.size.get()
}
fn resize(&self, size: PhysicalSize<u32>) {
match self.surfman_context.resize_surface(size) {
Ok(..) => self.size.set(size),
Err(error) => warn!("Error resizing surface: {error:?}"),
}
}
fn present(&self) {
if let Err(error) = self.surfman_context.present_bound_surface() {
warn!("Error presenting surface: {error:?}");
}
}
fn make_current(&self) -> Result<(), Error> {
self.surfman_context.make_current()
}
#[allow(unsafe_code)]
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
self.surfman_context.gl.clone()
}
fn create_texture(
&self,
surface: Surface,
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
self.surfman_context.create_texture(surface)
}
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
self.surfman_context.destroy_texture(surface_texture)
}
fn connection(&self) -> Option<Connection> {
self.surfman_context.connection()
}
}
struct Framebuffer {
gl: Rc<dyn Gl>,
size: PhysicalSize<u32>,
framebuffer_id: gl::GLuint,
renderbuffer_id: gl::GLuint,
texture_id: gl::GLuint,
}
impl Framebuffer {
fn bind(&self) {
trace!("Binding FBO {}", self.framebuffer_id);
self.gl
.bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id)
}
}
impl Drop for Framebuffer {
fn drop(&mut self) {
self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
self.gl.delete_textures(&[self.texture_id]);
self.gl.delete_renderbuffers(&[self.renderbuffer_id]);
self.gl.delete_framebuffers(&[self.framebuffer_id]);
}
}
impl Framebuffer {
fn new(gl: Rc<dyn Gl>, size: PhysicalSize<u32>) -> Self {
let framebuffer_ids = gl.gen_framebuffers(1);
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
let texture_ids = gl.gen_textures(1);
gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
gl.tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RGBA as gl::GLint,
size.width as gl::GLsizei,
size.height as gl::GLsizei,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
None,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
gl::NEAREST as gl::GLint,
);
gl.tex_parameter_i(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
gl::NEAREST as gl::GLint,
);
gl.framebuffer_texture_2d(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture_ids[0],
0,
);
gl.bind_texture(gl::TEXTURE_2D, 0);
let renderbuffer_ids = gl.gen_renderbuffers(1);
let depth_rb = renderbuffer_ids[0];
gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
gl.renderbuffer_storage(
gl::RENDERBUFFER,
gl::DEPTH_COMPONENT24,
size.width as gl::GLsizei,
size.height as gl::GLsizei,
);
gl.framebuffer_renderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
depth_rb,
);
Self {
gl,
size,
framebuffer_id: *framebuffer_ids
.first()
.expect("Guaranteed by GL operations"),
renderbuffer_id: *renderbuffer_ids
.first()
.expect("Guaranteed by GL operations"),
texture_id: *texture_ids.first().expect("Guaranteed by GL operations"),
}
}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
Self::read_framebuffer_to_image(&self.gl, self.framebuffer_id, source_rectangle)
}
fn read_framebuffer_to_image(
gl: &Rc<dyn Gl>,
framebuffer_id: u32,
source_rectangle: DeviceIntRect,
) -> Option<RgbaImage> {
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
gl.bind_vertex_array(0);
let mut pixels = gl.read_pixels(
source_rectangle.min.x,
source_rectangle.min.y,
source_rectangle.width(),
source_rectangle.height(),
gl::RGBA,
gl::UNSIGNED_BYTE,
);
let gl_error = gl.get_error();
if gl_error != gl::NO_ERROR {
warn!("GL error code 0x{gl_error:x} set after read_pixels");
}
let source_rectangle = source_rectangle.to_usize();
let orig_pixels = pixels.clone();
let stride = source_rectangle.width() * 4;
for y in 0..source_rectangle.height() {
let dst_start = y * stride;
let src_start = (source_rectangle.height() - y - 1) * stride;
let src_slice = &orig_pixels[src_start..src_start + stride];
pixels[dst_start..dst_start + stride].clone_from_slice(&src_slice[..stride]);
}
RgbaImage::from_raw(
source_rectangle.width() as u32,
source_rectangle.height() as u32,
pixels,
)
}
}
pub struct OffscreenRenderingContext {
parent_context: Rc<WindowRenderingContext>,
size: Cell<PhysicalSize<u32>>,
back_framebuffer: RefCell<Framebuffer>,
front_framebuffer: RefCell<Option<Framebuffer>>,
}
type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>;
impl OffscreenRenderingContext {
fn new(parent_context: Rc<WindowRenderingContext>, size: PhysicalSize<u32>) -> Self {
let next_framebuffer = Framebuffer::new(parent_context.gl_api(), size);
Self {
parent_context,
size: Cell::new(size),
back_framebuffer: RefCell::new(next_framebuffer),
front_framebuffer: Default::default(),
}
}
pub fn parent_context(&self) -> &WindowRenderingContext {
&self.parent_context
}
pub fn front_framebuffer_id(&self) -> Option<gl::GLuint> {
self.front_framebuffer
.borrow()
.as_ref()
.map(|framebuffer| framebuffer.framebuffer_id)
}
pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> {
let front_framebuffer_id =
NonZeroU32::new(self.front_framebuffer_id()?).map(NativeFramebuffer)?;
let parent_context_framebuffer_id = self.parent_context.surfman_context.framebuffer();
let size = self.size.get();
let size = Size2D::new(size.width as i32, size.height as i32);
Some(Box::new(move |gl, target_rect| {
Self::render_framebuffer_to_parent_context(
gl,
Rect::new(Point2D::origin(), size.to_i32()),
front_framebuffer_id,
target_rect,
parent_context_framebuffer_id,
);
}))
}
#[allow(unsafe_code)]
fn render_framebuffer_to_parent_context(
gl: &glow::Context,
source_rect: Rect<i32>,
source_framebuffer_id: NativeFramebuffer,
target_rect: Rect<i32>,
target_framebuffer_id: Option<NativeFramebuffer>,
) {
use glow::HasContext as _;
unsafe {
gl.clear_color(0.0, 0.0, 0.0, 0.0);
gl.scissor(
target_rect.origin.x,
target_rect.origin.y,
target_rect.width(),
target_rect.height(),
);
gl.enable(gl::SCISSOR_TEST);
gl.clear(gl::COLOR_BUFFER_BIT);
gl.disable(gl::SCISSOR_TEST);
gl.bind_framebuffer(gl::READ_FRAMEBUFFER, Some(source_framebuffer_id));
gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, target_framebuffer_id);
gl.blit_framebuffer(
source_rect.origin.x,
source_rect.origin.y,
source_rect.origin.x + source_rect.width(),
source_rect.origin.y + source_rect.height(),
target_rect.origin.x,
target_rect.origin.y,
target_rect.origin.x + target_rect.width(),
target_rect.origin.y + target_rect.height(),
gl::COLOR_BUFFER_BIT,
gl::NEAREST,
);
gl.bind_framebuffer(gl::FRAMEBUFFER, target_framebuffer_id);
}
}
}
impl RenderingContext for OffscreenRenderingContext {
fn size(&self) -> PhysicalSize<u32> {
self.size.get()
}
fn resize(&self, size: PhysicalSize<u32>) {
self.size.set(size);
}
fn prepare_for_rendering(&self) {
self.back_framebuffer.borrow().bind();
}
fn present(&self) {
trace!(
"Unbinding FBO {}",
self.back_framebuffer.borrow().framebuffer_id
);
self.gl_api().bind_framebuffer(gl::FRAMEBUFFER, 0);
let new_back_framebuffer = match self.front_framebuffer.borrow_mut().take() {
Some(framebuffer) if framebuffer.size == self.size.get() => framebuffer,
_ => Framebuffer::new(self.gl_api(), self.size.get()),
};
let new_front_framebuffer = std::mem::replace(
&mut *self.back_framebuffer.borrow_mut(),
new_back_framebuffer,
);
*self.front_framebuffer.borrow_mut() = Some(new_front_framebuffer);
}
fn make_current(&self) -> Result<(), surfman::Error> {
self.parent_context.make_current()
}
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
self.parent_context.gl_api()
}
fn create_texture(
&self,
surface: Surface,
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
self.parent_context.create_texture(surface)
}
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
self.parent_context.destroy_texture(surface_texture)
}
fn connection(&self) -> Option<Connection> {
self.parent_context.connection()
}
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
self.back_framebuffer
.borrow()
.read_to_image(source_rectangle)
}
}
#[cfg(test)]
mod test {
use dpi::PhysicalSize;
use euclid::{Box2D, Point2D, Size2D};
use gleam::gl;
use image::Rgba;
use surfman::{Connection, ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion};
use super::Framebuffer;
#[test]
#[allow(unsafe_code)]
fn test_read_pixels() -> Result<(), Error> {
let connection = Connection::new()?;
let adapter = connection.create_software_adapter()?;
let mut device = connection.create_device(&adapter)?;
let context_descriptor = device.create_context_descriptor(&ContextAttributes {
version: GLVersion::new(3, 0),
flags: ContextAttributeFlags::empty(),
})?;
let mut context = device.create_context(&context_descriptor, None)?;
let gl = match connection.gl_api() {
GLApi::GL => unsafe { gl::GlFns::load_with(|s| device.get_proc_address(&context, s)) },
GLApi::GLES => unsafe {
gl::GlesFns::load_with(|s| device.get_proc_address(&context, s))
},
};
device.make_context_current(&context)?;
{
const SIZE: u32 = 16;
let framebuffer = Framebuffer::new(gl, PhysicalSize::new(SIZE, SIZE));
framebuffer.bind();
framebuffer
.gl
.clear_color(12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0, 78.0 / 255.0);
framebuffer.gl.clear(gl::COLOR_BUFFER_BIT);
let rect = Box2D::from_origin_and_size(Point2D::zero(), Size2D::new(SIZE, SIZE));
let img = framebuffer
.read_to_image(rect.to_i32())
.expect("Should have been able to read back image.");
assert_eq!(img.width(), SIZE);
assert_eq!(img.height(), SIZE);
let expected_pixel: Rgba<u8> = Rgba([12, 34, 56, 78]);
assert!(img.pixels().all(|&p| p == expected_pixel));
}
device.destroy_context(&mut context)?;
Ok(())
}
}