Skip to content

Instantly share code, notes, and snippets.

@Frizi
Last active January 4, 2019 11:42
Show Gist options
  • Save Frizi/22e5e16ad3c9d33164504dec01142882 to your computer and use it in GitHub Desktop.
Save Frizi/22e5e16ad3c9d33164504dec01142882 to your computer and use it in GitHub Desktop.
StreamEncoder related types prototype
use amethyst::{
core::nalgebra::{Vector2, Vector4},
ecs::{
join::{BitAnd, Join, JoinIter},
world::Index,
Component, ReadStorage,
},
shred::{DynamicSystemData, Resources, SystemData},
};
/// Represents shader input attribute.
/// Holds a spir-v side type name and corresponding rust-side type.
trait ShaderInputType {
const NAME: &'static str;
type Value;
}
struct EncVec4;
impl ShaderInputType for EncVec4 {
const NAME: &'static str = "vec4";
type Value = Vector4<f32>;
}
struct EncVec2;
impl ShaderInputType for EncVec2 {
const NAME: &'static str = "vec2";
type Value = Vector2<f32>;
}
/// Combined type that maps a shader attribute layout (a tuple of `ShaderInputType`s)
/// into the corresponding output of an encoder.
trait EncodingValue {
type Value;
}
impl<A: ShaderInputType> EncodingValue for A {
type Value = A::Value;
}
impl<A: EncodingValue, B: EncodingValue> EncodingValue for (A, B) {
type Value = (A::Value, B::Value);
}
impl<A: EncodingValue, B: EncodingValue, C: EncodingValue> EncodingValue for (A, B, C) {
type Value = (A::Value, B::Value, C::Value);
}
// TODO: more tuple implementations in a macro
/// A compile-time definition of a shader attribute to encode.
///
/// It is defined by a combination of `ShaderInputType` and a property name.
/// Allows to target attributes like `mat4 model` or `vec2 pos`;
trait EncAttribute {
const PROPERTY: &'static str;
type EncodedType: ShaderInputType + EncodingValue;
}
/// A compile-time list of `EncAttribute`s.
trait EncAttributes {
type EncodedType: EncodingValue;
/// Retreive a vec of associated (type name, property) tuples at runtime
fn get_props() -> Vec<(&'static str, &'static str)>;
}
impl<A: EncAttribute> EncAttributes for A {
type EncodedType = A::EncodedType;
fn get_props() -> Vec<(&'static str, &'static str)> {
vec![(A::EncodedType::NAME, A::PROPERTY)]
}
}
impl<A: EncAttributes, B: EncAttributes> EncAttributes for (A, B) {
type EncodedType = (A::EncodedType, B::EncodedType);
fn get_props() -> Vec<(&'static str, &'static str)> {
let mut vec = A::get_props();
vec.extend(B::get_props());
vec
}
}
impl<A: EncAttributes, B: EncAttributes, C: EncAttributes> EncAttributes for (A, B, C) {
type EncodedType = (A::EncodedType, B::EncodedType, C::EncodedType);
fn get_props() -> Vec<(&'static str, &'static str)> {
let mut vec = A::get_props();
vec.extend(B::get_props());
vec.extend(C::get_props());
vec
}
}
// TODO: more tuple implementations in a macro
/// Shader attribute `vec4 tint`
struct TintAttribute;
impl EncAttribute for TintAttribute {
const PROPERTY: &'static str = "tint";
type EncodedType = EncVec4;
}
/// Shader attribute `vec4 pos`
struct Pos2DAttribute;
impl EncAttribute for Pos2DAttribute {
const PROPERTY: &'static str = "pos";
type EncodedType = EncVec4;
}
/// Shader attribute `vec4 dir_x`
struct DirXAttribute;
impl EncAttribute for DirXAttribute {
const PROPERTY: &'static str = "dir_x";
type EncodedType = EncVec4;
}
/// Shader attribute `vec4 dir_y`
struct DirYAttribute;
impl EncAttribute for DirYAttribute {
const PROPERTY: &'static str = "dir_y";
type EncodedType = EncVec4;
}
/// Trait that defines the encoding buffer writing stragety for a specified
/// shader layout.
/// Every encoder must push exactly one value per iterated entity to the buffer.
///
/// The encoding scheduler is free to implement it in any way that is appropriate
/// for given situation. For example, multiple `EncodeBuffer` views might use
/// the same underlying buffer, but write with a common stride and different offsets.
trait EncodeBuffer<T: EncodingValue> {
fn push(&mut self, data: T::Value);
}
type EncType<'a, 'j, T> =
<<T as StreamEncoder<'a, 'j>>::EncodedAttributes as EncAttributes>::EncodedType;
/// A main trait that defines a strategy to encode specified stream of attributes
/// by iteration over declared set of components in the world. The encoder might also
/// use additional resources from the world.
///
/// Every encoder must push exactly one value per iterated entity to the buffer.
trait StreamEncoder<'a, 'j> {
type EncodedAttributes: EncAttributes;
type Components: EncoderJoin<'a> + Join;
type SystemData: DynamicSystemData<'a>;
fn encode<B: EncodeBuffer<EncType<'a, 'j, Self>>>(
buffer: &mut B,
iter: JoinIter<Self::Components>,
system_data: Self::SystemData,
);
}
/// A list of encoders that have to run (possibly in parallel) in order to encode
/// the entire set of required shader attributes (shader layout).
struct EncoderBundle {} // TODO
/// A list of encoders that have to run (usually in sequence) in order to encode
/// all possible component permutations for a given shader layout.
struct LayoutEncoder {} // TODO
// example implementations
use amethyst::renderer::Rgba;
/// An encoder that encodes `Rgba` component into a stream of `vec4 tint`.
pub struct RgbaTintEncoder;
impl<'a: 'j, 'j> StreamEncoder<'a, 'j> for RgbaTintEncoder {
type EncodedAttributes = TintAttribute;
type Components = Encode<'a, 'j, Rgba>;
type SystemData = ();
fn encode<B: EncodeBuffer<EncType<'a, 'j, Self>>>(
buffer: &mut B,
iter: JoinIter<Self::Components>,
system_data: Self::SystemData,
) {
for rgba in iter {
buffer.push(Vector4::new(rgba.0, rgba.1, rgba.2, rgba.3));
}
}
}
use amethyst::{
assets::AssetStorage,
core::GlobalTransform,
ecs::Read,
renderer::{SpriteRender, SpriteSheet},
};
/// An encoder that encodes `GlobalTransform` and `RenderSpriteFlat2D` components
/// into streams of `vec4 pos`, `vec4 dir_x` and `vec4 dir_y`.
pub struct SpriteTransformEncoder;
impl<'a: 'j, 'j> StreamEncoder<'a, 'j> for SpriteTransformEncoder {
type EncodedAttributes = (Pos2DAttribute, DirXAttribute, DirYAttribute);
type Components = (
Encode<'a, 'j, GlobalTransform>,
Encode<'a, 'j, SpriteRender>,
);
type SystemData = (Read<'a, AssetStorage<SpriteSheet>>);
fn encode<B: EncodeBuffer<EncType<'a, 'j, Self>>>(
buffer: &mut B,
iter: JoinIter<Self::Components>,
storage: Self::SystemData,
) {
for (transform, sprite_render) in iter {
let ref sprite_sheet = storage.get(&sprite_render.sprite_sheet).unwrap();
let ref sprite = sprite_sheet.sprites[sprite_render.sprite_number];
let dir_x = transform.0.column(0) * sprite.width;
let dir_y = transform.0.column(1) * sprite.height;
let pos = transform.0 * Vector4::new(-sprite.offsets[0], -sprite.offsets[1], 0.0, 1.0);
buffer.push((pos, dir_x, dir_y));
}
}
}
/// A read-only access to a component storage. Component types listed in the list of `Encoder`s
/// on a `StreamEncoder` trait are used for scheduling the encoding for rendering.
///
/// Constrained in the same way as `ReadStorage`. You can't use `WriteStorage` with the same inner type at the same time.
pub struct Encode<'a, 'j, A: Component>(&'j ReadStorage<'a, A>);
/// A read-only joinable composable list of component types.
/// TODO: Allow for constraining the iterated list of components by external BitVec
trait EncoderJoin<'a> {
type Data: Join;
unsafe fn open(self) -> (<Self::Data as Join>::Mask, <Self::Data as Join>::Value);
}
impl<'a: 'j, 'j, A: Component> Join for Encode<'a, 'j, A> {
type Mask = <&'j ReadStorage<'a, A> as Join>::Mask;
type Value = <&'j ReadStorage<'a, A> as Join>::Value;
type Type = <&'j ReadStorage<'a, A> as Join>::Type;
unsafe fn open(self) -> (Self::Mask, Self::Value) {
Join::open(self.0)
}
unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type {
<&'j ReadStorage<'a, A> as Join>::get(value, id)
}
}
impl<'a, D: Join> Join for EncoderJoin<'a, Data = D>
where
Self: Sized,
{
type Mask = <D as Join>::Mask;
type Value = <D as Join>::Value;
type Type = <D as Join>::Type;
unsafe fn open(self) -> (Self::Mask, Self::Value) {
EncoderJoin::open(self)
}
unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type {
D::get(value, id)
}
}
impl<'a, 'j, A: Component> EncoderJoin<'a> for Encode<'a, 'j, A> {
type Data = &'j ReadStorage<'a, A>;
unsafe fn open(self) -> (<Self::Data as Join>::Mask, <Self::Data as Join>::Value) {
Join::open(self.0)
}
}
macro_rules! define_open {
// use variables to indicate the arity of the tuple
($($from:ident),*) => {
impl<'a, $($from,)*> EncoderJoin<'a> for ($($from),*,)
where $($from: EncoderJoin<'a> + Join),*,
($(<$from as Join>::Mask,)*): BitAnd,
{
type Data = ($($from::Data),*,);
#[allow(non_snake_case)]
unsafe fn open(self) -> (<Self::Data as Join>::Mask, <Self::Data as Join>::Value) {
let ($($from,)*) = self;
let ($($from,)*) = ($(EncoderJoin::open($from),)*);
(
($($from.0),*,).and(),
($($from.1),*,)
)
}
}
}
}
define_open! {A}
define_open! {A, B}
define_open! {A, B, C}
define_open! {A, B, C, D}
define_open! {A, B, C, D, E}
define_open! {A, B, C, D, E, F}
define_open! {A, B, C, D, E, F, G}
define_open! {A, B, C, D, E, F, G, H}
define_open! {A, B, C, D, E, F, G, H, I}
define_open! {A, B, C, D, E, F, G, H, I, J}
define_open! {A, B, C, D, E, F, G, H, I, J, K}
define_open! {A, B, C, D, E, F, G, H, I, J, K, L}
define_open! {A, B, C, D, E, F, G, H, I, J, K, L, M}
define_open! {A, B, C, D, E, F, G, H, I, J, K, L, M, N}
define_open! {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O}
define_open! {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P}
// Fetching
trait EncodeFetcher<'a> {
type SystemData: SystemData<'a>;
fn scoped_fetch<F: FnOnce(Self)>(res: &'a Resources, f: F)
where
Self: Sized;
}
impl<'a: 'j, 'j, A: Component> EncodeFetcher<'a> for Encode<'a, 'j, A> {
type SystemData = ReadStorage<'a, A>;
fn scoped_fetch<F: FnOnce(Encode<'a, 'j, A>)>(res: &'a Resources, f: F)
where
Self: Sized,
{
let data = <Self::SystemData as SystemData>::fetch(res);
let enc = Self(&data);
f(enc);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment