use gl;
use gl::types::GLuint;
use std::ffi::CString;
const FSR_UPSCALE_SRC: &str = include_str!("../shaders/fsr_upscale.glsl");
const FSR_SHARPEN_SRC: &str = include_str!("../shaders/fsr_sharpen.glsl");
const FSR_VS_SRC: &str = r#"#version 330 core
layout(location=0) in vec3 aPos;
out vec2 vUV;
void main(){
gl_Position = vec4(aPos.xy, 0.0, 1.0);
vUV = aPos.xy * 0.5 + 0.5;
}"#;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FsrQuality {
Performance, Balanced, Quality, }
impl FsrQuality {
pub fn scale(&self) -> f32 {
match self {
FsrQuality::Performance => 0.5,
FsrQuality::Balanced => 0.66,
FsrQuality::Quality => 0.75,
}
}
pub fn sharpness(&self) -> f32 {
match self {
FsrQuality::Performance => 0.7, FsrQuality::Balanced => 0.5,
FsrQuality::Quality => 0.3,
}
}
}
pub struct FsrUpscaler {
program: GLuint,
vao: GLuint,
vbo: GLuint,
input_size_loc: i32,
output_size_loc: i32,
texel_size_loc: i32,
sharpness_loc: i32,
quality: FsrQuality,
enabled: bool,
}
pub struct FboFrame {
fbo: GLuint,
texture: GLuint,
rbo: GLuint, width: u32,
height: u32,
}
impl FsrUpscaler {
pub fn new() -> Result<Self, String> {
let (vao, vbo) = Self::create_quad();
let program = Self::load_shaders_from_src(FSR_VS_SRC, FSR_UPSCALE_SRC)?;
unsafe {
gl::UseProgram(program);
let input_size_loc =
gl::GetUniformLocation(program, b"inputSize\0".as_ptr() as *const _);
let output_size_loc =
gl::GetUniformLocation(program, b"outputSize\0".as_ptr() as *const _);
let texel_size_loc =
gl::GetUniformLocation(program, b"texelSize\0".as_ptr() as *const _);
let sharpness_loc =
gl::GetUniformLocation(program, b"sharpness\0".as_ptr() as *const _);
gl::UseProgram(0);
Ok(Self {
program,
vao,
vbo,
input_size_loc,
output_size_loc,
texel_size_loc,
sharpness_loc,
quality: FsrQuality::Performance,
enabled: true,
})
}
}
fn create_quad() -> (GLuint, GLuint) {
unsafe {
let (mut vao, mut vbo) = (0, 0);
gl::GenVertexArrays(1, &mut vao);
gl::GenBuffers(1, &mut vbo);
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
let vertices: [f32; 18] = [
-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,
-1.0, 1.0, 0.0,
];
gl::BufferData(
gl::ARRAY_BUFFER,
(vertices.len() * 4) as isize,
vertices.as_ptr() as *const _,
gl::STATIC_DRAW,
);
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 0, std::ptr::null());
gl::EnableVertexAttribArray(0);
gl::BindVertexArray(0);
(vao, vbo)
}
}
fn load_shaders_from_src(vertex_source: &str, fragment_source: &str) -> Result<GLuint, String> {
unsafe {
let vertex_shader =
Self::compile_shader(gl::VERTEX_SHADER, vertex_source, "Vertex shader")?;
let fragment_shader =
Self::compile_shader(gl::FRAGMENT_SHADER, fragment_source, "Fragment shader")?;
let program = gl::CreateProgram();
gl::AttachShader(program, vertex_shader);
gl::AttachShader(program, fragment_shader);
gl::LinkProgram(program);
let mut success = 0;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut success);
if success == 0 {
let mut len = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = vec![0u8; len as usize];
gl::GetProgramInfoLog(program, len, &mut len, buf.as_mut_ptr() as *mut _);
let msg = String::from_utf8_lossy(&buf[..len as usize]);
gl::DeleteProgram(program);
return Err(format!("Error linkeando programa: {}", msg));
}
gl::DetachShader(program, vertex_shader);
gl::DetachShader(program, fragment_shader);
gl::DeleteShader(vertex_shader);
gl::DeleteShader(fragment_shader);
Ok(program)
}
}
fn compile_shader(shader_type: GLuint, source: &str, name: &str) -> Result<GLuint, String> {
unsafe {
let shader = gl::CreateShader(shader_type);
let c_str = CString::new(source).unwrap();
gl::ShaderSource(shader, 1, &c_str.as_ptr(), std::ptr::null());
gl::CompileShader(shader);
let mut success = 0;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
if success == 0 {
let mut len = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = vec![0u8; len as usize];
gl::GetShaderInfoLog(shader, len, &mut len, buf.as_mut_ptr() as *mut _);
let msg = String::from_utf8_lossy(&buf[..len as usize]);
gl::DeleteShader(shader);
return Err(format!("Error compilando {}: {}", name, msg));
}
Ok(shader)
}
}
pub fn render(&self, input_texture: GLuint, input_size: (u32, u32), output_size: (u32, u32)) {
if !self.enabled {
return;
}
unsafe {
gl::UseProgram(self.program);
gl::Uniform2f(
self.input_size_loc,
input_size.0 as f32,
input_size.1 as f32,
);
gl::Uniform2f(
self.output_size_loc,
output_size.0 as f32,
output_size.1 as f32,
);
gl::Uniform2f(
self.texel_size_loc,
1.0 / output_size.0 as f32,
1.0 / output_size.1 as f32,
);
gl::Uniform1f(self.sharpness_loc, self.quality.sharpness());
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_2D, input_texture);
gl::BindVertexArray(self.vao);
gl::DrawArrays(gl::TRIANGLES, 0, 6);
gl::BindVertexArray(0);
gl::UseProgram(0);
}
}
pub fn set_quality(&mut self, quality: FsrQuality) {
self.quality = quality;
}
pub fn quality(&self) -> FsrQuality {
self.quality
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn cycle_quality(&mut self) {
self.quality = match self.quality {
FsrQuality::Performance => FsrQuality::Balanced,
FsrQuality::Balanced => FsrQuality::Quality,
FsrQuality::Quality => FsrQuality::Performance,
};
}
}
impl FboFrame {
pub fn new(width: u32, height: u32) -> Result<Self, String> {
unsafe {
let mut fbo = 0;
let mut texture = 0;
let mut rbo = 0;
gl::GenFramebuffers(1, &mut fbo);
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
gl::GenTextures(1, &mut texture);
gl::BindTexture(gl::TEXTURE_2D, texture);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as i32,
width as i32,
height as i32,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
std::ptr::null(),
);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
gl::FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture,
0,
);
gl::GenRenderbuffers(1, &mut rbo);
gl::BindRenderbuffer(gl::RENDERBUFFER, rbo);
gl::RenderbufferStorage(
gl::RENDERBUFFER,
gl::DEPTH24_STENCIL8,
width as i32,
height as i32,
);
gl::FramebufferRenderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_STENCIL_ATTACHMENT,
gl::RENDERBUFFER,
rbo,
);
if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
return Err("Framebuffer incompleto".to_string());
}
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
Ok(Self {
fbo,
texture,
rbo,
width,
height,
})
}
}
pub fn bind(&self) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo);
gl::Viewport(0, 0, self.width as i32, self.height as i32);
}
}
pub fn unbind(&self, screen_width: u32, screen_height: u32) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::Viewport(0, 0, screen_width as i32, screen_height as i32);
}
}
pub fn texture(&self) -> GLuint {
self.texture
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
}
impl Drop for FboFrame {
fn drop(&mut self) {
unsafe {
gl::DeleteFramebuffers(1, &self.fbo);
gl::DeleteTextures(1, &self.texture);
gl::DeleteRenderbuffers(1, &self.rbo);
}
}
}
impl Drop for FsrUpscaler {
fn drop(&mut self) {
unsafe {
gl::DeleteProgram(self.program);
gl::DeleteVertexArrays(1, &self.vao);
gl::DeleteBuffers(1, &self.vbo);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_scale() {
assert_eq!(FsrQuality::Performance.scale(), 0.5);
assert_eq!(FsrQuality::Balanced.scale(), 0.66);
assert_eq!(FsrQuality::Quality.scale(), 0.75);
}
#[test]
fn test_quality_sharpness() {
assert!(FsrQuality::Performance.sharpness() > FsrQuality::Quality.sharpness());
}
}