Using freetype c in zig

const std = @import("std");
const Io = std.Io;
const pow = std.math.pow;
const gl = @cImport({
    @cDefine("GLAD_GL_IMPLEMENTATION", "");
    @cInclude("glad/gl.h");
    @cDefine("GLFW_INCLUDE_NONE", "");
    @cInclude("GLFW/glfw3.h");
    @cInclude("GL/gl.h");
});
const ftp = @cImport({
    @cInclude("freetype2/ft2build.h");
    @cInclude("freetype2/freetype/freetype.h");
    @cInclude("freetype2/freetype/config/ftconfig.h");
});


const SCREEN_WIDTH = 1000;
const SCREEN_HEIGHT = 800;
const PI = 3.14159265;

fn getShaderFile(init:std.process.Init,filename:[]const u8) []u8 {
    const gpa = init.gpa;
    const dir = Io.Dir.cwd();

    const file = dir.readFileAlloc(init.io, filename, gpa, .unlimited) catch @panic("shader file not found");

    return file;
}

fn orthoProjection(w: f32, h: f32) [4]@Vector(4, f32) {
    return .{
        .{ 2.0 / w, 0.0, 0.0, -1.0 },
        .{ 0.0, -2.0 / h, 0.0, 1.0 },
        .{ 0.0, 0.0, -1.0, 0.0 },
        .{ 0.0, 0.0, 0.0, 1.0 }
    };
}


