feat: add rendering export progress bar
This commit is contained in:
+89
-22
@@ -4,7 +4,9 @@ use eframe::CreationContext;
|
|||||||
use eframe::egui_wgpu;
|
use eframe::egui_wgpu;
|
||||||
use eframe::egui_wgpu::wgpu;
|
use eframe::egui_wgpu::wgpu;
|
||||||
|
|
||||||
use crate::fractal::{FractalCallback, FractalRenderer, MAX_REF_POINTS, ShareState, Uniforms};
|
use crate::fractal::{
|
||||||
|
ExportRender, FractalCallback, FractalRenderer, MAX_REF_POINTS, ShareState, Uniforms,
|
||||||
|
};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::fractal::{compute_mandelbrot_reference, compute_reference};
|
use crate::fractal::{compute_mandelbrot_reference, compute_reference};
|
||||||
use crate::view::{
|
use crate::view::{
|
||||||
@@ -502,16 +504,15 @@ impl FractalApp {
|
|||||||
|
|
||||||
let device = rs.device.clone();
|
let device = rs.device.clone();
|
||||||
let queue = rs.queue.clone();
|
let queue = rs.queue.clone();
|
||||||
let (buffer, padded_bpr, swap) = {
|
let (pipeline, bind_group_layout, format) = {
|
||||||
let guard = rs.renderer.read();
|
let guard = rs.renderer.read();
|
||||||
let Some(renderer) = guard.callback_resources.get::<FractalRenderer>() else {
|
let Some(renderer) = guard.callback_resources.get::<FractalRenderer>() else {
|
||||||
self.status = Some("export unavailable".into());
|
self.status = Some("export unavailable".into());
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
renderer.upload_reference(&queue, &self.reference);
|
renderer.export_handles()
|
||||||
let (buffer, bpr) = renderer.render_to_readback(&device, &queue, w, h, uniforms);
|
|
||||||
(buffer, bpr, renderer.needs_rb_swap())
|
|
||||||
};
|
};
|
||||||
|
let reference = Arc::clone(&self.reference);
|
||||||
|
|
||||||
let shared = Arc::new(Mutex::new(ExportShared {
|
let shared = Arc::new(Mutex::new(ExportShared {
|
||||||
fraction: 0.0,
|
fraction: 0.0,
|
||||||
@@ -521,14 +522,42 @@ impl FractalApp {
|
|||||||
self.status = None;
|
self.status = None;
|
||||||
self.export = Some(Arc::clone(&shared));
|
self.export = Some(Arc::clone(&shared));
|
||||||
|
|
||||||
|
// Progress budget: rendering fills [0, RENDER_END], encoding the rest.
|
||||||
|
const RENDER_END: f32 = 0.6;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let name = std::env::var("MANDEL_EXPORT_PATH")
|
let name = std::env::var("MANDEL_EXPORT_PATH")
|
||||||
.unwrap_or_else(|_| format!("fractal-{}.png", unix_timestamp()));
|
.unwrap_or_else(|_| format!("fractal-{}.png", unix_timestamp()));
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// Wait for the GPU render + copy, then read the mapped bytes.
|
let er = ExportRender::new(
|
||||||
|
&device,
|
||||||
|
&queue,
|
||||||
|
pipeline,
|
||||||
|
&bind_group_layout,
|
||||||
|
format,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
uniforms,
|
||||||
|
reference.as_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render the image tile by tile, waiting for each so progress
|
||||||
|
// reflects real GPU work.
|
||||||
|
for t in 0..er.tiles {
|
||||||
|
er.render_tile(&device, &queue, t);
|
||||||
|
let _ = device.poll(wgpu::PollType::Wait {
|
||||||
|
submission_index: None,
|
||||||
|
timeout: None,
|
||||||
|
});
|
||||||
|
let done = (t + 1) as f32 / er.tiles as f32;
|
||||||
|
set_progress(&shared, "Rendering", RENDER_END * done);
|
||||||
|
}
|
||||||
|
er.copy_to_readback(&device, &queue);
|
||||||
|
|
||||||
|
// Wait for the copy, then read the mapped bytes.
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
buffer.slice(..).map_async(wgpu::MapMode::Read, move |res| {
|
er.readback().slice(..).map_async(wgpu::MapMode::Read, move |res| {
|
||||||
let _ = tx.send(res);
|
let _ = tx.send(res);
|
||||||
});
|
});
|
||||||
let _ = device.poll(wgpu::PollType::Wait {
|
let _ = device.poll(wgpu::PollType::Wait {
|
||||||
@@ -537,20 +566,26 @@ impl FractalApp {
|
|||||||
});
|
});
|
||||||
let _ = rx.recv();
|
let _ = rx.recv();
|
||||||
|
|
||||||
set_progress(&shared, "Encoding", 0.02);
|
set_progress(&shared, "Encoding", RENDER_END);
|
||||||
let png = {
|
let png = {
|
||||||
let data = buffer
|
let data = er
|
||||||
|
.readback()
|
||||||
.slice(..)
|
.slice(..)
|
||||||
.get_mapped_range()
|
.get_mapped_range()
|
||||||
.expect("map readback buffer");
|
.expect("map readback buffer");
|
||||||
let sh = Arc::clone(&shared);
|
let sh = Arc::clone(&shared);
|
||||||
crate::fractal::encode_png_with_progress(&data, w, h, padded_bpr, swap, |f| {
|
crate::fractal::encode_png_with_progress(
|
||||||
set_progress(&sh, "Encoding", 0.02 + 0.93 * f);
|
&data,
|
||||||
})
|
er.width,
|
||||||
|
er.height,
|
||||||
|
er.padded_bpr,
|
||||||
|
er.swap_rb,
|
||||||
|
|f| set_progress(&sh, "Encoding", RENDER_END + (0.97 - RENDER_END) * f),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
buffer.unmap();
|
er.readback().unmap();
|
||||||
|
|
||||||
set_progress(&shared, "Saving", 0.97);
|
set_progress(&shared, "Saving", 0.98);
|
||||||
let result = std::fs::write(&name, &png)
|
let result = std::fs::write(&name, &png)
|
||||||
.map(|_| format!("saved {name} ({w}×{h})"))
|
.map(|_| format!("saved {name} ({w}×{h})"))
|
||||||
.map_err(|e| format!("save failed: {e}"));
|
.map_err(|e| format!("save failed: {e}"));
|
||||||
@@ -560,26 +595,58 @@ impl FractalApp {
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
let er = ExportRender::new(
|
||||||
|
&device,
|
||||||
|
&queue,
|
||||||
|
pipeline,
|
||||||
|
&bind_group_layout,
|
||||||
|
format,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
uniforms,
|
||||||
|
reference.as_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render tile by tile, awaiting each submission so the browser
|
||||||
|
// executes it and the UI can repaint between tiles.
|
||||||
|
for t in 0..er.tiles {
|
||||||
|
er.render_tile(&device, &queue, t);
|
||||||
|
let (tx, rx) = futures_channel::oneshot::channel();
|
||||||
|
queue.on_submitted_work_done(move || {
|
||||||
|
let _ = tx.send(());
|
||||||
|
});
|
||||||
|
let _ = rx.await;
|
||||||
|
let done = (t + 1) as f32 / er.tiles as f32;
|
||||||
|
set_progress(&shared, "Rendering", RENDER_END * done);
|
||||||
|
}
|
||||||
|
er.copy_to_readback(&device, &queue);
|
||||||
|
|
||||||
let (tx, rx) = futures_channel::oneshot::channel();
|
let (tx, rx) = futures_channel::oneshot::channel();
|
||||||
buffer.slice(..).map_async(wgpu::MapMode::Read, move |res| {
|
er.readback().slice(..).map_async(wgpu::MapMode::Read, move |res| {
|
||||||
let _ = tx.send(res);
|
let _ = tx.send(res);
|
||||||
});
|
});
|
||||||
let _ = rx.await;
|
let _ = rx.await;
|
||||||
|
|
||||||
set_progress(&shared, "Encoding", 0.02);
|
set_progress(&shared, "Encoding", RENDER_END);
|
||||||
let png = {
|
let png = {
|
||||||
let data = buffer
|
let data = er
|
||||||
|
.readback()
|
||||||
.slice(..)
|
.slice(..)
|
||||||
.get_mapped_range()
|
.get_mapped_range()
|
||||||
.expect("map readback buffer");
|
.expect("map readback buffer");
|
||||||
let sh = Arc::clone(&shared);
|
let sh = Arc::clone(&shared);
|
||||||
crate::fractal::encode_png_with_progress(&data, w, h, padded_bpr, swap, |f| {
|
crate::fractal::encode_png_with_progress(
|
||||||
set_progress(&sh, "Encoding", 0.02 + 0.93 * f);
|
&data,
|
||||||
})
|
er.width,
|
||||||
|
er.height,
|
||||||
|
er.padded_bpr,
|
||||||
|
er.swap_rb,
|
||||||
|
|f| set_progress(&sh, "Encoding", RENDER_END + (0.97 - RENDER_END) * f),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
buffer.unmap();
|
er.readback().unmap();
|
||||||
|
|
||||||
set_progress(&shared, "Saving", 0.97);
|
set_progress(&shared, "Saving", 0.98);
|
||||||
web_download_png(&png, "fractal.png");
|
web_download_png(&png, "fractal.png");
|
||||||
finish_export(&shared, Ok(format!("downloaded {w}×{h}")));
|
finish_export(&shared, Ok(format!("downloaded {w}×{h}")));
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-1
@@ -7,6 +7,7 @@ pub mod share;
|
|||||||
|
|
||||||
pub use reference::{compute_mandelbrot_reference, compute_reference};
|
pub use reference::{compute_mandelbrot_reference, compute_reference};
|
||||||
pub use renderer::{
|
pub use renderer::{
|
||||||
FractalCallback, FractalRenderer, MAX_REF_POINTS, Uniforms, encode_png_with_progress,
|
ExportRender, FractalCallback, FractalRenderer, MAX_REF_POINTS, Uniforms,
|
||||||
|
encode_png_with_progress,
|
||||||
};
|
};
|
||||||
pub use share::ShareState;
|
pub use share::ShareState;
|
||||||
|
|||||||
+143
-38
@@ -61,6 +61,7 @@ struct RenderedState {
|
|||||||
|
|
||||||
pub struct FractalRenderer {
|
pub struct FractalRenderer {
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group_layout: wgpu::BindGroupLayout,
|
||||||
uniform_buffer: wgpu::Buffer,
|
uniform_buffer: wgpu::Buffer,
|
||||||
ref_buffer: wgpu::Buffer,
|
ref_buffer: wgpu::Buffer,
|
||||||
bind_group: wgpu::BindGroup,
|
bind_group: wgpu::BindGroup,
|
||||||
@@ -242,6 +243,7 @@ impl FractalRenderer {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
|
bind_group_layout,
|
||||||
uniform_buffer,
|
uniform_buffer,
|
||||||
ref_buffer,
|
ref_buffer,
|
||||||
bind_group,
|
bind_group,
|
||||||
@@ -306,37 +308,85 @@ impl FractalRenderer {
|
|||||||
self.rendered = None;
|
self.rendered = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a reference orbit to the storage buffer (used by PNG export to
|
/// Handles needed to build a standalone [`ExportRender`] off the UI thread:
|
||||||
/// guarantee the buffer is current before an offscreen render).
|
/// the (immutable) pipeline and its bind-group layout, plus the target
|
||||||
pub fn upload_reference(&self, queue: &wgpu::Queue, points: &[[f32; 2]]) {
|
/// format. Cloned so the caller can drop the render-state lock before use.
|
||||||
let count = points.len().min(MAX_REF_POINTS);
|
pub fn export_handles(&self) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout, wgpu::TextureFormat) {
|
||||||
if count > 0 {
|
(
|
||||||
queue.write_buffer(&self.ref_buffer, 0, bytemuck::cast_slice(&points[..count]));
|
self.pipeline.clone(),
|
||||||
}
|
self.bind_group_layout.clone(),
|
||||||
}
|
|
||||||
|
|
||||||
/// True if the render target stores bytes as BGRA (so a PNG needs R/B
|
|
||||||
/// swapped). Surfaces are usually `Bgra8UnormSrgb`.
|
|
||||||
pub fn needs_rb_swap(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self.target_format,
|
self.target_format,
|
||||||
wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the current fractal (using `uniforms` and the already-uploaded
|
/// A self-contained render of one export image. It owns its own uniform and
|
||||||
/// reference orbit) into an offscreen texture at `width`x`height`, then copy
|
/// reference buffers (a snapshot of the view at export time), so it is unaffected
|
||||||
/// it into a mappable buffer. Returns the buffer and its padded row stride.
|
/// by panning/zooming on the main thread, and can run on a background thread.
|
||||||
/// The caller maps the buffer (blocking on native, async on web).
|
/// The image is rendered in horizontal tiles so progress can be reported as the
|
||||||
pub fn render_to_readback(
|
/// GPU works through it.
|
||||||
&self,
|
pub struct ExportRender {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
texture: wgpu::Texture,
|
||||||
|
view: wgpu::TextureView,
|
||||||
|
readback: wgpu::Buffer,
|
||||||
|
/// Padded bytes-per-row of the readback buffer.
|
||||||
|
pub padded_bpr: u32,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
/// Number of horizontal tiles the render is split into.
|
||||||
|
pub tiles: u32,
|
||||||
|
pub swap_rb: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExportRender {
|
||||||
|
/// Allocate the export's dedicated GPU resources and upload the snapshot.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group_layout: &wgpu::BindGroupLayout,
|
||||||
|
target_format: wgpu::TextureFormat,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
uniforms: Uniforms,
|
uniforms: Uniforms,
|
||||||
) -> (wgpu::Buffer, u32) {
|
reference: &[[f32; 2]],
|
||||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
|
) -> Self {
|
||||||
|
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("export uniforms"),
|
||||||
|
size: std::mem::size_of::<Uniforms>() as u64,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
queue.write_buffer(&uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
|
||||||
|
|
||||||
|
let count = reference.len().min(MAX_REF_POINTS);
|
||||||
|
let ref_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("export reference orbit"),
|
||||||
|
size: (count.max(1) * std::mem::size_of::<[f32; 2]>()) as u64,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
if count > 0 {
|
||||||
|
queue.write_buffer(&ref_buffer, 0, bytemuck::cast_slice(&reference[..count]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("export bind group"),
|
||||||
|
layout: bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: ref_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: Some("export target"),
|
label: Some("export target"),
|
||||||
@@ -348,16 +398,14 @@ impl FractalRenderer {
|
|||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: self.target_format,
|
format: target_format,
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
});
|
});
|
||||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||||
let unpadded_bpr = width * 4;
|
let padded_bpr = (width * 4).div_ceil(align) * align;
|
||||||
let padded_bpr = unpadded_bpr.div_ceil(align) * align;
|
|
||||||
|
|
||||||
let readback = device.create_buffer(&wgpu::BufferDescriptor {
|
let readback = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("export readback"),
|
label: Some("export readback"),
|
||||||
size: (padded_bpr * height) as u64,
|
size: (padded_bpr * height) as u64,
|
||||||
@@ -365,18 +413,62 @@ impl FractalRenderer {
|
|||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ~128px bands, kept to a sane range so progress is smooth without too
|
||||||
|
// many submissions.
|
||||||
|
let tiles = (height / 128).clamp(8, 64).min(height.max(1));
|
||||||
|
|
||||||
|
let swap_rb = matches!(
|
||||||
|
target_format,
|
||||||
|
wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
texture,
|
||||||
|
view,
|
||||||
|
readback,
|
||||||
|
padded_bpr,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
tiles,
|
||||||
|
swap_rb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pixel row range `[y0, y1)` covered by tile `t`.
|
||||||
|
fn tile_rows(&self, t: u32) -> (u32, u32) {
|
||||||
|
let band = self.height.div_ceil(self.tiles);
|
||||||
|
let y0 = (t * band).min(self.height);
|
||||||
|
let y1 = (y0 + band).min(self.height);
|
||||||
|
(y0, y1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render one horizontal tile into the export texture and submit it. Tile 0
|
||||||
|
/// clears the whole attachment; later tiles preserve earlier ones.
|
||||||
|
pub fn render_tile(&self, device: &wgpu::Device, queue: &wgpu::Queue, t: u32) {
|
||||||
|
let (y0, y1) = self.tile_rows(t);
|
||||||
|
if y1 <= y0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let load = if t == 0 {
|
||||||
|
wgpu::LoadOp::Clear(wgpu::Color::BLACK)
|
||||||
|
} else {
|
||||||
|
wgpu::LoadOp::Load
|
||||||
|
};
|
||||||
|
|
||||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
label: Some("export"),
|
label: Some("export tile"),
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("export pass"),
|
label: Some("export tile pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &view,
|
view: &self.view,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
load,
|
||||||
store: wgpu::StoreOp::Store,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
@@ -385,35 +477,48 @@ impl FractalRenderer {
|
|||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
// Full-viewport triangle (so pixel→plane mapping matches the whole
|
||||||
|
// image), scissored to this tile's rows.
|
||||||
|
pass.set_scissor_rect(0, y0, self.width, y1 - y0);
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
pass.draw(0..3, 0..1);
|
pass.draw(0..3, 0..1);
|
||||||
}
|
}
|
||||||
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy the finished texture into the mappable readback buffer and submit.
|
||||||
|
pub fn copy_to_readback(&self, device: &wgpu::Device, queue: &wgpu::Queue) {
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("export copy"),
|
||||||
|
});
|
||||||
encoder.copy_texture_to_buffer(
|
encoder.copy_texture_to_buffer(
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: &texture,
|
texture: &self.texture,
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: wgpu::Origin3d::ZERO,
|
origin: wgpu::Origin3d::ZERO,
|
||||||
aspect: wgpu::TextureAspect::All,
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
wgpu::TexelCopyBufferInfo {
|
wgpu::TexelCopyBufferInfo {
|
||||||
buffer: &readback,
|
buffer: &self.readback,
|
||||||
layout: wgpu::TexelCopyBufferLayout {
|
layout: wgpu::TexelCopyBufferLayout {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
bytes_per_row: Some(padded_bpr),
|
bytes_per_row: Some(self.padded_bpr),
|
||||||
rows_per_image: Some(height),
|
rows_per_image: Some(self.height),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wgpu::Extent3d {
|
wgpu::Extent3d {
|
||||||
width,
|
width: self.width,
|
||||||
height,
|
height: self.height,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
queue.submit(std::iter::once(encoder.finish()));
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
(readback, padded_bpr)
|
}
|
||||||
|
|
||||||
|
/// The mappable readback buffer (valid after [`copy_to_readback`]).
|
||||||
|
pub fn readback(&self) -> &wgpu::Buffer {
|
||||||
|
&self.readback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user