OpenGL text rendering is flipped vertically

84 Views Asked by At

I am trying to render some text with OpenGL using a font atlas texture from this generator. The settings I used were Open Sans, font size 32, with transparent background and font color #000. My code, however, results in a vertically flipped image:

Flipped image error

I think the issue most likely lies in how I specified the vertices of each quad corresponding to each character:

 for char in text.chars() {
    let character_tex = get_charcoord_from_char(char).unwrap();
    // Top left
    let x0 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p1
    let y0 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
    let s0 = character_tex.x as f32 / ATLAS_WIDTH;
    let t0 = character_tex.y as f32 / ATLAS_HEIGHT;
    // Top right
    let x1 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p2
    let y1 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
    let s1 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
    let t1 = character_tex.y as f32 / ATLAS_HEIGHT;
    // Bottom left
    let x2 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p4
    let y2 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
    let s2 = character_tex.x as f32 / ATLAS_WIDTH;
    let t2 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
    // Bottom right
    let x3 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p3
    let y3 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
    let s3 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
    let t3 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;

    // Vertex order: position (x, y); vertex color (r, g, b, a); texcoord (s, t)
    let p1 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
    let p2 = [x1, y1, 1.0, 1.0, 1.0, 0.0, s1, t1];
    let p3 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
    let p4 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
    let p5 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
    let p6 = [x2, y2, 1.0, 1.0, 1.0, 0.0, s2, t2];
    x += character_tex.advance as f32;
    vertices.push(p1);
    vertices.push(p2);
    vertices.push(p3);
    vertices.push(p4);
    vertices.push(p5);
    vertices.push(p6);
}

I think it is likely that the order of my vertices is incorrect, but I can't seem to tell what the correct order is.

And additionally, if it is any help, this is my current Cargo.toml:

[package]
name = "font-rendering"
version = "0.1.0"
edition = "2021"

[dependencies]
elara_gfx = { git = "https://github.com/elaraproject/elara-gfx.git" }

And complete code:

use elara_gfx::{gl_info, Buffer, BufferType, Program, Shader, VertexArray, PixelArray, Texture2D};
use elara_gfx::{GLWindow, HandlerResult, WindowHandler};
use elara_log::prelude::*;
use std::error::Error;

const VERT_SHADER: &str = r#"
#version 330 core
in vec2 position;
in vec2 tex_coord;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
    TexCoord = vec2(tex_coord.x, tex_coord.y);
}
"#;

const FRAG_SHADER: &str = r#"
#version 330 core

in vec2 TexCoord;
out vec4 fragColor;

// texture sampler
uniform sampler2D texture1;

void main()
{
    fragColor = texture(texture1, TexCoord);
}


"#;

const ATLAS_FONT_SIZE: i32 = 32;
const ATLAS_WIDTH: f32 = 358.0;
const ATLAS_HEIGHT: f32 = 133.0;
const ATLAS_CHARS: [char; 95] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '!', '\"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'
];

#[derive(Clone, Copy)]
struct CharCoord {
    pub x: i32,
    pub y: i32,
    pub w: i32,
    pub h: i32,
    pub originX: i32,
    pub originY: i32,
    pub advance: i32
}

