mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00
Text rendering using mach-freetype.
This commit is contained in:
parent
2aac5e1081
commit
249b8baa3b
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "mach-freetype"]
|
||||
path = mach-freetype
|
||||
url = https://github.com/hexops/mach-freetype
|
BIN
assets/cubyz/fonts/unscii-16-full.ttf
Normal file
BIN
assets/cubyz/fonts/unscii-16-full.ttf
Normal file
Binary file not shown.
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
27
build.zig
27
build.zig
@ -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
1
mach-freetype
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9cfd1177292533c68f7d4d9a2a123722c8b494f4
|
214
src/graphics.zig
214
src/graphics.zig
@ -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..]);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user