mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Implement screenshots
This commit is contained in:
parent
2084e07ab4
commit
5df5f910c2
16
assets/voxygen/shaders/blit-frag.glsl
Normal file
16
assets/voxygen/shaders/blit-frag.glsl
Normal file
@ -0,0 +1,16 @@
|
||||
#version 420 core
|
||||
|
||||
layout(set = 0, binding = 0)
|
||||
uniform texture2D t_src_color;
|
||||
layout(set = 0, binding = 1)
|
||||
uniform sampler s_src_color;
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
|
||||
layout(location = 0) out vec4 tgt_color;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture(sampler2D(t_src_color, s_src_color), uv);
|
||||
|
||||
tgt_color = vec4(color.rgb, 1);
|
||||
}
|
15
assets/voxygen/shaders/blit-vert.glsl
Normal file
15
assets/voxygen/shaders/blit-vert.glsl
Normal file
@ -0,0 +1,15 @@
|
||||
#version 420 core
|
||||
|
||||
layout(location = 0) out vec2 uv;
|
||||
|
||||
void main() {
|
||||
// Generate fullscreen triangle
|
||||
vec2 v_pos = vec2(
|
||||
float(gl_VertexIndex / 2) * 4.0 - 1.0,
|
||||
float(gl_VertexIndex % 2) * 4.0 - 1.0
|
||||
);
|
||||
|
||||
uv = (v_pos * vec2(1.0, -1.0) + 1.0) * 0.5;
|
||||
|
||||
gl_Position = vec4(v_pos, 0.0, 1.0);
|
||||
}
|
129
voxygen/src/render/pipelines/blit.rs
Normal file
129
voxygen/src/render/pipelines/blit.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use super::{
|
||||
super::{AaMode, Consts},
|
||||
GlobalsLayouts,
|
||||
};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use vek::*;
|
||||
|
||||
pub struct BindGroup {
|
||||
pub(in super::super) bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
pub struct BlitLayout {
|
||||
pub layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl BlitLayout {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
Self {
|
||||
layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: None,
|
||||
entries: &[
|
||||
// Color source
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler {
|
||||
filtering: true,
|
||||
comparison: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
src_color: &wgpu::TextureView,
|
||||
sampler: &wgpu::Sampler,
|
||||
) -> BindGroup {
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &self.layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(src_color),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
BindGroup { bind_group }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlitPipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl BlitPipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
vs_module: &wgpu::ShaderModule,
|
||||
fs_module: &wgpu::ShaderModule,
|
||||
sc_desc: &wgpu::SwapChainDescriptor,
|
||||
layout: &BlitLayout,
|
||||
) -> Self {
|
||||
common_base::span!(_guard, "BlitPipeline::new");
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Blit pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&layout.layout],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Blit pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: vs_module,
|
||||
entry_point: "main",
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: fs_module,
|
||||
entry_point: "main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: sc_desc.format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
}),
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod blit;
|
||||
pub mod clouds;
|
||||
pub mod figure;
|
||||
pub mod fluid;
|
||||
|
@ -2,6 +2,7 @@ mod binding;
|
||||
pub(super) mod drawer;
|
||||
// Consts and bind groups for post-process and clouds
|
||||
mod locals;
|
||||
mod screenshot;
|
||||
mod shaders;
|
||||
mod shadow_map;
|
||||
|
||||
@ -16,8 +17,8 @@ use super::{
|
||||
mesh::Mesh,
|
||||
model::{DynamicModel, Model},
|
||||
pipelines::{
|
||||
clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain,
|
||||
ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||
blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite,
|
||||
terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||
},
|
||||
texture::Texture,
|
||||
AaMode, AddressMode, CloudMode, FilterMode, FluidMode, LightingMode, RenderError, RenderMode,
|
||||
@ -50,6 +51,7 @@ struct Layouts {
|
||||
sprite: sprite::SpriteLayout,
|
||||
terrain: terrain::TerrainLayout,
|
||||
ui: ui::UiLayout,
|
||||
blit: blit::BlitLayout,
|
||||
}
|
||||
|
||||
/// A type that stores all the pipelines associated with this renderer.
|
||||
@ -66,6 +68,7 @@ struct Pipelines {
|
||||
sprite: sprite::SpritePipeline,
|
||||
terrain: terrain::TerrainPipeline,
|
||||
ui: ui::UiPipeline,
|
||||
blit: blit::BlitPipeline,
|
||||
}
|
||||
|
||||
/// Render target views
|
||||
@ -117,6 +120,9 @@ pub struct Renderer {
|
||||
mode: RenderMode,
|
||||
resolution: Vec2<u32>,
|
||||
|
||||
// If this is Some then a screenshot will be taken and passed to the handler here
|
||||
take_screenshot: Option<screenshot::ScreenshotFn>,
|
||||
|
||||
profiler: wgpu_profiler::GpuProfiler,
|
||||
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
|
||||
}
|
||||
@ -217,6 +223,7 @@ impl Renderer {
|
||||
let sprite = sprite::SpriteLayout::new(&device);
|
||||
let terrain = terrain::TerrainLayout::new(&device);
|
||||
let ui = ui::UiLayout::new(&device);
|
||||
let blit = blit::BlitLayout::new(&device);
|
||||
|
||||
Layouts {
|
||||
global,
|
||||
@ -229,6 +236,7 @@ impl Renderer {
|
||||
sprite,
|
||||
terrain,
|
||||
ui,
|
||||
blit,
|
||||
}
|
||||
};
|
||||
|
||||
@ -371,6 +379,8 @@ impl Renderer {
|
||||
mode,
|
||||
resolution: Vec2::new(dims.width, dims.height),
|
||||
|
||||
take_screenshot: None,
|
||||
|
||||
profiler,
|
||||
profile_times: Vec::new(),
|
||||
})
|
||||
@ -882,7 +892,7 @@ impl Renderer {
|
||||
Err(wgpu::SwapChainError::Timeout) => {
|
||||
// This will probably be resolved on the next frame
|
||||
// NOTE: we don't log this because it happens very frequently with
|
||||
// PresentMode::Fifo on certain machines
|
||||
// PresentMode::Fifo and unlimited FPS on certain machines
|
||||
return Ok(None);
|
||||
},
|
||||
Err(err @ wgpu::SwapChainError::Outdated) => {
|
||||
@ -1133,12 +1143,13 @@ impl Renderer {
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a download buffer, downloads the win_color_view, and converts to
|
||||
/// a image::DynamicImage.
|
||||
//pub fn create_screenshot(&mut self) -> Result<image::DynamicImage,
|
||||
// RenderError> {
|
||||
pub fn create_screenshot(&mut self) {
|
||||
// TODO: save alongside a screenshot
|
||||
/// Queue to obtain a screenshot on the next frame render
|
||||
pub fn create_screenshot(
|
||||
&mut self,
|
||||
screenshot_handler: impl FnOnce(image::DynamicImage) + Send + 'static,
|
||||
) {
|
||||
// Queue screenshot
|
||||
self.take_screenshot = Some(Box::new(screenshot_handler));
|
||||
// Take profiler snapshot
|
||||
if self.mode.profiler_enabled {
|
||||
let file_name = format!(
|
||||
@ -1158,58 +1169,6 @@ impl Renderer {
|
||||
info!("Saved GPU timing snapshot as: {}", file_name);
|
||||
}
|
||||
}
|
||||
//todo!()
|
||||
// let (width, height) = self.get_resolution().into_tuple();
|
||||
|
||||
// let download_buf = self
|
||||
// .device
|
||||
// .create_buffer(&wgpu::BufferDescriptor {
|
||||
// label: None,
|
||||
// size: width * height * 4,
|
||||
// usage : wgpu::BufferUsage::COPY_DST,
|
||||
// mapped_at_creation: true
|
||||
// });
|
||||
|
||||
// let encoder =
|
||||
// self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor
|
||||
// {label: None});
|
||||
|
||||
// encoder.copy_texture_to_buffer(&wgpu::TextureCopyViewBase {
|
||||
// origin: &self.wi
|
||||
// }, destination, copy_size)
|
||||
|
||||
// self.encoder.copy_texture_to_buffer_raw(
|
||||
// self.win_color_view.raw().get_texture(),
|
||||
// None,
|
||||
// gfx::texture::RawImageInfo {
|
||||
// xoffset: 0,
|
||||
// yoffset: 0,
|
||||
// zoffset: 0,
|
||||
// width,
|
||||
// height,
|
||||
// depth: 0,
|
||||
// format: WinColorFmt::get_format(),
|
||||
// mipmap: 0,
|
||||
// },
|
||||
// download.raw(),
|
||||
// 0,
|
||||
// )?;
|
||||
// self.flush();
|
||||
|
||||
// // Assumes that the format is Rgba8.
|
||||
// let raw_data = self
|
||||
// .factory
|
||||
// .read_mapping(&download)?
|
||||
// .chunks_exact(width as usize)
|
||||
// .rev()
|
||||
// .flatten()
|
||||
// .flatten()
|
||||
// .map(|&e| e)
|
||||
// .collect::<Vec<_>>();
|
||||
// Ok(image::DynamicImage::ImageRgba8(
|
||||
// // Should not fail if the dimensions are correct.
|
||||
// image::ImageBuffer::from_raw(width as u32, height as u32,
|
||||
// raw_data).unwrap(), ))
|
||||
}
|
||||
|
||||
// /// Queue the rendering of the provided skybox model in the upcoming frame.
|
||||
@ -2167,6 +2126,15 @@ fn create_pipelines(
|
||||
&layouts.postprocess,
|
||||
);
|
||||
|
||||
// Construct a pipeline for blitting, used during screenshotting
|
||||
let blit_pipeline = blit::BlitPipeline::new(
|
||||
device,
|
||||
&create_shader("blit-vert", ShaderKind::Vertex)?,
|
||||
&create_shader("blit-frag", ShaderKind::Fragment)?,
|
||||
sc_desc,
|
||||
&layouts.blit,
|
||||
);
|
||||
|
||||
// Consider reenabling at some time in the future
|
||||
//
|
||||
// // Construct a pipeline for rendering the player silhouette
|
||||
@ -2233,6 +2201,7 @@ fn create_pipelines(
|
||||
lod_terrain: lod_terrain_pipeline,
|
||||
clouds: clouds_pipeline,
|
||||
postprocess: postprocess_pipeline,
|
||||
blit: blit_pipeline,
|
||||
},
|
||||
// player_shadow_pipeline,
|
||||
Some(point_shadow_pipeline),
|
||||
|
@ -5,8 +5,8 @@ use super::{
|
||||
instances::Instances,
|
||||
model::{DynamicModel, Model, SubModel},
|
||||
pipelines::{
|
||||
clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite,
|
||||
terrain, ui, ColLights, GlobalsBindGroup, Light, Shadow,
|
||||
blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox,
|
||||
sprite, terrain, ui, ColLights, GlobalsBindGroup, Light, Shadow,
|
||||
},
|
||||
},
|
||||
Renderer, ShadowMap, ShadowMapRenderer,
|
||||
@ -35,6 +35,9 @@ pub struct Drawer<'frame> {
|
||||
borrow: RendererBorrow<'frame>,
|
||||
swap_tex: wgpu::SwapChainTexture,
|
||||
globals: &'frame GlobalsBindGroup,
|
||||
// Texture and other info for taking a screenshot
|
||||
// Writes to this instead in the third pass if it is present
|
||||
taking_screenshot: Option<super::screenshot::TakeScreenshot>,
|
||||
}
|
||||
|
||||
impl<'frame> Drawer<'frame> {
|
||||
@ -44,6 +47,16 @@ impl<'frame> Drawer<'frame> {
|
||||
swap_tex: wgpu::SwapChainTexture,
|
||||
globals: &'frame GlobalsBindGroup,
|
||||
) -> Self {
|
||||
let taking_screenshot = renderer.take_screenshot.take().map(|screenshot_fn| {
|
||||
super::screenshot::TakeScreenshot::new(
|
||||
&renderer.device,
|
||||
&renderer.layouts.blit,
|
||||
&renderer.sampler,
|
||||
&renderer.sc_desc,
|
||||
screenshot_fn,
|
||||
)
|
||||
});
|
||||
|
||||
let borrow = RendererBorrow {
|
||||
queue: &renderer.queue,
|
||||
device: &renderer.device,
|
||||
@ -64,6 +77,7 @@ impl<'frame> Drawer<'frame> {
|
||||
borrow,
|
||||
swap_tex,
|
||||
globals,
|
||||
taking_screenshot,
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +183,12 @@ impl<'frame> Drawer<'frame> {
|
||||
encoder.scoped_render_pass("third_pass", device, &wgpu::RenderPassDescriptor {
|
||||
label: Some("third pass (postprocess + ui)"),
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &self.swap_tex.view,
|
||||
// If a screenshot was requested render to that as an intermediate texture
|
||||
// instead
|
||||
attachment: self
|
||||
.taking_screenshot
|
||||
.as_ref()
|
||||
.map_or(&self.swap_tex.view, |s| s.texture_view()),
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
@ -329,9 +348,41 @@ impl<'frame> Drop for Drawer<'frame> {
|
||||
fn drop(&mut self) {
|
||||
// TODO: submitting things to the queue can let the gpu start on them sooner
|
||||
// maybe we should submit each render pass to the queue as they are produced?
|
||||
let (mut encoder, profiler) = self.encoder.take().unwrap().end_scope();
|
||||
let mut encoder = self.encoder.take().unwrap();
|
||||
|
||||
// If taking a screenshot
|
||||
if let Some(screenshot) = self.taking_screenshot.take() {
|
||||
// Image needs to be copied from the screenshot texture to the swapchain texture
|
||||
let mut render_pass = encoder.scoped_render_pass(
|
||||
"screenshot blit",
|
||||
self.borrow.device,
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("Blit screenshot pass"),
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &self.swap_tex.view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
},
|
||||
);
|
||||
render_pass.set_pipeline(&self.borrow.pipelines.blit.pipeline);
|
||||
render_pass.set_bind_group(0, &screenshot.bind_group(), &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
drop(render_pass);
|
||||
// Issues a command to copy from the texture to a buffer and then sends the
|
||||
// buffer off to another thread to be mapped and processed
|
||||
screenshot.download_and_handle(&mut encoder);
|
||||
}
|
||||
|
||||
let (mut encoder, profiler) = encoder.end_scope();
|
||||
profiler.resolve_queries(&mut encoder);
|
||||
|
||||
self.borrow.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
profiler
|
||||
.end_frame()
|
||||
.expect("Gpu profiler error! Maybe there was an unclosed scope?");
|
||||
|
185
voxygen/src/render/renderer/screenshot.rs
Normal file
185
voxygen/src/render/renderer/screenshot.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use super::super::pipelines::blit;
|
||||
use tracing::error;
|
||||
|
||||
pub type ScreenshotFn = Box<dyn FnOnce(image::DynamicImage) + Send>;
|
||||
|
||||
pub struct TakeScreenshot {
|
||||
bind_group: blit::BindGroup,
|
||||
view: wgpu::TextureView,
|
||||
texture: wgpu::Texture,
|
||||
buffer: wgpu::Buffer,
|
||||
screenshot_fn: ScreenshotFn,
|
||||
// Dimensions used for copying from the screenshot texture to a buffer
|
||||
width: u32,
|
||||
height: u32,
|
||||
bytes_per_pixel: u8,
|
||||
}
|
||||
|
||||
impl TakeScreenshot {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
blit_layout: &blit::BlitLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
// Used to determine the resolution and texture format
|
||||
sc_desc: &wgpu::SwapChainDescriptor,
|
||||
// Function that is given the image after downloading it from the GPU
|
||||
// This is executed in a background thread
|
||||
screenshot_fn: ScreenshotFn,
|
||||
) -> Self {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("screenshot tex"),
|
||||
size: wgpu::Extent3d {
|
||||
width: sc_desc.width,
|
||||
height: sc_desc.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: sc_desc.format,
|
||||
usage: wgpu::TextureUsage::COPY_SRC
|
||||
| wgpu::TextureUsage::SAMPLED
|
||||
| wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("screenshot tex view"),
|
||||
format: Some(sc_desc.format),
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let bind_group = blit_layout.bind(device, &view, sampler);
|
||||
|
||||
let bytes_per_pixel = sc_desc.format.describe().block_size;
|
||||
let padded_bytes_per_row = padded_bytes_per_row(sc_desc.width, bytes_per_pixel);
|
||||
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("screenshot download buffer"),
|
||||
size: (padded_bytes_per_row * sc_desc.height) as u64,
|
||||
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
Self {
|
||||
bind_group,
|
||||
texture,
|
||||
view,
|
||||
buffer,
|
||||
screenshot_fn,
|
||||
width: sc_desc.width,
|
||||
height: sc_desc.height,
|
||||
bytes_per_pixel,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the texture view for the screenshot
|
||||
/// This can then be used as a render attachment
|
||||
pub fn texture_view(&self) -> &wgpu::TextureView { &self.view }
|
||||
|
||||
/// Get the bind group used for blitting the screenshot to the current
|
||||
/// swapchain image
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup { &self.bind_group.bind_group }
|
||||
|
||||
/// NOTE: spawns thread
|
||||
/// Call this after rendering to the screenshot texture
|
||||
pub fn download_and_handle(self, encoder: &mut wgpu::CommandEncoder) {
|
||||
// Calculate padded bytes per row
|
||||
let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel);
|
||||
// Copy image to a buffer
|
||||
encoder.copy_texture_to_buffer(
|
||||
wgpu::TextureCopyView {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &self.buffer,
|
||||
layout: wgpu::TextureDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: padded_bytes_per_row,
|
||||
rows_per_image: 0,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
// Send buffer to another thread for async mapping, downloading, and passing to
|
||||
// the given handler function (which probably saves it to the disk)
|
||||
std::thread::Builder::new()
|
||||
.name("screenshot".into())
|
||||
.spawn(move || {
|
||||
self.download_and_handle_internal();
|
||||
})
|
||||
.expect("Failed to spawn screenshot thread");
|
||||
}
|
||||
|
||||
fn download_and_handle_internal(self) {
|
||||
// Calculate padded bytes per row
|
||||
let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel);
|
||||
let singlethread_rt = match tokio::runtime::Builder::new_current_thread().build() {
|
||||
Ok(rt) => rt,
|
||||
Err(err) => {
|
||||
error!(?err, "Could not create tokio runtime");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Map buffer
|
||||
let buffer_slice = self.buffer.slice(..);
|
||||
let buffer_map_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||
|
||||
// Wait on buffer mapping
|
||||
let pixel_bytes = match singlethread_rt.block_on(buffer_map_future) {
|
||||
// Buffer is mapped and we can read it
|
||||
Ok(()) => {
|
||||
// Copy to a Vec
|
||||
let padded_buffer = buffer_slice.get_mapped_range();
|
||||
let mut pixel_bytes = Vec::new();
|
||||
padded_buffer
|
||||
.chunks(padded_bytes_per_row as usize)
|
||||
.map(|padded_chunk| {
|
||||
&padded_chunk[..self.width as usize * self.bytes_per_pixel as usize]
|
||||
})
|
||||
.for_each(|row| pixel_bytes.extend_from_slice(row));
|
||||
pixel_bytes
|
||||
},
|
||||
// Error
|
||||
Err(err) => {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to map buffer for downloading a screenshot from the GPU"
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Construct image
|
||||
// TODO: support other formats
|
||||
let image = image::ImageBuffer::<image::Bgra<u8>, Vec<u8>>::from_vec(
|
||||
self.width,
|
||||
self.height,
|
||||
pixel_bytes,
|
||||
)
|
||||
.expect("Failed to create ImageBuffer! Buffer was not large enough. This should not occur");
|
||||
let image = image::DynamicImage::ImageBgra8(image);
|
||||
|
||||
// Call supplied handler
|
||||
(self.screenshot_fn)(image);
|
||||
}
|
||||
}
|
||||
|
||||
// Graphics API requires a specific alignment for buffer copies
|
||||
fn padded_bytes_per_row(width: u32, bytes_per_pixel: u8) -> u32 {
|
||||
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||
let unpadded_bytes_per_row = width * bytes_per_pixel as u32;
|
||||
let padding = (align - unpadded_bytes_per_row % align) % align;
|
||||
unpadded_bytes_per_row + padding
|
||||
}
|
@ -66,6 +66,8 @@ impl assets::Compound for Shaders {
|
||||
"clouds-frag",
|
||||
"postprocess-vert",
|
||||
"postprocess-frag",
|
||||
"blit-vert",
|
||||
"blit-frag",
|
||||
"player-shadow-frag",
|
||||
"light-shadows-geom",
|
||||
"light-shadows-frag",
|
||||
|
@ -1341,44 +1341,34 @@ impl Window {
|
||||
pub fn send_event(&mut self, event: Event) { self.events.push(event) }
|
||||
|
||||
pub fn take_screenshot(&mut self, settings: &Settings) {
|
||||
let _ = self.renderer.create_screenshot();
|
||||
// TODO
|
||||
/*match self.renderer.create_screenshot() {
|
||||
Ok(img) => {
|
||||
let mut path = settings.screenshots_path.clone();
|
||||
let sender = self.message_sender.clone();
|
||||
|
||||
let builder = std::thread::Builder::new().name("screenshot".into());
|
||||
builder
|
||||
.spawn(move || {
|
||||
use std::time::SystemTime;
|
||||
// Check if folder exists and create it if it does not
|
||||
if !path.exists() {
|
||||
if let Err(e) = std::fs::create_dir_all(&path) {
|
||||
warn!(?e, "Couldn't create folder for screenshot");
|
||||
let _result = sender
|
||||
.send(String::from("Couldn't create folder for screenshot"));
|
||||
}
|
||||
}
|
||||
path.push(format!(
|
||||
"screenshot_{}.png",
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis())
|
||||
.unwrap_or(0)
|
||||
));
|
||||
if let Err(e) = img.save(&path) {
|
||||
warn!(?e, "Couldn't save screenshot");
|
||||
let _result = sender.send(String::from("Couldn't save screenshot"));
|
||||
} else {
|
||||
let _result = sender
|
||||
.send(format!("Screenshot saved to {}", path.to_string_lossy()));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
},
|
||||
Err(e) => error!(?e, "Couldn't create screenshot due to renderer error"),
|
||||
}*/
|
||||
let sender = self.message_sender.clone();
|
||||
let mut path = settings.screenshots_path.clone();
|
||||
self.renderer.create_screenshot(move |image| {
|
||||
use std::time::SystemTime;
|
||||
// Check if folder exists and create it if it does not
|
||||
if !path.exists() {
|
||||
if let Err(e) = std::fs::create_dir_all(&path) {
|
||||
warn!(?e, "Couldn't create folder for screenshot");
|
||||
let _result =
|
||||
sender.send(String::from("Couldn't create folder for screenshot"));
|
||||
}
|
||||
}
|
||||
path.push(format!(
|
||||
"screenshot_{}.png",
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis())
|
||||
.unwrap_or(0)
|
||||
));
|
||||
// Try to save the image
|
||||
if let Err(e) = image.into_rgba8().save(&path) {
|
||||
warn!(?e, "Couldn't save screenshot");
|
||||
let _result = sender.send(String::from("Couldn't save screenshot"));
|
||||
} else {
|
||||
let _result =
|
||||
sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn is_pressed(
|
||||
|
Loading…
Reference in New Issue
Block a user