gl46: initial dsa hello_triangle

This commit is contained in:
chyyran 2022-11-28 17:42:29 -05:00
parent 2081e00956
commit 9265113e9a
15 changed files with 2598 additions and 0 deletions

View file

@ -0,0 +1,23 @@
[package]
name = "librashader-runtime-gl46"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
librashader-common = { path = "../librashader-common", features = ["opengl"] }
librashader-presets = { path = "../librashader-presets" }
librashader-preprocess = { path = "../librashader-preprocess" }
librashader-reflect = { path = "../librashader-reflect" }
spirv_cross = "0.23.1"
rustc-hash = "1.1.0"
gl = "0.14.0"
bytemuck = "1.12.3"
thiserror = "1.0.37"
[dev-dependencies]
glfw = "0.47.0"
[features]
gl4 = []

View file

@ -0,0 +1,35 @@
use gl::types::GLint;
use librashader_reflect::reflect::semantics::BindingStage;
#[derive(Debug)]
pub enum VariableLocation {
Ubo(UniformLocation<GLint>),
Push(UniformLocation<GLint>),
}
impl VariableLocation {
pub fn location(&self) -> UniformLocation<GLint> {
match self {
VariableLocation::Ubo(l) | VariableLocation::Push(l) => *l,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct UniformLocation<T> {
pub vertex: T,
pub fragment: T,
}
impl UniformLocation<GLint> {
pub fn is_valid(&self, stage: BindingStage) -> bool {
let mut validity = false;
if stage.contains(BindingStage::FRAGMENT) {
validity = validity || self.fragment >= 0;
}
if stage.contains(BindingStage::VERTEX) {
validity = validity || self.vertex >= 0;
}
validity
}
}

View file

@ -0,0 +1,26 @@
use gl::types::GLenum;
use librashader_common::image::ImageError;
use librashader_preprocess::PreprocessError;
use librashader_presets::ParsePresetError;
use librashader_reflect::error::{ShaderCompileError, ShaderReflectError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FilterChainError {
#[error("fbo initialization error")]
FramebufferInit(GLenum),
#[error("SPIRV reflection error")]
SpirvCrossReflectError(#[from] spirv_cross::ErrorCode),
#[error("shader preset parse error")]
ShaderPresetError(#[from] ParsePresetError),
#[error("shader preprocess error")]
ShaderPreprocessError(#[from] PreprocessError),
#[error("shader compile error")]
ShaderCompileError(#[from] ShaderCompileError),
#[error("shader reflect error")]
ShaderReflectError(#[from] ShaderReflectError),
#[error("lut loading error")]
LutLoadError(#[from] ImageError)
}
pub type Result<T> = std::result::Result<T, FilterChainError>;

View file

@ -0,0 +1,720 @@
use crate::binding::{UniformLocation, VariableLocation};
use crate::filter_pass::FilterPass;
use crate::framebuffer::{Framebuffer, GlImage, Viewport};
use crate::quad_render::DrawQuad;
use crate::render_target::RenderTarget;
use crate::util;
use crate::util::{gl_get_version, gl_u16_to_version, InlineRingBuffer};
use crate::error::{FilterChainError, Result};
use gl::types::{GLint, GLsizei, GLsizeiptr, GLuint};
use librashader_common::image::Image;
use librashader_common::{FilterMode, Size, WrapMode};
use librashader_preprocess::ShaderSource;
use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig};
use librashader_reflect::back::cross::{GlslangGlslContext, GlVersion};
use librashader_reflect::back::targets::GLSL;
use librashader_reflect::reflect::semantics::{MemberOffset, ReflectSemantics, SemanticMap, TextureSemantics, UniformBinding, UniformMeta, UniformSemantic, VariableSemantics};
use librashader_reflect::reflect::ReflectShader;
use rustc_hash::FxHashMap;
use spirv_cross::spirv::Decoration;
use std::collections::VecDeque;
use std::path::Path;
use librashader_reflect::back::{CompilerBackend, CompileShader, FromCompilation};
use librashader_reflect::front::shaderc::GlslangCompilation;
use crate::options::{FilterChainOptions, FrameOptions};
use crate::samplers::SamplerSet;
use crate::texture::Texture;
pub struct FilterChain {
passes: Box<[FilterPass]>,
common: FilterCommon,
filter_vao: GLuint,
output_framebuffers: Box<[Framebuffer]>,
feedback_framebuffers: Box<[Framebuffer]>,
history_framebuffers: VecDeque<Framebuffer>,
}
pub struct FilterCommon {
// semantics: ReflectSemantics,
pub(crate) config: FilterMutable,
pub(crate) luts: FxHashMap<usize, Texture>,
pub(crate) samplers: SamplerSet,
pub output_textures: Box<[Texture]>,
pub feedback_textures: Box<[Texture]>,
pub history_textures: Box<[Texture]>,
pub(crate) draw_quad: DrawQuad,
}
pub struct FilterMutable {
pub(crate) passes_enabled: usize,
pub(crate) parameters: FxHashMap<String, f32>
}
impl FilterChain {
fn load_pass_semantics(
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
texture_semantics: &mut FxHashMap<String, SemanticMap<TextureSemantics>>,
config: &ShaderPassConfig,
) {
let Some(alias) = &config.alias else {
return;
};
// Ignore empty aliases
if alias.trim().is_empty() {
return;
}
let index = config.id as usize;
// PassOutput
texture_semantics.insert(
alias.clone(),
SemanticMap {
semantics: TextureSemantics::PassOutput,
index,
},
);
uniform_semantics.insert(
format!("{alias}Size"),
UniformSemantic::Texture(SemanticMap {
semantics: TextureSemantics::PassOutput,
index,
}),
);
// PassFeedback
texture_semantics.insert(
format!("{alias}Feedback"),
SemanticMap {
semantics: TextureSemantics::PassFeedback,
index,
},
);
uniform_semantics.insert(
format!("{alias}FeedbackSize"),
UniformSemantic::Texture(SemanticMap {
semantics: TextureSemantics::PassFeedback,
index,
}),
);
}
fn reflect_uniform_location(pipeline: GLuint, meta: &impl UniformMeta) -> VariableLocation {
// todo: support both ubo and pushco
// todo: fix this.
match meta.offset() {
MemberOffset::Ubo(_) => {
let vert_name = format!("LIBRA_UBO_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_UBO_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
VariableLocation::Ubo(UniformLocation { vertex, fragment })
}
}
MemberOffset::PushConstant(_) => {
let vert_name = format!("LIBRA_PUSH_VERTEX_INSTANCE.{}\0", meta.id());
let frag_name = format!("LIBRA_PUSH_FRAGMENT_INSTANCE.{}\0", meta.id());
unsafe {
let vertex = gl::GetUniformLocation(pipeline, vert_name.as_ptr().cast());
let fragment = gl::GetUniformLocation(pipeline, frag_name.as_ptr().cast());
VariableLocation::Push(UniformLocation { vertex, fragment })
}
}
}
}
}
type ShaderPassMeta = (
ShaderPassConfig,
ShaderSource,
CompilerBackend<
impl CompileShader<GLSL, Options = GlVersion, Context = GlslangGlslContext> + ReflectShader,
>,
);
impl FilterChain {
/// Load a filter chain from a pre-parsed `ShaderPreset`.
pub fn load_from_preset(preset: ShaderPreset, options: Option<&FilterChainOptions>) -> Result<FilterChain> {
let (passes, semantics) = FilterChain::load_preset(preset.shaders, &preset.textures)?;
let version = options.map(|o| gl_u16_to_version(o.gl_version))
.unwrap_or_else(|| gl_get_version());
// initialize passes
let filters = FilterChain::init_passes(version, passes, &semantics)?;
let default_filter = filters.first().map(|f| f.config.filter).unwrap_or_default();
let default_wrap = filters
.first()
.map(|f| f.config.wrap_mode)
.unwrap_or_default();
let samplers = SamplerSet::new();
// initialize output framebuffers
let mut output_framebuffers = Vec::new();
output_framebuffers.resize_with(filters.len(), || Framebuffer::new(1));
let mut output_textures = Vec::new();
output_textures.resize_with(filters.len(), Texture::default);
// initialize feedback framebuffers
let mut feedback_framebuffers = Vec::new();
feedback_framebuffers.resize_with(filters.len(), || Framebuffer::new(1));
let mut feedback_textures = Vec::new();
feedback_textures.resize_with(filters.len(), Texture::default);
// load luts
let luts = FilterChain::load_luts(&preset.textures)?;
let (history_framebuffers, history_textures) =
FilterChain::init_history(&filters, default_filter, default_wrap);
// create VBO objects
let draw_quad = DrawQuad::new();
let mut filter_vao = 0;
unsafe {
gl::GenVertexArrays(1, &mut filter_vao);
}
Ok(FilterChain {
passes: filters,
output_framebuffers: output_framebuffers.into_boxed_slice(),
feedback_framebuffers: feedback_framebuffers.into_boxed_slice(),
history_framebuffers,
filter_vao,
common: FilterCommon {
config: FilterMutable {
passes_enabled: preset.shader_count as usize,
parameters: preset.parameters.into_iter()
.map(|param| (param.name, param.value)).collect(),
},
luts,
samplers,
output_textures: output_textures.into_boxed_slice(),
feedback_textures: feedback_textures.into_boxed_slice(),
history_textures,
draw_quad,
},
})
}
/// Load the shader preset at the given path into a filter chain.
pub fn load_from_path(path: impl AsRef<Path>, options: Option<&FilterChainOptions>) -> Result<FilterChain> {
// load passes from preset
let preset = ShaderPreset::try_parse(path)?;
Self::load_from_preset(preset, options)
}
fn load_preset(
passes: Vec<ShaderPassConfig>,
textures: &[TextureConfig]
) -> Result<(Vec<ShaderPassMeta>, ReflectSemantics)> {
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
let mut texture_semantics: FxHashMap<String, SemanticMap<TextureSemantics>> =
Default::default();
let passes = passes
.into_iter()
.map(|shader| {
eprintln!("[gl] loading {}", &shader.name.display());
let source: ShaderSource = ShaderSource::load(&shader.name)?;
let spirv = GlslangCompilation::compile(&source)?;
let reflect = GLSL::from_compilation(spirv)?;
for parameter in source.parameters.iter() {
uniform_semantics.insert(
parameter.id.clone(),
UniformSemantic::Variable(SemanticMap {
semantics: VariableSemantics::FloatParameter,
index: (),
}),
);
}
Ok::<_, FilterChainError>((shader, source, reflect))
})
.into_iter()
.collect::<Result<Vec<(ShaderPassConfig, ShaderSource, CompilerBackend<_>)>>>()?;
for details in &passes {
FilterChain::load_pass_semantics(
&mut uniform_semantics,
&mut texture_semantics,
&details.0,
)
}
// add lut params
for (index, texture) in textures.iter().enumerate() {
texture_semantics.insert(
texture.name.clone(),
SemanticMap {
semantics: TextureSemantics::User,
index,
},
);
uniform_semantics.insert(
format!("{}Size", texture.name),
UniformSemantic::Texture(SemanticMap {
semantics: TextureSemantics::User,
index,
}),
);
}
let semantics = ReflectSemantics {
uniform_semantics,
texture_semantics,
};
Ok((passes, semantics))
}
fn load_luts(textures: &[TextureConfig]) -> Result<FxHashMap<usize, Texture>> {
let mut luts = FxHashMap::default();
for (index, texture) in textures.iter().enumerate() {
let image = Image::load(&texture.path)?;
let levels = if texture.mipmap {
util::calc_miplevel(image.size)
} else {
1u32
};
let mut handle = 0;
unsafe {
gl::GenTextures(1, &mut handle);
gl::BindTexture(gl::TEXTURE_2D, handle);
gl::TexStorage2D(
gl::TEXTURE_2D,
levels as GLsizei,
gl::RGBA8,
image.size.width as GLsizei,
image.size.height as GLsizei,
);
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
gl::PixelStorei(gl::UNPACK_ALIGNMENT, 4);
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
gl::TexSubImage2D(
gl::TEXTURE_2D,
0,
0,
0,
image.size.width as GLsizei,
image.size.height as GLsizei,
gl::RGBA,
gl::UNSIGNED_BYTE,
image.bytes.as_ptr().cast(),
);
let mipmap = levels > 1;
if mipmap {
gl::GenerateMipmap(gl::TEXTURE_2D);
}
gl::BindTexture(gl::TEXTURE_2D, 0);
}
luts.insert(
index,
Texture {
image: GlImage {
handle,
format: gl::RGBA8,
size: image.size,
padded_size: Size::default(),
},
filter: texture.filter_mode,
mip_filter: texture.filter_mode,
wrap_mode: texture.wrap_mode,
},
);
}
Ok(luts)
}
fn init_passes(
version: GlVersion,
passes: Vec<ShaderPassMeta>,
semantics: &ReflectSemantics,
) -> Result<Box<[FilterPass]>> {
let mut filters = Vec::new();
// initialize passes
for (index, (config, source, mut reflect)) in passes.into_iter().enumerate() {
let reflection = reflect.reflect(index, semantics)?;
let glsl = reflect.compile(version)?;
let vertex_resources = glsl.context.compiler.vertex.get_shader_resources()?;
// todo: split this out.
let (program, ubo_location) = unsafe {
let vertex = util::gl_compile_shader(gl::VERTEX_SHADER, glsl.vertex.as_str());
let fragment = util::gl_compile_shader(gl::FRAGMENT_SHADER, glsl.fragment.as_str());
let program = gl::CreateProgram();
gl::AttachShader(program, vertex);
gl::AttachShader(program, fragment);
for res in vertex_resources.stage_inputs {
let loc = glsl
.context
.compiler
.vertex
.get_decoration(res.id, Decoration::Location)?;
let mut name = res.name;
name.push('\0');
gl::BindAttribLocation(program, loc, name.as_str().as_ptr().cast())
}
gl::LinkProgram(program);
gl::DeleteShader(vertex);
gl::DeleteShader(fragment);
let mut status = 0;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
if status != 1 {
panic!("failed to link program")
}
gl::UseProgram(program);
for (name, binding) in &glsl.context.sampler_bindings {
let location =
gl::GetUniformLocation(program, name.as_str().as_ptr().cast());
if location >= 0 {
// eprintln!("setting sampler {location} to sample from {binding}");
gl::Uniform1i(location, *binding as GLint);
}
}
gl::UseProgram(0);
(
program,
UniformLocation {
vertex: gl::GetUniformBlockIndex(
program,
b"LIBRA_UBO_VERTEX\0".as_ptr().cast(),
),
fragment: gl::GetUniformBlockIndex(
program,
b"LIBRA_UBO_FRAGMENT\0".as_ptr().cast(),
),
},
)
};
let ubo_ring = if let Some(ubo) = &reflection.ubo {
let size = ubo.size;
let mut ring: InlineRingBuffer<GLuint, 16> = InlineRingBuffer::new();
unsafe {
gl::GenBuffers(16, ring.items_mut().as_mut_ptr());
for buffer in ring.items() {
gl::BindBuffer(gl::UNIFORM_BUFFER, *buffer);
gl::BufferData(
gl::UNIFORM_BUFFER,
size as GLsizeiptr,
std::ptr::null(),
gl::STREAM_DRAW,
);
}
gl::BindBuffer(gl::UNIFORM_BUFFER, 0);
}
Some(ring)
} else {
None
};
let uniform_buffer = vec![
0;
reflection
.ubo
.as_ref()
.map(|ubo| ubo.size as usize)
.unwrap_or(0)
]
.into_boxed_slice();
let push_buffer = vec![
0;
reflection
.push_constant
.as_ref()
.map(|push| push.size as usize)
.unwrap_or(0)
]
.into_boxed_slice();
let mut uniform_bindings = FxHashMap::default();
for param in reflection.meta.parameter_meta.values() {
uniform_bindings.insert(
UniformBinding::Parameter(param.id.clone()),
(
FilterChain::reflect_uniform_location(program, param),
param.offset,
),
);
}
for (semantics, param) in &reflection.meta.variable_meta {
uniform_bindings.insert(
UniformBinding::SemanticVariable(*semantics),
(
FilterChain::reflect_uniform_location(program, param),
param.offset,
),
);
}
for (semantics, param) in &reflection.meta.texture_size_meta {
uniform_bindings.insert(
UniformBinding::TextureSize(*semantics),
(
FilterChain::reflect_uniform_location(program, param),
param.offset,
),
);
}
// eprintln!("{:#?}", reflection.meta.texture_meta);
// eprintln!("{:#?}", reflection.meta);
// eprintln!("{:#?}", locations);
// eprintln!("{:#?}", reflection.push_constant);
// eprintln!("====fragment====");
// eprintln!("{:#}", glsl.fragment);
// eprintln!("====vertex====");
// eprintln!("{:#}", glsl.vertex);
filters.push(FilterPass {
reflection,
compiled: glsl,
program,
ubo_location,
ubo_ring,
uniform_buffer,
push_buffer,
uniform_bindings,
source,
config
});
}
Ok(filters.into_boxed_slice())
}
fn init_history(
filters: &[FilterPass],
filter: FilterMode,
wrap_mode: WrapMode,
) -> (VecDeque<Framebuffer>, Box<[Texture]>) {
let mut required_images = 0;
for pass in filters {
// If a shader uses history size, but not history, we still need to keep the texture.
let texture_count = pass
.reflection
.meta
.texture_meta
.iter()
.filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
.count();
let texture_size_count = pass
.reflection
.meta
.texture_size_meta
.iter()
.filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
.count();
required_images = std::cmp::max(required_images, texture_count);
required_images = std::cmp::max(required_images, texture_size_count);
}
// not using frame history;
if required_images <= 1 {
println!("[history] not using frame history");
return (VecDeque::new(), Box::new([]));
}
// history0 is aliased with the original
eprintln!("[history] using frame history with {required_images} images");
let mut framebuffers = VecDeque::with_capacity(required_images);
framebuffers.resize_with(required_images, || Framebuffer::new(1));
let mut history_textures = Vec::new();
history_textures.resize_with(required_images, || Texture {
image: Default::default(),
filter,
mip_filter: filter,
wrap_mode,
});
(framebuffers, history_textures.into_boxed_slice())
}
fn push_history(&mut self, input: &GlImage) -> Result<()> {
if let Some(mut back) = self.history_framebuffers.pop_back() {
if back.size != input.size || (input.format != 0 && input.format != back.format) {
eprintln!("[history] resizing");
back.init(input.size, input.format)?;
}
back.copy_from(input)?;
self.history_framebuffers.push_front(back)
}
Ok(())
}
/// Process a frame with the input image.
///
/// When this frame returns, GL_FRAMEBUFFER is bound to 0.
pub fn frame(&mut self, count: usize, viewport: &Viewport, input: &GlImage, options: Option<&FrameOptions>) -> Result<()> {
// limit number of passes to those enabled.
let passes = &mut self.passes[0..self.common.config.passes_enabled];
if let Some(options) = options {
if options.clear_history {
for framebuffer in &self.history_framebuffers {
framebuffer.clear()
}
}
}
if passes.is_empty() {
return Ok(());
}
unsafe {
// do not need to rebind FBO 0 here since first `draw` will
// bind automatically.
gl::BindVertexArray(self.filter_vao);
}
let filter = passes[0].config.filter;
let wrap_mode = passes[0].config.wrap_mode;
// update history
for (texture, fbo) in self
.common
.history_textures
.iter_mut()
.zip(self.history_framebuffers.iter())
{
texture.image = fbo.as_texture(filter, wrap_mode).image;
}
for ((texture, fbo), pass) in self
.common
.feedback_textures
.iter_mut()
.zip(self.feedback_framebuffers.iter())
.zip(passes.iter())
{
texture.image = fbo
.as_texture(pass.config.filter, pass.config.wrap_mode)
.image;
}
// shader_gl3: 2067
let original = Texture {
image: *input,
filter,
mip_filter: filter,
wrap_mode,
};
let mut source = original;
// rescale render buffers to ensure all bindings are valid.
for (index, pass) in passes.iter_mut().enumerate() {
self.output_framebuffers[index].scale(
pass.config.scaling.clone(),
pass.get_format(),
viewport,
&original,
&source,
)?;
self.feedback_framebuffers[index].scale(
pass.config.scaling.clone(),
pass.get_format(),
viewport,
&original,
&source,
)?;
}
let passes_len = passes.len();
let (pass, last) = passes.split_at_mut(passes_len - 1);
for (index, pass) in pass.iter_mut().enumerate() {
let target = &self.output_framebuffers[index];
pass.draw(
index,
&self.common,
if pass.config.frame_count_mod > 0 {
count % pass.config.frame_count_mod as usize
} else {
count
} as u32,
1,
viewport,
&original,
&source,
RenderTarget::new(target, None, 0, 0),
);
let target = target.as_texture(pass.config.filter, pass.config.wrap_mode);
self.common.output_textures[index] = target;
source = target;
}
assert_eq!(last.len(), 1);
for pass in last {
source.filter = pass.config.filter;
source.mip_filter = pass.config.filter;
pass.draw(
passes_len - 1,
&self.common,
if pass.config.frame_count_mod > 0 {
count % pass.config.frame_count_mod as usize
} else {
count
} as u32,
1,
viewport,
&original,
&source,
RenderTarget::new(viewport.output, viewport.mvp, viewport.x, viewport.y),
);
}
// swap feedback framebuffers with output
for (output, feedback) in self
.output_framebuffers
.iter_mut()
.zip(self.feedback_framebuffers.iter_mut())
{
std::mem::swap(output, feedback);
}
self.push_history(input)?;
// pass.draw should return framebuffer bound to 0.
unsafe {
gl::BindVertexArray(0);
}
Ok(())
}
}

View file

@ -0,0 +1,545 @@
use gl::types::{GLint, GLsizei, GLsizeiptr, GLuint};
use librashader_reflect::back::cross::GlslangGlslContext;
use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::ShaderReflection;
use librashader_common::{ShaderFormat, Size};
use librashader_preprocess::ShaderSource;
use librashader_presets::ShaderPassConfig;
use librashader_reflect::reflect::semantics::{BindingStage, MemberOffset, TextureBinding, TextureSemantics, UniformBinding, VariableSemantics};
use rustc_hash::FxHashMap;
use crate::binding::{UniformLocation, VariableLocation};
use crate::filter_chain::FilterCommon;
use crate::framebuffer::Viewport;
use crate::render_target::RenderTarget;
use crate::samplers::SamplerSet;
use crate::texture::Texture;
use crate::util::{InlineRingBuffer, RingBuffer};
pub struct FilterPass {
pub reflection: ShaderReflection,
pub compiled: ShaderCompilerOutput<String, GlslangGlslContext>,
pub program: GLuint,
pub ubo_location: UniformLocation<GLuint>,
pub ubo_ring: Option<InlineRingBuffer<GLuint, 16>>,
pub uniform_buffer: Box<[u8]>,
pub push_buffer: Box<[u8]>,
pub uniform_bindings: FxHashMap<UniformBinding, (VariableLocation, MemberOffset)>,
pub source: ShaderSource,
pub config: ShaderPassConfig,
}
impl FilterPass {
fn build_mat4(location: UniformLocation<GLint>, buffer: &mut [u8], mvp: &[f32; 16]) {
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
unsafe {
if location.is_valid(BindingStage::VERTEX) {
gl::UniformMatrix4fv(location.vertex, 1, gl::FALSE, mvp.as_ptr());
}
if location.is_valid(BindingStage::FRAGMENT) {
gl::UniformMatrix4fv(location.fragment, 1, gl::FALSE, mvp.as_ptr());
}
}
} else {
let mvp = bytemuck::cast_slice(mvp);
buffer.copy_from_slice(mvp);
}
}
fn build_vec4(location: UniformLocation<GLint>, buffer: &mut [u8], size: impl Into<[f32; 4]>) {
let vec4 = size.into();
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
unsafe {
if location.is_valid(BindingStage::VERTEX) {
gl::Uniform4fv(location.vertex, 1, vec4.as_ptr());
}
if location.is_valid(BindingStage::FRAGMENT) {
gl::Uniform4fv(location.fragment, 1, vec4.as_ptr());
}
}
} else {
let vec4 = bytemuck::cast_slice(&vec4);
buffer.copy_from_slice(vec4);
}
}
#[inline(always)]
fn build_uniform<T>(
location: UniformLocation<GLint>,
buffer: &mut [u8],
value: T,
glfn: unsafe fn(GLint, T) -> (),
) where
T: Copy,
T: bytemuck::Pod,
{
if location.is_valid(BindingStage::VERTEX | BindingStage::FRAGMENT) {
unsafe {
if location.is_valid(BindingStage::VERTEX) {
glfn(location.vertex, value);
}
if location.is_valid(BindingStage::FRAGMENT) {
glfn(location.fragment, value);
}
}
} else {
let buffer = bytemuck::cast_slice_mut(buffer);
buffer[0] = value;
}
}
fn build_uint(location: UniformLocation<GLint>, buffer: &mut [u8], value: u32) {
Self::build_uniform(location, buffer, value, gl::Uniform1ui)
}
fn build_sint(location: UniformLocation<GLint>, buffer: &mut [u8], value: i32) {
Self::build_uniform(location, buffer, value, gl::Uniform1i)
}
fn build_float(location: UniformLocation<GLint>, buffer: &mut [u8], value: f32) {
Self::build_uniform(location, buffer, value, gl::Uniform1f)
}
fn bind_texture(samplers: &SamplerSet, binding: &TextureBinding, texture: &Texture) {
unsafe {
// eprintln!("setting {} to texunit {}", texture.image.handle, binding.binding);
gl::ActiveTexture(gl::TEXTURE0 + binding.binding);
gl::BindTexture(gl::TEXTURE_2D, texture.image.handle);
gl::BindSampler(binding.binding,
samplers.get(texture.wrap_mode, texture.filter, texture.mip_filter));
}
}
pub fn get_format(&self) -> ShaderFormat {
let mut fb_format = ShaderFormat::R8G8B8A8Unorm;
if self.config.srgb_framebuffer {
fb_format = ShaderFormat::R8G8B8A8Srgb;
} else if self.config.float_framebuffer {
fb_format = ShaderFormat::R16G16B16A16Sfloat;
}
fb_format
}
// todo: fix rendertargets (i.e. non-final pass is internal, final pass is user provided fbo)
pub fn draw(
&mut self,
pass_index: usize,
parent: &FilterCommon,
frame_count: u32,
frame_direction: i32,
viewport: &Viewport,
original: &Texture,
source: &Texture,
output: RenderTarget,
) {
let framebuffer = output.framebuffer;
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer.handle);
gl::UseProgram(self.program);
}
self.build_semantics(
pass_index,
parent,
output.mvp,
frame_count,
frame_direction,
framebuffer.size,
viewport,
original,
source,
);
// shader_gl3:1514
if self.ubo_location.vertex != gl::INVALID_INDEX
&& self.ubo_location.fragment != gl::INVALID_INDEX
{
if let (Some(ubo), Some(ring)) = (&self.reflection.ubo, &mut self.ubo_ring) {
let size = ubo.size;
let buffer = ring.current();
unsafe {
gl::BindBuffer(gl::UNIFORM_BUFFER, *buffer);
gl::BufferSubData(
gl::UNIFORM_BUFFER,
0,
size as GLsizeiptr,
self.uniform_buffer.as_ptr().cast(),
);
gl::BindBuffer(gl::UNIFORM_BUFFER, 0);
if self.ubo_location.vertex != gl::INVALID_INDEX {
gl::BindBufferBase(gl::UNIFORM_BUFFER, self.ubo_location.vertex, *buffer);
}
if self.ubo_location.fragment != gl::INVALID_INDEX {
gl::BindBufferBase(gl::UNIFORM_BUFFER, self.ubo_location.fragment, *buffer);
}
}
ring.next()
}
}
unsafe {
gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE);
gl::ClearColor(0.0f32, 0.0f32, 0.0f32, 0.0f32);
gl::Clear(gl::COLOR_BUFFER_BIT);
//
gl::Viewport(
output.x,
output.y,
framebuffer.size.width as GLsizei,
framebuffer.size.height as GLsizei,
);
if framebuffer.format == gl::SRGB8_ALPHA8 {
gl::Enable(gl::FRAMEBUFFER_SRGB);
} else {
gl::Disable(gl::FRAMEBUFFER_SRGB);
}
gl::Disable(gl::CULL_FACE);
gl::Disable(gl::BLEND);
gl::Disable(gl::DEPTH_TEST);
gl::EnableVertexAttribArray(0);
gl::EnableVertexAttribArray(1);
gl::BindBuffer(gl::ARRAY_BUFFER, parent.draw_quad.vbo);
// the provided pointers are of OpenGL provenance with respect to the buffer bound to quad_vbo,
// and not a known provenance to the Rust abstract machine, therefore we give it invalid pointers.
// that are inexpressible in Rust
gl::VertexAttribPointer(
0,
2,
gl::FLOAT,
gl::FALSE,
(4 * std::mem::size_of::<f32>()) as GLsizei,
std::ptr::invalid(0),
);
gl::VertexAttribPointer(
1,
2,
gl::FLOAT,
gl::FALSE,
(4 * std::mem::size_of::<f32>()) as GLsizei,
std::ptr::invalid(2 * std::mem::size_of::<f32>()),
);
gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::DisableVertexAttribArray(0);
gl::DisableVertexAttribArray(1);
gl::Disable(gl::FRAMEBUFFER_SRGB);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
}
// framecount should be pre-modded
fn build_semantics(
&mut self,
pass_index: usize,
parent: &FilterCommon,
mvp: &[f32; 16],
frame_count: u32,
frame_direction: i32,
fb_size: Size<u32>,
viewport: &Viewport,
original: &Texture,
source: &Texture,
) {
// Bind MVP
if let Some((location, offset)) =
self.uniform_bindings.get(&VariableSemantics::MVP.into())
{
let mvp_size = mvp.len() * std::mem::size_of::<f32>();
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_mat4(location.location(), &mut buffer[offset..][..mvp_size], mvp)
}
// bind OutputSize
if let Some((location, offset)) = self
.uniform_bindings
.get(&VariableSemantics::Output.into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(location.location(), &mut buffer[offset..][..16], fb_size)
}
// bind FinalViewportSize
if let Some((location, offset)) = self
.uniform_bindings
.get(&VariableSemantics::FinalViewport.into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
viewport.output.size,
)
}
// bind FrameCount
if let Some((location, offset)) = self
.uniform_bindings
.get(&VariableSemantics::FrameCount.into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_uint(location.location(), &mut buffer[offset..][..4], frame_count)
}
// bind FrameDirection
if let Some((location, offset)) = self
.uniform_bindings
.get(&VariableSemantics::FrameDirection.into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_sint(
location.location(),
&mut buffer[offset..][..4],
frame_direction,
)
}
// bind Original sampler
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::Original.semantics(0))
{
FilterPass::bind_texture(&parent.samplers, binding, original);
}
// bind OriginalSize
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::Original.semantics(0).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
original.image.size,
);
}
// bind Source sampler
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::Source.semantics(0))
{
// eprintln!("setting source binding to {}", binding.binding);
FilterPass::bind_texture(&parent.samplers, binding, source);
}
// bind SourceSize
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::Source.semantics(0).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
source.image.size,
);
}
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::OriginalHistory.semantics(0))
{
FilterPass::bind_texture(&parent.samplers, binding, original);
}
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::OriginalHistory.semantics(0).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
original.image.size,
);
}
for (index, output) in parent.history_textures.iter().enumerate() {
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::OriginalHistory.semantics(index + 1))
{
FilterPass::bind_texture(&parent.samplers, binding, output);
}
if let Some((location, offset)) = self.uniform_bindings.get(
&TextureSemantics::OriginalHistory
.semantics(index + 1)
.into(),
) {
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
output.image.size,
);
}
}
// PassOutput
for (index, output) in parent.output_textures.iter().enumerate() {
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::PassOutput.semantics(index))
{
FilterPass::bind_texture(&parent.samplers, binding, output);
}
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::PassOutput.semantics(index).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
output.image.size,
);
}
}
// PassFeedback
for (index, feedback) in parent.feedback_textures.iter().enumerate() {
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::PassFeedback.semantics(index))
{
if feedback.image.handle == 0 {
eprintln!("[WARNING] trying to bind PassFeedback: {index} which has texture 0 to slot {} in pass {pass_index}", binding.binding)
}
FilterPass::bind_texture(&parent.samplers, binding, feedback);
}
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::PassFeedback.semantics(index).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
feedback.image.size,
);
}
}
// bind float parameters
for (id, (location, offset)) in
self.uniform_bindings
.iter()
.filter_map(|(binding, value)| match binding {
UniformBinding::Parameter(id) => Some((id, value)),
_ => None,
})
{
let id = id.as_str();
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
// todo: cache parameters.
// presets override params
let default = self
.source
.parameters
.iter()
.find(|&p| p.id == id)
.map(|f| f.initial)
.unwrap_or(0f32);
let value = *parent
.config
.parameters
.get(id)
.unwrap_or(&default);
FilterPass::build_float(location.location(), &mut buffer[offset..][..4], value)
}
// bind luts
for (index, lut) in &parent.luts {
if let Some(binding) = self
.reflection
.meta
.texture_meta
.get(&TextureSemantics::User.semantics(*index))
{
FilterPass::bind_texture(&parent.samplers, binding, lut);
}
if let Some((location, offset)) = self
.uniform_bindings
.get(&TextureSemantics::User.semantics(*index).into())
{
let (buffer, offset) = match offset {
MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset),
MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset),
};
FilterPass::build_vec4(
location.location(),
&mut buffer[offset..][..16],
lut.image.size,
);
}
}
}
}