pub fn main(init:std.process.Init) !void {
    _ = gl.glfwInit();
    defer gl.glfwTerminate();

    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MAJOR, 3);
    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MINOR, 3);
    gl.glfwWindowHint(gl.GLFW_OPENGL_PROFILE, gl.GLFW_OPENGL_CORE_PROFILE);

    const window: *gl.GLFWwindow = gl.glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "amsasjnd", null, null) orelse unreachable;
    defer gl.glfwDestroyWindow(window);

    gl.glfwMakeContextCurrent(window);
    _ = gl.gladLoadGL(gl.glfwGetProcAddress);
    gl.glfwSwapInterval(1);

    gl.glEnable(gl.GL_CULL_FACE);
    gl.glEnable(gl.GL_BLEND);
    gl.glEnable(gl.GL_DEBUG_OUTPUT);
    gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);

    const vertexSource = getShaderFile(init, "shaders/custom3/text1.vert");
    const fragmentSource = getShaderFile(init, "shaders/custom3/text1.frag");
    defer init.gpa.free(vertexSource);
    defer init.gpa.free(fragmentSource);

    const vertexShader = gl.glCreateShader(gl.GL_VERTEX_SHADER);
    gl.glShaderSource(vertexShader, 1, &vertexSource.ptr, null);
    gl.glCompileShader(vertexShader);
    
    const fragmentShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER);
    gl.glShaderSource(fragmentShader, 1, &fragmentSource.ptr, null);
    gl.glCompileShader(fragmentShader);

    const shaderProgram = gl.glCreateProgram();
    gl.glAttachShader(shaderProgram, vertexShader);
    gl.glAttachShader(shaderProgram, fragmentShader);
    gl.glLinkProgram(shaderProgram);
    gl.glDeleteShader(fragmentShader);
    gl.glDeleteShader(vertexShader);

    gl.glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    var ft:ftp.FT_Library = undefined;
    var face:ftp.FT_Face = undefined;
    
    _ = ftp.FT_Init_FreeType(&ft);
    _ = ftp.FT_New_Face(ft, "fonts/MapleMono-Bold.ttf", 0, &face);
    _ = ftp.FT_Set_Pixel_Sizes(face, 0, 48);

    const character = struct {
        texture:c_uint,
        size:@Vector(2, i32),
        bearing:@Vector(2, i32),
        advance:gl.GLuint
    };
    var mapCharacter:std.ArrayList(character) = .empty ;
    defer mapCharacter.deinit(init.gpa);
    defer mapCharacter.clearAndFree(init.gpa);

    gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1);

    for (0..128) |c| {
        _ = ftp.FT_Load_Char(face, c, ftp.FT_LOAD_RENDER);

        // create texture
        // note: * = pointer atau ambil dari memory
        var texture:c_uint = undefined;
        gl.glCreateTextures(gl.GL_TEXTURE_2D, 1, &texture);
        gl.glTextureStorage2D(texture, 1, gl.GL_R8, @intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows));
        gl.glTextureSubImage2D(texture, 0, 0, 0, @intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows), gl.GL_RED, gl.GL_UNSIGNED_BYTE, face.*.glyph.*.bitmap.buffer);

        // bind texture
        gl.glBindTexture(gl.GL_TEXTURE_2D, texture);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR);
		// unbind
		// gl.glBindTexture(gl.GL_TEXTURE_2D, 0);

		// append character
		mapCharacter.append(init.gpa, .{
		    .texture = texture,
			.size = @Vector(2, i32){@intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows)},
			.bearing = @Vector(2, i32){@intCast(face.*.glyph.*.bitmap_left), @intCast(face.*.glyph.*.bitmap_top)},
			.advance = @intCast(face.*.glyph.*.advance.x)
		}) catch @panic("character not appenned");
    }

    _ = ftp.FT_Done_Face(face);
    _ = ftp.FT_Done_FreeType(ft);


    var vao:c_uint, var vbo:c_uint = .{undefined,undefined};
    gl.glCreateVertexArrays(1, &vao);
    gl.glCreateBuffers(1, &vbo);

    gl.glBindVertexArray(vao);

    gl.glNamedBufferData(vbo, @sizeOf(f32) * 24, null, gl.GL_DYNAMIC_DRAW);
    gl.glVertexArrayVertexBuffer(vao, 0, vbo, 0, @sizeOf(f32) * 4);
    gl.glVertexArrayAttribFormat(vao, 0, 4, gl.GL_FLOAT, gl.GL_FALSE, 0);
    gl.glVertexArrayAttribBinding(vao, 0, 0);
	gl.glEnableVertexArrayAttrib(vao, 0);

	const text:[]const u8 = "hell world";

	while (gl.glfwWindowShouldClose(window) == 0) {
	    gl.glClearColor(0.0, 0.0, 0.0, 1.0);
	    gl.glClear(gl.GL_COLOR_BUFFER_BIT);

		var x:f32 = 1.0;
		const y:f32 = 300.0;
		const scale:f32 = 1.0;
		gl.glUseProgram(shaderProgram);
		gl.glBindVertexArray(vao);
		const textLoc = gl.glGetUniformLocation(shaderProgram, "text");
        if(textLoc != -1) gl.glUniform1i(textLoc, 0);
        gl.glActiveTexture(gl.GL_TEXTURE0);

		for (text) |v| {
		    const ch = mapCharacter.items[v];
			const xPos:f32 = x + @as(f32,@floatFromInt(ch.bearing[0])) * scale;
			const yPos:f32 = y - @as(f32,@floatFromInt(ch.size[1] - ch.bearing[1])) * scale;

			const w:f32 = @as(f32,@floatFromInt(ch.size[0])) * scale;
			const h:f32 = @as(f32,@floatFromInt(ch.size[1])) * scale;

			const vertices = [6][4]f32{
			    [4]f32{xPos,yPos+h, 0.0, 0.0},
			    [4]f32{xPos,yPos, 0.0, 1.0},
			    [4]f32{xPos + w,yPos, 1.0, 1.0},

				[4]f32{xPos, yPos + h, 0.0,0.0},
				[4]f32{xPos+w, yPos, 1.0, 1.0},
				[4]f32{xPos+w, yPos+h, 1.0, 0.0}
			};

			gl.glNamedBufferSubData(vbo, 0, @sizeOf(f32) * 6 * 4, &vertices);
			gl.glBindTexture(gl.GL_TEXTURE_2D, ch.texture);
			// gl.glActiveTexture(gl.GL_TEXTURE0);
    		const projection = orthoProjection(SCREEN_WIDTH, SCREEN_HEIGHT);
    		const projectionLoc = gl.glGetUniformLocation(shaderProgram, "projection");
    		const texLoc = gl.glGetUniformLocation(shaderProgram, "textColor");
            if(projectionLoc != -1) gl.glUniformMatrix4fv(projectionLoc, 1, gl.GL_FALSE, @ptrCast(&projection));
            if(texLoc != -1) gl.glUniform3f(texLoc, 0.88, 0.53, 0.03);
			gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6);
			const px = @as(f32,@floatFromInt(ch.advance >> 6));
			x += px * scale;
		}

		gl.glfwSwapBuffers(window);
		gl.glfwPollEvents();
	}
}

