diff --git a/build.zig b/build.zig index 055b3a54b..2775042a3 100644 --- a/build.zig +++ b/build.zig @@ -4,15 +4,25 @@ const LibExeObjStep = std.build.LibExeObjStep; const glfw = @import("vendor/mach/glfw/build.zig"); const ft = @import("src/freetype/build.zig"); const uv = @import("src/libuv/build.zig"); +const tracylib = @import("src/tracy/build.zig"); const system_sdk = @import("vendor/mach/glfw/system_sdk.zig"); pub fn build(b: *std.build.Builder) !void { const target = b.standardTargetOptions(.{}); const mode = b.standardReleaseOptions(); + const tracy = b.option( + bool, + "tracy", + "Enable Tracy integration (default true in Debug)", + ) orelse (mode == .Debug); + + const exe_options = b.addOptions(); + exe_options.addOption(bool, "tracy_enabled", tracy); const exe = b.addExecutable("ghostty", "src/main.zig"); exe.setTarget(target); exe.setBuildMode(mode); + exe.addOptions("build_options", exe_options); exe.install(); exe.addIncludeDir("src/"); exe.addCSourceFile("src/gb_math.c", &.{}); @@ -22,6 +32,9 @@ pub fn build(b: *std.build.Builder) !void { .opengl = true, }); + // Tracy + if (tracy) try tracylib.link(b, exe, target); + // GLAD exe.addIncludeDir("vendor/glad/include/"); exe.addCSourceFile("vendor/glad/src/gl.c", &.{}); diff --git a/src/main.zig b/src/main.zig index 2c0aaa023..ac1076c5c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,9 +1,14 @@ +const options = @import("build_options"); const std = @import("std"); const glfw = @import("glfw"); const App = @import("App.zig"); +const trace = @import("tracy/tracy.zig").trace; pub fn main() !void { + const tracy = trace(@src()); + defer tracy.end(); + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; const gpa = general_purpose_allocator.allocator(); defer _ = general_purpose_allocator.deinit(); @@ -18,6 +23,11 @@ pub fn main() !void { try app.run(); } +// Required by tracy/tracy.zig to enable/disable tracy support. +pub fn tracy_enabled() bool { + return options.tracy_enabled; +} + test { _ = @import("Atlas.zig"); _ = @import("FontAtlas.zig"); diff --git a/src/tracy/build.zig b/src/tracy/build.zig new file mode 100644 index 000000000..4afe8f4e0 --- /dev/null +++ b/src/tracy/build.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const LibExeObjStep = std.build.LibExeObjStep; + +/// Build and link the Tracy client into the given executable. +pub fn link( + b: *Builder, + exe: *LibExeObjStep, + target: std.zig.CrossTarget, +) !void { + var flags = std.ArrayList([]const u8).init(b.allocator); + defer flags.deinit(); + + try flags.appendSlice(&.{ + "-DTRACY_ENABLE", + "-fno-sanitize=undefined", + }); + + if (target.isWindows()) { + try flags.appendSlice(&.{ + "-D_WIN32_WINNT=0x601", + }); + } + + const path = root(); + exe.addIncludePath(path); + exe.addCSourceFile(try std.fs.path.join( + exe.builder.allocator, + &.{ path, "TracyClient.cpp" }, + ), flags.items); + + exe.linkLibC(); + exe.linkSystemLibrary("c++"); + + if (exe.target.isWindows()) { + exe.linkSystemLibrary("Advapi32"); + exe.linkSystemLibrary("User32"); + exe.linkSystemLibrary("Ws2_32"); + exe.linkSystemLibrary("DbgHelp"); + } +} + +fn root() []const u8 { + return (std.fs.path.dirname(@src().file) orelse unreachable) ++ "/../../vendor/tracy/"; +} diff --git a/src/tracy/tracy.zig b/src/tracy/tracy.zig new file mode 100644 index 000000000..93e99e59b --- /dev/null +++ b/src/tracy/tracy.zig @@ -0,0 +1,81 @@ +//! Tracy API. +//! +//! Forked and modified from https://github.com/SpexGuy/Zig-Tracy +const std = @import("std"); +const builtin = @import("builtin"); +const root = @import("root"); +const SourceLocation = std.builtin.SourceLocation; + +// Tracy is enabled if the root function tracy_enabled returns true. +pub const enabled = @import("root").tracy_enabled(); + +// Bring in the correct implementation depending on if we're enabled or not. +// See Impl for all the real doc comments. +pub usingnamespace if (enabled) Impl else Noop; + +const Impl = struct { + const c = @cImport({ + @cDefine("TRACY_ENABLE", ""); + @cInclude("TracyC.h"); + }); + + const has_callstack_support = @hasDecl(c, "TRACY_HAS_CALLSTACK") and @hasDecl(c, "TRACY_CALLSTACK"); + const callstack_enabled: c_int = if (has_callstack_support) c.TRACY_CALLSTACK else 0; + + pub const ZoneCtx = struct { + zone: c.___tracy_c_zone_context, + + pub inline fn text(self: ZoneCtx, val: []const u8) void { + c.___tracy_emit_zone_text(self.zone, val.ptr, val.len); + } + + pub inline fn name(self: ZoneCtx, val: []const u8) void { + c.___tracy_emit_zone_name(self.zone, val.ptr, val.len); + } + + pub inline fn value(self: ZoneCtx, val: u64) void { + c.___tracy_emit_zone_value(self.zone, val); + } + + pub inline fn end(self: ZoneCtx) void { + c.___tracy_emit_zone_end(self.zone); + } + }; + + /// Start a trace. Defer calling end() to end the trace. + pub inline fn trace(comptime src: SourceLocation) ZoneCtx { + const callstack_depth = 10; // TODO configurable + + const static = struct { + var loc: c.___tracy_source_location_data = undefined; + }; + static.loc = .{ + .name = null, + .function = src.fn_name.ptr, + .file = src.file.ptr, + .line = src.line, + .color = 0, + }; + + const zone = if (has_callstack_support) + c.___tracy_emit_zone_begin_callstack(&static.loc, callstack_depth, 1) + else + c.___tracy_emit_zone_begin(&static.loc, 1); + + return ZoneCtx{ .zone = zone }; + } +}; + +const Noop = struct { + pub const ZoneCtx = struct { + pub inline fn text(_: ZoneCtx, _: []const u8) void {} + pub inline fn name(_: ZoneCtx, _: []const u8) void {} + pub inline fn value(_: ZoneCtx, _: u64) void {} + pub inline fn end(_: ZoneCtx) void {} + }; + + pub inline fn trace(comptime src: SourceLocation) ZoneCtx { + _ = src; + return .{}; + } +};