coffee 0.4.0

An opinionated 2D game engine focused on simplicity, explicitness, and type-safety
Documentation
use gfx::traits::FactoryExt;
use gfx::{self, *};
use gfx_device_gl as gl;

use super::format;
use crate::graphics::Transformation;

gfx_defines! {
    vertex Vertex {
        position: [f32; 2] = "a_Pos",
        color: [f32; 4] = "a_Color",
    }

    constant Globals {
        mvp: [[f32; 4]; 4] = "u_MVP",
    }

    pipeline pipe {
        vertices: gfx::VertexBuffer<Vertex> = (),
        globals: gfx::ConstantBuffer<Globals> = "Globals",
        out: gfx::RawRenderTarget =
          (
              "Target0",
               format::COLOR,
               gfx::state::ColorMask::all(),
               Some(gfx::preset::blend::ALPHA)
          ),
    }
}

pub struct Pipeline {
    data: pipe::Data<gl::Resources>,
    indices: gfx::handle::Buffer<gl::Resources, u32>,
    shader: Shader,
    globals: Globals,
}

impl Pipeline {
    const INITIAL_BUFFER_SIZE: usize = 100_000;

    pub fn new(
        factory: &mut gl::Factory,
        encoder: &mut gfx::Encoder<gl::Resources, gl::CommandBuffer>,
        target: &gfx::handle::RawRenderTargetView<gl::Resources>,
    ) -> Pipeline {
        let vertices = factory
            .create_buffer(
                Self::INITIAL_BUFFER_SIZE,
                gfx::buffer::Role::Vertex,
                gfx::memory::Usage::Dynamic,
                gfx::memory::Bind::SHADER_RESOURCE,
            )
            .expect("Vertex buffer creation");

        let indices = factory
            .create_buffer(
                Self::INITIAL_BUFFER_SIZE,
                gfx::buffer::Role::Index,
                gfx::memory::Usage::Dynamic,
                gfx::memory::Bind::empty(),
            )
            .expect("Index buffer creation");

        let data = pipe::Data {
            vertices,
            globals: factory.create_constant_buffer(1),
            out: target.clone(),
        };

        let init = pipe::Init {
            out: (
                "Target0",
                format::COLOR,
                gfx::state::ColorMask::all(),
                Some(gfx::preset::blend::ALPHA),
            ),
            ..pipe::new()
        };

        let shader = Shader::new(factory, init);

        let globals = Globals {
            mvp: Transformation::identity().into(),
        };

        encoder
            .update_buffer(&data.globals, &[globals], 0)
            .expect("Globals initialization");

        Pipeline {
            data,
            indices,
            shader,
            globals,
        }
    }

    pub fn draw(
        &mut self,
        factory: &mut gl::Factory,
        encoder: &mut gfx::Encoder<gl::Resources, gl::CommandBuffer>,
        vertices: &[Vertex],
        indices: &[u32],
        transformation: &Transformation,
        view: &gfx::handle::RawRenderTargetView<gl::Resources>,
    ) {
        let transformation_matrix: [[f32; 4]; 4] =
            transformation.clone().into();

        if self.globals.mvp != transformation_matrix {
            self.globals.mvp = transformation_matrix;

            encoder
                .update_buffer(&self.data.globals, &[self.globals], 0)
                .expect("Globals upload");
        }

        self.data.out = view.clone();

        if self.data.vertices.len() < vertices.len()
            || self.indices.len() < indices.len()
        {
            let vertices = factory
                .create_buffer(
                    self.data.vertices.len(),
                    gfx::buffer::Role::Vertex,
                    gfx::memory::Usage::Dynamic,
                    gfx::memory::Bind::SHADER_RESOURCE,
                )
                .expect("Vertex buffer creation");

            let indices = factory
                .create_buffer(
                    indices.len(),
                    gfx::buffer::Role::Index,
                    gfx::memory::Usage::Dynamic,
                    gfx::memory::Bind::empty(),
                )
                .expect("Index buffer creation");

            self.data.vertices = vertices;
            self.indices = indices;
        }

        encoder
            .update_buffer(&self.data.vertices, &vertices, 0)
            .expect("Vertex upload");

        encoder
            .update_buffer(&self.indices, &indices, 0)
            .expect("Index upload");

        let slice = gfx::Slice {
            start: 0,
            end: indices.len() as u32,
            base_vertex: 0,
            instances: None,
            buffer: gfx::IndexBuffer::Index32(self.indices.clone()),
        };

        encoder.draw(&slice, &self.shader.state, &self.data);
    }
}

pub struct Shader {
    state: gfx::pso::PipelineState<gl::Resources, pipe::Meta>,
}

impl Shader {
    pub fn new(factory: &mut gl::Factory, init: pipe::Init<'_>) -> Shader {
        let set = factory
            .create_shader_set(
                include_bytes!("shader/triangle.vert"),
                include_bytes!("shader/triangle.frag"),
            )
            .expect("Shader set creation");

        let rasterizer = gfx::state::Rasterizer {
            front_face: gfx::state::FrontFace::CounterClockwise,
            cull_face: gfx::state::CullFace::Nothing,
            method: gfx::state::RasterMethod::Fill,
            offset: None,
            samples: None,
        };

        let state = factory
            .create_pipeline_state(
                &set,
                Primitive::TriangleList,
                rasterizer,
                init,
            )
            .expect("Pipeline state creation");

        Shader { state }
    }
}

impl Vertex {
    pub fn new(position: [f32; 2], color: [f32; 4]) -> Vertex {
        Vertex { position, color }
    }
}