View file

@ -0,0 +1,357 @@
use crate::util;
use crate::texture::Texture;
use gl::types::{GLenum, GLint, GLsizei, GLuint};
use librashader_common::{FilterMode, ShaderFormat, Size, WrapMode};
use librashader_presets::{Scale2D, ScaleType, Scaling};
use crate::error::FilterChainError;
use crate::error::Result;
#[derive(Debug)]
pub struct Framebuffer {
pub image: GLuint,
pub handle: GLuint,
pub size: Size<u32>,
pub format: GLenum,
pub max_levels: u32,
pub levels: u32,
is_raw: bool,
}
impl Framebuffer {
pub fn new(max_levels: u32) -> Framebuffer {
let mut framebuffer = 0;
unsafe {
gl::GenFramebuffers(1, &mut framebuffer);
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
Framebuffer {
image: 0,
size: Size {
width: 1,
height: 1,
},
format: 0,
max_levels,
levels: 0,
handle: framebuffer,
is_raw: false,
}
}
pub fn new_from_raw(
texture: GLuint,
handle: GLuint,
format: GLenum,
size: Size<u32>,
miplevels: u32,
) -> Framebuffer {
Framebuffer {
image: texture,
size,
format,
max_levels: miplevels,
levels: miplevels,
handle,
is_raw: true,
}
}
pub(crate) fn as_texture(&self, filter: FilterMode, wrap_mode: WrapMode) -> Texture {
Texture {
image: GlImage {
handle: self.image,
format: self.format,
size: self.size,
padded_size: Default::default(),
},
filter,
mip_filter: filter,
wrap_mode,
}
}
pub(crate) fn scale(
&mut self,
scaling: Scale2D,
format: ShaderFormat,
viewport: &Viewport,
_original: &Texture,
source: &Texture,
) -> Result<Size<u32>> {
if self.is_raw {
return Ok(self.size);
}
let width;
let height;
match scaling.x {
Scaling {
scale_type: ScaleType::Input,
factor,
} => width = source.image.size.width * factor,
Scaling {
scale_type: ScaleType::Absolute,
factor,
} => width = factor.into(),
Scaling {
scale_type: ScaleType::Viewport,
factor,
} => width = viewport.output.size.width * factor,
};
match scaling.y {
Scaling {
scale_type: ScaleType::Input,
factor,
} => height = source.image.size.height * factor,
Scaling {
scale_type: ScaleType::Absolute,
factor,
} => height = factor.into(),
Scaling {
scale_type: ScaleType::Viewport,
factor,
} => height = viewport.output.size.height * factor,
};
let size = Size {
width: width.round() as u32,
height: height.round() as u32,
};
if self.size != size {
self.size = size;
self.init(
size,
if format == ShaderFormat::Unknown {
ShaderFormat::R8G8B8A8Unorm
} else {
format
},
)?;
}
Ok(size)
}
pub(crate) fn clear(&self) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.handle);
gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE);
gl::ClearColor(0.0, 0.0, 0.0, 0.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
}
pub(crate) fn copy_from(&mut self, image: &GlImage) -> Result<()> {
// todo: may want to use a shader and draw a quad to be faster.
if image.size != self.size || image.format != self.format {
self.init(image.size, image.format)?;
}
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.handle);
gl::FramebufferTexture2D(
gl::READ_FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
image.handle,
0,
);
gl::FramebufferTexture2D(
gl::DRAW_FRAMEBUFFER,
gl::COLOR_ATTACHMENT1,
gl::TEXTURE_2D,
self.image,
0,
);
gl::DrawBuffer(gl::COLOR_ATTACHMENT1);
gl::BlitFramebuffer(
0,
0,
self.size.width as GLint,
self.size.height as GLint,
0,
0,
self.size.width as GLint,
self.size.height as GLint,
gl::COLOR_BUFFER_BIT,
gl::NEAREST,
);
// cleanup after ourselves.
gl::FramebufferTexture2D(
gl::READ_FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
0,
0,
);
gl::FramebufferTexture2D(
gl::DRAW_FRAMEBUFFER,
gl::COLOR_ATTACHMENT1,
gl::TEXTURE_2D,
0,
0,
);
// set this back to color_attachment 0
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
self.image,
0,
);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
Ok(())
}
pub(crate) fn init(&mut self, mut size: Size<u32>, format: impl Into<GLenum>) -> Result<()> {
if self.is_raw {
return Ok(());
}
self.format = format.into();
self.size = size;
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.handle);
// reset the framebuffer image
if self.image != 0 {
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
0,
0,
);
gl::DeleteTextures(1, &self.image);
}
gl::GenTextures(1, &mut self.image);
gl::BindTexture(gl::TEXTURE_2D, self.image);
if size.width == 0 {
size.width = 1;
}
if size.height == 0 {
size.height = 1;
}
self.levels = util::calc_miplevel(size);
if self.levels > self.max_levels {
self.levels = self.max_levels;
}
if self.levels == 0 {
self.levels = 1;
}
gl::TexStorage2D(
gl::TEXTURE_2D,
self.levels as GLsizei,
self.format,
size.width as GLsizei,
size.height as GLsizei,
);
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
self.image,
0,
);
let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
if status != gl::FRAMEBUFFER_COMPLETE {
match status {
gl::FRAMEBUFFER_UNSUPPORTED => {
eprintln!("unsupported fbo");
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
0,
0,
);
gl::DeleteTextures(1, &self.image);
gl::GenTextures(1, &mut self.image);
gl::BindTexture(1, self.image);
self.levels = util::calc_miplevel(size);
if self.levels > self.max_levels {
self.levels = self.max_levels;
}
if self.levels == 0 {
self.levels = 1;
}
gl::TexStorage2D(
gl::TEXTURE_2D,
self.levels as GLsizei,
ShaderFormat::R8G8B8A8Unorm.into(),
size.width as GLsizei,
size.height as GLsizei,
);
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
self.image,
0,
);
// self.init =
// gl::CheckFramebufferStatus(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE;
}
_ => return Err(FilterChainError::FramebufferInit(status))
}
}
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::BindTexture(gl::TEXTURE_2D, 0);
}
Ok(())
}
}
impl Drop for Framebuffer {
fn drop(&mut self) {
unsafe {
if self.handle != 0 {
gl::DeleteFramebuffers(1, &self.handle);
}
if self.image != 0 {
gl::DeleteTextures(1, &self.image);
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct Viewport<'a> {
pub x: i32,
pub y: i32,
pub output: &'a Framebuffer,
pub mvp: Option<&'a [f32; 16]>,
}
#[derive(Default, Debug, Copy, Clone)]
pub struct GlImage {
pub handle: GLuint,
pub format: GLenum,
pub size: Size<u32>,
pub padded_size: Size<u32>,
}

View file

@ -0,0 +1,546 @@
use std::convert::TryInto;
use std::ffi::{c_void, CStr};
use std::sync::mpsc::Receiver;
use glfw::{Context, Glfw, Window, WindowEvent};
use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint};
use librashader_common::Size;
use crate::filter_chain::FilterChain;
use crate::framebuffer::{Framebuffer, GlImage, Viewport};
const WIDTH: u32 = 900;
const HEIGHT: u32 = 700;
const TITLE: &str = "librashader OpenGL";
pub fn compile_program(vertex: &str, fragment: &str) -> GLuint {
let vertex_shader = unsafe { gl::CreateShader(gl::VERTEX_SHADER) };
unsafe {
gl::ShaderSource(
vertex_shader,
1,
&vertex.as_bytes().as_ptr().cast(),
&vertex.len().try_into().unwrap(),
);
gl::CompileShader(vertex_shader);
let mut success = 0;
gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success);
if success == 0 {
let mut log_len = 0_i32;
// gl::GetShaderiv(vertex_shader, gl::INFO_LOG_LENGTH, &mut log_len);
// let mut v: Vec<u8> = Vec::with_capacity(log_len as usize);
// gl::GetShaderInfoLog(vertex_shader, log_len, &mut log_len, v.as_mut_ptr().cast());
let mut v: Vec<u8> = Vec::with_capacity(1024);
gl::GetShaderInfoLog(vertex_shader, 1024, &mut log_len, v.as_mut_ptr().cast());
v.set_len(log_len.try_into().unwrap());
panic!(
"Vertex Shader Compile Error: {}",
String::from_utf8_lossy(&v)
);
}
}
let fragment_shader = unsafe { gl::CreateShader(gl::FRAGMENT_SHADER) };
unsafe {
gl::ShaderSource(
fragment_shader,
1,
&fragment.as_bytes().as_ptr().cast(),
&fragment.len().try_into().unwrap(),
);
gl::CompileShader(fragment_shader);
let mut success = 0;
gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success);
if success == 0 {
let mut v: Vec<u8> = Vec::with_capacity(1024);
let mut log_len = 0_i32;
gl::GetShaderInfoLog(fragment_shader, 1024, &mut log_len, v.as_mut_ptr().cast());
v.set_len(log_len.try_into().unwrap());
panic!(
"Fragment Shader Compile Error: {}",
String::from_utf8_lossy(&v)
);
}
}
let shader_program = unsafe { gl::CreateProgram() };
unsafe {
gl::AttachShader(shader_program, vertex_shader);
gl::AttachShader(shader_program, fragment_shader);
gl::LinkProgram(shader_program);
let mut success = 0;
gl::GetProgramiv(shader_program, gl::LINK_STATUS, &mut success);
if success == 0 {
let mut v: Vec<u8> = Vec::with_capacity(1024);
let mut log_len = 0_i32;
gl::GetProgramInfoLog(shader_program, 1024, &mut log_len, v.as_mut_ptr().cast());
v.set_len(log_len.try_into().unwrap());
panic!("Program Link Error: {}", String::from_utf8_lossy(&v));
}
gl::DetachShader(shader_program, vertex_shader);
gl::DetachShader(shader_program, fragment_shader);
gl::DeleteShader(vertex_shader);
gl::DeleteShader(fragment_shader);
}
shader_program
}
extern "system" fn debug_callback(
_source: GLenum,
_err_type: GLenum,
_id: GLuint,
_severity: GLenum,
_length: GLsizei,
message: *const GLchar,
_user: *mut c_void,
) {
unsafe {
let message = CStr::from_ptr(message);
println!("[gl] {message:?}");
}
}
pub fn setup() -> (Glfw, Window, Receiver<(f64, WindowEvent)>, GLuint, GLuint) {
let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
glfw.window_hint(glfw::WindowHint::OpenGlProfile(
glfw::OpenGlProfileHint::Core,
));
glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(glfw::WindowHint::Resizable(true));
glfw.window_hint(glfw::WindowHint::OpenGlDebugContext(true));
let (mut window, events) = glfw
.create_window(WIDTH, HEIGHT, TITLE, glfw::WindowMode::Windowed)
.unwrap();
let (screen_width, screen_height) = window.get_framebuffer_size();
window.make_current();
window.set_key_polling(true);
gl::load_with(|ptr| window.get_proc_address(ptr) as *const _);
unsafe {
gl::Enable(gl::DEBUG_OUTPUT);
gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
gl::DebugMessageCallback(Some(debug_callback), std::ptr::null_mut());
gl::DebugMessageControl(
gl::DONT_CARE,
gl::DONT_CARE,
gl::DONT_CARE,
0,
std::ptr::null(),
gl::TRUE,
);
}
unsafe {
gl::Viewport(0, 0, screen_width, screen_height);
clear_color(Color(0.4, 0.4, 0.4, 1.0));
}
// -------------------------------------------
const VERT_SHADER: &str = "#version 330 core
layout (location = 0) in vec3 Position;
layout (location = 1) in vec3 Color;
out VS_OUTPUT {
vec3 Color;
} OUT;
void main()
{
gl_Position = vec4(Position, 1.0);
OUT.Color = Color;
}";
const FRAG_SHADER: &str = "#version 330 core
in VS_OUTPUT {
vec3 Color;
} IN;
layout(location = 0) out vec4 Color;
void main()
{
Color = vec4(IN.Color, 1.0f);
}";
let shader_program = compile_program(VERT_SHADER, FRAG_SHADER);
unsafe {
gl::ObjectLabel(
gl::SHADER,
shader_program,
-1,
b"color_shader\0".as_ptr().cast(),
);
}
let vertices = &[
// positions // colors
0.5f32, -0.5, 0.0, 1.0, 0.0, 0.0, // bottom right
-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, // bottom left
0.0, 0.5, 0.0, 0.0, 0.0, 1.0, // top
];
let mut vbo: gl::types::GLuint = 0;
unsafe {
gl::CreateBuffers(1, &mut vbo);
gl::ObjectLabel(gl::BUFFER, vbo, -1, b"triangle_vbo\0".as_ptr().cast());
}
unsafe {
gl::NamedBufferData(
vbo,
(vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
vertices.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STATIC_DRAW, // usage
);
}
// set up vertex array object
let mut vao: gl::types::GLuint = 0;
// todo: figure this shit out
unsafe {
gl::CreateVertexArrays(1, &mut vao);
gl::ObjectLabel(gl::VERTEX_ARRAY, vao, -1, b"triangle_vao\0".as_ptr().cast());
gl::VertexArrayVertexBuffer(vao, 0,
vbo, 0, (6 * std::mem::size_of::<f32>()) as GLint
);
gl::EnableVertexArrayAttrib(vao, 0); // this is "layout (location = 0)" in vertex shader
gl::VertexArrayAttribFormat(vao, 0, 3,
gl::FLOAT, gl::FALSE, 0);
gl::EnableVertexArrayAttrib(vao, 1);
gl::VertexArrayAttribFormat(vao, 1, 3,
gl::FLOAT, gl::FALSE, (3 * std::mem::size_of::<f32>() as GLuint));
gl::VertexArrayAttribBinding(vao, 0, 0);
gl::VertexArrayAttribBinding(vao, 1, 0);
}
// set up shared state for window
unsafe {
gl::Viewport(0, 0, 900, 700);
gl::ClearColor(0.3, 0.3, 0.5, 1.0);
}
// -------------------------------------------
println!("OpenGL version: {}", gl_get_string(gl::VERSION));
println!(
"GLSL version: {}",
gl_get_string(gl::SHADING_LANGUAGE_VERSION)
);
(glfw, window, events, shader_program, vao)
}
pub fn do_loop(
mut glfw: Glfw,
mut window: Window,
events: Receiver<(f64, WindowEvent)>,
triangle_program: GLuint,
triangle_vao: GLuint,
filter: &mut FilterChain,
) {
let mut framecount = 0;
let mut rendered_framebuffer = 0;
let mut rendered_texture = 0;
let mut quad_vbuf = 0;
let mut output_texture = 0;
let mut output_framebuffer_handle = 0;
let mut output_quad_vbuf = 0;
unsafe {
// do frmaebuffer
gl::CreateFramebuffers(1, &mut rendered_framebuffer);
gl::ObjectLabel(
gl::FRAMEBUFFER,
rendered_framebuffer,
-1,
b"rendered_framebuffer\0".as_ptr().cast(),
);
// make tetxure
gl::CreateTextures(gl::TEXTURE_2D,1, &mut rendered_texture);
gl::ObjectLabel(
gl::TEXTURE,
rendered_texture,
-1,
b"rendered_texture\0".as_ptr().cast(),
);
// empty image
gl::TextureStorage2D(
rendered_texture,
1,
gl::RGBA8,
WIDTH as GLsizei,
HEIGHT as GLsizei,
);
gl::TextureParameteri(rendered_texture, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
gl::TextureParameteri(rendered_texture, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
gl::TextureParameteri(
rendered_texture,
gl::TEXTURE_WRAP_S,
gl::CLAMP_TO_EDGE as GLint,
);
gl::TextureParameteri(
rendered_texture,
gl::TEXTURE_WRAP_T,
gl::CLAMP_TO_EDGE as GLint,
);
// set color attachment
gl::NamedFramebufferTexture(
rendered_framebuffer,
gl::COLOR_ATTACHMENT0,
rendered_texture,
0,
);
let buffers = [gl::COLOR_ATTACHMENT0];
gl::NamedFramebufferDrawBuffers(rendered_framebuffer, 1, buffers.as_ptr());
if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
panic!("failed to create fbo")
}
let fullscreen_fbo = [
-1.0f32, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0,
1.0, 1.0, 0.0,
];
gl::CreateBuffers(1, &mut quad_vbuf);
gl::NamedBufferData(
quad_vbuf, // target
(fullscreen_fbo.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
fullscreen_fbo.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STATIC_DRAW, // usage
);
}
unsafe {
// do frmaebuffer
gl::CreateFramebuffers(1, &mut output_framebuffer_handle);
gl::ObjectLabel(
gl::FRAMEBUFFER,
output_framebuffer_handle,
-1,
b"output_framebuffer\0".as_ptr().cast(),
);
// make tetxure
gl::CreateTextures(gl::TEXTURE_2D,1, &mut output_texture);
gl::ObjectLabel(
gl::TEXTURE,
output_texture,
-1,
b"output_texture\0".as_ptr().cast(),
);
// empty image
gl::TextureStorage2D(
output_texture,
1,
gl::RGBA8,
WIDTH as GLsizei,
HEIGHT as GLsizei,
);
gl::TextureParameteri(output_texture,gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
gl::TextureParameteri(output_texture, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
gl::TextureParameteri(
output_texture,
gl::TEXTURE_WRAP_S,
gl::CLAMP_TO_EDGE as GLint,
);
gl::TextureParameteri(
output_texture,
gl::TEXTURE_WRAP_T,
gl::CLAMP_TO_EDGE as GLint,
);
// set color attachment
gl::NamedFramebufferTexture(
output_framebuffer_handle,
gl::COLOR_ATTACHMENT0,
output_texture,
0,
);
let buffers = [gl::COLOR_ATTACHMENT0];
gl::NamedFramebufferDrawBuffers(output_framebuffer_handle, 1, buffers.as_ptr());
if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
panic!("failed to create fbo")
}
let fullscreen_fbo = [
-1.0f32, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0,
1.0, 1.0, 0.0,
];
gl::CreateBuffers(1, &mut output_quad_vbuf);
gl::NamedBufferData(
output_quad_vbuf,
(fullscreen_fbo.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
fullscreen_fbo.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STATIC_DRAW, // usage
);
}
const VERT_SHADER: &str = r"#version 150 core
out vec2 v_tex;
const vec2 pos[4]=vec2[4](vec2(-1.0, 1.0),
vec2(-1.0,-1.0),
vec2( 1.0, 1.0),
vec2( 1.0,-1.0));
void main()
{
v_tex=0.5*pos[gl_VertexID] + vec2(0.5);
gl_Position=vec4(pos[gl_VertexID], 0.0, 1.0);
}
";
const FRAG_SHADER: &str = r"
#version 150 core
in vec2 v_tex;
uniform sampler2D texSampler;
out vec4 color;
void main()
{
color=texture(texSampler, v_tex);
}";
let quad_programid = compile_program(VERT_SHADER, FRAG_SHADER);
let mut quad_vao = 0;
unsafe {
gl::CreateVertexArrays(1, &mut quad_vao);
}
let (fb_width, fb_height) = window.get_framebuffer_size();
let (vp_width, vp_height) = window.get_size();
let output = Framebuffer::new_from_raw(
output_texture,
output_framebuffer_handle,
gl::RGBA8,
Size::new(vp_width as u32, vp_height as u32),
1,
);
while !window.should_close() {
glfw.poll_events();
for (_, event) in glfw::flush_messages(&events) {
glfw_handle_event(&mut window, event);
}
unsafe {
// render to fb
gl::BindFramebuffer(gl::FRAMEBUFFER, rendered_framebuffer);
gl::Viewport(0, 0, vp_width, vp_height);
// clear color
clear_color(Color(0.3, 0.4, 0.6, 1.0));
gl::Clear(gl::COLOR_BUFFER_BIT);
// do the drawing
gl::UseProgram(triangle_program);
// select vertices
gl::BindVertexArray(triangle_vao);
// draw to bound target
gl::DrawArrays(gl::TRIANGLES, 0, 3);
// unselect vertices
gl::BindVertexArray(0);
// unselect fbo
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
let viewport = Viewport {
x: 0,
y: 0,
output: &output,
mvp: None,
};
let rendered = GlImage {
handle: rendered_texture,
format: gl::RGBA8,
size: Size {
width: fb_width as u32,
height: fb_height as u32,
},
padded_size: Default::default(),
};
filter.frame(framecount, &viewport, &rendered, None)
.unwrap();
unsafe {
// texture is done now.
// draw quad to screen
gl::UseProgram(quad_programid);
gl::BindTextureUnit(0, output_texture);
gl::BindVertexArray(quad_vao);
gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
}
framecount += 1;
window.swap_buffers();
}
}
pub struct Color(f32, f32, f32, f32);
pub fn clear_color(c: Color) {
unsafe { gl::ClearColor(c.0, c.1, c.2, c.3) }
}
pub fn gl_get_string<'a>(name: gl::types::GLenum) -> &'a str {
let v = unsafe { gl::GetString(name) };
let v: &std::ffi::CStr = unsafe { std::ffi::CStr::from_ptr(v as *const i8) };
v.to_str().unwrap()
}
fn glfw_handle_event(window: &mut glfw::Window, event: glfw::WindowEvent) {
use glfw::Action;
use glfw::Key;
use glfw::WindowEvent as Event;
match event {
Event::Key(Key::Escape, _, Action::Press, _) => {
window.set_should_close(true);
}
Event::Size(width, height) => window.set_size(width, height),
_ => {}
}
}

View file

@ -0,0 +1,38 @@
#![feature(strict_provenance)]
#![feature(type_alias_impl_trait)]
mod binding;
mod filter_chain;
mod filter_pass;
mod framebuffer;
mod quad_render;
mod render_target;
mod util;
pub mod error;
mod samplers;
pub use filter_chain::FilterChain;
pub use framebuffer::Framebuffer;
pub use framebuffer::GlImage;
pub use framebuffer::Viewport;
#[cfg(test)]
mod hello_triangle;
mod texture;
mod options;
#[cfg(test)]
mod tests {
use super::*;
use crate::filter_chain::FilterChain;
#[test]
fn triangle_gl46() {
let (glfw, window, events, shader, vao) = hello_triangle::setup();
let mut filter =
FilterChain::load_from_path("../test/slang-shaders/vhs/VHSPro.slangp", None)
.unwrap();
hello_triangle::do_loop(glfw, window, events, shader, vao, &mut filter);
}
}

View file

@ -0,0 +1,11 @@
#[repr(C)]
#[derive(Debug, Clone)]
pub struct FrameOptions {
pub clear_history: bool
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct FilterChainOptions {
pub gl_version: u16
}

View file

@ -0,0 +1,32 @@
use gl::types::{GLsizeiptr, GLuint};
#[rustfmt::skip]
static QUAD_VBO_DATA: &[f32; 16] = &[
0.0f32, 0.0f32, 0.0f32, 0.0f32,
1.0f32, 0.0f32, 1.0f32, 0.0f32,
0.0f32, 1.0f32, 0.0f32, 1.0f32,
1.0f32, 1.0f32, 1.0f32, 1.0f32,
];
pub struct DrawQuad {
pub vbo: GLuint,
}
impl DrawQuad {
pub fn new() -> DrawQuad {
let mut vbo = 0;
unsafe {
gl::GenBuffers(1, &mut vbo);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(
gl::ARRAY_BUFFER,
std::mem::size_of_val(QUAD_VBO_DATA) as GLsizeiptr,
QUAD_VBO_DATA.as_ptr().cast(),
gl::STATIC_DRAW,
);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
}
DrawQuad { vbo }
}
}

View file

@ -0,0 +1,43 @@
use crate::framebuffer::{Framebuffer, Viewport};
#[rustfmt::skip]
static DEFAULT_MVP: &[f32; 16] = &[
2f32, 0.0, 0.0, 0.0,
0.0, 2.0, 0.0, 0.0,
0.0, 0.0, 2.0, 0.0,
-1.0, -1.0, 0.0, 1.0,
];
#[derive(Debug, Copy, Clone)]
pub struct RenderTarget<'a> {
pub mvp: &'a [f32; 16],
pub framebuffer: &'a Framebuffer,
pub x: i32,
pub y: i32
}
impl<'a> RenderTarget<'a> {
pub fn new(backbuffer: &'a Framebuffer, mvp: Option<&'a [f32; 16]>, x: i32, y: i32) -> Self {
if let Some(mvp) = mvp {
RenderTarget {
framebuffer: backbuffer,
x,
mvp,
y,
}
} else {
RenderTarget {
framebuffer: backbuffer,
x,
mvp: DEFAULT_MVP,
y,
}
}
}
}
impl<'a> From<&Viewport<'a>> for RenderTarget<'a> {
fn from(value: &Viewport<'a>) -> Self {
RenderTarget::new(value.output, value.mvp, value.x, value.y)
}
}

View file

@ -0,0 +1,83 @@
use gl::types::{GLenum, GLint, GLuint};
use rustc_hash::FxHashMap;
use librashader_common::{FilterMode, WrapMode};
pub struct SamplerSet {
// todo: may need to deal with differences in mip filter.
samplers: FxHashMap<(WrapMode, FilterMode, FilterMode), GLuint>
}
impl SamplerSet {
pub fn get(&self, wrap: WrapMode, filter: FilterMode, mip: FilterMode) -> GLuint {
// eprintln!("{wrap}, {filter}, {mip}");
*self.samplers.get(&(wrap, filter, mip))
.unwrap()
}
fn make_sampler(sampler: GLuint, wrap: WrapMode, filter: FilterMode, mip: FilterMode) {
unsafe {
gl::SamplerParameteri(
sampler,
gl::TEXTURE_WRAP_S,
GLenum::from(wrap) as GLint,
);
gl::SamplerParameteri(
sampler,
gl::TEXTURE_WRAP_T,
GLenum::from(wrap) as GLint,
);
gl::SamplerParameteri(
sampler,
gl::TEXTURE_MAG_FILTER,
GLenum::from(filter) as GLint,
);
gl::SamplerParameteri(
sampler,
gl::TEXTURE_MIN_FILTER,
GLenum::from(filter.gl_mip(mip)) as GLint,
);
}
}
pub fn new() -> SamplerSet {
let mut samplers = FxHashMap::default();
let wrap_modes =
&[WrapMode::ClampToBorder, WrapMode::ClampToEdge, WrapMode::Repeat, WrapMode::MirroredRepeat];
for wrap_mode in wrap_modes {
unsafe {
let mut linear_linear = 0;
let mut linear_nearest = 0;
let mut nearest_nearest = 0;
let mut nearest_linear = 0;
gl::GenSamplers(1, &mut linear_linear);
gl::GenSamplers(1, &mut linear_nearest);
gl::GenSamplers(1, &mut nearest_linear);
gl::GenSamplers(1, &mut nearest_nearest);
SamplerSet::make_sampler(linear_linear, *wrap_mode,
FilterMode::Linear, FilterMode::Linear);
SamplerSet::make_sampler(linear_nearest, *wrap_mode,
FilterMode::Linear, FilterMode::Nearest);
SamplerSet::make_sampler(nearest_linear, *wrap_mode,
FilterMode::Nearest, FilterMode::Linear);
SamplerSet::make_sampler(nearest_nearest, *wrap_mode,
FilterMode::Nearest, FilterMode::Nearest);
samplers.insert((*wrap_mode, FilterMode::Linear, FilterMode::Linear), linear_linear);
samplers.insert((*wrap_mode, FilterMode::Linear, FilterMode::Nearest), linear_nearest);
samplers.insert((*wrap_mode, FilterMode::Nearest, FilterMode::Nearest), nearest_nearest);
samplers.insert((*wrap_mode, FilterMode::Nearest, FilterMode::Linear), nearest_linear);
}
}
SamplerSet {
samplers
}
}
}

View file

@ -0,0 +1,10 @@
use librashader_common::{FilterMode, WrapMode};
use crate::GlImage;
#[derive(Default, Debug, Copy, Clone)]
pub struct Texture {
pub image: GlImage,
pub filter: FilterMode,
pub mip_filter: FilterMode,
pub wrap_mode: WrapMode,
}

View file

@ -0,0 +1,129 @@
use gl::types::{GLenum, GLuint};
use librashader_common::Size;
use librashader_reflect::back::cross::GlVersion;
pub fn calc_miplevel(size: Size<u32>) -> u32 {
let mut size = std::cmp::max(size.width, size.height);
let mut levels = 0;
while size != 0 {
levels += 1;
size >>= 1;
}
levels
}
pub trait RingBuffer<T> {
fn current(&self) -> &T;
fn current_mut(&mut self) -> &mut T;
fn next(&mut self);
}
impl<T, const SIZE: usize> RingBuffer<T> for InlineRingBuffer<T, SIZE> {
fn current(&self) -> &T {
&self.items[self.index]
}
fn current_mut(&mut self) -> &mut T {
&mut self.items[self.index]
}
fn next(&mut self) {
self.index += 1;
if self.index >= SIZE {
self.index = 0
}
}
}
pub struct InlineRingBuffer<T, const SIZE: usize> {
items: [T; SIZE],
index: usize,
}
impl<T, const SIZE: usize> InlineRingBuffer<T, SIZE>
where
T: Copy,
T: Default,
{
pub fn new() -> Self {
Self {
items: [T::default(); SIZE],
index: 0,
}
}
pub fn items(&self) -> &[T; SIZE] {
&self.items
}
pub fn items_mut(&mut self) -> &mut [T; SIZE] {
&mut self.items
}
}
pub unsafe fn gl_compile_shader(stage: GLenum, source: &str) -> GLuint {
let shader = gl::CreateShader(stage);
gl::ShaderSource(
shader,
1,
&source.as_bytes().as_ptr().cast(),
std::ptr::null(),
);
gl::CompileShader(shader);
let mut compile_status = 0;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut compile_status);
if compile_status == 0 {
panic!("failed to compile")
}
shader
}
pub fn gl_get_version() -> GlVersion {
let mut maj_ver = 0;
let mut min_ver = 0;
unsafe {
gl::GetIntegerv(gl::MAJOR_VERSION, &mut maj_ver);
gl::GetIntegerv(gl::MINOR_VERSION, &mut min_ver);
}
match maj_ver {
3 => match min_ver {
3 => GlVersion::V3_30,
2 => GlVersion::V1_50,
1 => GlVersion::V1_40,
0 => GlVersion::V1_30,
_ => GlVersion::V1_50,
}
4 => match min_ver {
6 => GlVersion::V4_60,
5 => GlVersion::V4_50,
4 => GlVersion::V4_40,
3 => GlVersion::V4_30,
2 => GlVersion::V4_20,
1 => GlVersion::V4_10,
0 => GlVersion::V4_00,
_ => GlVersion::V1_50
}
_ => GlVersion::V1_50
}
}
pub fn gl_u16_to_version(version: u16) -> GlVersion {
match version {
300 => GlVersion::V1_30,
310 => GlVersion::V1_40,
320 => GlVersion::V1_50,
330 => GlVersion::V3_30,
400 => GlVersion::V4_00,
410 => GlVersion::V4_10,
420 => GlVersion::V4_20,
430 => GlVersion::V4_30,
440 => GlVersion::V4_40,
450 => GlVersion::V4_50,
460 => GlVersion::V4_60,
_ => GlVersion::V1_50
}
}