//! 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)>, instance_data: Vec, } 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, ) -> Self { self.transparency = Some((mask, blend, depth)); self } fn attributes() -> Attributes<'static> { >::QUERIED_ATTRIBUTES } } impl<'a> PassData<'a> for DrawFlat2DEncoded { type Data = ( Option>, ReadStorage<'a, Camera>, ReadStorage<'a, GlobalTransform>, Write<'a, EncodingBuffer>, Read<'a, AssetStorage>, ); } impl Pass for DrawFlat2DEncoded { fn compile(&mut self, effect: NewEffect<'_>) -> Result { 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::<::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): >::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(); } }