//! Flat forward drawing pass that mimics a blit.
use crate::pass::{
encoded_2d::*,
encoder::{EncodingBuffer, Flat2DData},
util::*,
};
use amethyst::{
assets::AssetStorage,
core::{
specs::prelude::{Read, ReadStorage, Write},
transform::GlobalTransform,
},
renderer::{
error::Result,
get_camera,
pipe::{
pass::{Pass, PassData},
DepthMode, Effect, NewEffect,
},
ActiveCamera, Attributes, Camera, Encoder, Factory, Query, Texture, VertexFormat,
},
};
use derivative::*;
use gfx::pso::buffer::ElemStride;
use gfx_core::state::{Blend, ColorMask};
use glsl_layout::Uniform;
/// Draws sprites on a 2D quad.
#[derive(Derivative, Clone, Debug)]
#[derivative(Default(bound = "Self: Pass"))]
pub struct DrawFlat2DEncoded {
transparency: Option<(ColorMask, Blend, Option<DepthMode>)>,
instance_data: Vec<f32>,
}
impl DrawFlat2DEncoded
where
Self: Pass,
{
/// Create instance of `DrawFlat2DEncoded` pass
pub fn new() -> Self {
Self {
transparency: None,
instance_data: Vec::with_capacity(1024),
}
}
/// Enable transparency
pub fn with_transparency(
mut self,
mask: ColorMask,
blend: Blend,
depth: Option<DepthMode>,
) -> Self {
self.transparency = Some((mask, blend, depth));
self
}
fn attributes() -> Attributes<'static> {
<SpriteInstance as Query<(DirX, DirY, Pos, OffsetU, OffsetV, Depth)>>::QUERIED_ATTRIBUTES
}
}
impl<'a> PassData<'a> for DrawFlat2DEncoded {
type Data = (
Option<Read<'a, ActiveCamera>>,
ReadStorage<'a, Camera>,
ReadStorage<'a, GlobalTransform>,
Write<'a, EncodingBuffer<Flat2DData>>,
Read<'a, AssetStorage<Texture>>,
);
}
impl Pass for DrawFlat2DEncoded {
fn compile(&mut self, effect: NewEffect<'_>) -> Result<Effect> {
use std::mem;
let mut builder = effect.simple(VERT_SRC, FRAG_SRC);
builder
.without_back_face_culling()
.with_raw_constant_buffer(
"ViewArgs",
mem::size_of::<<ViewArgs as Uniform>::Std140>(),
1,
)
.with_raw_vertex_buffer(Self::attributes(), SpriteInstance::size() as ElemStride, 1);
setup_textures(&mut builder, &TEXTURES);
match self.transparency {
Some((mask, blend, depth)) => builder.with_blended_output("color", mask, blend, depth),
None => builder.with_output("color", Some(DepthMode::LessEqualWrite)),
};
builder.build()
}
fn apply<'a, 'b: 'a>(
&'a mut self,
encoder: &mut Encoder,
effect: &mut Effect,
mut factory: Factory,
(active, camera, global, mut buffer, tex_storage): <Self as PassData<'a>>::Data,
) {
let camera = get_camera(active, &camera, &global);
use gfx::{
buffer,
memory::{Bind, Typed},
Factory,
};
// Sprite vertex shader
set_view_args(effect, encoder, camera);
// We might be able to improve performance here if we
// preallocate the maximum needed capacity. We need to
// iterate over the sprites though to find out the longest
// chain of sprites with the same texture, so we would need
// to check if it actually results in an improvement over just
// doing the allocations.
let instance_data = &mut self.instance_data;
let mut num_instances = 0;
let num_quads = buffer.vec.len();
let mut current_tex_id = buffer.vec.first().map(|d| d.texture.id()).unwrap_or(0);
for (i, quad) in buffer.vec.iter().enumerate() {
let Flat2DData {
dir_x,
dir_y,
pos,
uv_left,
uv_right,
uv_bottom,
uv_top,
texture,
..
} = quad;
instance_data.extend(&[
dir_x.x, dir_x.y, dir_y.x, dir_y.y, pos.x, pos.y, *uv_left, *uv_right, *uv_bottom,
*uv_top, pos.z,
]);
num_instances += 1;
// Need to flush outstanding draw calls due to state switch (texture).
//
// 1. We are at the last sprite and want to submit all pending work.
// 2. The next sprite will use a different texture triggering a flush.
let need_flush = i >= num_quads - 1 || current_tex_id != texture.id();
current_tex_id = texture.id();
if need_flush {
if let Some(texture) = tex_storage.get(texture) {
add_texture(effect, texture);
let vbuf = factory
.create_buffer_immutable(
&instance_data,
buffer::Role::Vertex,
Bind::empty(),
)
.expect("Unable to create immutable buffer for `TextureBatch`");
for _ in DrawFlat2DEncoded::attributes() {
effect.data.vertex_bufs.push(vbuf.raw().clone());
}
effect.draw(
&Slice {
start: 0,
end: 6,
base_vertex: 0,
instances: Some((num_instances, 0)),
buffer: Default::default(),
},
encoder,
);
effect.clear();
}
num_instances = 0;
instance_data.clear();
}
}
buffer.vec.clear();
}
}