Text rendering using mach-freetype.

This commit is contained in:
IntegratedQuantum 2022-12-25 17:39:45 +01:00
parent 2aac5e1081
commit 249b8baa3b
10 changed files with 231 additions and 35 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "mach-freetype"]
path = mach-freetype
url = https://github.com/hexops/mach-freetype

Binary file not shown.

View File

@ -28,10 +28,10 @@ void main(){
);
if ((fontEffects & 0x01000000) != 0) { // make it bold in y by sampling more pixels.
vec2 pixel_offset = 1/fontSize;
frag_color = color*max(texture(texture_sampler, texture_position),
texture(texture_sampler, texture_position + vec2(0, 0.5f/fontSize.y)));
frag_color = color*max(texture(texture_sampler, texture_position).r,
texture(texture_sampler, texture_position + vec2(0, 0.5f/fontSize.y)).r);
} else {
frag_color = color*texture(texture_sampler,
texture_position);
texture_position).r;
}
}

View File

@ -1,7 +1,6 @@
#version 330
layout (location=0) in vec2 in_vertex_pos;
layout (location=1) in vec2 face_pos;
layout (location=0) in vec2 face_pos;
out vec2 frag_face_pos;
out vec4 color;
@ -24,7 +23,7 @@ vec2 convert2Proportional(vec2 original, vec2 full) {
void main() {
vec2 vertex_pos = in_vertex_pos;
vec2 vertex_pos = face_pos*vec2(1, -1);
vec2 position_percentage = convert2Proportional(offset*ratio, scene);
vec2 size_percentage = convert2Proportional(vec2(texture_rect.z, texture_rect.w)*ratio, scene);
if ((fontEffects & 0x02000000) != 0) { // italic

View File

@ -1,6 +1,29 @@
const std = @import("std");
const freetype = @import("mach-freetype/build.zig");
pub fn build(b: *std.build.Builder) void {
fn sdkPath(comptime suffix: []const u8) []const u8 {
if (suffix[0] != '/') @compileError("suffix must be an absolute path");
return comptime blk: {
const root_dir = std.fs.path.dirname(@src().file) orelse ".";
break :blk root_dir ++ suffix;
};
}
fn ensureDependencySubmodule(allocator: std.mem.Allocator, path: []const u8) !void {
if (std.process.getEnvVarOwned(allocator, "NO_ENSURE_SUBMODULES")) |no_ensure_submodules| {
defer allocator.free(no_ensure_submodules);
if (std.mem.eql(u8, no_ensure_submodules, "true")) return;
} else |_| {}
var child = std.ChildProcess.init(&.{ "git", "submodule", "update", "--init", path }, allocator);
child.cwd = sdkPath("/");
child.stderr = std.io.getStdErr();
child.stdout = std.io.getStdOut();
_ = try child.spawnAndWait();
}
pub fn build(b: *std.build.Builder) !void {
try ensureDependencySubmodule(b.allocator, "mach-freetype");
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
@ -39,6 +62,8 @@ pub fn build(b: *std.build.Builder) void {
}
}
exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c", "lib/cross_platform_udp_socket.c"}, &[_][]const u8{"-g"});
exe.addPackage(freetype.pkg);
freetype.link(b, exe, .{});
exe.setTarget(target);
exe.setBuildMode(mode);
//exe.sanitize_thread = true;

1
mach-freetype Submodule

@ -0,0 +1 @@
Subproject commit 9cfd1177292533c68f7d4d9a2a123722c8b494f4

View File

@ -3,8 +3,11 @@
const std = @import("std");
const freetype = @import("freetype");
const Vec4i = @import("vec.zig").Vec4i;
const Vec2f = @import("vec.zig").Vec2f;
const Vec2i = @import("vec.zig").Vec2i;
const Vec3f = @import("vec.zig").Vec3f;
const main = @import("main.zig");
@ -288,36 +291,198 @@ pub const Draw = struct {
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
}
// // ----------------------------------------------------------------------------
// // TODO: Stuff for drawText:
//
// private static CubyzFont font;
// private static float fontSize;
// public static void setFont(CubyzFont font, float fontSize) {
// Graphics.font = font;
// Graphics.fontSize = fontSize;
// }
//
// /**
// * Draws a given string.
// * Uses TextLine.
// * @param x left
// * @param y top
// * @param text
// */
// public static void drawText(float x, float y, String text) {
// text = String.format("#%06x", 0xffffff & color) + text; // Add the coloring information.
// TextLine line = new TextLine(font, text, fontSize, false);
// line.render(x, y);
// }
// ----------------------------------------------------------------------------
pub fn text(_text: []const u8, x: f32, y: f32, fontSize: f32) !void {
try TextRendering.renderText(_text, x, y, fontSize);
}
};
pub fn init() void {
const TextRendering = struct {
const Glyph = struct {
textureX: i32,
size: Vec2i,
bearing: Vec2i,
advance: f32,
};
var shader: Shader = undefined;
var uniforms: struct {
texture_rect: c_int,
scene: c_int,
offset: c_int,
ratio: c_int,
fontEffects: c_int,
fontSize: c_int,
texture_sampler: c_int,
alpha: c_int,
} = undefined;
var freetypeLib: freetype.Library = undefined;
var font: freetype.Face = undefined;
var glyphMapping: std.ArrayList(u31) = undefined;
var glyphData: std.ArrayList(Glyph) = undefined;
var glyphTexture: [2]c_uint = undefined;
var textureWidth: i32 = 1024;
const textureHeight: i32 = 16;
var textureOffset: i32 = 0;
fn init() !void {
shader = try Shader.create("assets/cubyz/shaders/graphics/Text.vs", "assets/cubyz/shaders/graphics/Text.fs");
uniforms = shader.bulkGetUniformLocation(@TypeOf(uniforms));
shader.bind();
c.glUniform1i(uniforms.texture_sampler, 0);
c.glUniform1f(uniforms.alpha, 1.0);
c.glUniform2f(uniforms.fontSize, @intToFloat(f32, textureWidth), @intToFloat(f32, textureHeight));
freetypeLib = try freetype.Library.init();
font = try freetypeLib.createFace("assets/cubyz/fonts/unscii-16-full.ttf", 0);
try font.setPixelSizes(0, textureHeight);
glyphMapping = std.ArrayList(u31).init(main.globalAllocator);
glyphData = std.ArrayList(Glyph).init(main.globalAllocator);
try glyphData.append(undefined); // 0 is a reserved value.
c.glCreateTextures(c.GL_TEXTURE_2D, 2, &glyphTexture);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]);
c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, textureWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT);
}
fn deinit() void {
shader.delete();
font.deinit();
freetypeLib.deinit();
glyphMapping.deinit();
glyphData.deinit();
c.glDeleteTextures(2, &glyphTexture);
}
fn resizeTexture(newWidth: i32) !void {
textureWidth = newWidth;
const swap = glyphTexture[1];
glyphTexture[1] = glyphTexture[0];
glyphTexture[0] = swap;
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]);
c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, newWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glCopyImageSubData(
glyphTexture[1], c.GL_TEXTURE_2D, 0, 0, 0, 0,
glyphTexture[0], c.GL_TEXTURE_2D, 0, 0, 0, 0,
textureOffset, textureHeight, 1
);
shader.bind();
c.glUniform2f(uniforms.fontSize, @intToFloat(f32, textureWidth), @intToFloat(f32, textureHeight));
}
fn uploadData(bitmap: freetype.Bitmap) !void {
const width = @bitCast(i32, bitmap.width());
const height = @bitCast(i32, bitmap.rows());
const buffer = bitmap.buffer() orelse return;
if(textureOffset + width > textureWidth) {
try resizeTexture(textureWidth*2);
}
c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1);
c.glTexSubImage2D(c.GL_TEXTURE_2D, 0, textureOffset, 0, width, height, c.GL_RED, c.GL_UNSIGNED_BYTE, buffer.ptr);
textureOffset += width;
}
fn getGlyph(char: u21) !Glyph {
if(char >= glyphMapping.items.len) {
try glyphMapping.appendNTimes(0, char - glyphMapping.items.len + 1);
}
if(glyphMapping.items[char] == 0) {// glyph was not initialized yet.
try font.loadChar(char, freetype.LoadFlags{.render = true});
const glyph = font.glyph();
const bitmap = glyph.bitmap();
const width = bitmap.width();
const height = bitmap.rows();
glyphMapping.items[char] = @intCast(u31, glyphData.items.len);
(try glyphData.addOne()).* = Glyph {
.textureX = textureOffset,
.size = Vec2i{@intCast(i32, width), @intCast(i32, height)},
.bearing = Vec2i{glyph.bitmapLeft(), 16 - glyph.bitmapTop()},
.advance = @intToFloat(f32, glyph.advance().x)/@intToFloat(f32, 1 << 6),
};
try uploadData(bitmap);
}
return glyphData.items[glyphMapping.items[char]];
}
fn drawGlyph(glyph: Glyph, x: f32, y: f32) void {
// Glyph glyph = glyphs.get(i);
// // Check if new markers are active:
// if (textMarkingInfo != null) {
// while (markerIndex < textMarkingInfo.length && glyph.charIndex >= textMarkingInfo[markerIndex].charPosition) {
// switch(textMarkingInfo[markerIndex].type) {
// case TextMarker.TYPE_BOLD:
// case TextMarker.TYPE_ITALIC:
// activeFontEffects ^= textMarkingInfo[markerIndex].type;
// break;
// case TextMarker.TYPE_COLOR:
// color = textMarkingInfo[markerIndex].color;
// break;
// case TextMarker.TYPE_COLOR_ANIMATION:
// color = textMarkingInfo[markerIndex].animation.getColor();
// break;
// }
// markerIndex++;
// }
// }
// Rectangle textureBounds = font.getGlyph(glyph.codepoint);
// if ((activeFontEffects & TextMarker.TYPE_BOLD) != 0) {
// // Increase the texture size for the bold shadering to work.
// textureBounds = new Rectangle(textureBounds.x, textureBounds.y-1, textureBounds.width, textureBounds.height+1);
// y -= 1; // Make sure that the glyph stays leveled.
// }
// if (isControlCharacter[i]) {
// // Control characters are drawn using a gray color and without font effects, to make them stand out.
// glUniform1i(TextUniforms.loc_fontEffects, 0x007f7f7f);
// } else {
// glUniform1i(TextUniforms.loc_fontEffects, color | (activeFontEffects << 24));
// }
c.glUniform2f(uniforms.offset, @intToFloat(f32, glyph.bearing[0]) + x, @intToFloat(f32, glyph.bearing[1]) + y);
c.glUniform4f(uniforms.texture_rect, @intToFloat(f32, glyph.textureX), 0, @intToFloat(f32, glyph.size[0]), @intToFloat(f32, glyph.size[1]));
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
// if ((activeFontEffects & TextMarker.TYPE_BOLD) != 0 && !isControlCharacter[i]) {
// // Just draw another thing on top in x direction. y-direction is handled in the shader.
// glUniform2f(TextUniforms.loc_offset, glyph.x + x + 0.5f, glyph.y + y);
// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// }
//
// if ((activeFontEffects & TextMarker.TYPE_BOLD) != 0) {
// y += 1; // Revert the previous transformation.
// }
}
fn renderText(text: []const u8, _x: f32, _y: f32, fontSize: f32) !void {
const fontScaling = fontSize/16.0;
var x = _x/fontScaling;
const y = _y/fontScaling;
shader.bind();
c.glUniform2f(uniforms.scene, @intToFloat(f32, main.Window.width), @intToFloat(f32, main.Window.height));
c.glUniform1f(uniforms.ratio, fontScaling);
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]);
c.glBindVertexArray(Draw.rectVAO);
var unicodeIterator = std.unicode.Utf8Iterator{.bytes = text, .i = 0};
while(unicodeIterator.nextCodepoint()) |codepoint| {
const glyph = try getGlyph(codepoint);
drawGlyph(glyph, x, y);
x += glyph.advance;
}
}
};
pub fn init() !void {
Draw.initCircle();
Draw.initDrawRect();
Draw.initImage();
Draw.initLine();
Draw.initRect();
try TextRendering.init();
}
pub fn deinit() void {
@ -326,6 +491,7 @@ pub fn deinit() void {
Draw.deinitImage();
Draw.deinitLine();
Draw.deinitRect();
TextRendering.deinit();
}
pub const Shader = struct {
@ -386,7 +552,7 @@ pub const Shader = struct {
pub fn bulkGetUniformLocation(self: *const Shader, comptime T: type) T {
var ret: T = undefined;
inline for(@typeInfo(T).Struct.fields) |field| {
if(field.field_type == c_int) {
if(field.type == c_int) {
@field(ret, field.name) = c.glGetUniformLocation(self.id, field.name[0..]);
}
}

View File

@ -247,7 +247,7 @@ pub fn main() !void {
try Window.init();
defer Window.deinit();
graphics.init();
try graphics.init();
defer graphics.deinit();
try rotation.init();
@ -315,8 +315,11 @@ pub fn main() !void {
}
{ // Render the GUI
c.glDisable(c.GL_CULL_FACE);
c.glDisable(c.GL_DEPTH_TEST);
try graphics.Draw.text("Hello test xyz αβγδε ĤΨ = -ħ²/2m ∇²Ψ + VΨ a⃗×b⃗ = -b⃗×a⃗", 500, 500, 64.0);
//graphics.Draw.setColor(0xff0000ff);
//graphics.Draw.rect(Vec2f{.x = 100, .y = 100}, Vec2f{.x = 200, .y = 100});
//graphics.Draw.circle(Vec2f{.x = 200, .y = 200}, 59);

View File

@ -63,7 +63,7 @@ const Socket = struct {
pub fn init() void {
Socket.c.startup();
inline for(@typeInfo(@TypeOf(Protocols)).Struct.fields) |field| {
if(field.field_type == type) {
if(field.type == type) {
const id = @field(Protocols, field.name).id;
if(id != Protocols.keepAlive and id != Protocols.important and Protocols.list[id] == null) {
Protocols.list[id] = @field(Protocols, field.name).receive;
@ -325,7 +325,6 @@ const STUN = struct {
}
};
// private volatile boolean running = true;
pub const ConnectionManager = struct {
socket: Socket = undefined,
thread: std.Thread = undefined,

View File

@ -286,7 +286,7 @@ pub fn register(comptime Mode: type) !void {
var result: RotationMode = RotationMode{.id = Mode.id};
inline for(@typeInfo(RotationMode).Struct.fields) |field| {
if(@hasDecl(Mode, field.name)) {
if(field.field_type == @TypeOf(@field(Mode, field.name))) {
if(field.type == @TypeOf(@field(Mode, field.name))) {
@field(result, field.name) = @field(Mode, field.name);
} else {
@field(result, field.name) = &@field(Mode, field.name);