From ca95dad26f82e26e375251ca84892eccb428f8ba Mon Sep 17 00:00:00 2001 From: Harald Reingruber Date: Thu, 1 Dec 2022 21:31:35 +0100 Subject: [PATCH] Adapting existing tests to be run with wasm in browser --- wgpu/tests/buffer.rs | 4 + wgpu/tests/buffer_usages.rs | 4 + wgpu/tests/clear_texture.rs | 5 + wgpu/tests/common/mod.rs | 91 +++++++- wgpu/tests/device.rs | 5 + wgpu/tests/encoder.rs | 4 + wgpu/tests/poll.rs | 8 + wgpu/tests/queue_transfer.rs | 4 + wgpu/tests/resource_descriptor_accessor.rs | 4 + wgpu/tests/resource_error.rs | 5 + wgpu/tests/root.rs | 2 +- wgpu/tests/shader.wgsl | 11 - wgpu/tests/wasm.rs | 210 ------------------ wgpu/tests/write_texture.rs | 4 + wgpu/tests/zero_init_texture_after_discard.rs | 7 + 15 files changed, 137 insertions(+), 231 deletions(-) delete mode 100644 wgpu/tests/shader.wgsl delete mode 100644 wgpu/tests/wasm.rs diff --git a/wgpu/tests/buffer.rs b/wgpu/tests/buffer.rs index b1170765ec7..8370c66b964 100644 --- a/wgpu/tests/buffer.rs +++ b/wgpu/tests/buffer.rs @@ -1,5 +1,8 @@ use crate::common::{initialize_test, TestParameters, TestingContext}; use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) { let status = Arc::new(AtomicBool::new(false)); @@ -97,6 +100,7 @@ fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) } #[test] +// #[wasm_bindgen_test] // 'Buffer slices can not be empty', wgpu\src\lib.rs:2402:39 fn empty_buffer() { initialize_test( TestParameters::default(), diff --git a/wgpu/tests/buffer_usages.rs b/wgpu/tests/buffer_usages.rs index db734c108b9..a5b3a862cd6 100644 --- a/wgpu/tests/buffer_usages.rs +++ b/wgpu/tests/buffer_usages.rs @@ -2,10 +2,14 @@ use crate::common::{fail_if, initialize_test, TestParameters}; use wgt::BufferAddress; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); const BUFFER_SIZE: BufferAddress = 1234; #[test] +#[wasm_bindgen_test] fn buffer_usage() { fn try_create(enable_mappable_primary_buffers: bool, usages: &[(bool, &[wgpu::BufferUsages])]) { let mut parameters = TestParameters::default(); diff --git a/wgpu/tests/clear_texture.rs b/wgpu/tests/clear_texture.rs index abe86cac437..9a12786bc7b 100644 --- a/wgpu/tests/clear_texture.rs +++ b/wgpu/tests/clear_texture.rs @@ -1,6 +1,9 @@ use crate::common::{initialize_test, TestParameters, TestingContext}; +use wasm_bindgen_test::*; use wgpu::util::align_to; +wasm_bindgen_test_configure!(run_in_browser); + static TEXTURE_FORMATS_UNCOMPRESSED: &[wgpu::TextureFormat] = &[ wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Snorm, @@ -304,6 +307,7 @@ fn clear_texture_tests( } #[test] +#[wasm_bindgen_test] fn clear_texture_2d_uncompressed() { initialize_test( TestParameters::default().features(wgpu::Features::CLEAR_TEXTURE), @@ -315,6 +319,7 @@ fn clear_texture_2d_uncompressed() { } #[test] +#[wasm_bindgen_test] fn clear_texture_d32_s8() { initialize_test( TestParameters::default() diff --git a/wgpu/tests/common/mod.rs b/wgpu/tests/common/mod.rs index dedf80f5a32..9c3361ff3e9 100644 --- a/wgpu/tests/common/mod.rs +++ b/wgpu/tests/common/mod.rs @@ -5,10 +5,17 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use wgt::{Backends, DeviceDescriptor, DownlevelCapabilities, Features, Limits}; -use wgpu::{util, Adapter, Device, DownlevelFlags, Instance, Queue}; +use wgpu::{Adapter, Device, DownlevelFlags, Instance, Queue}; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web_sys::HtmlCanvasElement; pub mod image; +const CANVAS_ID: &str = "test-canvas"; + async fn initialize_device( adapter: &Adapter, features: Features, @@ -170,14 +177,7 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te // We don't actually care if it fails let _ = env_logger::try_init(); - let backend_bits = util::backend_bits_from_env().unwrap_or_else(Backends::all); - let instance = Instance::new(backend_bits); - let adapter = pollster::block_on(util::initialize_adapter_from_env_or_default( - &instance, - backend_bits, - None, - )) - .expect("could not find suitable adapter on the system"); + let adapter = initialize_adapter(); let adapter_info = adapter.get_info(); let adapter_lowercase_name = adapter_info.name.to_lowercase(); @@ -187,11 +187,18 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te let missing_features = parameters.required_features - adapter_features; if !missing_features.is_empty() { + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + delete_html_canvas(); + + // TODO: we probably should use log crate here for logging also to wasm console println!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features); return; } if !parameters.required_limits.check_limits(&adapter_limits) { + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + delete_html_canvas(); + println!("TEST SKIPPED: LIMIT TOO LOW"); return; } @@ -199,6 +206,9 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te let missing_downlevel_flags = parameters.required_downlevel_properties.flags - adapter_downlevel_capabilities.flags; if !missing_downlevel_flags.is_empty() { + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + delete_html_canvas(); + println!( "TEST SKIPPED: MISSING DOWNLEVEL FLAGS {:?}", missing_downlevel_flags @@ -209,6 +219,9 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te if adapter_downlevel_capabilities.shader_model < parameters.required_downlevel_properties.shader_model { + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + delete_html_canvas(); + println!( "TEST SKIPPED: LOW SHADER MODEL {:?}", adapter_downlevel_capabilities.shader_model @@ -273,6 +286,9 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te }); if let Some((reason, true)) = expected_failure_reason { + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + delete_html_canvas(); + println!("EXPECTED TEST FAILURE SKIPPED: {:?}", reason); return; } @@ -314,6 +330,63 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te } } +#[cfg(not(all(target_arch = "wasm32", feature = "webgl")))] +fn initialize_adapter() -> Adapter { + let backend_bits = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all); + let instance = Instance::new(backend_bits); + + pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default( + &instance, + backend_bits, + None, + )) + .expect("could not find suitable adapter on the system") +} + +#[cfg(all(target_arch = "wasm32", feature = "webgl"))] +fn initialize_adapter() -> Adapter { + // On wasm, append a canvas to the document body for initializing the adapter + delete_html_canvas(); // if there is a previous one + let canvas = create_html_canvas(); + + let instance = Instance::new(Backends::GL); + let surface = instance.create_surface_from_canvas(&canvas); + + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + })) + .expect("could not find suitable adapter on the system") +} + +#[cfg(all(target_arch = "wasm32", feature = "webgl"))] +fn create_html_canvas() -> HtmlCanvasElement { + return web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let body = doc.body().unwrap(); + let canvas = doc.create_element("Canvas").unwrap(); + canvas.set_id(CANVAS_ID); + body.append_child(&canvas).unwrap(); + canvas.dyn_into::().ok() + }) + .expect("couldn't append canvas to document body"); +} + +#[cfg(all(target_arch = "wasm32", feature = "webgl"))] +fn delete_html_canvas() { + web_sys::window() + .and_then(|win| win.document()) + .and_then(|document| { + if let Some(element) = document.get_element_by_id(CANVAS_ID) { + element.remove(); + } + Some(()) + }); +} + // Run some code in an error scope and assert that validation fails. pub fn fail(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { device.push_error_scope(wgpu::ErrorFilter::Validation); diff --git a/wgpu/tests/device.rs b/wgpu/tests/device.rs index 2763a6a21ad..8672716d46c 100644 --- a/wgpu/tests/device.rs +++ b/wgpu/tests/device.rs @@ -1,6 +1,11 @@ +use wasm_bindgen_test::*; + use crate::common::{initialize_test, TestParameters}; +wasm_bindgen_test_configure!(run_in_browser); + #[test] +#[wasm_bindgen_test] fn device_initialization() { initialize_test(TestParameters::default(), |_ctx| { // intentionally empty diff --git a/wgpu/tests/encoder.rs b/wgpu/tests/encoder.rs index f722f8e34bd..c27db703499 100644 --- a/wgpu/tests/encoder.rs +++ b/wgpu/tests/encoder.rs @@ -1,6 +1,10 @@ use crate::common::{initialize_test, TestParameters}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); #[test] +#[wasm_bindgen_test] fn drop_encoder() { initialize_test(TestParameters::default(), |ctx| { let encoder = ctx diff --git a/wgpu/tests/poll.rs b/wgpu/tests/poll.rs index 6113436d0bb..8aaa708d1b2 100644 --- a/wgpu/tests/poll.rs +++ b/wgpu/tests/poll.rs @@ -7,6 +7,9 @@ use wgpu::{ }; use crate::common::{initialize_test, TestParameters, TestingContext}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer { let buffer = ctx.device.create_buffer(&BufferDescriptor { @@ -53,6 +56,7 @@ fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer { } #[test] +#[wasm_bindgen_test] fn wait() { initialize_test(TestParameters::default().skip(), |ctx| { let cmd_buf = generate_dummy_work(&ctx); @@ -63,6 +67,7 @@ fn wait() { } #[test] +#[wasm_bindgen_test] fn double_wait() { initialize_test(TestParameters::default().skip(), |ctx| { let cmd_buf = generate_dummy_work(&ctx); @@ -74,6 +79,7 @@ fn double_wait() { } #[test] +#[wasm_bindgen_test] fn wait_on_submission() { initialize_test(TestParameters::default().skip(), |ctx| { let cmd_buf = generate_dummy_work(&ctx); @@ -84,6 +90,7 @@ fn wait_on_submission() { } #[test] +#[wasm_bindgen_test] fn double_wait_on_submission() { initialize_test(TestParameters::default().skip(), |ctx| { let cmd_buf = generate_dummy_work(&ctx); @@ -95,6 +102,7 @@ fn double_wait_on_submission() { } #[test] +#[wasm_bindgen_test] fn wait_out_of_order() { initialize_test(TestParameters::default().skip(), |ctx| { let cmd_buf1 = generate_dummy_work(&ctx); diff --git a/wgpu/tests/queue_transfer.rs b/wgpu/tests/queue_transfer.rs index 7724c291cdd..4b3c0f111aa 100644 --- a/wgpu/tests/queue_transfer.rs +++ b/wgpu/tests/queue_transfer.rs @@ -3,8 +3,12 @@ use std::num::NonZeroU32; use crate::common::{fail, initialize_test, TestParameters}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); #[test] +#[wasm_bindgen_test] fn queue_write_texture_overflow() { initialize_test(TestParameters::default(), |ctx| { let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { diff --git a/wgpu/tests/resource_descriptor_accessor.rs b/wgpu/tests/resource_descriptor_accessor.rs index f43a996a404..051a46706ce 100644 --- a/wgpu/tests/resource_descriptor_accessor.rs +++ b/wgpu/tests/resource_descriptor_accessor.rs @@ -1,7 +1,11 @@ use crate::common::{initialize_test, TestParameters}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); /// Buffer's size and usage can be read back. #[test] +#[wasm_bindgen_test] fn buffer_size_and_usage() { initialize_test(TestParameters::default(), |ctx| { let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { diff --git a/wgpu/tests/resource_error.rs b/wgpu/tests/resource_error.rs index 81d50e5800c..06319982735 100644 --- a/wgpu/tests/resource_error.rs +++ b/wgpu/tests/resource_error.rs @@ -1,6 +1,10 @@ use crate::common::{fail, initialize_test, valid, TestParameters}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); #[test] +#[wasm_bindgen_test] fn bad_buffer() { // Create a buffer with bad parameters and call a few methods. // Validation should fail but there should be not panic. @@ -24,6 +28,7 @@ fn bad_buffer() { } #[test] +#[wasm_bindgen_test] fn bad_texture() { // Create a texture with bad parameters and call a few methods. // Validation should fail but there should be not panic. diff --git a/wgpu/tests/root.rs b/wgpu/tests/root.rs index 8d5678b3f7b..eae0332fcc8 100644 --- a/wgpu/tests/root.rs +++ b/wgpu/tests/root.rs @@ -1,6 +1,7 @@ // All files containing tests mod common; +mod buffer; mod buffer_copy; mod buffer_usages; mod clear_texture; @@ -16,6 +17,5 @@ mod shader; mod shader_primitive_index; mod texture_bounds; mod vertex_indices; -mod wasm; mod write_texture; mod zero_init_texture_after_discard; diff --git a/wgpu/tests/shader.wgsl b/wgpu/tests/shader.wgsl deleted file mode 100644 index f84ccfe94da..00000000000 --- a/wgpu/tests/shader.wgsl +++ /dev/null @@ -1,11 +0,0 @@ -@vertex -fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { - let x = f32(i32(in_vertex_index) - 1); - let y = f32(i32(in_vertex_index & 1u) * 2 - 1); - return vec4(x, y, 0.0, 1.0); -} - -@fragment -fn fs_main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} diff --git a/wgpu/tests/wasm.rs b/wgpu/tests/wasm.rs deleted file mode 100644 index ef83b0a4886..00000000000 --- a/wgpu/tests/wasm.rs +++ /dev/null @@ -1,210 +0,0 @@ -#![cfg(target_arch = "wasm32")] - -use std::borrow::Cow; - -use wasm_bindgen_test::*; -use winit::{ - event::Event, - event_loop::{ControlFlow, EventLoop}, - platform::web::{EventLoopExtWebSys, WindowExtWebSys}, - window::Window, -}; - -use wasm_bindgen::JsCast; -use web_sys::HtmlCanvasElement; -use wgpu::{Adapter, Device, Queue, Surface}; - -wasm_bindgen_test_configure!(run_in_browser); - -#[wasm_bindgen_test] -async fn test_triangle_rendering() { - render_triangle(|window| { - let size = window.inner_size(); - - // fetch triangle pixel - let result = read_pixel( - window.canvas(), - (size.width as f32 * 0.5) as i32, - (size.height as f32 * 0.5) as i32, - ); - let red = [255, 0, 0, 255_u8]; - assert_eq!(result, red); - - // fetch background pixel - let result = read_pixel( - window.canvas(), - (size.width as f32 * 0.1) as i32, - (size.height as f32 * 0.9) as i32, - ); - let green = [0, 255, 0, 255_u8]; - assert_eq!(result, green); - }) - .await; -} - -async fn render_triangle(assert_rendering_result: F) -where - F: Fn(&Window) + 'static, -{ - let (window, event_loop, surface, adapter, device, queue): ( - Window, - EventLoop<()>, - Surface, - Adapter, - Device, - Queue, - ) = init().await; - - let size = window.inner_size(); - - // Load the shaders from disk - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let swapchain_format = surface.get_supported_formats(&adapter)[0]; - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(swapchain_format.into())], - }), - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: surface.get_supported_alpha_modes(&adapter)[0], - }; - - surface.configure(&device, &config); - - event_loop.spawn(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - if let Event::RedrawRequested(_) = event { - let frame = surface - .get_current_texture() - .expect("Failed to acquire next swap chain texture"); - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - rpass.set_pipeline(&render_pipeline); - rpass.draw(0..3, 0..1); - } - - queue.submit(Some(encoder.finish())); - frame.present(); - - assert_rendering_result(&window); - - *control_flow = ControlFlow::Exit; - } - }); -} - -fn read_pixel(canvas: HtmlCanvasElement, x: i32, y: i32) -> [u8; 4] { - let mut result = [0_u8; 4]; - let context = canvas - .get_context("webgl2") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); - - context - .read_pixels_with_u8_array_and_dst_offset( - x, - y, - 1, - 1, - web_sys::WebGl2RenderingContext::RGBA, - web_sys::WebGl2RenderingContext::UNSIGNED_BYTE, - &mut result, - 0, - ) - .unwrap(); - result -} - -async fn init() -> (Window, EventLoop<()>, Surface, Adapter, Device, Queue) { - let event_loop = EventLoop::new(); - let window = winit::window::Window::new(&event_loop).unwrap(); - - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("could not initialize logger"); - // On wasm, append the canvas to the document body - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.body()) - .and_then(|body| { - body.append_child(&web_sys::Element::from(window.canvas())) - .ok() - }) - .expect("couldn't append canvas to document body"); - - let instance = wgpu::Instance::new(wgpu::Backends::GL); - let surface = unsafe { instance.create_surface(&window) }; - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - force_fallback_adapter: false, - // Request an adapter which can render to our surface - compatible_surface: Some(&surface), - }) - .await - .expect("Failed to find an appropriate adapter"); - - // Create the logical device and command queue - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. - limits: wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()), - }, - None, - ) - .await - .expect("Failed to create device"); - - (window, event_loop, surface, adapter, device, queue) -} diff --git a/wgpu/tests/write_texture.rs b/wgpu/tests/write_texture.rs index 742e97d8187..b13d94960d0 100644 --- a/wgpu/tests/write_texture.rs +++ b/wgpu/tests/write_texture.rs @@ -3,8 +3,12 @@ use crate::common::{initialize_test, TestParameters}; use std::num::NonZeroU32; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); #[test] +#[wasm_bindgen_test] fn write_texture_subset() { let size = 256; let parameters = TestParameters::default().backend_failure(wgpu::Backends::DX12); diff --git a/wgpu/tests/zero_init_texture_after_discard.rs b/wgpu/tests/zero_init_texture_after_discard.rs index 6043d0388f6..05a52b9f9a2 100644 --- a/wgpu/tests/zero_init_texture_after_discard.rs +++ b/wgpu/tests/zero_init_texture_after_discard.rs @@ -1,9 +1,13 @@ use std::num::NonZeroU32; use crate::common::{initialize_test, TestParameters}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); // Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in after submit of the encoder. #[test] +#[wasm_bindgen_test] fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after_submit() { initialize_test(TestParameters::default(), |ctx| { let (texture, readback_buffer) = @@ -39,6 +43,7 @@ fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after // Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in the same encoder to a buffer. #[test] +#[wasm_bindgen_test] fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { initialize_test(TestParameters::default(), |ctx| { let (texture, readback_buffer) = @@ -67,6 +72,7 @@ fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_sa } #[test] +#[wasm_bindgen_test] #[allow(clippy::single_element_loop)] fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { initialize_test( @@ -109,6 +115,7 @@ fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_sa } #[test] +#[wasm_bindgen_test] fn discarding_either_depth_or_stencil_aspect() { initialize_test(TestParameters::default(), |ctx| { let (texture, _) = create_white_texture_and_readback_buffer(