wgpu_examples/render_to_texture/
mod.rs

1#[cfg(not(target_arch = "wasm32"))]
2use crate::utils::output_image_native;
3#[cfg(target_arch = "wasm32")]
4use crate::utils::output_image_wasm;
5
6const TEXTURE_DIMS: (usize, usize) = (512, 512);
7
8async fn run(_path: Option<String>) {
9    // This will later store the raw pixel value data locally. We'll create it now as
10    // a convenient size reference.
11    let mut texture_data = Vec::<u8>::with_capacity(TEXTURE_DIMS.0 * TEXTURE_DIMS.1 * 4);
12
13    let instance = wgpu::Instance::default();
14    let adapter = instance
15        .request_adapter(&wgpu::RequestAdapterOptions::default())
16        .await
17        .unwrap();
18    let (device, queue) = adapter
19        .request_device(&wgpu::DeviceDescriptor {
20            label: None,
21            required_features: wgpu::Features::empty(),
22            required_limits: wgpu::Limits::downlevel_defaults(),
23            experimental_features: wgpu::ExperimentalFeatures::disabled(),
24            memory_hints: wgpu::MemoryHints::MemoryUsage,
25            trace: wgpu::Trace::Off,
26        })
27        .await
28        .unwrap();
29
30    let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
31
32    let render_target = device.create_texture(&wgpu::TextureDescriptor {
33        label: None,
34        size: wgpu::Extent3d {
35            width: TEXTURE_DIMS.0 as u32,
36            height: TEXTURE_DIMS.1 as u32,
37            depth_or_array_layers: 1,
38        },
39        mip_level_count: 1,
40        sample_count: 1,
41        dimension: wgpu::TextureDimension::D2,
42        format: wgpu::TextureFormat::Rgba8UnormSrgb,
43        usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
44        view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
45    });
46    let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
47        label: None,
48        size: texture_data.capacity() as u64,
49        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
50        mapped_at_creation: false,
51    });
52
53    let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
54        label: None,
55        layout: None,
56        vertex: wgpu::VertexState {
57            module: &shader,
58            entry_point: Some("vs_main"),
59            compilation_options: Default::default(),
60            buffers: &[],
61        },
62        fragment: Some(wgpu::FragmentState {
63            module: &shader,
64            entry_point: Some("fs_main"),
65            compilation_options: Default::default(),
66            targets: &[Some(wgpu::TextureFormat::Rgba8UnormSrgb.into())],
67        }),
68        primitive: wgpu::PrimitiveState::default(),
69        depth_stencil: None,
70        multisample: wgpu::MultisampleState::default(),
71        multiview_mask: None,
72        cache: None,
73    });
74
75    log::info!("Wgpu context set up.");
76
77    //-----------------------------------------------
78
79    let texture_view = render_target.create_view(&wgpu::TextureViewDescriptor::default());
80
81    let mut command_encoder =
82        device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
83    {
84        let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
85            label: None,
86            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
87                view: &texture_view,
88                depth_slice: None,
89                resolve_target: None,
90                ops: wgpu::Operations {
91                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
92                    store: wgpu::StoreOp::Store,
93                },
94            })],
95            depth_stencil_attachment: None,
96            occlusion_query_set: None,
97            timestamp_writes: None,
98            multiview_mask: None,
99        });
100        render_pass.set_pipeline(&pipeline);
101        render_pass.draw(0..3, 0..1);
102    }
103    // The texture now contains our rendered image
104    command_encoder.copy_texture_to_buffer(
105        wgpu::TexelCopyTextureInfo {
106            texture: &render_target,
107            mip_level: 0,
108            origin: wgpu::Origin3d::ZERO,
109            aspect: wgpu::TextureAspect::All,
110        },
111        wgpu::TexelCopyBufferInfo {
112            buffer: &output_staging_buffer,
113            layout: wgpu::TexelCopyBufferLayout {
114                offset: 0,
115                // This needs to be a multiple of 256. Normally we would need to pad
116                // it but we here know it will work out anyways.
117                bytes_per_row: Some((TEXTURE_DIMS.0 * 4) as u32),
118                rows_per_image: Some(TEXTURE_DIMS.1 as u32),
119            },
120        },
121        wgpu::Extent3d {
122            width: TEXTURE_DIMS.0 as u32,
123            height: TEXTURE_DIMS.1 as u32,
124            depth_or_array_layers: 1,
125        },
126    );
127    queue.submit(Some(command_encoder.finish()));
128    log::info!("Commands submitted.");
129
130    //-----------------------------------------------
131
132    // Time to get our image.
133    let buffer_slice = output_staging_buffer.slice(..);
134    let (sender, receiver) = flume::bounded(1);
135    buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap());
136    device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
137    receiver.recv_async().await.unwrap().unwrap();
138    log::info!("Output buffer mapped.");
139    {
140        let view = buffer_slice.get_mapped_range();
141        texture_data.extend_from_slice(&view[..]);
142    }
143    log::info!("Image data copied to local.");
144    output_staging_buffer.unmap();
145
146    #[cfg(not(target_arch = "wasm32"))]
147    output_image_native(texture_data.to_vec(), TEXTURE_DIMS, _path.unwrap());
148    #[cfg(target_arch = "wasm32")]
149    output_image_wasm(texture_data.to_vec(), TEXTURE_DIMS);
150    log::info!("Done.");
151}
152
153pub fn main() {
154    #[cfg(not(target_arch = "wasm32"))]
155    {
156        env_logger::builder()
157            .filter_level(log::LevelFilter::Info)
158            .format_timestamp_nanos()
159            .init();
160
161        let path = std::env::args()
162            .nth(2)
163            .unwrap_or_else(|| "please_don't_git_push_me.png".to_string());
164        pollster::block_on(run(Some(path)));
165    }
166    #[cfg(target_arch = "wasm32")]
167    {
168        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
169        console_log::init_with_level(log::Level::Info).expect("could not initialize logger");
170        wasm_bindgen_futures::spawn_local(run(None));
171    }
172}