diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..33bced9f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mach-freetype"] + path = mach-freetype + url = https://github.com/hexops/mach-freetype diff --git a/assets/cubyz/fonts/unscii-16-full.ttf b/assets/cubyz/fonts/unscii-16-full.ttf new file mode 100644 index 00000000..0a3a84eb Binary files /dev/null and b/assets/cubyz/fonts/unscii-16-full.ttf differ diff --git a/assets/cubyz/shaders/graphics/Text.fs b/assets/cubyz/shaders/graphics/Text.fs index 0a343ce6..c77c2fca 100644 --- a/assets/cubyz/shaders/graphics/Text.fs +++ b/assets/cubyz/shaders/graphics/Text.fs @@ -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; } } \ No newline at end of file diff --git a/assets/cubyz/shaders/graphics/Text.vs b/assets/cubyz/shaders/graphics/Text.vs index fd0ea3e8..bafbac27 100644 --- a/assets/cubyz/shaders/graphics/Text.vs +++ b/assets/cubyz/shaders/graphics/Text.vs @@ -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 diff --git a/build.zig b/build.zig index 4021afb6..0607c79f 100644 --- a/build.zig +++ b/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; diff --git a/mach-freetype b/mach-freetype new file mode 160000 index 00000000..9cfd1177 --- /dev/null +++ b/mach-freetype @@ -0,0 +1 @@ +Subproject commit 9cfd1177292533c68f7d4d9a2a123722c8b494f4 diff --git a/src/graphics.zig b/src/graphics.zig index 1731543d..ad6a682c 100644 --- a/src/graphics.zig +++ b/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..]); } } diff --git a/src/main.zig b/src/main.zig index edfbca3c..f548e2d9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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); diff --git a/src/network.zig b/src/network.zig index f20a9768..144ed725 100644 --- a/src/network.zig +++ b/src/network.zig @@ -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, diff --git a/src/rotation.zig b/src/rotation.zig index 4ce57b6c..ec433758 100644 --- a/src/rotation.zig +++ b/src/rotation.zig @@ -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);