const ATLAS: [CharCoord; 95] = [
    CharCoord {  x: 65, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 62, y: 88, w: 12, h: 26, originX: -1, originY: 25, advance: 18 },
    CharCoord {  x: 274, y: 62, w: 18, h: 26, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 83, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 155, y: 62, w: 20, h: 26, originX: 1, originY: 25, advance: 18 },
    CharCoord {  x: 101, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 119, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 292, y: 62, w: 18, h: 26, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 137, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 155, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 199, y: 114, w: 3, h: 3, originX: 1, originY: 1, advance: 8 },
    CharCoord {  x: 257, y: 35, w: 7, h: 27, originX: -1, originY: 25, advance: 8 },
    CharCoord {  x: 91, y: 114, w: 12, h: 12, originX: 0, originY: 25, advance: 13 },
    CharCoord {  x: 0, y: 62, w: 23, h: 26, originX: 1, originY: 25, advance: 21 },
    CharCoord {  x: 151, y: 0, w: 18, h: 29, originX: 0, originY: 26, advance: 18 },
    CharCoord {  x: 281, y: 0, w: 27, h: 27, originX: 0, originY: 25, advance: 26 },
    CharCoord {  x: 308, y: 0, w: 25, h: 27, originX: 0, originY: 25, advance: 23 },
    CharCoord {  x: 111, y: 114, w: 7, h: 12, originX: 0, originY: 25, advance: 7 },
    CharCoord {  x: 102, y: 0, w: 10, h: 32, originX: 0, originY: 25, advance: 9 },
    CharCoord {  x: 69, y: 0, w: 11, h: 32, originX: 1, originY: 25, advance: 9 },
    CharCoord {  x: 73, y: 114, w: 18, h: 17, originX: 0, originY: 26, advance: 18 },
    CharCoord {  x: 0, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
    CharCoord {  x: 103, y: 114, w: 8, h: 12, originX: 0, originY: 6, advance: 8 },
    CharCoord {  x: 170, y: 114, w: 11, h: 6, originX: 0, originY: 12, advance: 10 },
    CharCoord {  x: 163, y: 114, w: 7, h: 7, originX: -1, originY: 5, advance: 8 },
    CharCoord {  x: 34, y: 88, w: 14, h: 26, originX: 1, originY: 25, advance: 12 },
    CharCoord {  x: 211, y: 88, w: 7, h: 21, originX: -1, originY: 19, advance: 8 },
    CharCoord {  x: 99, y: 88, w: 9, h: 25, originX: 1, originY: 19, advance: 8 },
    CharCoord {  x: 18, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
    CharCoord {  x: 118, y: 114, w: 18, h: 11, originX: 0, originY: 17, advance: 18 },
    CharCoord {  x: 36, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
    CharCoord {  x: 226, y: 35, w: 16, h: 27, originX: 1, originY: 25, advance: 14 },
    CharCoord {  x: 122, y: 0, w: 29, h: 29, originX: 0, originY: 25, advance: 29 },
    CharCoord {  x: 328, y: 35, w: 24, h: 26, originX: 2, originY: 25, advance: 20 },
    CharCoord {  x: 175, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 21 },
    CharCoord {  x: 23, y: 35, w: 21, h: 27, originX: 0, originY: 25, advance: 20 },
    CharCoord {  x: 46, y: 62, w: 22, h: 26, originX: -1, originY: 25, advance: 23 },
    CharCoord {  x: 328, y: 62, w: 17, h: 26, originX: -1, originY: 25, advance: 18 },
    CharCoord {  x: 0, y: 88, w: 17, h: 26, originX: -1, originY: 25, advance: 17 },
    CharCoord {  x: 0, y: 35, w: 23, h: 27, originX: 0, originY: 25, advance: 23 },
    CharCoord {  x: 134, y: 62, w: 21, h: 26, originX: -1, originY: 25, advance: 24 },
    CharCoord {  x: 74, y: 88, w: 6, h: 26, originX: -1, originY: 25, advance: 9 },
    CharCoord {  x: 80, y: 0, w: 11, h: 32, originX: 4, originY: 25, advance: 9 },
    CharCoord {  x: 195, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 20 },
    CharCoord {  x: 17, y: 88, w: 17, h: 26, originX: -1, originY: 25, advance: 17 },
    CharCoord {  x: 302, y: 35, w: 26, h: 26, originX: -1, originY: 25, advance: 29 },
    CharCoord {  x: 68, y: 62, w: 22, h: 26, originX: -1, originY: 25, advance: 24 },
    CharCoord {  x: 333, y: 0, w: 25, h: 27, originX: 0, originY: 25, advance: 25 },
    CharCoord {  x: 310, y: 62, w: 18, h: 26, originX: -1, originY: 25, advance: 19 },
    CharCoord {  x: 16, y: 0, w: 25, h: 32, originX: 0, originY: 25, advance: 25 },
    CharCoord {  x: 215, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 20 },
    CharCoord {  x: 173, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 235, y: 62, w: 20, h: 26, originX: 1, originY: 25, advance: 18 },
    CharCoord {  x: 44, y: 35, w: 21, h: 27, originX: -1, originY: 25, advance: 23 },
    CharCoord {  x: 23, y: 62, w: 23, h: 26, originX: 2, originY: 25, advance: 19 },
    CharCoord {  x: 270, y: 35, w: 32, h: 26, originX: 1, originY: 25, advance: 30 },
    CharCoord {  x: 90, y: 62, w: 22, h: 26, originX: 2, originY: 25, advance: 19 },
    CharCoord {  x: 112, y: 62, w: 22, h: 26, originX: 2, originY: 25, advance: 18 },
    CharCoord {  x: 255, y: 62, w: 19, h: 26, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 112, y: 0, w: 10, h: 32, originX: -1, originY: 25, advance: 10 },
    CharCoord {  x: 48, y: 88, w: 14, h: 26, originX: 1, originY: 25, advance: 12 },
    CharCoord {  x: 91, y: 0, w: 11, h: 32, originX: 1, originY: 25, advance: 10 },
    CharCoord {  x: 54, y: 114, w: 19, h: 18, originX: 0, originY: 25, advance: 18 },
    CharCoord {  x: 181, y: 114, w: 18, h: 5, originX: 2, originY: -1, advance: 14 },
    CharCoord {  x: 136, y: 114, w: 9, h: 8, originX: 0, originY: 26, advance: 9 },
    CharCoord {  x: 163, y: 88, w: 17, h: 21, originX: 0, originY: 19, advance: 18 },
    CharCoord {  x: 227, y: 0, w: 18, h: 28, originX: -1, originY: 26, advance: 20 },
    CharCoord {  x: 180, y: 88, w: 16, h: 21, originX: 0, originY: 19, advance: 15 },
    CharCoord {  x: 189, y: 0, w: 19, h: 28, originX: 0, originY: 26, advance: 20 },
    CharCoord {  x: 127, y: 88, w: 18, h: 21, originX: 0, originY: 19, advance: 18 },
    CharCoord {  x: 242, y: 35, w: 15, h: 27, originX: 1, originY: 26, advance: 11 },
    CharCoord {  x: 208, y: 0, w: 19, h: 28, originX: 1, originY: 19, advance: 17 },
    CharCoord {  x: 191, y: 35, w: 18, h: 27, originX: -1, originY: 26, advance: 20 },
    CharCoord {  x: 80, y: 88, w: 6, h: 26, originX: -1, originY: 25, advance: 8 },
    CharCoord {  x: 6, y: 0, w: 10, h: 34, originX: 3, originY: 25, advance: 8 },
    CharCoord {  x: 209, y: 35, w: 17, h: 27, originX: -1, originY: 26, advance: 17 },
    CharCoord {  x: 264, y: 35, w: 6, h: 27, originX: -1, originY: 26, advance: 8 },
    CharCoord {  x: 218, y: 88, w: 28, h: 20, originX: -1, originY: 19, advance: 30 },
    CharCoord {  x: 312, y: 88, w: 18, h: 20, originX: -1, originY: 19, advance: 20 },
    CharCoord {  x: 108, y: 88, w: 19, h: 21, originX: 0, originY: 19, advance: 19 },
    CharCoord {  x: 245, y: 0, w: 18, h: 28, originX: -1, originY: 19, advance: 20 },
    CharCoord {  x: 263, y: 0, w: 18, h: 28, originX: 0, originY: 19, advance: 20 },
    CharCoord {  x: 345, y: 88, w: 13, h: 20, originX: -1, originY: 19, advance: 13 },
    CharCoord {  x: 196, y: 88, w: 15, h: 21, originX: 0, originY: 19, advance: 15 },
    CharCoord {  x: 86, y: 88, w: 13, h: 25, originX: 1, originY: 23, advance: 11 },
    CharCoord {  x: 145, y: 88, w: 18, h: 21, originX: -1, originY: 19, advance: 20 },
    CharCoord {  x: 273, y: 88, w: 20, h: 20, originX: 2, originY: 19, advance: 16 },
    CharCoord {  x: 246, y: 88, w: 27, h: 20, originX: 1, originY: 19, advance: 25 },
    CharCoord {  x: 293, y: 88, w: 19, h: 20, originX: 1, originY: 19, advance: 17 },
    CharCoord {  x: 169, y: 0, w: 20, h: 28, originX: 2, originY: 19, advance: 16 },
    CharCoord {  x: 330, y: 88, w: 15, h: 20, originX: 0, originY: 19, advance: 15 },
    CharCoord {  x: 41, y: 0, w: 14, h: 32, originX: 1, originY: 25, advance: 12 },
    CharCoord {  x: 0, y: 0, w: 6, h: 35, originX: -6, originY: 26, advance: 18 },
    CharCoord {  x: 55, y: 0, w: 14, h: 32, originX: 1, originY: 25, advance: 12 },
    CharCoord {  x: 145, y: 114, w: 18, h: 7, originX: 0, originY: 15, advance: 18 },
];

fn get_charcoord_from_char(character: char) -> Option<CharCoord> {
    let index = ATLAS_CHARS.iter().position(|&c| c == character);
    if let Some(idx) = index {
        Some(ATLAS[idx])
    } else {
        None
    }
}

struct Handler {
    num_vertices: f32,
    vao: VertexArray,
    texture: Texture2D
}

impl Handler {
    fn new(win: &GLWindow) -> Result<Handler, String> {
        let text = "Hello Serendipitous World!";
        let img = PixelArray::load_png_from_path("src/resources/font-tex.png").unwrap();
        let mut vertices = Vec::new();

        let mut total_advance = 0;
        for char in text.chars() {
            let character_tex = get_charcoord_from_char(char).unwrap();
            total_advance += character_tex.advance;
        }
        
        let mut x = -(total_advance as f32 / 2.0);
        let y = (ATLAS_FONT_SIZE as f32 / 2.0);

        for char in text.chars() {
            let character_tex = get_charcoord_from_char(char).unwrap();
            // Top left
            let x0 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p1
            let y0 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
            let s0 = character_tex.x as f32 / ATLAS_WIDTH;
            let t0 = character_tex.y as f32 / ATLAS_HEIGHT;
            // Top right
            let x1 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p2
            let y1 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
            let s1 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
            let t1 = character_tex.y as f32 / ATLAS_HEIGHT;
            // Bottom left
            let x2 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p4
            let y2 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
            let s2 = character_tex.x as f32 / ATLAS_WIDTH;
            let t2 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
            // Bottom right
            let x3 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p3
            let y3 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
            let s3 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
            let t3 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;

            // Vertex order: position (x, y); vertex color (r, g, b, a); texcoord (s, t)
            let p1 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
            let p2 = [x1, y1, 1.0, 1.0, 1.0, 0.0, s1, t1];
            let p3 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
            let p4 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
            let p5 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
            let p6 = [x2, y2, 1.0, 1.0, 1.0, 0.0, s2, t2];
            x += character_tex.advance as f32;
            vertices.push(p1);
            vertices.push(p2);
            vertices.push(p3);
            vertices.push(p4);
            vertices.push(p5);
            vertices.push(p6);
        }
        let num_vertices = vertices.len() as f32;
        let flattened_vertices: Vec<f32> = vertices.into_iter().flatten().collect();

        let texture = Texture2D::new()?;
        texture.bind();
        texture.parameter_2d(gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
        texture.parameter_2d(gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
        texture.parameter_2d(gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
        texture.parameter_2d(gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
        texture.enable_alpha_blend();

        texture.set_image_2d(img);
        texture.generate_mipmap();

        let vao = VertexArray::new()?;
        vao.bind();

        let vbo = Buffer::new()?;
        vbo.bind(BufferType::Array);
        vbo.data::<f32>(BufferType::Array, &flattened_vertices, gl::STATIC_DRAW);

        let vertex_shader = Shader::new(&VERT_SHADER, gl::VERTEX_SHADER)?;
        let fragment_shader = Shader::new(&FRAG_SHADER, gl::FRAGMENT_SHADER)?;
        let program = Program::new(&[vertex_shader, fragment_shader])?;
        program.use_program();

        let pos_attrib = vao.get_attrib_location(&program, "position");
        let col_attrib = vao.get_attrib_location(&program, "vertex_color");
        let tex_coord_attrib = vao.get_attrib_location(&program, "tex_coord");

        vao.enable_vertex_attrib(pos_attrib as u32);
        vao.enable_vertex_attrib(col_attrib as u32);
        vao.enable_vertex_attrib(tex_coord_attrib as u32);

        vao.vertex_attrib_pointer::<f32>(pos_attrib as u32, 2, gl::FLOAT, false, 8, 0);
        vao.vertex_attrib_pointer::<f32>(col_attrib as u32, 4, gl::FLOAT, false, 8, 2);
        vao.vertex_attrib_pointer::<f32>(tex_coord_attrib as u32, 2, gl::FLOAT, false, 8, 6);

        vao.unbind();
        vbo.unbind(BufferType::Array);
        Ok(Handler { vao, num_vertices, texture })
    }
}

impl WindowHandler for Handler {
    fn on_draw(&mut self) -> HandlerResult<()> {
       unsafe {
            gl::ClearColor(1.0, 1.0, 1.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
            self.texture.bind();
            self.vao.bind();
            gl::DrawArrays(gl::TRIANGLES, 0, self.num_vertices as i32);
            self.vao.unbind();
            self.texture.unbind();
       } 
       Ok(())
    }
}


fn main() -> Result<(), Box<dyn Error>> {
    Logger::new().init().unwrap();
    info!("Starting logging...");

    let (app, window) = GLWindow::new_with_title("OpenGL text")?;
    window.get_context()?;
    gl_info();


    let render_handler = Handler::new(&window)?;

    // Event handling
    app.run_loop(window, render_handler);
    Ok(())
}
0

There are 0 best solutions below