#![deny(missing_docs)]
#![deny(unused_results)]
#![warn(bare_trait_objects)]
#![warn(missing_copy_implementations)]
use std::collections::HashMap;
use std::convert::TryFrom;
use std::mem;
use std::rc::Rc;
pub use glow;
use bytemuck;
use glam::Mat4;
use glow::*;
use log::*;
type GlTexture = <Context as glow::HasContext>::Texture;
type GlSampler = <Context as glow::HasContext>::Sampler;
type GlProgram = <Context as glow::HasContext>::Program;
type GlVertexArray = <Context as glow::HasContext>::VertexArray;
type GlFramebuffer = <Context as glow::HasContext>::Framebuffer;
type GlRenderbuffer = <Context as glow::HasContext>::Renderbuffer;
type GlBuffer = <Context as glow::HasContext>::Buffer;
type GlUniformLocation = <Context as glow::HasContext>::UniformLocation;
pub struct GlContext {
pub gl: Rc<glow::Context>,
pub passes: Vec<RenderPass>,
samplers: HashMap<SamplerSpec, GlSampler>,
quad_shader: Shader,
}
fn ortho(left: f32, right: f32, top: f32, bottom: f32, far: f32, near: f32) -> [[f32; 4]; 4] {
let c0r0 = 2.0 / (right - left);
let c0r1 = 0.0;
let c0r2 = 0.0;
let c0r3 = 0.0;
let c1r0 = 0.0;
let c1r1 = 2.0 / (top - bottom);
let c1r2 = 0.0;
let c1r3 = 0.0;
let c2r0 = 0.0;
let c2r1 = 0.0;
let c2r2 = -2.0 / (far - near);
let c2r3 = 0.0;
let c3r0 = -(right + left) / (right - left);
let c3r1 = -(top + bottom) / (top - bottom);
let c3r2 = -(far + near) / (far - near);
let c3r3 = 1.0;
[
[c0r0, c0r1, c0r2, c0r3],
[c1r0, c1r1, c1r2, c1r3],
[c2r0, c2r1, c2r2, c2r3],
[c3r0, c3r1, c3r2, c3r3],
]
}
fn ortho_mat(left: f32, right: f32, top: f32, bottom: f32, far: f32, near: f32) -> Mat4 {
Mat4::from_cols_array_2d(&ortho(left, right, top, bottom, far, near))
}
const VERTEX_SHADER_SOURCE: &str = include_str!("data/quad.vert.glsl");
const FRAGMENT_SHADER_SOURCE: &str = include_str!("data/quad.frag.glsl");
impl GlContext {
pub fn new(gl: glow::Context) -> Self {
unsafe {
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
let gl = Rc::new(gl);
let quad_shader =
ShaderHandle::new_raw(gl.clone(), VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
let s = GlContext {
gl,
passes: vec![],
samplers: HashMap::new(),
quad_shader: quad_shader.into_shared(),
};
s.register_debug_callback();
s
}
}
pub fn default_shader(&self) -> Shader {
self.quad_shader.clone()
}
fn register_debug_callback(&self) {
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
unsafe {
self.gl
.debug_message_callback(|source, typ, id, severity, message| {
match severity {
glow::DEBUG_SEVERITY_HIGH => {
error!(
"GL error type {} id {} from {}: {}",
typ, id, source, message
);
}
glow::DEBUG_SEVERITY_MEDIUM => {
warn!(
"GL error type {} id {} from {}: {}",
typ, id, source, message
);
}
glow::DEBUG_SEVERITY_LOW => {
info!(
"GL error type {} id {} from {}: {}",
typ, id, source, message
);
}
glow::DEBUG_SEVERITY_NOTIFICATION => (),
_ => (),
}
});
}
}
pub fn get_sampler(&mut self, spec: &SamplerSpec) -> GlSampler {
let gl = &*self.gl;
*self.samplers.entry(*spec).or_insert_with(|| unsafe {
let sampler = gl.create_sampler().unwrap();
gl.sampler_parameter_i32(
sampler,
glow::TEXTURE_MIN_FILTER,
spec.min_filter.to_gl() as i32,
);
gl.sampler_parameter_i32(
sampler,
glow::TEXTURE_MAG_FILTER,
spec.mag_filter.to_gl() as i32,
);
gl.sampler_parameter_i32(sampler, glow::TEXTURE_WRAP_S, spec.wrap.to_gl() as i32);
gl.sampler_parameter_i32(sampler, glow::TEXTURE_WRAP_T, spec.wrap.to_gl() as i32);
sampler
})
}
pub fn draw(&mut self) {
unsafe {
for pass in self.passes.iter_mut() {
pass.draw(&self.gl);
}
}
}
pub fn get_info(&self) -> (String, String, String, String) {
unsafe {
let vendor = self.gl.get_parameter_string(glow::VENDOR);
let rend = self.gl.get_parameter_string(glow::RENDERER);
let vers = self.gl.get_parameter_string(glow::VERSION);
let glsl_vers = self.gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION);
(vendor, rend, vers, glsl_vers)
}
}
pub fn set_screen_viewport(&mut self, x: i32, y: i32, w: i32, h: i32) {
let pass = self
.passes
.last_mut()
.expect("set_screen_viewport() requires a render pass to function on");
if let RenderTarget::Screen = pass.target {
pass.set_viewport(x, y, w, h);
} else {
panic!("Last render pass is not rendering to screen, aiee!");
}
}
}
#[derive(Debug)]
pub struct TextureHandle {
ctx: Rc<glow::Context>,
tex: GlTexture,
}
impl Drop for TextureHandle {
fn drop(&mut self) {
unsafe {
self.ctx.delete_texture(self.tex);
}
}
}
pub type Texture = Rc<TextureHandle>;
impl TextureHandle {
pub fn new(ctx: &GlContext, rgba: &[u8], width: usize, height: usize) -> Self {
assert_eq!(width * height * 4, rgba.len());
let gl = &*ctx.gl;
unsafe {
let t = gl.create_texture().unwrap();
gl.active_texture(glow::TEXTURE0);
gl.bind_texture(glow::TEXTURE_2D, Some(t));
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
i32::try_from(glow::RGBA).unwrap(),
i32::try_from(width).unwrap(),
i32::try_from(height).unwrap(),
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
Some(rgba),
);
gl.bind_texture(glow::TEXTURE_2D, None);
Self {
ctx: ctx.gl.clone(),
tex: t,
}
}
}
pub unsafe fn new_empty(
ctx: &GlContext,
format: u32,
component_format: u32,
width: usize,
height: usize,
) -> Self {
let gl = &*ctx.gl;
let t = gl.create_texture().unwrap();
gl.active_texture(glow::TEXTURE0);
gl.bind_texture(glow::TEXTURE_2D, Some(t));
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
i32::try_from(format).unwrap(),
i32::try_from(width).unwrap(),
i32::try_from(height).unwrap(),
0,
format,
component_format,
None,
);
gl.bind_texture(glow::TEXTURE_2D, None);
Self {
ctx: ctx.gl.clone(),
tex: t,
}
}
pub fn into_shared(self) -> Texture {
Rc::new(self)
}
}
#[derive(Debug)]
pub struct ShaderHandle {
ctx: Rc<glow::Context>,
program: GlProgram,
}
impl Drop for ShaderHandle {
fn drop(&mut self) {
unsafe {
self.ctx.delete_program(self.program);
}
}
}
pub type Shader = Rc<ShaderHandle>;
impl ShaderHandle {
fn new_raw(gl: Rc<glow::Context>, vertex_src: &str, fragment_src: &str) -> ShaderHandle {
let shader_sources = [
(glow::VERTEX_SHADER, vertex_src),
(glow::FRAGMENT_SHADER, fragment_src),
];
unsafe {
let program = gl.create_program().expect("Cannot create program");
let mut shaders = Vec::with_capacity(shader_sources.len());
for (shader_type, shader_source) in shader_sources.iter() {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(shader, shader_source);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
panic!(gl.get_shader_info_log(shader));
}
gl.attach_shader(program, shader);
shaders.push(shader);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!(gl.get_program_info_log(program));
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
ShaderHandle { ctx: gl, program }
}
}
pub fn new(ctx: &GlContext, vertex_src: &str, fragment_src: &str) -> ShaderHandle {
Self::new_raw(ctx.gl.clone(), vertex_src, fragment_src)
}
pub fn into_shared(self) -> Shader {
Rc::new(self)
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct QuadData {
pub color: [f32; 4],
pub src_rect: [f32; 4],
pub dst_rect: [f32; 4],
pub offset: [f32; 2],
pub rotation: f32,
}
unsafe impl bytemuck::Zeroable for QuadData {}
unsafe impl bytemuck::Pod for QuadData {}
impl QuadData {
pub const fn empty() -> Self {
QuadData {
offset: [0.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
src_rect: [0.0, 0.0, 1.0, 1.0],
dst_rect: [0.0, 0.0, 1.0, 1.0],
rotation: 0.0,
}
}
unsafe fn layout() -> Vec<(&'static str, usize, usize)> {
let thing = QuadData::empty();
let thing_base = &thing as *const QuadData;
let offset_offset = (&thing.offset as *const [f32; 2] as usize) - thing_base as usize;
let offset_size = mem::size_of_val(&thing.offset);
let color_offset = (&thing.color as *const [f32; 4] as usize) - thing_base as usize;
let color_size = mem::size_of_val(&thing.color);
let src_rect_offset = (&thing.src_rect as *const [f32; 4] as usize) - thing_base as usize;
let src_rect_size = mem::size_of_val(&thing.src_rect);
let dst_rect_offset = (&thing.dst_rect as *const [f32; 4] as usize) - thing_base as usize;
let dst_rect_size = mem::size_of_val(&thing.dst_rect);
let rotation_offset = (&thing.rotation as *const f32 as usize) - thing_base as usize;
let rotation_size = mem::size_of_val(&thing.rotation);
vec![
("model_offset", offset_offset, offset_size),
("model_color", color_offset, color_size),
("model_src_rect", src_rect_offset, src_rect_size),
("model_dst_rect", dst_rect_offset, dst_rect_size),
("model_rotation", rotation_offset, rotation_size),
]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FilterMode {
Nearest,
Linear,
}
impl FilterMode {
fn to_gl(self) -> u32 {
match self {
FilterMode::Nearest => glow::NEAREST,
FilterMode::Linear => glow::LINEAR,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum WrapMode {
Clamp,
Tile,
Mirror,
}
impl WrapMode {
fn to_gl(self) -> u32 {
match self {
WrapMode::Clamp => glow::CLAMP_TO_EDGE,
WrapMode::Tile => glow::REPEAT,
WrapMode::Mirror => glow::MIRRORED_REPEAT,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BlendMode {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SamplerSpec {
min_filter: FilterMode,
mag_filter: FilterMode,
wrap: WrapMode,
}
impl SamplerSpec {
pub fn new(min: FilterMode, mag: FilterMode, wrap: WrapMode) -> Self {
Self {
min_filter: min,
mag_filter: mag,
wrap,
}
}
}
impl Default for SamplerSpec {
fn default() -> Self {
Self::new(FilterMode::Nearest, FilterMode::Nearest, WrapMode::Tile)
}
}
#[derive(Debug)]
pub struct QuadDrawCall {
ctx: Rc<glow::Context>,
texture: Texture,
sampler: GlSampler,
pub instances: Vec<QuadData>,
vbo: GlBuffer,
vao: GlVertexArray,
instance_vbo: GlBuffer,
texture_location: GlUniformLocation,
pub dirty: bool,
}
impl Drop for QuadDrawCall {
fn drop(&mut self) {
unsafe {
self.ctx.delete_vertex_array(self.vao);
self.ctx.delete_buffer(self.vbo);
self.ctx.delete_buffer(self.instance_vbo);
}
}
}
impl QuadDrawCall {
unsafe fn set_vertex_pointers(ctx: &GlContext, shader: &ShaderHandle) {
let gl = &*ctx.gl;
let layout = QuadData::layout();
for (name, offset, size) in layout {
info!("Layout: {} offset, {} size", offset, size);
let element_size = mem::size_of::<f32>();
let attrib_location = gl.get_attrib_location(shader.program, name).unwrap();
gl.vertex_attrib_pointer_f32(
attrib_location,
(size / element_size) as i32,
glow::FLOAT,
false,
mem::size_of::<QuadData>() as i32,
offset as i32,
);
gl.vertex_attrib_divisor(attrib_location, 1);
gl.enable_vertex_attrib_array(attrib_location);
}
}
pub fn new(
ctx: &mut GlContext,
texture: Texture,
sampler: SamplerSpec,
pipeline: &QuadPipeline,
) -> Self {
let sampler = ctx.get_sampler(&sampler);
let gl = &*ctx.gl;
unsafe {
let vao = gl.create_vertex_array().unwrap();
gl.bind_vertex_array(Some(vao));
let vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
let dummy_attrib = gl
.get_attrib_location(pipeline.shader.program, "vertex_dummy")
.unwrap();
gl.vertex_attrib_pointer_f32(
dummy_attrib,
2,
glow::FLOAT,
false,
0,
0,
);
gl.enable_vertex_attrib_array(dummy_attrib);
let empty_slice: &[u8] = &[0; mem::size_of::<f32>() * 2 * 6];
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, empty_slice, glow::STREAM_DRAW);
let instance_vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(instance_vbo));
Self::set_vertex_pointers(ctx, &pipeline.shader);
let texture_location = gl
.get_uniform_location(pipeline.shader.program, "tex")
.unwrap();
gl.bind_vertex_array(None);
Self {
ctx: ctx.gl.clone(),
vbo,
vao,
texture,
sampler,
instance_vbo,
texture_location,
instances: vec![],
dirty: true,
}
}
}
pub fn add(&mut self, quad: QuadData) {
self.dirty = true;
self.instances.push(quad);
}
pub fn clear(&mut self) {
self.dirty = true;
self.instances.clear();
}
unsafe fn upload_instances(&mut self, gl: &Context) {
let bytes_slice: &[u8] = bytemuck::try_cast_slice(self.instances.as_slice()).unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_vbo));
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, bytes_slice, glow::STREAM_DRAW);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
self.dirty = false;
}
unsafe fn draw(&mut self, gl: &Context) {
if self.dirty {
self.upload_instances(gl);
}
let num_instances = self.instances.len();
let num_vertices = 6;
gl.bind_vertex_array(Some(self.vao));
gl.active_texture(glow::TEXTURE0);
gl.bind_texture(glow::TEXTURE_2D, Some(self.texture.tex));
gl.uniform_1_i32(Some(self.texture_location), 0);
gl.bind_sampler(0, Some(self.sampler));
gl.draw_arrays_instanced(
glow::TRIANGLES,
0,
num_vertices as i32,
num_instances as i32,
);
}
}
pub trait DrawCall {
fn add(&mut self, quad: QuadData);
fn clear(&mut self);
unsafe fn draw(&mut self, gl: &Context);
}
impl DrawCall for QuadDrawCall {
fn add(&mut self, quad: QuadData) {
self.add(quad);
}
fn clear(&mut self) {
self.clear();
}
unsafe fn draw(&mut self, gl: &Context) {
self.draw(gl);
}
}
#[derive(Debug)]
pub struct MeshDrawCall {
ctx: Rc<glow::Context>,
texture: Texture,
sampler: GlSampler,
pub instances: Vec<QuadData>,
vbo: GlBuffer,
vao: GlVertexArray,
instance_vbo: GlBuffer,
texture_location: GlUniformLocation,
pub dirty: bool,
}
impl Drop for MeshDrawCall {
fn drop(&mut self) {
unsafe {
self.ctx.delete_vertex_array(self.vao);
self.ctx.delete_buffer(self.vbo);
self.ctx.delete_buffer(self.instance_vbo);
}
}
}
impl MeshDrawCall {
unsafe fn set_vertex_pointers(ctx: &GlContext, shader: &ShaderHandle) {
let gl = &*ctx.gl;
let layout = QuadData::layout();
for (name, offset, size) in layout {
info!("Layout: {} offset, {} size", offset, size);
let element_size = mem::size_of::<f32>();
let attrib_location = gl.get_attrib_location(shader.program, name).unwrap();
gl.vertex_attrib_pointer_f32(
attrib_location,
(size / element_size) as i32,
glow::FLOAT,
false,
mem::size_of::<QuadData>() as i32,
offset as i32,
);
gl.vertex_attrib_divisor(attrib_location, 1);
gl.enable_vertex_attrib_array(attrib_location);
}
}
pub fn new(
ctx: &mut GlContext,
texture: Texture,
sampler: SamplerSpec,
pipeline: &MeshPipeline,
) -> Self {
let sampler = ctx.get_sampler(&sampler);
let gl = &*ctx.gl;
unsafe {
let vao = gl.create_vertex_array().unwrap();
gl.bind_vertex_array(Some(vao));
let vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
let dummy_attrib = gl
.get_attrib_location(pipeline.shader.program, "vertex_dummy")
.unwrap();
gl.vertex_attrib_pointer_f32(
dummy_attrib,
2,
glow::FLOAT,
false,
0,
0,
);
gl.enable_vertex_attrib_array(dummy_attrib);
let empty_slice: &[u8] = &[0; mem::size_of::<f32>() * 2 * 6];
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, empty_slice, glow::STREAM_DRAW);
let instance_vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(instance_vbo));
Self::set_vertex_pointers(ctx, &pipeline.shader);
let texture_location = gl
.get_uniform_location(pipeline.shader.program, "tex")
.unwrap();
gl.bind_vertex_array(None);
Self {
ctx: ctx.gl.clone(),
vbo,
vao,
texture,
sampler,
instance_vbo,
texture_location,
instances: vec![],
dirty: true,
}
}
}
pub fn add(&mut self, quad: QuadData) {
self.dirty = true;
self.instances.push(quad);
}
pub fn clear(&mut self) {
self.dirty = true;
self.instances.clear();
}
unsafe fn upload_instances(&mut self, gl: &Context) {
let bytes_slice: &[u8] = bytemuck::try_cast_slice(self.instances.as_slice()).unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_vbo));
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, bytes_slice, glow::STREAM_DRAW);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
self.dirty = false;
}
unsafe fn draw(&mut self, gl: &Context) {
if self.dirty {
self.upload_instances(gl);
}
let num_instances = self.instances.len();
let num_vertices = 6;
gl.bind_vertex_array(Some(self.vao));
gl.active_texture(glow::TEXTURE0);
gl.bind_texture(glow::TEXTURE_2D, Some(self.texture.tex));
gl.uniform_1_i32(Some(self.texture_location), 0);
gl.bind_sampler(0, Some(self.sampler));
gl.draw_arrays_instanced(
glow::TRIANGLES,
0,
num_vertices as i32,
num_instances as i32,
);
}
}
impl DrawCall for MeshDrawCall {
fn add(&mut self, quad: QuadData) {
self.add(quad);
}
fn clear(&mut self) {
self.clear();
}
unsafe fn draw(&mut self, gl: &Context) {
self.draw(gl);
}
}
pub struct QuadPipeline {
pub drawcalls: Vec<QuadDrawCall>,
pub projection: Mat4,
shader: Shader,
projection_location: GlUniformLocation,
}
impl QuadPipeline {
pub unsafe fn new(ctx: &GlContext, shader: Shader) -> Self {
let gl = &*ctx.gl;
let projection = ortho_mat(-1.0, 1.0, 1.0, -1.0, 1.0, -1.0);
let projection_location = gl
.get_uniform_location(shader.program, "projection")
.unwrap();
Self {
drawcalls: vec![],
shader,
projection,
projection_location,
}
}
pub unsafe fn draw(&mut self, gl: &Context) {
gl.use_program(Some(self.shader.program));
gl.uniform_matrix_4_f32_slice(
Some(self.projection_location),
false,
&self.projection.to_cols_array(),
);
for dc in self.drawcalls.iter_mut() {
dc.draw(gl);
}
}
}
pub trait Pipeline {
unsafe fn draw(&mut self, gl: &Context);
fn new_drawcall(
&mut self,
ctx: &mut GlContext,
texture: Texture,
sampler: SamplerSpec,
) -> &mut dyn DrawCall;
fn get(&self, idx: usize) -> &dyn DrawCall;
fn get_mut(&mut self, idx: usize) -> &mut dyn DrawCall;
fn clear(&mut self);
fn drawcalls<'a>(&'a self) -> Box<dyn Iterator<Item = &'a dyn DrawCall> + 'a>;
fn drawcalls_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut dyn DrawCall> + 'a>;
}
pub struct PipelineIter<'a> {
i: std::slice::Iter<'a, QuadDrawCall>,
}
impl<'a> PipelineIter<'a> {
pub fn new(p: &'a QuadPipeline) -> Self {
Self {
i: p.drawcalls.iter(),
}
}
}
impl<'a> Iterator for PipelineIter<'a> {
type Item = &'a dyn DrawCall;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|x| x as _)
}
}
pub struct PipelineIterMut<'a> {
i: std::slice::IterMut<'a, QuadDrawCall>,
}
impl<'a> PipelineIterMut<'a> {
pub fn new(p: &'a mut QuadPipeline) -> Self {
Self {
i: p.drawcalls.iter_mut(),
}
}
}
impl<'a> Iterator for PipelineIterMut<'a> {
type Item = &'a mut dyn DrawCall;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|x| x as _)
}
}
impl Pipeline for QuadPipeline {
unsafe fn draw(&mut self, gl: &Context) {
self.draw(gl);
}
fn new_drawcall(
&mut self,
ctx: &mut GlContext,
texture: Texture,
sampler: SamplerSpec,
) -> &mut dyn DrawCall {
let x = QuadDrawCall::new(ctx, texture, sampler, self);
self.drawcalls.push(x);
&mut *self.drawcalls.last_mut().unwrap()
}
fn clear(&mut self) {
self.drawcalls.clear()
}
fn get(&self, idx: usize) -> &dyn DrawCall {
&self.drawcalls[idx]
}
fn get_mut(&mut self, idx: usize) -> &mut dyn DrawCall {
&mut self.drawcalls[idx]
}
fn drawcalls<'a>(&'a self) -> Box<dyn Iterator<Item = &'a dyn DrawCall> + 'a> {
let i = PipelineIter::new(self);
Box::new(i)
}
fn drawcalls_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut dyn DrawCall> + 'a> {
let i = PipelineIterMut::new(self);
Box::new(i)
}
}
pub struct MeshPipeline {
pub drawcalls: Vec<MeshDrawCall>,
pub projection: Mat4,
shader: Shader,
projection_location: GlUniformLocation,
}
impl MeshPipeline {
pub unsafe fn new(ctx: &GlContext, shader: Shader) -> Self {
let gl = &*ctx.gl;
let projection = ortho_mat(-1.0, 1.0, 1.0, -1.0, 1.0, -1.0);
let projection_location = gl
.get_uniform_location(shader.program, "projection")
.unwrap();
Self {
drawcalls: vec![],
shader,
projection,
projection_location,
}
}
pub unsafe fn draw(&mut self, gl: &Context) {
gl.use_program(Some(self.shader.program));
gl.uniform_matrix_4_f32_slice(
Some(self.projection_location),
false,
&self.projection.to_cols_array(),
);
for dc in self.drawcalls.iter_mut() {
dc.draw(gl);
}
}
}
pub struct MeshPipelineIter<'a> {
i: std::slice::Iter<'a, MeshDrawCall>,
}
impl<'a> MeshPipelineIter<'a> {
pub fn new(p: &'a MeshPipeline) -> Self {
Self {
i: p.drawcalls.iter(),
}
}
}
impl<'a> Iterator for MeshPipelineIter<'a> {
type Item = &'a dyn DrawCall;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|x| x as _)
}
}
pub struct MeshPipelineIterMut<'a> {
i: std::slice::IterMut<'a, MeshDrawCall>,
}
impl<'a> MeshPipelineIterMut<'a> {
pub fn new(p: &'a mut MeshPipeline) -> Self {
Self {
i: p.drawcalls.iter_mut(),
}
}
}
impl<'a> Iterator for MeshPipelineIterMut<'a> {
type Item = &'a mut dyn DrawCall;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|x| x as _)
}
}
impl Pipeline for MeshPipeline {
unsafe fn draw(&mut self, gl: &Context) {
self.draw(gl);
}
fn new_drawcall(
&mut self,
ctx: &mut GlContext,
texture: Texture,
sampler: SamplerSpec,
) -> &mut dyn DrawCall {
let x = MeshDrawCall::new(ctx, texture, sampler, self);
self.drawcalls.push(x);
&mut *self.drawcalls.last_mut().unwrap()
}
fn clear(&mut self) {
self.drawcalls.clear()
}
fn get(&self, idx: usize) -> &dyn DrawCall {
&self.drawcalls[idx]
}
fn get_mut(&mut self, idx: usize) -> &mut dyn DrawCall {
&mut self.drawcalls[idx]
}
fn drawcalls<'a>(&'a self) -> Box<dyn Iterator<Item = &'a dyn DrawCall> + 'a> {
let i = MeshPipelineIter::new(self);
Box::new(i)
}
fn drawcalls_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut dyn DrawCall> + 'a> {
let i = MeshPipelineIterMut::new(self);
Box::new(i)
}
}
#[derive(Debug)]
pub struct TextureRenderTarget {
ctx: Rc<glow::Context>,
output_framebuffer: GlFramebuffer,
output_texture: Texture,
_output_depthbuffer: GlRenderbuffer,
}
impl Drop for TextureRenderTarget {
fn drop(&mut self) {
unsafe {
self.ctx.delete_framebuffer(self.output_framebuffer);
self.ctx.delete_renderbuffer(self._output_depthbuffer);
}
}
}
impl TextureRenderTarget {
pub unsafe fn new(ctx: &GlContext, width: usize, height: usize) -> Self {
let gl = &*ctx.gl;
let t = TextureHandle::new_empty(ctx, glow::RGBA, glow::UNSIGNED_BYTE, width, height)
.into_shared();
let depth = gl.create_renderbuffer().unwrap();
let fb = gl.create_framebuffer().unwrap();
gl.bind_texture(glow::TEXTURE_2D, Some(t.tex));
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
glow::NEAREST as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
glow::NEAREST as i32,
);
gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fb));
gl.framebuffer_texture_2d(
glow::FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::TEXTURE_2D,
Some(t.tex),
0,
);
let draw_buffers = &[glow::COLOR_ATTACHMENT0];
gl.draw_buffers(draw_buffers);
if gl.check_framebuffer_status(glow::FRAMEBUFFER) != glow::FRAMEBUFFER_COMPLETE {
panic!("Framebuffer hecked up");
}
gl.bind_framebuffer(glow::FRAMEBUFFER, None);
gl.bind_texture(glow::TEXTURE_2D, None);
Self {
ctx: ctx.gl.clone(),
output_framebuffer: fb,
output_texture: t,
_output_depthbuffer: depth,
}
}
}
#[derive(Debug)]
pub enum RenderTarget {
Texture(TextureRenderTarget),
Screen,
}
impl RenderTarget {
pub fn screen_target() -> Self {
Self::Screen
}
pub fn new_target(ctx: &GlContext, width: usize, height: usize) -> Self {
unsafe {
let target = TextureRenderTarget::new(ctx, width, height);
Self::Texture(target)
}
}
unsafe fn bind(&self, gl: &glow::Context) {
let fb = match self {
Self::Screen => None,
Self::Texture(trt) => Some(trt.output_framebuffer),
};
gl.bind_framebuffer(glow::FRAMEBUFFER, fb);
}
}
pub struct RenderPass {
target: RenderTarget,
clear_color: (f32, f32, f32, f32),
viewport: (i32, i32, i32, i32),
pub pipelines: Vec<Box<dyn Pipeline>>,
}
impl RenderPass {
pub unsafe fn new(
ctx: &mut GlContext,
width: usize,
height: usize,
clear_color: (f32, f32, f32, f32),
) -> Self {
let target = RenderTarget::new_target(ctx, width, height);
Self {
target,
pipelines: vec![],
viewport: (0, 0, width as i32, height as i32),
clear_color,
}
}
pub unsafe fn new_screen(
_ctx: &mut GlContext,
width: usize,
height: usize,
clear_color: (f32, f32, f32, f32),
) -> Self {
Self {
target: RenderTarget::Screen,
pipelines: vec![],
viewport: (0, 0, width as i32, height as i32),
clear_color,
}
}
pub fn add_pipeline(&mut self, pipeline: impl Pipeline + 'static) {
self.pipelines.push(Box::new(pipeline))
}
unsafe fn draw(&mut self, gl: &Context) {
self.target.bind(gl);
let (r, g, b, a) = self.clear_color;
let (x, y, w, h) = self.viewport;
gl.viewport(x, y, w, h);
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT);
for pipeline in self.pipelines.iter_mut() {
pipeline.draw(gl);
}
}
pub fn get_texture(&self) -> Option<Texture> {
match &self.target {
RenderTarget::Screen => None,
RenderTarget::Texture(trt) => Some(trt.output_texture.clone()),
}
}
pub fn set_viewport(&mut self, x: i32, y: i32, w: i32, h: i32) {
self.viewport = (x, y, w, h);
}
}