use {
super::Op,
crate::{
color::TRANSPARENT_BLACK,
gpu::{
def::{
push_const::WritePushConsts, ColorRenderPassMode, Graphics, GraphicsMode,
RenderPassMode,
},
driver::{
bind_graphics_descriptor_set, CommandPool, Device, Driver, Fence, Framebuffer2d,
},
pool::{Lease, Pool},
BlendMode, Texture2d,
},
math::{vec3, Area, CoordF, Mat4, RectF, Vec2},
},
gfx_hal::{
command::{CommandBuffer as _, CommandBufferFlags, ImageCopy, Level, SubpassContents},
device::Device as _,
format::Aspects,
image::{Access, Layout, Offset, SubresourceLayers, Usage},
pool::CommandPool as _,
pso::{Descriptor, DescriptorSetWrite, PipelineStage, ShaderStageFlags, Viewport},
queue::{CommandQueue as _, Submission},
Backend,
},
gfx_impl::Backend as _Backend,
std::{
any::Any,
iter::{empty, once},
u8,
},
};
const SUBPASS_IDX: u8 = 0;
#[derive(Clone, Copy, Hash, PartialEq)]
pub enum Mode {
Blend((u8, BlendMode)),
Texture,
}
pub struct Write<'s> {
src: &'s Texture2d,
src_region: Area,
transform: Mat4,
}
impl<'s> Write<'s> {
pub fn position<D: Into<CoordF>>(src: &'s Texture2d, dst: D) -> Self {
Self::tile_position(src, src.borrow().dims().into(), dst)
}
pub fn region<D: Into<RectF>>(src: &'s Texture2d, dst: D) -> Self {
Self::tile_region(src, src.borrow().dims().into(), dst)
}
pub fn tile_position<D: Into<CoordF>>(src: &'s Texture2d, src_tile: Area, dst: D) -> Self {
Self::tile_region(
src,
src_tile,
RectF {
dims: src.borrow().dims().into(),
pos: dst.into(),
},
)
}
pub fn tile_region<D: Into<RectF>>(src: &'s Texture2d, src_tile: Area, dst: D) -> Self {
let dst = dst.into();
let src_dims: CoordF = src.borrow().dims().into();
let dst_transform = Mat4::from_translation(vec3(-1.0, -1.0, 0.0))
* Mat4::from_scale(vec3(
dst.dims.x * 2.0 / src_dims.x,
dst.dims.y * 2.0 / src_dims.y,
1.0,
))
* Mat4::from_translation(vec3(dst.pos.x / dst.dims.x, dst.pos.y / dst.dims.y, 0.0));
Self::tile_transform(src, src_tile, dst_transform)
}
pub fn tile_transform(src: &'s Texture2d, src_tile: Area, dst: Mat4) -> Self {
Self {
src,
src_region: src_tile,
transform: dst,
}
}
pub fn transform(src: &'s Texture2d, dst: Mat4) -> Self {
Self::tile_transform(src, src.borrow().dims().into(), dst)
}
}
pub struct WriteOp {
back_buf: Lease<Texture2d>,
cmd_buf: <_Backend as Backend>::CommandBuffer,
cmd_pool: Lease<CommandPool>,
driver: Driver,
dst: Texture2d,
dst_preserve: bool,
fence: Lease<Fence>,
frame_buf: Option<Framebuffer2d>,
graphics: Option<Lease<Graphics>>,
mode: Mode,
#[cfg(feature = "debug-names")]
name: String,
pool: Option<Lease<Pool>>,
src_textures: Vec<Texture2d>,
}
impl WriteOp {
#[must_use]
pub(crate) fn new(
#[cfg(feature = "debug-names")] name: &str,
driver: &Driver,
mut pool: Lease<Pool>,
dst: &Texture2d,
) -> Self {
let family = Device::queue_family(&driver.borrow());
let mut cmd_pool = pool.cmd_pool(driver, family);
let (dims, fmt) = {
let dst = dst.borrow();
(dst.dims(), dst.format())
};
let fence = pool.fence(
#[cfg(feature = "debug-names")]
name,
driver,
);
Self {
back_buf: pool.texture(
#[cfg(feature = "debug-names")]
&format!("{} backbuffer", name),
driver,
dims,
fmt,
Layout::Undefined,
Usage::COLOR_ATTACHMENT | Usage::INPUT_ATTACHMENT,
1,
1,
1,
),
cmd_buf: unsafe { cmd_pool.allocate_one(Level::Primary) },
cmd_pool,
driver: Driver::clone(driver),
dst: Texture2d::clone(dst),
dst_preserve: false,
fence,
frame_buf: None,
graphics: None,
mode: Mode::Texture,
#[cfg(feature = "debug-names")]
name: name.to_owned(),
pool: Some(pool),
src_textures: Default::default(),
}
}
#[must_use]
pub fn with_mode(&mut self, mode: Mode) -> &mut Self {
self.mode = mode;
self
}
#[must_use]
pub fn with_preserve(&mut self) -> &mut Self {
self.with_preserve_is(true)
}
#[must_use]
pub fn with_preserve_is(&mut self, val: bool) -> &mut Self {
self.dst_preserve = val;
self
}
pub fn record(&mut self, writes: &mut [Write]) {
assert!(self.src_textures.is_empty());
assert_ne!(writes.len(), 0);
if writes.len() > 1 {
for write in writes.iter() {
let write_src_ptr = Texture2d::as_ptr(&write.src);
if let Err(idx) = self.src_textures.binary_search_by(|probe| {
let probe = Texture2d::as_ptr(probe);
probe.cmp(&write_src_ptr)
}) {
self.src_textures.insert(idx, Texture2d::clone(write.src));
}
}
writes.sort_unstable_by(|lhs, rhs| {
let lhs = Texture2d::as_ptr(&lhs.src);
let rhs = Texture2d::as_ptr(&rhs.src);
lhs.cmp(&rhs)
});
} else {
self.src_textures.push(Texture2d::clone(writes[0].src));
}
let render_pass_mode = RenderPassMode::Color(ColorRenderPassMode {
fmt: self.dst.borrow().format(),
preserve: self.dst_preserve,
});
{
let pool = self.pool.as_mut().unwrap();
let render_pass = pool.render_pass(&self.driver, render_pass_mode);
self.frame_buf.replace(Framebuffer2d::new(
#[cfg(feature = "debug-names")]
self.name.as_str(),
&self.driver,
render_pass,
once(self.back_buf.borrow().as_default_view().as_ref()),
self.dst.borrow().dims(),
));
let graphics_mode = match self.mode {
Mode::Blend((_, mode)) => GraphicsMode::Blend(mode),
Mode::Texture => GraphicsMode::Texture,
};
self.graphics.replace(pool.graphics_desc_sets(
#[cfg(feature = "debug-names")]
&self.name,
&self.driver,
render_pass_mode,
SUBPASS_IDX,
graphics_mode,
self.src_textures.len(),
));
}
unsafe {
self.write_descriptors();
self.submit_begin(render_pass_mode);
let mut set_idx = 0;
for write in writes.iter() {
self.submit_write(write, &mut set_idx);
}
self.submit_finish();
}
}
unsafe fn submit_begin(&mut self, render_pass_mode: RenderPassMode) {
trace!("submit_begin");
let pool = self.pool.as_mut().unwrap();
let render_pass = pool.render_pass(&self.driver, render_pass_mode);
let mut back_buf = self.back_buf.borrow_mut();
let mut dst = self.dst.borrow_mut();
let graphics = self.graphics.as_ref().unwrap();
let dims = dst.dims();
let rect = dims.into();
let viewport = Viewport {
rect,
depth: 0.0..1.0,
};
self.cmd_buf
.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
if self.dst_preserve {
dst.set_layout(
&mut self.cmd_buf,
Layout::TransferSrcOptimal,
PipelineStage::TRANSFER,
Access::TRANSFER_READ,
);
back_buf.set_layout(
&mut self.cmd_buf,
Layout::TransferDstOptimal,
PipelineStage::TRANSFER,
Access::TRANSFER_WRITE,
);
self.cmd_buf.copy_image(
dst.as_ref(),
Layout::TransferSrcOptimal,
back_buf.as_ref(),
Layout::TransferDstOptimal,
once(ImageCopy {
src_subresource: SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
layers: 0..1,
},
src_offset: Offset::ZERO,
dst_subresource: SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
layers: 0..1,
},
dst_offset: Offset::ZERO,
extent: dims.as_extent_depth(1),
}),
);
dst.set_layout(
&mut self.cmd_buf,
Layout::ShaderReadOnlyOptimal,
PipelineStage::FRAGMENT_SHADER,
Access::SHADER_READ,
);
}
back_buf.set_layout(
&mut self.cmd_buf,
Layout::ColorAttachmentOptimal,
PipelineStage::COLOR_ATTACHMENT_OUTPUT,
Access::COLOR_ATTACHMENT_WRITE,
);
self.cmd_buf.begin_render_pass(
render_pass,
self.frame_buf.as_ref().unwrap(),
rect,
&[TRANSPARENT_BLACK.into()],
SubpassContents::Inline,
);
self.cmd_buf.bind_graphics_pipeline(graphics.pipeline());
self.cmd_buf.set_scissors(0, &[rect]);
self.cmd_buf.set_viewports(0, &[viewport]);
bind_graphics_descriptor_set(&mut self.cmd_buf, graphics.layout(), graphics.desc_set(0));
}
unsafe fn submit_write(&mut self, write: &Write, set_idx: &mut usize) {
trace!("submit_write");
let graphics = self.graphics.as_ref().unwrap();
let layout = graphics.layout();
if !Texture2d::ptr_eq(write.src, &self.src_textures[*set_idx]) {
*set_idx += 1;
bind_graphics_descriptor_set(&mut self.cmd_buf, layout, graphics.desc_set(*set_idx));
}
let offset = Vec2::zero();
let scale = Vec2::one();
self.cmd_buf.push_graphics_constants(
graphics.layout(),
ShaderStageFlags::VERTEX,
0,
WritePushConsts {
offset,
scale,
transform: write.transform,
}
.as_ref(),
);
if let Mode::Blend((ab, _)) = self.mode {
let ab = ab as f32 / u8::MAX as f32;
let inv = 1.0 - ab;
self.cmd_buf.push_graphics_constants(
graphics.layout(),
ShaderStageFlags::FRAGMENT,
WritePushConsts::BYTE_LEN,
&[ab.to_bits(), inv.to_bits()],
);
}
self.cmd_buf.draw(0..6, 0..1);
}
unsafe fn submit_finish(&mut self) {
trace!("submit_finish");
let mut device = self.driver.borrow_mut();
let mut back_buf = self.back_buf.borrow_mut();
let mut dst = self.dst.borrow_mut();
let dims = dst.dims();
self.cmd_buf.end_render_pass();
back_buf.set_layout(
&mut self.cmd_buf,
Layout::TransferSrcOptimal,
PipelineStage::TRANSFER,
Access::TRANSFER_READ,
);
dst.set_layout(
&mut self.cmd_buf,
Layout::TransferDstOptimal,
PipelineStage::TRANSFER,
Access::TRANSFER_WRITE,
);
self.cmd_buf.copy_image(
back_buf.as_ref(),
Layout::TransferSrcOptimal,
dst.as_ref(),
Layout::TransferDstOptimal,
once(ImageCopy {
src_subresource: SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
layers: 0..1,
},
src_offset: Offset::ZERO,
dst_subresource: SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
layers: 0..1,
},
dst_offset: Offset::ZERO,
extent: dims.as_extent_depth(1),
}),
);
self.cmd_buf.finish();
Device::queue_mut(&mut device).submit(
Submission {
command_buffers: once(&self.cmd_buf),
wait_semaphores: empty(),
signal_semaphores: empty::<&<_Backend as Backend>::Semaphore>(),
},
Some(&self.fence),
);
}
unsafe fn write_descriptors(&mut self) {
trace!("write_descriptors");
let dst = self.dst.borrow();
let dst_view = dst.as_default_view();
let graphics = self.graphics.as_ref().unwrap();
let sampler = graphics.sampler(0).as_ref();
for (idx, src) in self.src_textures.iter().enumerate() {
let set = graphics.desc_set(idx);
let src_ref = src.borrow();
let src_view = src_ref.as_default_view();
let src_desc = DescriptorSetWrite {
set,
binding: 0,
array_offset: 0,
descriptors: once(Descriptor::CombinedImageSampler(
&**src_view,
Layout::ShaderReadOnlyOptimal,
sampler,
)),
};
if let Mode::Blend(_) = self.mode {
let dst_desc = DescriptorSetWrite {
set,
binding: 1,
array_offset: 0,
descriptors: once(Descriptor::CombinedImageSampler(
&**dst_view,
Layout::ShaderReadOnlyOptimal,
sampler,
)),
};
self.driver
.borrow_mut()
.write_descriptor_sets(vec![src_desc, dst_desc]);
} else {
self.driver
.borrow_mut()
.write_descriptor_sets(once(src_desc));
}
}
}
}
impl Drop for WriteOp {
fn drop(&mut self) {
self.wait();
}
}
impl Op for WriteOp {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn take_pool(&mut self) -> Option<Lease<Pool>> {
self.pool.take()
}
fn wait(&self) {
Fence::wait(&self.fence);
}
}