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
0ad51204ec
commit
d7b651451b
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 clouds;
|
||||||
pub mod figure;
|
pub mod figure;
|
||||||
pub mod fluid;
|
pub mod fluid;
|
||||||
|
@ -2,6 +2,7 @@ mod binding;
|
|||||||
pub(super) mod drawer;
|
pub(super) mod drawer;
|
||||||
// Consts and bind groups for post-process and clouds
|
// Consts and bind groups for post-process and clouds
|
||||||
mod locals;
|
mod locals;
|
||||||
|
mod screenshot;
|
||||||
mod shaders;
|
mod shaders;
|
||||||
mod shadow_map;
|
mod shadow_map;
|
||||||
|
|
||||||
@ -16,8 +17,8 @@ use super::{
|
|||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
model::{DynamicModel, Model},
|
model::{DynamicModel, Model},
|
||||||
pipelines::{
|
pipelines::{
|
||||||
clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain,
|
blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite,
|
||||||
ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
|
||||||
},
|
},
|
||||||
texture::Texture,
|
texture::Texture,
|
||||||
AaMode, AddressMode, CloudMode, FilterMode, FluidMode, LightingMode, RenderError, RenderMode,
|
AaMode, AddressMode, CloudMode, FilterMode, FluidMode, LightingMode, RenderError, RenderMode,
|
||||||
@ -50,6 +51,7 @@ struct Layouts {
|
|||||||
sprite: sprite::SpriteLayout,
|
sprite: sprite::SpriteLayout,
|
||||||
terrain: terrain::TerrainLayout,
|
terrain: terrain::TerrainLayout,
|
||||||
ui: ui::UiLayout,
|
ui: ui::UiLayout,
|
||||||
|
blit: blit::BlitLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that stores all the pipelines associated with this renderer.
|
/// A type that stores all the pipelines associated with this renderer.
|
||||||
@ -66,6 +68,7 @@ struct Pipelines {
|
|||||||
sprite: sprite::SpritePipeline,
|
sprite: sprite::SpritePipeline,
|
||||||
terrain: terrain::TerrainPipeline,
|
terrain: terrain::TerrainPipeline,
|
||||||
ui: ui::UiPipeline,
|
ui: ui::UiPipeline,
|
||||||
|
blit: blit::BlitPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render target views
|
/// Render target views
|
||||||
@ -117,6 +120,9 @@ pub struct Renderer {
|
|||||||
mode: RenderMode,
|
mode: RenderMode,
|
||||||
resolution: Vec2<u32>,
|
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,
|
profiler: wgpu_profiler::GpuProfiler,
|
||||||
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
|
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
|
||||||
}
|
}
|
||||||
@ -217,6 +223,7 @@ impl Renderer {
|
|||||||
let sprite = sprite::SpriteLayout::new(&device);
|
let sprite = sprite::SpriteLayout::new(&device);
|
||||||
let terrain = terrain::TerrainLayout::new(&device);
|
let terrain = terrain::TerrainLayout::new(&device);
|
||||||
let ui = ui::UiLayout::new(&device);
|
let ui = ui::UiLayout::new(&device);
|
||||||
|
let blit = blit::BlitLayout::new(&device);
|
||||||
|
|
||||||
Layouts {
|
Layouts {
|
||||||
global,
|
global,
|
||||||
@ -229,6 +236,7 @@ impl Renderer {
|
|||||||
sprite,
|
sprite,
|
||||||
terrain,
|
terrain,
|
||||||
ui,
|
ui,
|
||||||
|
blit,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,6 +379,8 @@ impl Renderer {
|
|||||||
mode,
|
mode,
|
||||||
resolution: Vec2::new(dims.width, dims.height),
|
resolution: Vec2::new(dims.width, dims.height),
|
||||||
|
|
||||||
|
take_screenshot: None,
|
||||||
|
|
||||||
profiler,
|
profiler,
|
||||||
profile_times: Vec::new(),
|
profile_times: Vec::new(),
|
||||||
})
|
})
|
||||||
@ -882,7 +892,7 @@ impl Renderer {
|
|||||||
Err(wgpu::SwapChainError::Timeout) => {
|
Err(wgpu::SwapChainError::Timeout) => {
|
||||||
// This will probably be resolved on the next frame
|
// This will probably be resolved on the next frame
|
||||||
// NOTE: we don't log this because it happens very frequently with
|
// 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);
|
return Ok(None);
|
||||||
},
|
},
|
||||||
Err(err @ wgpu::SwapChainError::Outdated) => {
|
Err(err @ wgpu::SwapChainError::Outdated) => {
|
||||||
@ -1133,12 +1143,13 @@ impl Renderer {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a download buffer, downloads the win_color_view, and converts to
|
/// Queue to obtain a screenshot on the next frame render
|
||||||
/// a image::DynamicImage.
|
pub fn create_screenshot(
|
||||||
//pub fn create_screenshot(&mut self) -> Result<image::DynamicImage,
|
&mut self,
|
||||||
// RenderError> {
|
screenshot_handler: impl FnOnce(image::DynamicImage) + Send + 'static,
|
||||||
pub fn create_screenshot(&mut self) {
|
) {
|
||||||
// TODO: save alongside a screenshot
|
// Queue screenshot
|
||||||
|
self.take_screenshot = Some(Box::new(screenshot_handler));
|
||||||
// Take profiler snapshot
|
// Take profiler snapshot
|
||||||
if self.mode.profiler_enabled {
|
if self.mode.profiler_enabled {
|
||||||
let file_name = format!(
|
let file_name = format!(
|
||||||
@ -1158,58 +1169,6 @@ impl Renderer {
|
|||||||
info!("Saved GPU timing snapshot as: {}", file_name);
|
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.
|
// /// Queue the rendering of the provided skybox model in the upcoming frame.
|
||||||
@ -2167,6 +2126,15 @@ fn create_pipelines(
|
|||||||
&layouts.postprocess,
|
&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
|
// Consider reenabling at some time in the future
|
||||||
//
|
//
|
||||||
// // Construct a pipeline for rendering the player silhouette
|
// // Construct a pipeline for rendering the player silhouette
|
||||||
@ -2233,6 +2201,7 @@ fn create_pipelines(
|
|||||||
lod_terrain: lod_terrain_pipeline,
|
lod_terrain: lod_terrain_pipeline,
|
||||||
clouds: clouds_pipeline,
|
clouds: clouds_pipeline,
|
||||||
postprocess: postprocess_pipeline,
|
postprocess: postprocess_pipeline,
|
||||||
|
blit: blit_pipeline,
|
||||||
},
|
},
|
||||||
// player_shadow_pipeline,
|
// player_shadow_pipeline,
|
||||||
Some(point_shadow_pipeline),
|
Some(point_shadow_pipeline),
|
||||||
|
@ -5,8 +5,8 @@ use super::{
|
|||||||
instances::Instances,
|
instances::Instances,
|
||||||
model::{DynamicModel, Model, SubModel},
|
model::{DynamicModel, Model, SubModel},
|
||||||
pipelines::{
|
pipelines::{
|
||||||
clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite,
|
blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox,
|
||||||
terrain, ui, ColLights, GlobalsBindGroup, Light, Shadow,
|
sprite, terrain, ui, ColLights, GlobalsBindGroup, Light, Shadow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Renderer, ShadowMap, ShadowMapRenderer,
|
Renderer, ShadowMap, ShadowMapRenderer,
|
||||||
@ -35,6 +35,9 @@ pub struct Drawer<'frame> {
|
|||||||
borrow: RendererBorrow<'frame>,
|
borrow: RendererBorrow<'frame>,
|
||||||
swap_tex: wgpu::SwapChainTexture,
|
swap_tex: wgpu::SwapChainTexture,
|
||||||
globals: &'frame GlobalsBindGroup,
|
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> {
|
impl<'frame> Drawer<'frame> {
|
||||||
@ -44,6 +47,16 @@ impl<'frame> Drawer<'frame> {
|
|||||||
swap_tex: wgpu::SwapChainTexture,
|
swap_tex: wgpu::SwapChainTexture,
|
||||||
globals: &'frame GlobalsBindGroup,
|
globals: &'frame GlobalsBindGroup,
|
||||||
) -> Self {
|
) -> 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 {
|
let borrow = RendererBorrow {
|
||||||
queue: &renderer.queue,
|
queue: &renderer.queue,
|
||||||
device: &renderer.device,
|
device: &renderer.device,
|
||||||
@ -64,6 +77,7 @@ impl<'frame> Drawer<'frame> {
|
|||||||
borrow,
|
borrow,
|
||||||
swap_tex,
|
swap_tex,
|
||||||
globals,
|
globals,
|
||||||
|
taking_screenshot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +183,12 @@ impl<'frame> Drawer<'frame> {
|
|||||||
encoder.scoped_render_pass("third_pass", device, &wgpu::RenderPassDescriptor {
|
encoder.scoped_render_pass("third_pass", device, &wgpu::RenderPassDescriptor {
|
||||||
label: Some("third pass (postprocess + ui)"),
|
label: Some("third pass (postprocess + ui)"),
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
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,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
@ -329,9 +348,41 @@ impl<'frame> Drop for Drawer<'frame> {
|
|||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// TODO: submitting things to the queue can let the gpu start on them sooner
|
// 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?
|
// 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);
|
profiler.resolve_queries(&mut encoder);
|
||||||
|
|
||||||
self.borrow.queue.submit(std::iter::once(encoder.finish()));
|
self.borrow.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
|
||||||
profiler
|
profiler
|
||||||
.end_frame()
|
.end_frame()
|
||||||
.expect("Gpu profiler error! Maybe there was an unclosed scope?");
|
.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",
|
"clouds-frag",
|
||||||
"postprocess-vert",
|
"postprocess-vert",
|
||||||
"postprocess-frag",
|
"postprocess-frag",
|
||||||
|
"blit-vert",
|
||||||
|
"blit-frag",
|
||||||
"player-shadow-frag",
|
"player-shadow-frag",
|
||||||
"light-shadows-geom",
|
"light-shadows-geom",
|
||||||
"light-shadows-frag",
|
"light-shadows-frag",
|
||||||
|
@ -1344,44 +1344,34 @@ impl Window {
|
|||||||
pub fn send_event(&mut self, event: Event) { self.events.push(event) }
|
pub fn send_event(&mut self, event: Event) { self.events.push(event) }
|
||||||
|
|
||||||
pub fn take_screenshot(&mut self, settings: &Settings) {
|
pub fn take_screenshot(&mut self, settings: &Settings) {
|
||||||
let _ = self.renderer.create_screenshot();
|
let sender = self.message_sender.clone();
|
||||||
// TODO
|
let mut path = settings.screenshots_path.clone();
|
||||||
/*match self.renderer.create_screenshot() {
|
self.renderer.create_screenshot(move |image| {
|
||||||
Ok(img) => {
|
use std::time::SystemTime;
|
||||||
let mut path = settings.screenshots_path.clone();
|
// Check if folder exists and create it if it does not
|
||||||
let sender = self.message_sender.clone();
|
if !path.exists() {
|
||||||
|
if let Err(e) = std::fs::create_dir_all(&path) {
|
||||||
let builder = std::thread::Builder::new().name("screenshot".into());
|
warn!(?e, "Couldn't create folder for screenshot");
|
||||||
builder
|
let _result =
|
||||||
.spawn(move || {
|
sender.send(String::from("Couldn't create folder for screenshot"));
|
||||||
use std::time::SystemTime;
|
}
|
||||||
// Check if folder exists and create it if it does not
|
}
|
||||||
if !path.exists() {
|
path.push(format!(
|
||||||
if let Err(e) = std::fs::create_dir_all(&path) {
|
"screenshot_{}.png",
|
||||||
warn!(?e, "Couldn't create folder for screenshot");
|
SystemTime::now()
|
||||||
let _result = sender
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.send(String::from("Couldn't create folder for screenshot"));
|
.map(|d| d.as_millis())
|
||||||
}
|
.unwrap_or(0)
|
||||||
}
|
));
|
||||||
path.push(format!(
|
// Try to save the image
|
||||||
"screenshot_{}.png",
|
if let Err(e) = image.into_rgba8().save(&path) {
|
||||||
SystemTime::now()
|
warn!(?e, "Couldn't save screenshot");
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
let _result = sender.send(String::from("Couldn't save screenshot"));
|
||||||
.map(|d| d.as_millis())
|
} else {
|
||||||
.unwrap_or(0)
|
let _result =
|
||||||
));
|
sender.send(format!("Screenshot saved to {}", path.to_string_lossy()));
|
||||||
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"),
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_pressed(
|
fn is_pressed(
|
||||||
|
Loading…
Reference in New Issue
Block a user