use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::io;
use std::sync::Arc;
use luminance::backend::shader::Uniformable;
use luminance::blending::{Blending, Equation, Factor};
use luminance::context::GraphicsContext;
use luminance::depth_stencil::Comparison;
use luminance::framebuffer::Framebuffer;
use luminance::pipeline::Pipeline;
use luminance::pipeline::{PipelineError, PipelineState, TextureBinding};
use luminance::pixel;
use luminance::pixel::{ColorPixel, Pixel, PixelFormat, RenderablePixel};
use luminance::render_state::RenderState;
use luminance::shader::UniformType;
use luminance::shader::{Program, Uniform};
use luminance::shading_gate::ShadingGate;
use luminance::tess::{Mode, Tess, TessBuilder};
use luminance::texture::{Dim2, MagFilter, MinFilter, Sampler, TexelUpload, Texture, Wrap};
use luminance_derive::{Semantics, UniformInterface, Vertex};
use luminance_gl::gl33;
use crate::gfx;
use crate::gfx::renderer::{Effect, Paint, TextureId, TextureStore};
use crate::gfx::*;
use crate::math::*;
use crate::platform::{self, LogicalSize};
type Gl = gl33::GL33;
const SAMPLER: Sampler = Sampler {
wrap_r: Wrap::Repeat,
wrap_s: Wrap::Repeat,
wrap_t: Wrap::Repeat,
min_filter: MinFilter::Nearest,
mag_filter: MagFilter::Nearest,
depth_comparison: None,
};
#[derive(Copy, Clone, Debug, Semantics)]
pub enum VertexSemantics {
#[sem(name = "position", repr = "[f32; 3]", wrapper = "VertexPosition")]
Position,
#[sem(name = "uv", repr = "[f32; 2]", wrapper = "VertexUv")]
Uv,
#[sem(name = "color", repr = "[u8; 4]", wrapper = "VertexColor")]
Color,
#[sem(name = "opacity", repr = "[f32; 1]", wrapper = "VertexOpacity")]
Opacity,
#[sem(name = "angle", repr = "[f32; 1]", wrapper = "VertexAngle")]
Angle,
#[sem(name = "center", repr = "[f32; 2]", wrapper = "VertexCenter")]
Center,
}
#[derive(UniformInterface)]
struct Sprite2dInterface {
tex: Uniform<TextureBinding<Dim2, pixel::NormUnsigned>>,
ortho: Uniform<Transform3D>,
transform: Uniform<Transform3D>,
}
#[repr(C)]
#[derive(Copy, Clone, Vertex, Debug)]
#[vertex(sem = "VertexSemantics")]
struct Sprite2dVertex {
position: VertexPosition,
uv: VertexUv,
#[vertex(normalized = "true")]
color: VertexColor,
opacity: VertexOpacity,
}
#[derive(UniformInterface)]
struct Shape2dInterface {
ortho: Uniform<Transform3D>,
transform: Uniform<Transform3D>,
}
#[repr(C)]
#[derive(Copy, Clone, Vertex, Debug)]
#[vertex(sem = "VertexSemantics")]
struct Shape2dVertex {
position: VertexPosition,
angle: VertexAngle,
center: VertexCenter,
#[vertex(normalized = "true")]
color: VertexColor,
}
#[derive(UniformInterface)]
struct Screen2dInterface {
framebuffer: Uniform<TextureBinding<Dim2, pixel::NormUnsigned>>,
}
unsafe impl Pixel for Rgba8 {
type Encoding = Rgba8;
type RawEncoding = u8;
type SamplerType = pixel::NormUnsigned;
fn pixel_format() -> PixelFormat {
pixel::SRGBA8UI::pixel_format()
}
}
unsafe impl RenderablePixel for Rgba8 {}
unsafe impl ColorPixel for Rgba8 {}
unsafe impl<'a> Uniformable<'a, Transform3D> for Gl {
type Target = Transform3D;
const SIZE: usize = 1;
unsafe fn ty() -> UniformType {
UniformType::M44
}
unsafe fn update(
_program: &mut Self::ProgramRepr,
uniform: &'a Uniform<Transform3D>,
transform: Self::Target,
) {
let mat4: [[f32; 4]; 4] = transform.into();
gl::UniformMatrix4fv(uniform.index(), 1, gl::FALSE, mat4.as_ptr() as _);
}
}
impl From<gfx::Blending> for Blending {
fn from(other: gfx::Blending) -> Self {
match other {
gfx::Blending::Alpha => Blending {
equation: Equation::Additive,
src: Factor::SrcAlpha,
dst: Factor::SrcAlphaComplement,
},
gfx::Blending::Constant => Blending {
equation: Equation::Additive,
src: Factor::One,
dst: Factor::Zero,
},
}
}
}
type RenderTargets = HashMap<TextureId, RenderTarget>;
#[derive(Default)]
struct Textures {
storage: HashMap<TextureId, Texture<Gl, Dim2, Rgba8>>,
}
impl Textures {
fn put(
&mut self,
id: TextureId,
image: Image,
backend: &mut Backend,
) -> Result<&Texture<Gl, Dim2, Rgba8>, Error> {
let texture: Texture<Gl, Dim2, Rgba8> = Texture::new(
backend,
image.size.into(),
SAMPLER,
TexelUpload::BaseLevel {
texels: &image.pixels,
mipmaps: 0,
},
)?;
Ok(self.storage.entry(id).or_insert(texture))
}
}
struct RenderTarget {
pub size: Size<u32>,
fb: Framebuffer<Gl, Dim2, Rgba8, pixel::Depth32F>,
}
impl RenderTarget {
fn new(size: Size<u32>, texels: &[Rgba8], backend: &mut Backend) -> Result<Self, Error> {
let mut fb: Framebuffer<Gl, Dim2, Rgba8, pixel::Depth32F> =
Framebuffer::new(backend, size.into(), 0, self::SAMPLER)?;
fb.color_slot()
.upload(TexelUpload::BaseLevel { texels, mipmaps: 0 })?;
Ok(Self { size, fb })
}
fn upload_part(
&mut self,
offset: impl Into<Vector2D<u32>>,
size: impl Into<Size<u32>>,
texels: &[Rgba8],
) -> Result<(), Error> {
let offset = offset.into();
let size = size.into();
self.fb
.color_slot()
.upload_part(
offset.into(),
size.into(),
TexelUpload::BaseLevel { texels, mipmaps: 0 },
)
.map_err(Error::from)
}
fn upload(&mut self, texels: &[Rgba8]) -> Result<(), Error> {
self.fb
.color_slot()
.upload(TexelUpload::BaseLevel { texels, mipmaps: 0 })
.map_err(Error::from)
}
fn pixels(&mut self) -> Result<Vec<Rgba8>, Error> {
let texels = self.fb.color_slot().get_raw_texels()?;
Ok(Rgba8::align(&texels).to_vec())
}
fn resized(mut self, size: Size<u32>, backend: &mut Backend) -> Result<Self, Error> {
let texels = self.pixels()?;
let texels = texels.as_slice();
let blank = Image::blank(size);
let tw = u32::min(size.w, self.size.w);
let th = u32::min(size.h, self.size.h);
let mut texture = self.fb.into_color_slot();
texture.resize(size.into(), TexelUpload::Reserve { mipmaps: 0 })?;
let mut resized = Self {
fb: Framebuffer::new(backend, size.into(), 0, self::SAMPLER)?,
size,
};
resized.upload(&blank.pixels)?;
resized.upload_part([0, size.h - th], [tw, th], texels)?;
Ok(resized)
}
}
pub struct Renderer {
pub win_size: LogicalSize,
pub win_scale: f64,
pub ui_scale: f32,
present_fb: Framebuffer<Gl, Dim2, (), ()>,
screen_fb: Framebuffer<Gl, Dim2, Rgba8, pixel::Depth32F>,
pipeline_st: PipelineState,
backend: Backend,
context: RenderContext,
}
struct RenderContext {
textures: Textures,
targets: RenderTargets,
render_st: RenderState,
sprite2d: Program<Gl, VertexSemantics, (), Sprite2dInterface>,
shape2d: Program<Gl, VertexSemantics, (), Shape2dInterface>,
screen2d: Program<Gl, VertexSemantics, (), Screen2dInterface>,
}
impl RenderContext {
fn render(
&mut self,
op: RenderOp,
identity: Transform3D,
ortho: Transform3D,
pipeline: &Pipeline<'_, Gl>,
shd_gate: &mut ShadingGate<'_, Gl>,
) -> Result<(), Error> {
match op {
RenderOp::Shape {
tess,
transform,
blending,
} => {
shd_gate.shade(&mut self.shape2d, |mut iface, uni, mut rdr_gate| {
iface.set(&uni.ortho, ortho);
iface.set(&uni.transform, identity * transform);
rdr_gate.render(
&self.render_st.clone().set_blending(blending),
|mut tess_gate| tess_gate.render(&tess),
)?;
Ok::<_, PipelineError>(())
})?;
}
RenderOp::Sprite {
tess,
transform,
texture,
blending,
} => {
let texture = if let Some(texture) = self.textures.storage.get_mut(&texture) {
texture
} else if let Some(target) = self.targets.get_mut(&texture) {
target.fb.color_slot()
} else {
return Err(Error::TextureNotFound(texture));
};
shd_gate.shade(&mut self.sprite2d, |mut iface, uni, mut rdr_gate| {
let bound = pipeline.bind_texture(texture)?;
iface.set(&uni.ortho, ortho);
iface.set(&uni.transform, identity * transform);
iface.set(&uni.tex, bound.binding());
rdr_gate.render(
&self.render_st.clone().set_blending(blending),
|mut tess_gate| tess_gate.render(&tess),
)?;
Ok::<_, PipelineError>(())
})?;
}
}
Ok(())
}
}
struct Backend {
gl: Gl,
}
unsafe impl GraphicsContext for Backend {
type Backend = self::Gl;
fn backend(&mut self) -> &mut Self::Backend {
&mut self.gl
}
}
impl Backend {
fn program<T>(
&mut self,
vert: &str,
frag: &str,
) -> Result<Program<Gl, VertexSemantics, (), T>, Error>
where
T: luminance::shader::UniformInterface<Gl>,
{
Ok(self
.new_shader_program()
.from_strings(vert, None, None, frag)?
.ignore_warnings())
}
fn tessellation<T, S>(&mut self, verts: &[T]) -> Result<Tess<Gl, S>, Error>
where
S: luminance::vertex::Vertex + Sized,
{
let (head, body, tail) = unsafe { verts.align_to::<S>() };
assert!(head.is_empty());
assert!(tail.is_empty());
TessBuilder::new(self)
.set_vertices(body)
.set_mode(Mode::Triangle)
.build()
.map_err(Error::from)
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("initialization error")]
Initialization,
#[error("texture error: {0}")]
Texture(#[from] luminance::texture::TextureError),
#[error("framebuffer error: {0}")]
Framebuffer(#[from] luminance::framebuffer::FramebufferError),
#[error("pipeline error: {0}")]
Pipeline(#[from] PipelineError),
#[error("state query error: {0}")]
State(#[from] luminance_gl::gl33::StateQueryError),
#[error("tesselation error: {0}")]
Tess(#[from] luminance::tess::TessError),
#[error("{0} not found")]
TextureNotFound(TextureId),
#[error("i/o error: {0}")]
Io(#[from] io::Error),
#[error("program error: {0}")]
Program(#[from] luminance::shader::ProgramError),
#[error("error: {0}")]
Custom(&'static str),
}
#[derive(Debug)]
enum RenderOp {
Shape {
tess: Tess<Gl, Shape2dVertex>,
transform: Transform3D,
blending: Blending,
},
Sprite {
tess: Tess<Gl, Sprite2dVertex>,
transform: Transform3D,
texture: TextureId,
blending: Blending,
},
}
#[derive(Default)]
struct Frame {
onscreen: Vec<RenderOp>,
offscreen: HashMap<TextureId, Vec<RenderOp>>,
clear: HashMap<TextureId, Rgba>,
upload: HashMap<TextureId, Arc<[Rgba8]>>,
dirty: HashSet<TextureId>,
}
impl Frame {
pub fn new(
effects: impl Iterator<Item = Effect>,
context: &mut RenderContext,
backend: &mut Backend,
) -> Result<Self, Error> {
let mut frame = Frame::default();
for eff in effects {
match eff {
Effect::Paint { paint, blending } => {
frame.paint(paint, blending.into(), backend)?;
}
Effect::Clear { id, color } => {
frame.clear(id, color);
}
Effect::Texture {
id,
image,
offscreen,
} => {
if offscreen {
frame.framebuffer(id, image, context, backend)?;
} else {
frame.texture(id, image, context, backend)?;
}
}
Effect::Resize { id, size } => {
frame.resize(id, size, context, backend)?;
}
Effect::Upload { id, texels } => {
frame.upload.insert(id, texels);
frame.offscreen.entry(id).or_default();
}
}
}
Ok(frame)
}
fn texture(
&mut self,
id: TextureId,
image: Image,
context: &mut RenderContext,
backend: &mut Backend,
) -> Result<(), Error> {
context.textures.put(id, image, backend)?;
Ok(())
}
fn framebuffer(
&mut self,
id: TextureId,
image: Image,
context: &mut RenderContext,
backend: &mut Backend,
) -> Result<(), Error> {
if let Entry::Vacant(e) = context.targets.entry(id) {
let rt = RenderTarget::new(image.size, image.pixels.as_ref(), backend)?;
e.insert(rt);
}
self.offscreen.entry(id).or_default();
Ok(())
}
fn clear(&mut self, texture: TextureId, color: Rgba8) {
self.clear.insert(texture, Rgba::from(color));
self.offscreen.entry(texture).or_default();
}
fn resize(
&mut self,
texture: TextureId,
size: Size<u32>,
context: &mut RenderContext,
backend: &mut Backend,
) -> Result<(), Error> {
if let Some(rt) = context.targets.remove(&texture) {
let rt = rt.resized(size, backend)?;
context.targets.insert(texture, rt);
}
Ok(())
}
fn paint(
&mut self,
paint: Paint,
blending: Blending,
backend: &mut Backend,
) -> Result<(), Error> {
match paint {
Paint::Shape {
transform,
vertices,
target,
} if !vertices.is_empty() => {
let tess = backend.tessellation::<_, Shape2dVertex>(&vertices)?;
let ops = if let Some(target) = target {
self.dirty.insert(target);
self.offscreen.entry(target).or_default()
} else {
&mut self.onscreen
};
ops.push(RenderOp::Shape {
tess,
transform: transform.into(),
blending,
});
}
Paint::Sprite {
transform,
vertices,
texture,
target,
} if !vertices.is_empty() => {
let tess = backend.tessellation::<_, Sprite2dVertex>(&vertices)?;
let ops = if let Some(target) = target {
self.dirty.insert(target);
self.offscreen.entry(target).or_default()
} else {
&mut self.onscreen
};
ops.push(RenderOp::Sprite {
tess,
transform: transform.into(),
texture,
blending,
});
}
_ => {}
}
Ok(())
}
}
impl gfx::Renderer for Renderer {
type Error = Error;
fn new(
win: &mut platform::backend::Window,
win_size: LogicalSize,
win_scale: f64,
ui_scale: f32,
) -> Result<Self, Error> {
gl::load_with(|s| win.get_proc_address(s) as *const _);
let gl = Gl::new()?;
let mut backend = Backend { gl };
let sprite2d = backend.program::<Sprite2dInterface>(
include_str!("gl/data/sprite.vert"),
include_str!("gl/data/sprite.frag"),
)?;
let shape2d = backend.program::<Shape2dInterface>(
include_str!("gl/data/shape.vert"),
include_str!("gl/data/shape.frag"),
)?;
let screen2d = backend.program::<Screen2dInterface>(
include_str!("gl/data/screen.vert"),
include_str!("gl/data/screen.frag"),
)?;
let physical = win_size.to_physical(win_scale);
let present_fb = Framebuffer::back_buffer(
&mut backend,
[physical.width as u32, physical.height as u32],
)?;
let screen_fb = Framebuffer::new(
&mut backend,
[win_size.width as u32, win_size.height as u32],
0,
self::SAMPLER,
)?;
let render_st = RenderState::default()
.set_blending(Some(gfx::Blending::default().into()))
.set_depth_test(Some(Comparison::LessOrEqual));
let pipeline_st = PipelineState::default()
.set_clear_color([0., 0., 0., 0.])
.set_clear_depth(1.)
.enable_srgb(true);
let context = RenderContext {
render_st,
sprite2d,
shape2d,
screen2d,
textures: Textures::default(),
targets: HashMap::new(),
};
Ok(Renderer {
context,
pipeline_st,
present_fb,
screen_fb,
backend,
win_size,
win_scale,
ui_scale,
})
}
fn frame<E, T>(&mut self, effects: E, store: &mut T) -> Result<(), Error>
where
E: Iterator<Item = Effect>,
T: TextureStore,
{
let frame = Frame::new(effects, &mut self.context, &mut self.backend)?;
let screen_tess = TessBuilder::<Gl, ()>::new(&mut self.backend)
.set_render_vertex_nb(6)
.set_mode(Mode::Triangle)
.build()?;
let mut builder = self.backend.new_pipeline_gate();
for (id, ops) in frame.offscreen {
let mut target = if let Some(t) = self.context.targets.remove(&id) {
t
} else {
continue;
};
let ortho = Transform3D::<f32, (), ()>::ortho(target.fb.size(), Origin::BottomLeft);
let clear = frame.clear.get(&id);
if let Some(texels) = frame.upload.get(&id) {
target.upload(texels)?;
if clear.is_some() {
warn!("ignoring `clear` operation due to `upload` on {}", id);
}
if !ops.is_empty() {
warn!("ignoring paint operations due to `upload` on {}", id);
}
} else {
let offscreen_st = self
.pipeline_st
.clone()
.set_clear_color(clear.map(|c| (*c).into()));
builder.pipeline(&target.fb, &offscreen_st, |pipeline, mut shd_gate| {
for op in ops {
self.context.render(
op,
Transform3D::identity(),
ortho,
&pipeline,
&mut shd_gate,
)?;
}
Ok::<_, Error>(())
});
}
self.context.targets.insert(id, target);
}
let ortho = Transform3D::<f32, (), ()>::ortho(self.screen_fb.size(), Origin::TopLeft);
let identity = Transform3D::from_nonuniform_scale(
self.win_scale as f32 * self.ui_scale,
self.win_scale as f32 * self.ui_scale,
0.,
);
builder.pipeline(
&self.screen_fb,
&self.pipeline_st,
|pipeline, mut shd_gate| {
for op in frame.onscreen {
self.context
.render(op, identity, ortho, &pipeline, &mut shd_gate)?;
}
Ok::<_, Error>(())
},
);
builder.pipeline::<PipelineError, _, _, _, _>(
&self.present_fb,
&self.pipeline_st,
|pipeline, mut shd_gate| {
let bound_screen = pipeline.bind_texture(self.screen_fb.color_slot())?;
shd_gate.shade(
&mut self.context.screen2d,
|mut iface, uni, mut rdr_gate| {
iface.set(&uni.framebuffer, bound_screen.binding());
rdr_gate.render(&self.context.render_st, |mut tess_gate| {
tess_gate.render(&screen_tess)
})
},
)?;
Ok(())
},
);
for target in &frame.dirty {
if let Some(fb) = self.context.targets.get_mut(target) {
store.put(*target, Image::new(fb.pixels()?, fb.size));
}
}
Ok(())
}
fn scale(&mut self, factor: f32) -> f32 {
self.ui_scale *= factor;
self.ui_scale
}
fn handle_scale_factor_changed(&mut self, win_scale: f64) {
self.win_scale = win_scale;
self.handle_resized(self.win_size);
}
}
impl Renderer {
pub fn handle_resized(&mut self, size: platform::LogicalSize) {
let physical = size.to_physical(self.win_scale);
self.present_fb = Framebuffer::back_buffer(
&mut self.backend,
[physical.width as u32, physical.height as u32],
)
.unwrap();
self.win_size = size;
self.handle_scale_changed(self.win_scale);
}
pub fn handle_scale_changed(&mut self, scale: f64) {
self.screen_fb = Framebuffer::new(
&mut self.backend,
[
(self.win_size.width / scale) as u32,
(self.win_size.height / scale) as u32,
],
0,
self::SAMPLER,
)
.unwrap();
}
}