use crate::shader::{VertexShader, FragmentShader};
use std::rc::Rc;
use glow::{HasContext, Context};
use std::borrow::Cow;
use crate::access::{AccessLock, UnitAccessLock};
use crate::{VertexBuffer, IndexBuffer, Framebuffer, FramebufferVariants, Color};
use std::convert::TryFrom;
use std::collections::HashMap;
use std::cell::Cell;
#[derive(Debug)]
pub(crate) struct RenderProgram {
pub(crate) program: <Context as HasContext>::Program,
pub(crate) attributes: HashMap<String, ActiveBinding>,
pub(crate) uniforms: HashMap<String, ActiveBinding>,
}
impl RenderProgram {
pub unsafe fn new(
gl: &Context,
program: <Context as HasContext>::Program) -> Self {
let attributes = 0..gl.get_active_attributes(program);
let uniforms = 0..gl.get_active_uniforms(program);
Self {
program,
attributes: attributes.into_iter()
.filter_map(|index| gl.get_active_attribute(program, index))
.map(|attribute| (
attribute.name,
ActiveBinding {
kind: attribute.atype,
size: u32::try_from(attribute.size).unwrap()
}
))
.collect(),
uniforms: uniforms.into_iter()
.filter_map(|index| gl.get_active_uniform(program, index))
.map(|attribute| (
attribute.name,
ActiveBinding {
kind: attribute.utype,
size: u32::try_from(attribute.size).unwrap()
}
))
.collect(),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) struct ActiveBinding {
pub kind: u32,
pub size: u32,
}
#[derive(Debug)]
pub(crate) struct InnerRenderPipeline {
pub(crate) context: Rc<Context>,
pub(crate) access: UnitAccessLock,
pub(crate) program: RenderProgram,
pub(crate) vao: Cell<Option<<Context as HasContext>::VertexArray>>,
pub(crate) vertex_layout: OwnedVertexBufferLayout,
pub(crate) vertex_shader: VertexShader,
pub(crate) fragment_shader: Option<FragmentShader>,
pub(crate) primitive_state: PrimitiveState,
pub(crate) depth_stencil: Option<DepthStencilState>,
pub(crate) color_target_state: ColorTargetState
}
impl Drop for InnerRenderPipeline {
fn drop(&mut self) {
unsafe {
let _atom = self.access.acquire_write_guarded();
self.context.delete_program(self.program.program);
if let Some(vao) = self.vao.replace(None) {
self.context.delete_vertex_array(vao);
}
}
}
}
pub struct RenderPipeline {
pub(crate) inner: Rc<InnerRenderPipeline>
}
impl AccessLock for RenderPipeline {
fn write_locks(&self) -> usize {
0
}
fn read_locks(&self) -> usize {
let a = self.inner.vertex_shader.read_locks();
if let Some(fragment_shader) = &self.inner.fragment_shader {
assert_eq!(a, fragment_shader.read_locks());
}
assert_eq!(a, self.inner.access.read_locks());
a
}
fn acquire_write(&self) {
panic!("tried to perform a write lock operation on a pipeline. \
pipelines are read-only objects");
}
fn release_write(&self) {
panic!("tried to perform a write lock operation on a pipeline. \
pipelines are read-only objects");
}
fn acquire_read(&self) {
self.inner.vertex_shader.acquire_read();
if let Some(fragment_shader) = &self.inner.fragment_shader {
fragment_shader.acquire_read();
}
self.inner.access.acquire_read();
}
fn release_read(&self) {
self.inner.vertex_shader.release_read();
if let Some(fragment_shader) = &self.inner.fragment_shader {
fragment_shader.release_read();
}
self.inner.access.release_read();
}
}
impl RenderPipeline {
pub(crate) unsafe fn bind(&self, gl: &Context) {
gl.use_program(Some(self.inner.program.program));
match self.inner.primitive_state.front_face {
FrontFace::Ccw => gl.front_face(glow::CCW),
FrontFace::Cw => gl.front_face(glow::CW)
}
match self.inner.primitive_state.cull_mode {
CullMode::None => gl.disable(glow::CULL_FACE),
CullMode::Back => {
gl.enable(glow::CULL_FACE);
gl.cull_face(glow::BACK)
},
CullMode::Front => {
gl.enable(glow::CULL_FACE);
gl.cull_face(glow::FRONT)
}
}
if let Some(ds) = self.inner.depth_stencil {
gl.enable(glow::DEPTH_TEST);
gl.depth_mask(ds.depth_write_enabled);
gl.depth_func(ds.depth_compare.as_opengl());
} else {
gl.disable(glow::DEPTH_TEST)
}
gl.color_mask(
self.inner.color_target_state.write_mask.contains(ColorWrite::RED),
self.inner.color_target_state.write_mask.contains(ColorWrite::GREEN),
self.inner.color_target_state.write_mask.contains(ColorWrite::BLUE),
self.inner.color_target_state.write_mask.contains(ColorWrite::ALPHA));
}
fn depth_write_enabled(&self) -> bool {
if let Some(ds) = self.inner.depth_stencil {
ds.depth_write_enabled
} else {
false
}
}
fn stencil_write_enabled(&self) -> bool {
if let Some(ds) = self.inner.depth_stencil {
let masked = ds.stencil.write_mask == 0;
let kept_pass = ds.stencil.pass_op == StencilOperation::Keep;
let kept_fail = ds.stencil.fail_op == StencilOperation::Keep;
let kept_dfal = ds.stencil.depth_fail_op == StencilOperation::Keep;
let kept = match ds.stencil.compare {
CompareFunction::Always =>
kept_dfal && kept_pass,
CompareFunction::Never =>
kept_fail,
_ =>
kept_pass && kept_fail && kept_dfal
};
!kept && !masked
} else {
false
}
}
pub(crate) fn framebuffer_acquire_write(&self, fb: &Framebuffer, strict: bool) {
let fb = match fb.variants {
FramebufferVariants::Default { .. } =>
return,
FramebufferVariants::Custom { ref inner } => &**inner
};
fb.access.acquire_write();
if self.depth_write_enabled() || self.stencil_write_enabled() {
for texture in &fb.depth_stencil { texture.acquire_write() }
} else {
for texture in &fb.depth_stencil {
if !strict {
texture.acquire_read()
} else {
if texture.read_locks() > 0 {
panic!("Tried to use a framebuffer with an active \
texture feedback loop in strict mode. This has \
most likely happened because you are trying to use \
this feature in a host that does not have \
Features::readonly_framebuffer_feedback.");
}
texture.acquire_write()
}
}
}
for texture in &fb.color_attachments { texture.acquire_write() }
}
pub(crate) fn framebuffer_release_write(&self, fb: &Framebuffer, strict: bool) {
let fb = match fb.variants {
FramebufferVariants::Default { .. } =>
return,
FramebufferVariants::Custom { ref inner } => &**inner
};
fb.access.release_write();
if self.depth_write_enabled() || self.stencil_write_enabled() || strict {
for texture in &fb.depth_stencil { texture.release_write() }
} else {
for texture in &fb.depth_stencil { texture.release_read() }
}
for texture in &fb.color_attachments { texture.release_write() }
}
pub(crate) unsafe fn stencil_setup(&self, gl: &Context, reference: u8) {
if let Some(DepthStencilState { stencil, .. }) = self.inner.depth_stencil {
gl.enable(glow::STENCIL_TEST);
gl.stencil_mask(u32::from(stencil.write_mask));
gl.stencil_func(
stencil.compare.as_opengl(),
i32::from(reference),
u32::from(stencil.read_mask));
gl.stencil_op(
stencil.fail_op.as_opengl(),
stencil.depth_fail_op.as_opengl(),
stencil.pass_op.as_opengl())
} else {
gl.disable(glow::STENCIL_TEST);
}
}
pub(crate) unsafe fn blending_setup(&self, gl: &Context, constant: Color) {
let state = &self.inner.color_target_state;
let alpha_required = !state.alpha_blend.may_be_skipped();
let color_required = !state.color_blend.may_be_skipped();
let required = alpha_required || color_required;
if required {
gl.enable(glow::BLEND);
gl.blend_color(
constant.red,
constant.green,
constant.blue,
constant.alpha);
gl.blend_func_separate(
state.color_blend.src_factor.as_opengl(),
state.color_blend.dst_factor.as_opengl(),
state.alpha_blend.src_factor.as_opengl(),
state.alpha_blend.dst_factor.as_opengl());
gl.blend_equation_separate(
state.color_blend.operation.as_opengl(),
state.alpha_blend.operation.as_opengl());
} else {
gl.disable(glow::BLEND);
}
}
pub(crate) unsafe fn vertex_array_setup(
&self,
gl: &Context,
vertex_buffer: Option<&VertexBuffer>,
index_buffer: Option<&IndexBuffer>) {
let vao = gl.create_vertex_array()
.expect("could not create clean vertex array for pipeline \
setup");
if let Some(old) = self.inner.vao.replace(Some(vao)) {
gl.delete_vertex_array(old);
}
gl.bind_vertex_array(Some(vao));
if vertex_buffer.is_none()
&& self.inner.vertex_layout.attributes.len() != 0 {
panic!("tried to use a non-empty vertex buffer layout with no \
vertex buffer to be bound")
}
let vertex_buffer = vertex_buffer.map(|buffer| buffer.inner.buffer);
let index_buffer = index_buffer.map(|buffer| buffer.inner.buffer);
gl.bind_buffer(glow::ARRAY_BUFFER, vertex_buffer);
for attribute in &self.inner.vertex_layout.attributes {
if let None = self.inner.program.attributes.get(attribute.binding.as_ref()) {
trace!("tried to bind to the inactive attribute \"{}\". data \
for this attribute will be missing",
attribute.binding);
continue
}
let binding = gl.get_attrib_location(
self.inner.program.program,
&attribute.binding)
.expect("could not find binding previously determined to \
be active");
let kind = attribute.kind.as_opengl();
let count = attribute.components as _;
let offset = i32::try_from(attribute.offset)
.expect("invalid vertex attribute offset");
let stride = i32::try_from(self.inner.vertex_layout.array_stride)
.expect("invalid vertex buffer stride");
gl.enable_vertex_attrib_array(binding);
gl.vertex_attrib_pointer_f32(
binding,
count,
kind,
false,
stride,
offset)
}
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, index_buffer);
}
pub(crate) fn drawing_mode(&self) -> u32 {
match self.inner.primitive_state.topology {
PrimitiveTopology::LineList => glow::LINES,
PrimitiveTopology::LineStrip => glow::LINE_STRIP,
PrimitiveTopology::PointList => glow::POINTS,
PrimitiveTopology::TriangleList => glow::TRIANGLES,
PrimitiveTopology::TriangleStrip => glow::TRIANGLE_STRIP
}
}
pub(crate) fn index_type(&self) -> u32 {
match self.inner.primitive_state.index_format {
IndexFormat::Uint16 => glow::UNSIGNED_SHORT,
IndexFormat::Uint32 => glow::UNSIGNED_INT
}
}
pub(crate) fn index_len(&self) -> u32 {
match self.inner.primitive_state.index_format {
IndexFormat::Uint16 => 2,
IndexFormat::Uint32 => 4
}
}
}
#[derive(Copy, Clone)]
pub struct RenderPipelineDescriptor<'a> {
pub vertex: VertexState<'a>,
pub primitive_state: PrimitiveState,
pub fragment: Option<FragmentState<'a>>,
pub depth_stencil: Option<DepthStencilState>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DepthStencilState {
pub depth_write_enabled: bool,
pub depth_compare: CompareFunction,
pub stencil: StencilState,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StencilState {
pub write_mask: u8,
pub read_mask: u8,
pub compare: CompareFunction,
pub fail_op: StencilOperation,
pub depth_fail_op: StencilOperation,
pub pass_op: StencilOperation,
}
impl StencilState {
pub const IGNORE: Self = Self {
write_mask: 0xff,
read_mask: 0xff,
compare: CompareFunction::Always,
fail_op: StencilOperation::Keep,
depth_fail_op: StencilOperation::Keep,
pass_op: StencilOperation::Keep
};
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum StencilOperation {
Keep,
Zero,
Replace,
Invert,
IncrementClamp,
DecrementClamp,
IncrementWrap,
DecrementWrap,
}
impl StencilOperation {
fn as_opengl(&self) -> u32 {
match self {
Self::Keep => glow::KEEP,
Self::Zero => glow::ZERO,
Self::Replace => glow::REPLACE,
Self::IncrementClamp => glow::INCR,
Self::IncrementWrap => glow::INCR_WRAP,
Self::DecrementClamp => glow::DECR,
Self::DecrementWrap => glow::DECR_WRAP,
Self::Invert => glow::INVERT
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum CompareFunction {
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always,
}
impl CompareFunction {
fn as_opengl(&self) -> u32 {
match self {
Self::Equal => glow::EQUAL,
Self::Always => glow::ALWAYS,
Self::Greater => glow::GREATER,
Self::GreaterEqual => glow::GEQUAL,
Self::Less => glow::LESS,
Self::LessEqual => glow::LEQUAL,
Self::NotEqual => glow::NOTEQUAL,
Self::Never => glow::NEVER
}
}
}
#[derive(Copy, Clone)]
pub struct FragmentState<'a> {
pub shader: &'a FragmentShader,
pub targets: ColorTargetState
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ColorTargetState {
pub alpha_blend: BlendState,
pub color_blend: BlendState,
pub write_mask: ColorWrite
}
bitflags::bitflags! {
#[repr(transparent)]
pub struct ColorWrite: u32 {
const RED = 1;
const GREEN = 2;
const BLUE = 4;
const ALPHA = 8;
const COLOR = 7;
const ALL = 15;
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct BlendState {
pub src_factor: BlendFactor,
pub dst_factor: BlendFactor,
pub operation: BlendOperation,
}
impl BlendState {
pub const REPLACE: Self = BlendState {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::Zero,
operation: BlendOperation::Add,
};
pub(crate) fn may_be_skipped(&self) -> bool {
*self == Self::REPLACE
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BlendFactor {
Zero,
One,
SrcColor,
OneMinusSrcColor,
SrcAlpha,
OneMinusSrcAlpha,
DstColor,
OneMinusDstColor,
DstAlpha,
OneMinusDstAlpha,
SrcAlphaSaturated,
BlendColor,
OneMinusBlendColor,
}
impl BlendFactor {
fn as_opengl(&self) -> u32 {
match self {
Self::Zero => glow::ZERO,
Self::One => glow::ONE,
Self::SrcColor => glow::SRC_COLOR,
Self::OneMinusSrcColor => glow::ONE_MINUS_SRC_COLOR,
Self::DstColor => glow::DST_COLOR,
Self::OneMinusDstColor => glow::ONE_MINUS_DST_COLOR,
Self::SrcAlpha => glow::SRC_ALPHA,
Self::OneMinusSrcAlpha => glow::ONE_MINUS_SRC_ALPHA,
Self::DstAlpha => glow::DST_ALPHA,
Self::OneMinusDstAlpha => glow::ONE_MINUS_DST_ALPHA,
Self::BlendColor => glow::CONSTANT_COLOR,
Self::OneMinusBlendColor => glow::ONE_MINUS_CONSTANT_COLOR,
Self::SrcAlphaSaturated =>
glow::SRC_ALPHA
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BlendOperation {
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
impl BlendOperation {
fn as_opengl(&self) -> u32 {
match self {
Self::Add => glow::FUNC_ADD,
Self::Subtract => glow::FUNC_SUBTRACT,
Self::ReverseSubtract => glow::FUNC_REVERSE_SUBTRACT,
Self::Min => glow::MIN,
Self::Max => glow::MAX,
}
}
}
impl Default for BlendOperation {
fn default() -> Self {
Self::Add
}
}
#[derive(Copy, Clone)]
pub struct VertexState<'a> {
pub shader: &'a VertexShader,
pub buffer: &'a VertexBufferLayout<'a>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct VertexBufferLayout<'a> {
pub array_stride: u32,
pub attributes: &'a [VertexAttribute<'a>]
}
#[derive(Debug)]
pub(crate) struct OwnedVertexBufferLayout {
pub(crate) array_stride: u32,
pub(crate) attributes: Vec<VertexAttribute<'static>>,
}
impl<'a> From<&'_ VertexBufferLayout<'a>> for OwnedVertexBufferLayout {
fn from(layout: &VertexBufferLayout<'a>) -> Self {
Self {
array_stride: layout.array_stride,
attributes: layout.attributes.iter()
.map(|attribute| VertexAttribute {
kind: attribute.kind,
components: attribute.components,
offset: attribute.offset,
binding: Cow::Owned(attribute.binding.to_string())
})
.collect()
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VertexAttribute<'a> {
pub kind: VertexType,
pub components: VertexComponents,
pub offset: u32,
pub binding: Cow<'a, str>
}
impl<'a> VertexAttribute<'a> {
pub fn len(&self) -> u32 {
let component = match self.kind {
VertexType::I8 | VertexType::U8 => 1,
VertexType::I16 | VertexType::U16 => 2,
VertexType::F16 => 2,
VertexType::F32 => 4,
};
let multiplier = self.components as u32;
component * multiplier
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VertexType {
I8,
U8,
I16,
U16,
F16,
F32
}
impl VertexType {
pub fn as_opengl(&self) -> u32 {
match self {
Self::I8 => glow::BYTE,
Self::U8 => glow::UNSIGNED_BYTE,
Self::I16 => glow::SHORT,
Self::U16 => glow::UNSIGNED_SHORT,
Self::F16 => glow::HALF_FLOAT,
Self::F32 => glow::FLOAT
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PrimitiveState {
pub topology: PrimitiveTopology,
pub index_format: IndexFormat,
pub front_face: FrontFace,
pub cull_mode: CullMode,
pub polygon_mode: PolygonMode,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum PolygonMode {
Fill,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum CullMode {
None,
Front,
Back,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FrontFace {
Ccw,
Cw
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum PrimitiveTopology {
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VertexComponents {
One = 1,
Two = 2,
Three = 3,
Four = 4
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum IndexFormat {
Uint16,
Uint32,
}
#[derive(Debug, thiserror::Error)]
pub enum RenderPipelineError {
#[error("Failed to create shader program: {what}")]
ProgramCreationFailed {
what: String
},
#[error("Failed to link shader program: {what}")]
ProgramLinkFailed {
what: String
},
#[error("Failed to create a new vertex array object: {what}")]
VertexArrayObjectCreationFailed {
what: String
},
#[error("Attribute binding name is missing from shader program: {binding}")]
AttributeBindingMissing {
binding: String,
}
}