diff --git a/librashader-runtime-d3d11/src/quad_render.rs b/librashader-runtime-d3d11/src/quad_render.rs new file mode 100644 index 0000000..e69de29 diff --git a/librashader-runtime-gl46/Cargo.toml b/librashader-runtime-gl46/Cargo.toml new file mode 100644 index 0000000..d36b7a0 --- /dev/null +++ b/librashader-runtime-gl46/Cargo.toml @@ -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 = [] \ No newline at end of file diff --git a/librashader-runtime-gl46/src/binding.rs b/librashader-runtime-gl46/src/binding.rs new file mode 100644 index 0000000..eb08aa4 --- /dev/null +++ b/librashader-runtime-gl46/src/binding.rs @@ -0,0 +1,35 @@ +use gl::types::GLint; +use librashader_reflect::reflect::semantics::BindingStage; + +#[derive(Debug)] +pub enum VariableLocation { + Ubo(UniformLocation), + Push(UniformLocation), +} + +impl VariableLocation { + pub fn location(&self) -> UniformLocation { + match self { + VariableLocation::Ubo(l) | VariableLocation::Push(l) => *l, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct UniformLocation { + pub vertex: T, + pub fragment: T, +} + +impl UniformLocation { + 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 + } +} diff --git a/librashader-runtime-gl46/src/error.rs b/librashader-runtime-gl46/src/error.rs new file mode 100644 index 0000000..c2d187c --- /dev/null +++ b/librashader-runtime-gl46/src/error.rs @@ -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 = std::result::Result; \ No newline at end of file diff --git a/librashader-runtime-gl46/src/filter_chain.rs b/librashader-runtime-gl46/src/filter_chain.rs new file mode 100644 index 0000000..1fec799 --- /dev/null +++ b/librashader-runtime-gl46/src/filter_chain.rs @@ -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, +} + +pub struct FilterCommon { + // semantics: ReflectSemantics, + pub(crate) config: FilterMutable, + pub(crate) luts: FxHashMap, + 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 +} + +impl FilterChain { + fn load_pass_semantics( + uniform_semantics: &mut FxHashMap, + texture_semantics: &mut FxHashMap>, + 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 + ReflectShader, + >, +); + +impl FilterChain { + /// Load a filter chain from a pre-parsed `ShaderPreset`. + pub fn load_from_preset(preset: ShaderPreset, options: Option<&FilterChainOptions>) -> Result { + 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, options: Option<&FilterChainOptions>) -> Result { + // load passes from preset + let preset = ShaderPreset::try_parse(path)?; + Self::load_from_preset(preset, options) + } + + fn load_preset( + passes: Vec, + textures: &[TextureConfig] + ) -> Result<(Vec, ReflectSemantics)> { + let mut uniform_semantics: FxHashMap = Default::default(); + let mut texture_semantics: FxHashMap> = + 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::)>>>()?; + + 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> { + 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, + semantics: &ReflectSemantics, + ) -> Result> { + 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 = 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, 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(()) + } +} diff --git a/librashader-runtime-gl46/src/filter_pass.rs b/librashader-runtime-gl46/src/filter_pass.rs new file mode 100644 index 0000000..40dd89f --- /dev/null +++ b/librashader-runtime-gl46/src/filter_pass.rs @@ -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, + pub program: GLuint, + pub ubo_location: UniformLocation, + pub ubo_ring: Option>, + pub uniform_buffer: Box<[u8]>, + pub push_buffer: Box<[u8]>, + pub uniform_bindings: FxHashMap, + pub source: ShaderSource, + pub config: ShaderPassConfig, +} + +impl FilterPass { + fn build_mat4(location: UniformLocation, 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, 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( + location: UniformLocation, + 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, buffer: &mut [u8], value: u32) { + Self::build_uniform(location, buffer, value, gl::Uniform1ui) + } + + fn build_sint(location: UniformLocation, buffer: &mut [u8], value: i32) { + Self::build_uniform(location, buffer, value, gl::Uniform1i) + } + + fn build_float(location: UniformLocation, 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::()) as GLsizei, + std::ptr::invalid(0), + ); + gl::VertexAttribPointer( + 1, + 2, + gl::FLOAT, + gl::FALSE, + (4 * std::mem::size_of::()) as GLsizei, + std::ptr::invalid(2 * std::mem::size_of::()), + ); + 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, + 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::(); + 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, + ); + } + } + } +} diff --git a/librashader-runtime-gl46/src/framebuffer.rs b/librashader-runtime-gl46/src/framebuffer.rs new file mode 100644 index 0000000..4df40dc --- /dev/null +++ b/librashader-runtime-gl46/src/framebuffer.rs @@ -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, + 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, + 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> { + 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, format: impl Into) -> 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, + pub padded_size: Size, +} diff --git a/librashader-runtime-gl46/src/hello_triangle.rs b/librashader-runtime-gl46/src/hello_triangle.rs new file mode 100644 index 0000000..c1760bf --- /dev/null +++ b/librashader-runtime-gl46/src/hello_triangle.rs @@ -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 = 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 = 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 = 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 = 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::()) 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::()) 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::() 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::()) 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::()) 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), + _ => {} + } +} \ No newline at end of file diff --git a/librashader-runtime-gl46/src/lib.rs b/librashader-runtime-gl46/src/lib.rs new file mode 100644 index 0000000..1c23733 --- /dev/null +++ b/librashader-runtime-gl46/src/lib.rs @@ -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); + } +} diff --git a/librashader-runtime-gl46/src/options.rs b/librashader-runtime-gl46/src/options.rs new file mode 100644 index 0000000..1b3985c --- /dev/null +++ b/librashader-runtime-gl46/src/options.rs @@ -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 +} diff --git a/librashader-runtime-gl46/src/quad_render.rs b/librashader-runtime-gl46/src/quad_render.rs new file mode 100644 index 0000000..272fd97 --- /dev/null +++ b/librashader-runtime-gl46/src/quad_render.rs @@ -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 } + } +} diff --git a/librashader-runtime-gl46/src/render_target.rs b/librashader-runtime-gl46/src/render_target.rs new file mode 100644 index 0000000..44e2d5c --- /dev/null +++ b/librashader-runtime-gl46/src/render_target.rs @@ -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) + } +} diff --git a/librashader-runtime-gl46/src/samplers.rs b/librashader-runtime-gl46/src/samplers.rs new file mode 100644 index 0000000..57cc5e9 --- /dev/null +++ b/librashader-runtime-gl46/src/samplers.rs @@ -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 + } + } +} + diff --git a/librashader-runtime-gl46/src/texture.rs b/librashader-runtime-gl46/src/texture.rs new file mode 100644 index 0000000..c621f55 --- /dev/null +++ b/librashader-runtime-gl46/src/texture.rs @@ -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, +} diff --git a/librashader-runtime-gl46/src/util.rs b/librashader-runtime-gl46/src/util.rs new file mode 100644 index 0000000..f13db26 --- /dev/null +++ b/librashader-runtime-gl46/src/util.rs @@ -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 { + 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 { + fn current(&self) -> &T; + fn current_mut(&mut self) -> &mut T; + fn next(&mut self); +} + +impl RingBuffer for InlineRingBuffer { + 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 { + items: [T; SIZE], + index: usize, +} + +impl InlineRingBuffer +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 + } +} \ No newline at end of file