vertex shader

#version 330 core
layout (location = 0) in vec4 vertex;
uniform mat4 projection;

out vec2 TexCoords;

void main()
{
    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
    TexCoords = vertex.zw;
}  

fragment shader

#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D text;
uniform vec3 textColor;

void main() {
    float alpha = texture(text, TexCoords).r;
    FragColor = vec4(textColor, alpha);
}

i trying making text in opengl with zig and freetype and try follow some example code, this code no error, but the text not show, can anyone tell me whats wrong with my code? , sorry im if my english not very good

Tbh there can be a thousand reasons why nothing shows up in an OpenGL application. GL is famously bad for this “black screen problem” because it doesn’t have a proper validation layer like all the other 3D APIs.

Your best bet is to inspect the application in a graphics debugger like RenderDoc. This lets you see what data arrives at the GPU, how the GL states are configured, and what the actual shader inputs and outputs are.

Unfortunately, RenderDoc doesn’t support shader debugging in OpenGL, and from googling around it looks like NVIDIA NSight (another graphics debugger) has removed GL shader debugging quite a while ago and only supports Vulkan shader debugging - clear sign that the GL bitrot is real :confused:

2 Likes

Your vertices are defined in clockwise order. I have (schizophrenically) illustrated this here:

However, by default, OpenGL assumes a counter-clockwise winding order, so your triangles are facing away from the viewer, completely invisible.
You can test whether I’m telling the truth by commenting out this line:
gl.glEnable(gl.GL_CULL_FACE);

1 Like

i try your suggestion earlier, but still same, like this.

thnx btw

PS: you should copy-paste your code into an LLM, e.g. Claude Opus comes up with a couple of potential bugs:

  • you call glShaderSource with a string length parameter of null, meaning it expects the string to be zero terminated, readFileAlloc returns a Zig slice though (not zero terminated) - this might be the most obvious problem (also be aware that the length parameter is actually a pointer to an array of integers)
  • it also mentioned @tholmes’s suggestion of disabling GL_CULL_FACE
  • there’s also various more problems if you’re on macOS (basically you’re using too recent GL functions for macOS), but I guess those don’t apply

As I said above, in any decent 3D API you would get proper errors back, but that’s just OpenGL being OpenGL :frowning:

PPS: it is actually possible to get errors back from glShaderSource, e.g. try this after the glCompileShader calls:

var ok: gl.GLint = 0;
gl.glGetShaderiv(vertexShader, gl.GL_COMPILE_STATUS, &ok);
if (ok == 0) {
    var log: [1024]u8 = undefined;
    var len: gl.GLsizei = 0;
    gl.glGetShaderInfoLog(vertexShader, log.len, &len, &log);
    std.debug.print("vert: {s}\n", .{log[0..@intCast(len)]});
}

If the shader compilation fails, this should print something to the console.

I hope I didn’t violate the Ziggit LLM rules with this post :wink:

PPS: also in RenderDoc this shader compilation problem would have shown up as an obvious error.

2 Likes

oke i’ll try

the ai doesn’t work for this issue, i think the ortho matrix i created is quite bad, but im still not sure,
i’ll try again, thanks btw :smiley: .