This commit is contained in:
Imbris 2021-02-28 17:11:43 -05:00
parent c7a4b93c90
commit 205d798fae
6 changed files with 286 additions and 2 deletions

View File

@ -8,6 +8,7 @@ pub mod mesh;
pub mod model;
pub mod pipelines;
pub mod renderer;
mod screenshot;
pub mod texture;
// Reexports

View 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::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: wgpu::CullMode::None,
polygon_mode: wgpu::PolygonMode::Fill,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: samples,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: fs_module,
entry_point: "main",
targets: &[wgpu::ColorTargetState {
format: sc_desc.format,
alpha_blend: wgpu::BlendState::REPLACE,
color_blend: wgpu::BlendState::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
}),
});
Self {
pipeline: render_pipeline,
}
}
}

View File

@ -1,3 +1,4 @@
pub mod blit;
pub mod clouds;
pub mod figure;
pub mod fluid;

View File

@ -1038,7 +1038,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 and unlimited FPS
// PresentMode::Fifo and unlimited FPS on certain machines
return Ok(None);
},
Err(err @ wgpu::SwapChainError::Outdated) => {

View File

@ -19,6 +19,8 @@ pub struct Drawer<'a> {
pub renderer: &'a mut Renderer,
tex: wgpu::SwapChainTexture,
globals: &'a GlobalsBindGroup,
// Texture to write in screenshot data if that was requested this frame
taking_screenshot: Option<super::screenshot::TakingScreenshot>,
}
impl<'a> Drawer<'a> {
@ -141,7 +143,9 @@ impl<'a> Drawer<'a> {
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("third pass (postprocess + ui)"),
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &self.tex.view,
// If a screenshot was requested render to that as an intermediate texture
// instead
attachment: self.taking_screenshot.map_or(&self.tex.view, |s| &s.tex),
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
@ -295,6 +299,31 @@ impl<'a> Drawer<'a> {
impl<'a> Drop for Drawer<'a> {
fn drop(&mut self) {
// If taking a screenshot
if let Some(screenshot) = self.taking_screenshot {
// Image needs to be copied from the screenshot texture to the swapchain texture
let mut render_pass =
self.encoder
.as_mut()
.unwrap()
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&label),
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &self.tex.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: true,
},
}],
});
self.render_pass.set_pipeline(&self.blit.pipeline);
self.render_pass
.set_bind_group(0, &screenshot.bind_group.bind_group, &[]);
self.render_pass.draw(0..3, 0..1);
drop(render_pass);
// TODO: Send screenshot off to another thread here
}
// 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?
self.renderer

View File

@ -0,0 +1,124 @@
use super::super::pipelines::blit;
pub type ScreenshotFn = Box<dyn FnMut(image::DynamicImage) + Send>;
pub struct ScreenshotDownloader {
buffer: wgpu::Buffer,
screenshot_fn: ScreenshotFn,
}
pub struct TakeScreenshot {
bind_group: blit::BindGroup,
view: wgpu::TextureView,
texture: wgpu::Texture,
// /// Option so that we can pass ownership of the contents of this field around (and eventually to a new thread) without
// /// taking ownership of this whole struct
downloader: ScreenshotDownloader,
// Dimensions used for copying from the screenshot texture to a buffer
width: u32,
height: u32,
bytes_per_pixel: u8,
}
//pub struct TakingScreenshot<'a> {
// pub bind_group: &'a blit::BindGroup,
// pub tex: &'a wgpu::TextureView,
// downloader: Option<ScreenshotDownloader>
//}
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: 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_ATTACHEMENT,
});
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, &taking_tex, sampler);
let bytes_per_pixel = sc_desc.format.describe().block_size;
let buffer = device.create_buffer(&wgpu::BufferDescriptor{
label: Some("screenshot download buffer"),
size: padded_bytes_per_row *
}
)
let downloader = ScreenshotDownloader { screenshot_fn, buffer };
Self {
bind_group,
texture,
view,
downloader: Some(Downloader),
}
/// 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 align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let unpadded_bytes_per_row =
// Copy image to a buffer
encoder.copy_texture_to_buffer(
wgpu::TextureCopyView {
texture: &self.texture,
mip_level: 0,
orgin: wgpu::Origin3d::ZERO,
},
wgpu::BufferCopyView {
buffer: &self.buffer,
layout: wgpu::TextureDataLayout {
offset: 0,
bytes_per_row: padded_bytes_per_row(self.width, self.bytes_per_pixel),
rows_per_image: 0,
}
},
)
// 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(|| {
});
.expect("Failed to spawn screenshot thread");
}
}
fn padded_bytes_per_row(width: u32, bytes_per_pixel: u8) -> u32 {
let unpadded_bytes_per_row = width * bytes_per_pixel;
let padding = (align - unpadded_bytes_per_row % align) % align;
unpadded_bytes_per_row + padding
}