diff --git a/build.zig b/build.zig index a793e19..0747ff8 100644 --- a/build.zig +++ b/build.zig @@ -119,6 +119,10 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }), + // https://codeberg.org/ziglang/zig/issues/31272#issuecomment-14606517 + // R_X86_64_PC64 .sframe relocation in 0.16.0 + .use_llvm = true, + .use_lld = true, }); tests.root_module.linkLibrary(tracy); b.installArtifact(tests); diff --git a/build.zig.zon b/build.zig.zon index 5e4ae17..d3f2859 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .ztracy, .fingerprint = 0xf803f1f7ab5272cc, .version = "0.14.0-dev", - .minimum_zig_version = "0.15.1", + .minimum_zig_version = "0.16.0", .paths = .{ "build.zig", "build.zig.zon", diff --git a/libs/tracy/TracyClient.F90 b/libs/tracy/TracyClient.F90 new file mode 100644 index 0000000..f98dabc --- /dev/null +++ b/libs/tracy/TracyClient.F90 @@ -0,0 +1,1272 @@ +module tracy + use, intrinsic :: iso_c_binding, only: c_ptr, c_loc, c_char, c_null_char, & + & c_size_t, c_int8_t, c_int16_t, c_int32_t, c_int64_t, c_int, c_float, c_double, c_null_ptr + implicit none + private + + integer(c_int32_t), parameter, public :: TRACY_PLOTFORMAT_NUMBER = 0 + integer(c_int32_t), parameter, public :: TRACY_PLOTFORMAT_MEMORY = 1 + integer(c_int32_t), parameter, public :: TRACY_PLOTFORMAT_PERCENTAGE = 2 + integer(c_int32_t), parameter, public :: TRACY_PLOTFORMAT_WATT = 3 + + character(c_char), parameter, public :: tracy_null_char = c_null_char + + type, bind(C) :: TracyColors_t + integer(c_int32_t) :: Snow = int(Z'fffafa', kind=c_int32_t) + integer(c_int32_t) :: GhostWhite = int(Z'f8f8ff', kind=c_int32_t) + integer(c_int32_t) :: WhiteSmoke = int(Z'f5f5f5', kind=c_int32_t) + integer(c_int32_t) :: Gainsboro = int(Z'dcdcdc', kind=c_int32_t) + integer(c_int32_t) :: FloralWhite = int(Z'fffaf0', kind=c_int32_t) + integer(c_int32_t) :: OldLace = int(Z'fdf5e6', kind=c_int32_t) + integer(c_int32_t) :: Linen = int(Z'faf0e6', kind=c_int32_t) + integer(c_int32_t) :: AntiqueWhite = int(Z'faebd7', kind=c_int32_t) + integer(c_int32_t) :: PapayaWhip = int(Z'ffefd5', kind=c_int32_t) + integer(c_int32_t) :: BlanchedAlmond = int(Z'ffebcd', kind=c_int32_t) + integer(c_int32_t) :: Bisque = int(Z'ffe4c4', kind=c_int32_t) + integer(c_int32_t) :: PeachPuff = int(Z'ffdab9', kind=c_int32_t) + integer(c_int32_t) :: NavajoWhite = int(Z'ffdead', kind=c_int32_t) + integer(c_int32_t) :: Moccasin = int(Z'ffe4b5', kind=c_int32_t) + integer(c_int32_t) :: Cornsilk = int(Z'fff8dc', kind=c_int32_t) + integer(c_int32_t) :: Ivory = int(Z'fffff0', kind=c_int32_t) + integer(c_int32_t) :: LemonChiffon = int(Z'fffacd', kind=c_int32_t) + integer(c_int32_t) :: Seashell = int(Z'fff5ee', kind=c_int32_t) + integer(c_int32_t) :: Honeydew = int(Z'f0fff0', kind=c_int32_t) + integer(c_int32_t) :: MintCream = int(Z'f5fffa', kind=c_int32_t) + integer(c_int32_t) :: Azure = int(Z'f0ffff', kind=c_int32_t) + integer(c_int32_t) :: AliceBlue = int(Z'f0f8ff', kind=c_int32_t) + integer(c_int32_t) :: Lavender = int(Z'e6e6fa', kind=c_int32_t) + integer(c_int32_t) :: LavenderBlush = int(Z'fff0f5', kind=c_int32_t) + integer(c_int32_t) :: MistyRose = int(Z'ffe4e1', kind=c_int32_t) + integer(c_int32_t) :: White = int(Z'ffffff', kind=c_int32_t) + integer(c_int32_t) :: Black = int(Z'000000', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGray = int(Z'2f4f4f', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGrey = int(Z'2f4f4f', kind=c_int32_t) + integer(c_int32_t) :: DimGray = int(Z'696969', kind=c_int32_t) + integer(c_int32_t) :: DimGrey = int(Z'696969', kind=c_int32_t) + integer(c_int32_t) :: SlateGray = int(Z'708090', kind=c_int32_t) + integer(c_int32_t) :: SlateGrey = int(Z'708090', kind=c_int32_t) + integer(c_int32_t) :: LightSlateGray = int(Z'778899', kind=c_int32_t) + integer(c_int32_t) :: LightSlateGrey = int(Z'778899', kind=c_int32_t) + integer(c_int32_t) :: Gray = int(Z'bebebe', kind=c_int32_t) + integer(c_int32_t) :: Grey = int(Z'bebebe', kind=c_int32_t) + integer(c_int32_t) :: X11Gray = int(Z'bebebe', kind=c_int32_t) + integer(c_int32_t) :: X11Grey = int(Z'bebebe', kind=c_int32_t) + integer(c_int32_t) :: WebGray = int(Z'808080', kind=c_int32_t) + integer(c_int32_t) :: WebGrey = int(Z'808080', kind=c_int32_t) + integer(c_int32_t) :: LightGrey = int(Z'd3d3d3', kind=c_int32_t) + integer(c_int32_t) :: LightGray = int(Z'd3d3d3', kind=c_int32_t) + integer(c_int32_t) :: MidnightBlue = int(Z'191970', kind=c_int32_t) + integer(c_int32_t) :: Navy = int(Z'000080', kind=c_int32_t) + integer(c_int32_t) :: NavyBlue = int(Z'000080', kind=c_int32_t) + integer(c_int32_t) :: CornflowerBlue = int(Z'6495ed', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateBlue = int(Z'483d8b', kind=c_int32_t) + integer(c_int32_t) :: SlateBlue = int(Z'6a5acd', kind=c_int32_t) + integer(c_int32_t) :: MediumSlateBlue = int(Z'7b68ee', kind=c_int32_t) + integer(c_int32_t) :: LightSlateBlue = int(Z'8470ff', kind=c_int32_t) + integer(c_int32_t) :: MediumBlue = int(Z'0000cd', kind=c_int32_t) + integer(c_int32_t) :: RoyalBlue = int(Z'4169e1', kind=c_int32_t) + integer(c_int32_t) :: Blue = int(Z'0000ff', kind=c_int32_t) + integer(c_int32_t) :: DodgerBlue = int(Z'1e90ff', kind=c_int32_t) + integer(c_int32_t) :: DeepSkyBlue = int(Z'00bfff', kind=c_int32_t) + integer(c_int32_t) :: SkyBlue = int(Z'87ceeb', kind=c_int32_t) + integer(c_int32_t) :: LightSkyBlue = int(Z'87cefa', kind=c_int32_t) + integer(c_int32_t) :: SteelBlue = int(Z'4682b4', kind=c_int32_t) + integer(c_int32_t) :: LightSteelBlue = int(Z'b0c4de', kind=c_int32_t) + integer(c_int32_t) :: LightBlue = int(Z'add8e6', kind=c_int32_t) + integer(c_int32_t) :: PowderBlue = int(Z'b0e0e6', kind=c_int32_t) + integer(c_int32_t) :: PaleTurquoise = int(Z'afeeee', kind=c_int32_t) + integer(c_int32_t) :: DarkTurquoise = int(Z'00ced1', kind=c_int32_t) + integer(c_int32_t) :: MediumTurquoise = int(Z'48d1cc', kind=c_int32_t) + integer(c_int32_t) :: Turquoise = int(Z'40e0d0', kind=c_int32_t) + integer(c_int32_t) :: Cyan = int(Z'00ffff', kind=c_int32_t) + integer(c_int32_t) :: Aqua = int(Z'00ffff', kind=c_int32_t) + integer(c_int32_t) :: LightCyan = int(Z'e0ffff', kind=c_int32_t) + integer(c_int32_t) :: CadetBlue = int(Z'5f9ea0', kind=c_int32_t) + integer(c_int32_t) :: MediumAquamarine = int(Z'66cdaa', kind=c_int32_t) + integer(c_int32_t) :: Aquamarine = int(Z'7fffd4', kind=c_int32_t) + integer(c_int32_t) :: DarkGreen = int(Z'006400', kind=c_int32_t) + integer(c_int32_t) :: DarkOliveGreen = int(Z'556b2f', kind=c_int32_t) + integer(c_int32_t) :: DarkSeaGreen = int(Z'8fbc8f', kind=c_int32_t) + integer(c_int32_t) :: SeaGreen = int(Z'2e8b57', kind=c_int32_t) + integer(c_int32_t) :: MediumSeaGreen = int(Z'3cb371', kind=c_int32_t) + integer(c_int32_t) :: LightSeaGreen = int(Z'20b2aa', kind=c_int32_t) + integer(c_int32_t) :: PaleGreen = int(Z'98fb98', kind=c_int32_t) + integer(c_int32_t) :: SpringGreen = int(Z'00ff7f', kind=c_int32_t) + integer(c_int32_t) :: LawnGreen = int(Z'7cfc00', kind=c_int32_t) + integer(c_int32_t) :: Green = int(Z'00ff00', kind=c_int32_t) + integer(c_int32_t) :: Lime = int(Z'00ff00', kind=c_int32_t) + integer(c_int32_t) :: X11Green = int(Z'00ff00', kind=c_int32_t) + integer(c_int32_t) :: WebGreen = int(Z'008000', kind=c_int32_t) + integer(c_int32_t) :: Chartreuse = int(Z'7fff00', kind=c_int32_t) + integer(c_int32_t) :: MediumSpringGreen = int(Z'00fa9a', kind=c_int32_t) + integer(c_int32_t) :: GreenYellow = int(Z'adff2f', kind=c_int32_t) + integer(c_int32_t) :: LimeGreen = int(Z'32cd32', kind=c_int32_t) + integer(c_int32_t) :: YellowGreen = int(Z'9acd32', kind=c_int32_t) + integer(c_int32_t) :: ForestGreen = int(Z'228b22', kind=c_int32_t) + integer(c_int32_t) :: OliveDrab = int(Z'6b8e23', kind=c_int32_t) + integer(c_int32_t) :: DarkKhaki = int(Z'bdb76b', kind=c_int32_t) + integer(c_int32_t) :: Khaki = int(Z'f0e68c', kind=c_int32_t) + integer(c_int32_t) :: PaleGoldenrod = int(Z'eee8aa', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrodYellow = int(Z'fafad2', kind=c_int32_t) + integer(c_int32_t) :: LightYellow = int(Z'ffffe0', kind=c_int32_t) + integer(c_int32_t) :: Yellow = int(Z'ffff00', kind=c_int32_t) + integer(c_int32_t) :: Gold = int(Z'ffd700', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrod = int(Z'eedd82', kind=c_int32_t) + integer(c_int32_t) :: Goldenrod = int(Z'daa520', kind=c_int32_t) + integer(c_int32_t) :: DarkGoldenrod = int(Z'b8860b', kind=c_int32_t) + integer(c_int32_t) :: RosyBrown = int(Z'bc8f8f', kind=c_int32_t) + integer(c_int32_t) :: IndianRed = int(Z'cd5c5c', kind=c_int32_t) + integer(c_int32_t) :: SaddleBrown = int(Z'8b4513', kind=c_int32_t) + integer(c_int32_t) :: Sienna = int(Z'a0522d', kind=c_int32_t) + integer(c_int32_t) :: Peru = int(Z'cd853f', kind=c_int32_t) + integer(c_int32_t) :: Burlywood = int(Z'deb887', kind=c_int32_t) + integer(c_int32_t) :: Beige = int(Z'f5f5dc', kind=c_int32_t) + integer(c_int32_t) :: Wheat = int(Z'f5deb3', kind=c_int32_t) + integer(c_int32_t) :: SandyBrown = int(Z'f4a460', kind=c_int32_t) + integer(c_int32_t) :: Tan = int(Z'd2b48c', kind=c_int32_t) + integer(c_int32_t) :: Chocolate = int(Z'd2691e', kind=c_int32_t) + integer(c_int32_t) :: Firebrick = int(Z'b22222', kind=c_int32_t) + integer(c_int32_t) :: Brown = int(Z'a52a2a', kind=c_int32_t) + integer(c_int32_t) :: DarkSalmon = int(Z'e9967a', kind=c_int32_t) + integer(c_int32_t) :: Salmon = int(Z'fa8072', kind=c_int32_t) + integer(c_int32_t) :: LightSalmon = int(Z'ffa07a', kind=c_int32_t) + integer(c_int32_t) :: Orange = int(Z'ffa500', kind=c_int32_t) + integer(c_int32_t) :: DarkOrange = int(Z'ff8c00', kind=c_int32_t) + integer(c_int32_t) :: Coral = int(Z'ff7f50', kind=c_int32_t) + integer(c_int32_t) :: LightCoral = int(Z'f08080', kind=c_int32_t) + integer(c_int32_t) :: Tomato = int(Z'ff6347', kind=c_int32_t) + integer(c_int32_t) :: OrangeRed = int(Z'ff4500', kind=c_int32_t) + integer(c_int32_t) :: Red = int(Z'ff0000', kind=c_int32_t) + integer(c_int32_t) :: HotPink = int(Z'ff69b4', kind=c_int32_t) + integer(c_int32_t) :: DeepPink = int(Z'ff1493', kind=c_int32_t) + integer(c_int32_t) :: Pink = int(Z'ffc0cb', kind=c_int32_t) + integer(c_int32_t) :: LightPink = int(Z'ffb6c1', kind=c_int32_t) + integer(c_int32_t) :: PaleVioletRed = int(Z'db7093', kind=c_int32_t) + integer(c_int32_t) :: Maroon = int(Z'b03060', kind=c_int32_t) + integer(c_int32_t) :: X11Maroon = int(Z'b03060', kind=c_int32_t) + integer(c_int32_t) :: WebMaroon = int(Z'800000', kind=c_int32_t) + integer(c_int32_t) :: MediumVioletRed = int(Z'c71585', kind=c_int32_t) + integer(c_int32_t) :: VioletRed = int(Z'd02090', kind=c_int32_t) + integer(c_int32_t) :: Magenta = int(Z'ff00ff', kind=c_int32_t) + integer(c_int32_t) :: Fuchsia = int(Z'ff00ff', kind=c_int32_t) + integer(c_int32_t) :: Violet = int(Z'ee82ee', kind=c_int32_t) + integer(c_int32_t) :: Plum = int(Z'dda0dd', kind=c_int32_t) + integer(c_int32_t) :: Orchid = int(Z'da70d6', kind=c_int32_t) + integer(c_int32_t) :: MediumOrchid = int(Z'ba55d3', kind=c_int32_t) + integer(c_int32_t) :: DarkOrchid = int(Z'9932cc', kind=c_int32_t) + integer(c_int32_t) :: DarkViolet = int(Z'9400d3', kind=c_int32_t) + integer(c_int32_t) :: BlueViolet = int(Z'8a2be2', kind=c_int32_t) + integer(c_int32_t) :: Purple = int(Z'a020f0', kind=c_int32_t) + integer(c_int32_t) :: X11Purple = int(Z'a020f0', kind=c_int32_t) + integer(c_int32_t) :: WebPurple = int(Z'800080', kind=c_int32_t) + integer(c_int32_t) :: MediumPurple = int(Z'9370db', kind=c_int32_t) + integer(c_int32_t) :: Thistle = int(Z'd8bfd8', kind=c_int32_t) + integer(c_int32_t) :: Snow1 = int(Z'fffafa', kind=c_int32_t) + integer(c_int32_t) :: Snow2 = int(Z'eee9e9', kind=c_int32_t) + integer(c_int32_t) :: Snow3 = int(Z'cdc9c9', kind=c_int32_t) + integer(c_int32_t) :: Snow4 = int(Z'8b8989', kind=c_int32_t) + integer(c_int32_t) :: Seashell1 = int(Z'fff5ee', kind=c_int32_t) + integer(c_int32_t) :: Seashell2 = int(Z'eee5de', kind=c_int32_t) + integer(c_int32_t) :: Seashell3 = int(Z'cdc5bf', kind=c_int32_t) + integer(c_int32_t) :: Seashell4 = int(Z'8b8682', kind=c_int32_t) + integer(c_int32_t) :: AntiqueWhite1 = int(Z'ffefdb', kind=c_int32_t) + integer(c_int32_t) :: AntiqueWhite2 = int(Z'eedfcc', kind=c_int32_t) + integer(c_int32_t) :: AntiqueWhite3 = int(Z'cdc0b0', kind=c_int32_t) + integer(c_int32_t) :: AntiqueWhite4 = int(Z'8b8378', kind=c_int32_t) + integer(c_int32_t) :: Bisque1 = int(Z'ffe4c4', kind=c_int32_t) + integer(c_int32_t) :: Bisque2 = int(Z'eed5b7', kind=c_int32_t) + integer(c_int32_t) :: Bisque3 = int(Z'cdb79e', kind=c_int32_t) + integer(c_int32_t) :: Bisque4 = int(Z'8b7d6b', kind=c_int32_t) + integer(c_int32_t) :: PeachPuff1 = int(Z'ffdab9', kind=c_int32_t) + integer(c_int32_t) :: PeachPuff2 = int(Z'eecbad', kind=c_int32_t) + integer(c_int32_t) :: PeachPuff3 = int(Z'cdaf95', kind=c_int32_t) + integer(c_int32_t) :: PeachPuff4 = int(Z'8b7765', kind=c_int32_t) + integer(c_int32_t) :: NavajoWhite1 = int(Z'ffdead', kind=c_int32_t) + integer(c_int32_t) :: NavajoWhite2 = int(Z'eecfa1', kind=c_int32_t) + integer(c_int32_t) :: NavajoWhite3 = int(Z'cdb38b', kind=c_int32_t) + integer(c_int32_t) :: NavajoWhite4 = int(Z'8b795e', kind=c_int32_t) + integer(c_int32_t) :: LemonChiffon1 = int(Z'fffacd', kind=c_int32_t) + integer(c_int32_t) :: LemonChiffon2 = int(Z'eee9bf', kind=c_int32_t) + integer(c_int32_t) :: LemonChiffon3 = int(Z'cdc9a5', kind=c_int32_t) + integer(c_int32_t) :: LemonChiffon4 = int(Z'8b8970', kind=c_int32_t) + integer(c_int32_t) :: Cornsilk1 = int(Z'fff8dc', kind=c_int32_t) + integer(c_int32_t) :: Cornsilk2 = int(Z'eee8cd', kind=c_int32_t) + integer(c_int32_t) :: Cornsilk3 = int(Z'cdc8b1', kind=c_int32_t) + integer(c_int32_t) :: Cornsilk4 = int(Z'8b8878', kind=c_int32_t) + integer(c_int32_t) :: Ivory1 = int(Z'fffff0', kind=c_int32_t) + integer(c_int32_t) :: Ivory2 = int(Z'eeeee0', kind=c_int32_t) + integer(c_int32_t) :: Ivory3 = int(Z'cdcdc1', kind=c_int32_t) + integer(c_int32_t) :: Ivory4 = int(Z'8b8b83', kind=c_int32_t) + integer(c_int32_t) :: Honeydew1 = int(Z'f0fff0', kind=c_int32_t) + integer(c_int32_t) :: Honeydew2 = int(Z'e0eee0', kind=c_int32_t) + integer(c_int32_t) :: Honeydew3 = int(Z'c1cdc1', kind=c_int32_t) + integer(c_int32_t) :: Honeydew4 = int(Z'838b83', kind=c_int32_t) + integer(c_int32_t) :: LavenderBlush1 = int(Z'fff0f5', kind=c_int32_t) + integer(c_int32_t) :: LavenderBlush2 = int(Z'eee0e5', kind=c_int32_t) + integer(c_int32_t) :: LavenderBlush3 = int(Z'cdc1c5', kind=c_int32_t) + integer(c_int32_t) :: LavenderBlush4 = int(Z'8b8386', kind=c_int32_t) + integer(c_int32_t) :: MistyRose1 = int(Z'ffe4e1', kind=c_int32_t) + integer(c_int32_t) :: MistyRose2 = int(Z'eed5d2', kind=c_int32_t) + integer(c_int32_t) :: MistyRose3 = int(Z'cdb7b5', kind=c_int32_t) + integer(c_int32_t) :: MistyRose4 = int(Z'8b7d7b', kind=c_int32_t) + integer(c_int32_t) :: Azure1 = int(Z'f0ffff', kind=c_int32_t) + integer(c_int32_t) :: Azure2 = int(Z'e0eeee', kind=c_int32_t) + integer(c_int32_t) :: Azure3 = int(Z'c1cdcd', kind=c_int32_t) + integer(c_int32_t) :: Azure4 = int(Z'838b8b', kind=c_int32_t) + integer(c_int32_t) :: SlateBlue1 = int(Z'836fff', kind=c_int32_t) + integer(c_int32_t) :: SlateBlue2 = int(Z'7a67ee', kind=c_int32_t) + integer(c_int32_t) :: SlateBlue3 = int(Z'6959cd', kind=c_int32_t) + integer(c_int32_t) :: SlateBlue4 = int(Z'473c8b', kind=c_int32_t) + integer(c_int32_t) :: RoyalBlue1 = int(Z'4876ff', kind=c_int32_t) + integer(c_int32_t) :: RoyalBlue2 = int(Z'436eee', kind=c_int32_t) + integer(c_int32_t) :: RoyalBlue3 = int(Z'3a5fcd', kind=c_int32_t) + integer(c_int32_t) :: RoyalBlue4 = int(Z'27408b', kind=c_int32_t) + integer(c_int32_t) :: Blue1 = int(Z'0000ff', kind=c_int32_t) + integer(c_int32_t) :: Blue2 = int(Z'0000ee', kind=c_int32_t) + integer(c_int32_t) :: Blue3 = int(Z'0000cd', kind=c_int32_t) + integer(c_int32_t) :: Blue4 = int(Z'00008b', kind=c_int32_t) + integer(c_int32_t) :: DodgerBlue1 = int(Z'1e90ff', kind=c_int32_t) + integer(c_int32_t) :: DodgerBlue2 = int(Z'1c86ee', kind=c_int32_t) + integer(c_int32_t) :: DodgerBlue3 = int(Z'1874cd', kind=c_int32_t) + integer(c_int32_t) :: DodgerBlue4 = int(Z'104e8b', kind=c_int32_t) + integer(c_int32_t) :: SteelBlue1 = int(Z'63b8ff', kind=c_int32_t) + integer(c_int32_t) :: SteelBlue2 = int(Z'5cacee', kind=c_int32_t) + integer(c_int32_t) :: SteelBlue3 = int(Z'4f94cd', kind=c_int32_t) + integer(c_int32_t) :: SteelBlue4 = int(Z'36648b', kind=c_int32_t) + integer(c_int32_t) :: DeepSkyBlue1 = int(Z'00bfff', kind=c_int32_t) + integer(c_int32_t) :: DeepSkyBlue2 = int(Z'00b2ee', kind=c_int32_t) + integer(c_int32_t) :: DeepSkyBlue3 = int(Z'009acd', kind=c_int32_t) + integer(c_int32_t) :: DeepSkyBlue4 = int(Z'00688b', kind=c_int32_t) + integer(c_int32_t) :: SkyBlue1 = int(Z'87ceff', kind=c_int32_t) + integer(c_int32_t) :: SkyBlue2 = int(Z'7ec0ee', kind=c_int32_t) + integer(c_int32_t) :: SkyBlue3 = int(Z'6ca6cd', kind=c_int32_t) + integer(c_int32_t) :: SkyBlue4 = int(Z'4a708b', kind=c_int32_t) + integer(c_int32_t) :: LightSkyBlue1 = int(Z'b0e2ff', kind=c_int32_t) + integer(c_int32_t) :: LightSkyBlue2 = int(Z'a4d3ee', kind=c_int32_t) + integer(c_int32_t) :: LightSkyBlue3 = int(Z'8db6cd', kind=c_int32_t) + integer(c_int32_t) :: LightSkyBlue4 = int(Z'607b8b', kind=c_int32_t) + integer(c_int32_t) :: SlateGray1 = int(Z'c6e2ff', kind=c_int32_t) + integer(c_int32_t) :: SlateGray2 = int(Z'b9d3ee', kind=c_int32_t) + integer(c_int32_t) :: SlateGray3 = int(Z'9fb6cd', kind=c_int32_t) + integer(c_int32_t) :: SlateGray4 = int(Z'6c7b8b', kind=c_int32_t) + integer(c_int32_t) :: LightSteelBlue1 = int(Z'cae1ff', kind=c_int32_t) + integer(c_int32_t) :: LightSteelBlue2 = int(Z'bcd2ee', kind=c_int32_t) + integer(c_int32_t) :: LightSteelBlue3 = int(Z'a2b5cd', kind=c_int32_t) + integer(c_int32_t) :: LightSteelBlue4 = int(Z'6e7b8b', kind=c_int32_t) + integer(c_int32_t) :: LightBlue1 = int(Z'bfefff', kind=c_int32_t) + integer(c_int32_t) :: LightBlue2 = int(Z'b2dfee', kind=c_int32_t) + integer(c_int32_t) :: LightBlue3 = int(Z'9ac0cd', kind=c_int32_t) + integer(c_int32_t) :: LightBlue4 = int(Z'68838b', kind=c_int32_t) + integer(c_int32_t) :: LightCyan1 = int(Z'e0ffff', kind=c_int32_t) + integer(c_int32_t) :: LightCyan2 = int(Z'd1eeee', kind=c_int32_t) + integer(c_int32_t) :: LightCyan3 = int(Z'b4cdcd', kind=c_int32_t) + integer(c_int32_t) :: LightCyan4 = int(Z'7a8b8b', kind=c_int32_t) + integer(c_int32_t) :: PaleTurquoise1 = int(Z'bbffff', kind=c_int32_t) + integer(c_int32_t) :: PaleTurquoise2 = int(Z'aeeeee', kind=c_int32_t) + integer(c_int32_t) :: PaleTurquoise3 = int(Z'96cdcd', kind=c_int32_t) + integer(c_int32_t) :: PaleTurquoise4 = int(Z'668b8b', kind=c_int32_t) + integer(c_int32_t) :: CadetBlue1 = int(Z'98f5ff', kind=c_int32_t) + integer(c_int32_t) :: CadetBlue2 = int(Z'8ee5ee', kind=c_int32_t) + integer(c_int32_t) :: CadetBlue3 = int(Z'7ac5cd', kind=c_int32_t) + integer(c_int32_t) :: CadetBlue4 = int(Z'53868b', kind=c_int32_t) + integer(c_int32_t) :: Turquoise1 = int(Z'00f5ff', kind=c_int32_t) + integer(c_int32_t) :: Turquoise2 = int(Z'00e5ee', kind=c_int32_t) + integer(c_int32_t) :: Turquoise3 = int(Z'00c5cd', kind=c_int32_t) + integer(c_int32_t) :: Turquoise4 = int(Z'00868b', kind=c_int32_t) + integer(c_int32_t) :: Cyan1 = int(Z'00ffff', kind=c_int32_t) + integer(c_int32_t) :: Cyan2 = int(Z'00eeee', kind=c_int32_t) + integer(c_int32_t) :: Cyan3 = int(Z'00cdcd', kind=c_int32_t) + integer(c_int32_t) :: Cyan4 = int(Z'008b8b', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGray1 = int(Z'97ffff', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGray2 = int(Z'8deeee', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGray3 = int(Z'79cdcd', kind=c_int32_t) + integer(c_int32_t) :: DarkSlateGray4 = int(Z'528b8b', kind=c_int32_t) + integer(c_int32_t) :: Aquamarine1 = int(Z'7fffd4', kind=c_int32_t) + integer(c_int32_t) :: Aquamarine2 = int(Z'76eec6', kind=c_int32_t) + integer(c_int32_t) :: Aquamarine3 = int(Z'66cdaa', kind=c_int32_t) + integer(c_int32_t) :: Aquamarine4 = int(Z'458b74', kind=c_int32_t) + integer(c_int32_t) :: DarkSeaGreen1 = int(Z'c1ffc1', kind=c_int32_t) + integer(c_int32_t) :: DarkSeaGreen2 = int(Z'b4eeb4', kind=c_int32_t) + integer(c_int32_t) :: DarkSeaGreen3 = int(Z'9bcd9b', kind=c_int32_t) + integer(c_int32_t) :: DarkSeaGreen4 = int(Z'698b69', kind=c_int32_t) + integer(c_int32_t) :: SeaGreen1 = int(Z'54ff9f', kind=c_int32_t) + integer(c_int32_t) :: SeaGreen2 = int(Z'4eee94', kind=c_int32_t) + integer(c_int32_t) :: SeaGreen3 = int(Z'43cd80', kind=c_int32_t) + integer(c_int32_t) :: SeaGreen4 = int(Z'2e8b57', kind=c_int32_t) + integer(c_int32_t) :: PaleGreen1 = int(Z'9aff9a', kind=c_int32_t) + integer(c_int32_t) :: PaleGreen2 = int(Z'90ee90', kind=c_int32_t) + integer(c_int32_t) :: PaleGreen3 = int(Z'7ccd7c', kind=c_int32_t) + integer(c_int32_t) :: PaleGreen4 = int(Z'548b54', kind=c_int32_t) + integer(c_int32_t) :: SpringGreen1 = int(Z'00ff7f', kind=c_int32_t) + integer(c_int32_t) :: SpringGreen2 = int(Z'00ee76', kind=c_int32_t) + integer(c_int32_t) :: SpringGreen3 = int(Z'00cd66', kind=c_int32_t) + integer(c_int32_t) :: SpringGreen4 = int(Z'008b45', kind=c_int32_t) + integer(c_int32_t) :: Green1 = int(Z'00ff00', kind=c_int32_t) + integer(c_int32_t) :: Green2 = int(Z'00ee00', kind=c_int32_t) + integer(c_int32_t) :: Green3 = int(Z'00cd00', kind=c_int32_t) + integer(c_int32_t) :: Green4 = int(Z'008b00', kind=c_int32_t) + integer(c_int32_t) :: Chartreuse1 = int(Z'7fff00', kind=c_int32_t) + integer(c_int32_t) :: Chartreuse2 = int(Z'76ee00', kind=c_int32_t) + integer(c_int32_t) :: Chartreuse3 = int(Z'66cd00', kind=c_int32_t) + integer(c_int32_t) :: Chartreuse4 = int(Z'458b00', kind=c_int32_t) + integer(c_int32_t) :: OliveDrab1 = int(Z'c0ff3e', kind=c_int32_t) + integer(c_int32_t) :: OliveDrab2 = int(Z'b3ee3a', kind=c_int32_t) + integer(c_int32_t) :: OliveDrab3 = int(Z'9acd32', kind=c_int32_t) + integer(c_int32_t) :: OliveDrab4 = int(Z'698b22', kind=c_int32_t) + integer(c_int32_t) :: DarkOliveGreen1 = int(Z'caff70', kind=c_int32_t) + integer(c_int32_t) :: DarkOliveGreen2 = int(Z'bcee68', kind=c_int32_t) + integer(c_int32_t) :: DarkOliveGreen3 = int(Z'a2cd5a', kind=c_int32_t) + integer(c_int32_t) :: DarkOliveGreen4 = int(Z'6e8b3d', kind=c_int32_t) + integer(c_int32_t) :: Khaki1 = int(Z'fff68f', kind=c_int32_t) + integer(c_int32_t) :: Khaki2 = int(Z'eee685', kind=c_int32_t) + integer(c_int32_t) :: Khaki3 = int(Z'cdc673', kind=c_int32_t) + integer(c_int32_t) :: Khaki4 = int(Z'8b864e', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrod1 = int(Z'ffec8b', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrod2 = int(Z'eedc82', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrod3 = int(Z'cdbe70', kind=c_int32_t) + integer(c_int32_t) :: LightGoldenrod4 = int(Z'8b814c', kind=c_int32_t) + integer(c_int32_t) :: LightYellow1 = int(Z'ffffe0', kind=c_int32_t) + integer(c_int32_t) :: LightYellow2 = int(Z'eeeed1', kind=c_int32_t) + integer(c_int32_t) :: LightYellow3 = int(Z'cdcdb4', kind=c_int32_t) + integer(c_int32_t) :: LightYellow4 = int(Z'8b8b7a', kind=c_int32_t) + integer(c_int32_t) :: Yellow1 = int(Z'ffff00', kind=c_int32_t) + integer(c_int32_t) :: Yellow2 = int(Z'eeee00', kind=c_int32_t) + integer(c_int32_t) :: Yellow3 = int(Z'cdcd00', kind=c_int32_t) + integer(c_int32_t) :: Yellow4 = int(Z'8b8b00', kind=c_int32_t) + integer(c_int32_t) :: Gold1 = int(Z'ffd700', kind=c_int32_t) + integer(c_int32_t) :: Gold2 = int(Z'eec900', kind=c_int32_t) + integer(c_int32_t) :: Gold3 = int(Z'cdad00', kind=c_int32_t) + integer(c_int32_t) :: Gold4 = int(Z'8b7500', kind=c_int32_t) + integer(c_int32_t) :: Goldenrod1 = int(Z'ffc125', kind=c_int32_t) + integer(c_int32_t) :: Goldenrod2 = int(Z'eeb422', kind=c_int32_t) + integer(c_int32_t) :: Goldenrod3 = int(Z'cd9b1d', kind=c_int32_t) + integer(c_int32_t) :: Goldenrod4 = int(Z'8b6914', kind=c_int32_t) + integer(c_int32_t) :: DarkGoldenrod1 = int(Z'ffb90f', kind=c_int32_t) + integer(c_int32_t) :: DarkGoldenrod2 = int(Z'eead0e', kind=c_int32_t) + integer(c_int32_t) :: DarkGoldenrod3 = int(Z'cd950c', kind=c_int32_t) + integer(c_int32_t) :: DarkGoldenrod4 = int(Z'8b6508', kind=c_int32_t) + integer(c_int32_t) :: RosyBrown1 = int(Z'ffc1c1', kind=c_int32_t) + integer(c_int32_t) :: RosyBrown2 = int(Z'eeb4b4', kind=c_int32_t) + integer(c_int32_t) :: RosyBrown3 = int(Z'cd9b9b', kind=c_int32_t) + integer(c_int32_t) :: RosyBrown4 = int(Z'8b6969', kind=c_int32_t) + integer(c_int32_t) :: IndianRed1 = int(Z'ff6a6a', kind=c_int32_t) + integer(c_int32_t) :: IndianRed2 = int(Z'ee6363', kind=c_int32_t) + integer(c_int32_t) :: IndianRed3 = int(Z'cd5555', kind=c_int32_t) + integer(c_int32_t) :: IndianRed4 = int(Z'8b3a3a', kind=c_int32_t) + integer(c_int32_t) :: Sienna1 = int(Z'ff8247', kind=c_int32_t) + integer(c_int32_t) :: Sienna2 = int(Z'ee7942', kind=c_int32_t) + integer(c_int32_t) :: Sienna3 = int(Z'cd6839', kind=c_int32_t) + integer(c_int32_t) :: Sienna4 = int(Z'8b4726', kind=c_int32_t) + integer(c_int32_t) :: Burlywood1 = int(Z'ffd39b', kind=c_int32_t) + integer(c_int32_t) :: Burlywood2 = int(Z'eec591', kind=c_int32_t) + integer(c_int32_t) :: Burlywood3 = int(Z'cdaa7d', kind=c_int32_t) + integer(c_int32_t) :: Burlywood4 = int(Z'8b7355', kind=c_int32_t) + integer(c_int32_t) :: Wheat1 = int(Z'ffe7ba', kind=c_int32_t) + integer(c_int32_t) :: Wheat2 = int(Z'eed8ae', kind=c_int32_t) + integer(c_int32_t) :: Wheat3 = int(Z'cdba96', kind=c_int32_t) + integer(c_int32_t) :: Wheat4 = int(Z'8b7e66', kind=c_int32_t) + integer(c_int32_t) :: Tan1 = int(Z'ffa54f', kind=c_int32_t) + integer(c_int32_t) :: Tan2 = int(Z'ee9a49', kind=c_int32_t) + integer(c_int32_t) :: Tan3 = int(Z'cd853f', kind=c_int32_t) + integer(c_int32_t) :: Tan4 = int(Z'8b5a2b', kind=c_int32_t) + integer(c_int32_t) :: Chocolate1 = int(Z'ff7f24', kind=c_int32_t) + integer(c_int32_t) :: Chocolate2 = int(Z'ee7621', kind=c_int32_t) + integer(c_int32_t) :: Chocolate3 = int(Z'cd661d', kind=c_int32_t) + integer(c_int32_t) :: Chocolate4 = int(Z'8b4513', kind=c_int32_t) + integer(c_int32_t) :: Firebrick1 = int(Z'ff3030', kind=c_int32_t) + integer(c_int32_t) :: Firebrick2 = int(Z'ee2c2c', kind=c_int32_t) + integer(c_int32_t) :: Firebrick3 = int(Z'cd2626', kind=c_int32_t) + integer(c_int32_t) :: Firebrick4 = int(Z'8b1a1a', kind=c_int32_t) + integer(c_int32_t) :: Brown1 = int(Z'ff4040', kind=c_int32_t) + integer(c_int32_t) :: Brown2 = int(Z'ee3b3b', kind=c_int32_t) + integer(c_int32_t) :: Brown3 = int(Z'cd3333', kind=c_int32_t) + integer(c_int32_t) :: Brown4 = int(Z'8b2323', kind=c_int32_t) + integer(c_int32_t) :: Salmon1 = int(Z'ff8c69', kind=c_int32_t) + integer(c_int32_t) :: Salmon2 = int(Z'ee8262', kind=c_int32_t) + integer(c_int32_t) :: Salmon3 = int(Z'cd7054', kind=c_int32_t) + integer(c_int32_t) :: Salmon4 = int(Z'8b4c39', kind=c_int32_t) + integer(c_int32_t) :: LightSalmon1 = int(Z'ffa07a', kind=c_int32_t) + integer(c_int32_t) :: LightSalmon2 = int(Z'ee9572', kind=c_int32_t) + integer(c_int32_t) :: LightSalmon3 = int(Z'cd8162', kind=c_int32_t) + integer(c_int32_t) :: LightSalmon4 = int(Z'8b5742', kind=c_int32_t) + integer(c_int32_t) :: Orange1 = int(Z'ffa500', kind=c_int32_t) + integer(c_int32_t) :: Orange2 = int(Z'ee9a00', kind=c_int32_t) + integer(c_int32_t) :: Orange3 = int(Z'cd8500', kind=c_int32_t) + integer(c_int32_t) :: Orange4 = int(Z'8b5a00', kind=c_int32_t) + integer(c_int32_t) :: DarkOrange1 = int(Z'ff7f00', kind=c_int32_t) + integer(c_int32_t) :: DarkOrange2 = int(Z'ee7600', kind=c_int32_t) + integer(c_int32_t) :: DarkOrange3 = int(Z'cd6600', kind=c_int32_t) + integer(c_int32_t) :: DarkOrange4 = int(Z'8b4500', kind=c_int32_t) + integer(c_int32_t) :: Coral1 = int(Z'ff7256', kind=c_int32_t) + integer(c_int32_t) :: Coral2 = int(Z'ee6a50', kind=c_int32_t) + integer(c_int32_t) :: Coral3 = int(Z'cd5b45', kind=c_int32_t) + integer(c_int32_t) :: Coral4 = int(Z'8b3e2f', kind=c_int32_t) + integer(c_int32_t) :: Tomato1 = int(Z'ff6347', kind=c_int32_t) + integer(c_int32_t) :: Tomato2 = int(Z'ee5c42', kind=c_int32_t) + integer(c_int32_t) :: Tomato3 = int(Z'cd4f39', kind=c_int32_t) + integer(c_int32_t) :: Tomato4 = int(Z'8b3626', kind=c_int32_t) + integer(c_int32_t) :: OrangeRed1 = int(Z'ff4500', kind=c_int32_t) + integer(c_int32_t) :: OrangeRed2 = int(Z'ee4000', kind=c_int32_t) + integer(c_int32_t) :: OrangeRed3 = int(Z'cd3700', kind=c_int32_t) + integer(c_int32_t) :: OrangeRed4 = int(Z'8b2500', kind=c_int32_t) + integer(c_int32_t) :: Red1 = int(Z'ff0000', kind=c_int32_t) + integer(c_int32_t) :: Red2 = int(Z'ee0000', kind=c_int32_t) + integer(c_int32_t) :: Red3 = int(Z'cd0000', kind=c_int32_t) + integer(c_int32_t) :: Red4 = int(Z'8b0000', kind=c_int32_t) + integer(c_int32_t) :: DeepPink1 = int(Z'ff1493', kind=c_int32_t) + integer(c_int32_t) :: DeepPink2 = int(Z'ee1289', kind=c_int32_t) + integer(c_int32_t) :: DeepPink3 = int(Z'cd1076', kind=c_int32_t) + integer(c_int32_t) :: DeepPink4 = int(Z'8b0a50', kind=c_int32_t) + integer(c_int32_t) :: HotPink1 = int(Z'ff6eb4', kind=c_int32_t) + integer(c_int32_t) :: HotPink2 = int(Z'ee6aa7', kind=c_int32_t) + integer(c_int32_t) :: HotPink3 = int(Z'cd6090', kind=c_int32_t) + integer(c_int32_t) :: HotPink4 = int(Z'8b3a62', kind=c_int32_t) + integer(c_int32_t) :: Pink1 = int(Z'ffb5c5', kind=c_int32_t) + integer(c_int32_t) :: Pink2 = int(Z'eea9b8', kind=c_int32_t) + integer(c_int32_t) :: Pink3 = int(Z'cd919e', kind=c_int32_t) + integer(c_int32_t) :: Pink4 = int(Z'8b636c', kind=c_int32_t) + integer(c_int32_t) :: LightPink1 = int(Z'ffaeb9', kind=c_int32_t) + integer(c_int32_t) :: LightPink2 = int(Z'eea2ad', kind=c_int32_t) + integer(c_int32_t) :: LightPink3 = int(Z'cd8c95', kind=c_int32_t) + integer(c_int32_t) :: LightPink4 = int(Z'8b5f65', kind=c_int32_t) + integer(c_int32_t) :: PaleVioletRed1 = int(Z'ff82ab', kind=c_int32_t) + integer(c_int32_t) :: PaleVioletRed2 = int(Z'ee799f', kind=c_int32_t) + integer(c_int32_t) :: PaleVioletRed3 = int(Z'cd6889', kind=c_int32_t) + integer(c_int32_t) :: PaleVioletRed4 = int(Z'8b475d', kind=c_int32_t) + integer(c_int32_t) :: Maroon1 = int(Z'ff34b3', kind=c_int32_t) + integer(c_int32_t) :: Maroon2 = int(Z'ee30a7', kind=c_int32_t) + integer(c_int32_t) :: Maroon3 = int(Z'cd2990', kind=c_int32_t) + integer(c_int32_t) :: Maroon4 = int(Z'8b1c62', kind=c_int32_t) + integer(c_int32_t) :: VioletRed1 = int(Z'ff3e96', kind=c_int32_t) + integer(c_int32_t) :: VioletRed2 = int(Z'ee3a8c', kind=c_int32_t) + integer(c_int32_t) :: VioletRed3 = int(Z'cd3278', kind=c_int32_t) + integer(c_int32_t) :: VioletRed4 = int(Z'8b2252', kind=c_int32_t) + integer(c_int32_t) :: Magenta1 = int(Z'ff00ff', kind=c_int32_t) + integer(c_int32_t) :: Magenta2 = int(Z'ee00ee', kind=c_int32_t) + integer(c_int32_t) :: Magenta3 = int(Z'cd00cd', kind=c_int32_t) + integer(c_int32_t) :: Magenta4 = int(Z'8b008b', kind=c_int32_t) + integer(c_int32_t) :: Orchid1 = int(Z'ff83fa', kind=c_int32_t) + integer(c_int32_t) :: Orchid2 = int(Z'ee7ae9', kind=c_int32_t) + integer(c_int32_t) :: Orchid3 = int(Z'cd69c9', kind=c_int32_t) + integer(c_int32_t) :: Orchid4 = int(Z'8b4789', kind=c_int32_t) + integer(c_int32_t) :: Plum1 = int(Z'ffbbff', kind=c_int32_t) + integer(c_int32_t) :: Plum2 = int(Z'eeaeee', kind=c_int32_t) + integer(c_int32_t) :: Plum3 = int(Z'cd96cd', kind=c_int32_t) + integer(c_int32_t) :: Plum4 = int(Z'8b668b', kind=c_int32_t) + integer(c_int32_t) :: MediumOrchid1 = int(Z'e066ff', kind=c_int32_t) + integer(c_int32_t) :: MediumOrchid2 = int(Z'd15fee', kind=c_int32_t) + integer(c_int32_t) :: MediumOrchid3 = int(Z'b452cd', kind=c_int32_t) + integer(c_int32_t) :: MediumOrchid4 = int(Z'7a378b', kind=c_int32_t) + integer(c_int32_t) :: DarkOrchid1 = int(Z'bf3eff', kind=c_int32_t) + integer(c_int32_t) :: DarkOrchid2 = int(Z'b23aee', kind=c_int32_t) + integer(c_int32_t) :: DarkOrchid3 = int(Z'9a32cd', kind=c_int32_t) + integer(c_int32_t) :: DarkOrchid4 = int(Z'68228b', kind=c_int32_t) + integer(c_int32_t) :: Purple1 = int(Z'9b30ff', kind=c_int32_t) + integer(c_int32_t) :: Purple2 = int(Z'912cee', kind=c_int32_t) + integer(c_int32_t) :: Purple3 = int(Z'7d26cd', kind=c_int32_t) + integer(c_int32_t) :: Purple4 = int(Z'551a8b', kind=c_int32_t) + integer(c_int32_t) :: MediumPurple1 = int(Z'ab82ff', kind=c_int32_t) + integer(c_int32_t) :: MediumPurple2 = int(Z'9f79ee', kind=c_int32_t) + integer(c_int32_t) :: MediumPurple3 = int(Z'8968cd', kind=c_int32_t) + integer(c_int32_t) :: MediumPurple4 = int(Z'5d478b', kind=c_int32_t) + integer(c_int32_t) :: Thistle1 = int(Z'ffe1ff', kind=c_int32_t) + integer(c_int32_t) :: Thistle2 = int(Z'eed2ee', kind=c_int32_t) + integer(c_int32_t) :: Thistle3 = int(Z'cdb5cd', kind=c_int32_t) + integer(c_int32_t) :: Thistle4 = int(Z'8b7b8b', kind=c_int32_t) + integer(c_int32_t) :: Gray0 = int(Z'000000', kind=c_int32_t) + integer(c_int32_t) :: Grey0 = int(Z'000000', kind=c_int32_t) + integer(c_int32_t) :: Gray1 = int(Z'030303', kind=c_int32_t) + integer(c_int32_t) :: Grey1 = int(Z'030303', kind=c_int32_t) + integer(c_int32_t) :: Gray2 = int(Z'050505', kind=c_int32_t) + integer(c_int32_t) :: Grey2 = int(Z'050505', kind=c_int32_t) + integer(c_int32_t) :: Gray3 = int(Z'080808', kind=c_int32_t) + integer(c_int32_t) :: Grey3 = int(Z'080808', kind=c_int32_t) + integer(c_int32_t) :: Gray4 = int(Z'0a0a0a', kind=c_int32_t) + integer(c_int32_t) :: Grey4 = int(Z'0a0a0a', kind=c_int32_t) + integer(c_int32_t) :: Gray5 = int(Z'0d0d0d', kind=c_int32_t) + integer(c_int32_t) :: Grey5 = int(Z'0d0d0d', kind=c_int32_t) + integer(c_int32_t) :: Gray6 = int(Z'0f0f0f', kind=c_int32_t) + integer(c_int32_t) :: Grey6 = int(Z'0f0f0f', kind=c_int32_t) + integer(c_int32_t) :: Gray7 = int(Z'121212', kind=c_int32_t) + integer(c_int32_t) :: Grey7 = int(Z'121212', kind=c_int32_t) + integer(c_int32_t) :: Gray8 = int(Z'141414', kind=c_int32_t) + integer(c_int32_t) :: Grey8 = int(Z'141414', kind=c_int32_t) + integer(c_int32_t) :: Gray9 = int(Z'171717', kind=c_int32_t) + integer(c_int32_t) :: Grey9 = int(Z'171717', kind=c_int32_t) + integer(c_int32_t) :: Gray10 = int(Z'1a1a1a', kind=c_int32_t) + integer(c_int32_t) :: Grey10 = int(Z'1a1a1a', kind=c_int32_t) + integer(c_int32_t) :: Gray11 = int(Z'1c1c1c', kind=c_int32_t) + integer(c_int32_t) :: Grey11 = int(Z'1c1c1c', kind=c_int32_t) + integer(c_int32_t) :: Gray12 = int(Z'1f1f1f', kind=c_int32_t) + integer(c_int32_t) :: Grey12 = int(Z'1f1f1f', kind=c_int32_t) + integer(c_int32_t) :: Gray13 = int(Z'212121', kind=c_int32_t) + integer(c_int32_t) :: Grey13 = int(Z'212121', kind=c_int32_t) + integer(c_int32_t) :: Gray14 = int(Z'242424', kind=c_int32_t) + integer(c_int32_t) :: Grey14 = int(Z'242424', kind=c_int32_t) + integer(c_int32_t) :: Gray15 = int(Z'262626', kind=c_int32_t) + integer(c_int32_t) :: Grey15 = int(Z'262626', kind=c_int32_t) + integer(c_int32_t) :: Gray16 = int(Z'292929', kind=c_int32_t) + integer(c_int32_t) :: Grey16 = int(Z'292929', kind=c_int32_t) + integer(c_int32_t) :: Gray17 = int(Z'2b2b2b', kind=c_int32_t) + integer(c_int32_t) :: Grey17 = int(Z'2b2b2b', kind=c_int32_t) + integer(c_int32_t) :: Gray18 = int(Z'2e2e2e', kind=c_int32_t) + integer(c_int32_t) :: Grey18 = int(Z'2e2e2e', kind=c_int32_t) + integer(c_int32_t) :: Gray19 = int(Z'303030', kind=c_int32_t) + integer(c_int32_t) :: Grey19 = int(Z'303030', kind=c_int32_t) + integer(c_int32_t) :: Gray20 = int(Z'333333', kind=c_int32_t) + integer(c_int32_t) :: Grey20 = int(Z'333333', kind=c_int32_t) + integer(c_int32_t) :: Gray21 = int(Z'363636', kind=c_int32_t) + integer(c_int32_t) :: Grey21 = int(Z'363636', kind=c_int32_t) + integer(c_int32_t) :: Gray22 = int(Z'383838', kind=c_int32_t) + integer(c_int32_t) :: Grey22 = int(Z'383838', kind=c_int32_t) + integer(c_int32_t) :: Gray23 = int(Z'3b3b3b', kind=c_int32_t) + integer(c_int32_t) :: Grey23 = int(Z'3b3b3b', kind=c_int32_t) + integer(c_int32_t) :: Gray24 = int(Z'3d3d3d', kind=c_int32_t) + integer(c_int32_t) :: Grey24 = int(Z'3d3d3d', kind=c_int32_t) + integer(c_int32_t) :: Gray25 = int(Z'404040', kind=c_int32_t) + integer(c_int32_t) :: Grey25 = int(Z'404040', kind=c_int32_t) + integer(c_int32_t) :: Gray26 = int(Z'424242', kind=c_int32_t) + integer(c_int32_t) :: Grey26 = int(Z'424242', kind=c_int32_t) + integer(c_int32_t) :: Gray27 = int(Z'454545', kind=c_int32_t) + integer(c_int32_t) :: Grey27 = int(Z'454545', kind=c_int32_t) + integer(c_int32_t) :: Gray28 = int(Z'474747', kind=c_int32_t) + integer(c_int32_t) :: Grey28 = int(Z'474747', kind=c_int32_t) + integer(c_int32_t) :: Gray29 = int(Z'4a4a4a', kind=c_int32_t) + integer(c_int32_t) :: Grey29 = int(Z'4a4a4a', kind=c_int32_t) + integer(c_int32_t) :: Gray30 = int(Z'4d4d4d', kind=c_int32_t) + integer(c_int32_t) :: Grey30 = int(Z'4d4d4d', kind=c_int32_t) + integer(c_int32_t) :: Gray31 = int(Z'4f4f4f', kind=c_int32_t) + integer(c_int32_t) :: Grey31 = int(Z'4f4f4f', kind=c_int32_t) + integer(c_int32_t) :: Gray32 = int(Z'525252', kind=c_int32_t) + integer(c_int32_t) :: Grey32 = int(Z'525252', kind=c_int32_t) + integer(c_int32_t) :: Gray33 = int(Z'545454', kind=c_int32_t) + integer(c_int32_t) :: Grey33 = int(Z'545454', kind=c_int32_t) + integer(c_int32_t) :: Gray34 = int(Z'575757', kind=c_int32_t) + integer(c_int32_t) :: Grey34 = int(Z'575757', kind=c_int32_t) + integer(c_int32_t) :: Gray35 = int(Z'595959', kind=c_int32_t) + integer(c_int32_t) :: Grey35 = int(Z'595959', kind=c_int32_t) + integer(c_int32_t) :: Gray36 = int(Z'5c5c5c', kind=c_int32_t) + integer(c_int32_t) :: Grey36 = int(Z'5c5c5c', kind=c_int32_t) + integer(c_int32_t) :: Gray37 = int(Z'5e5e5e', kind=c_int32_t) + integer(c_int32_t) :: Grey37 = int(Z'5e5e5e', kind=c_int32_t) + integer(c_int32_t) :: Gray38 = int(Z'616161', kind=c_int32_t) + integer(c_int32_t) :: Grey38 = int(Z'616161', kind=c_int32_t) + integer(c_int32_t) :: Gray39 = int(Z'636363', kind=c_int32_t) + integer(c_int32_t) :: Grey39 = int(Z'636363', kind=c_int32_t) + integer(c_int32_t) :: Gray40 = int(Z'666666', kind=c_int32_t) + integer(c_int32_t) :: Grey40 = int(Z'666666', kind=c_int32_t) + integer(c_int32_t) :: Gray41 = int(Z'696969', kind=c_int32_t) + integer(c_int32_t) :: Grey41 = int(Z'696969', kind=c_int32_t) + integer(c_int32_t) :: Gray42 = int(Z'6b6b6b', kind=c_int32_t) + integer(c_int32_t) :: Grey42 = int(Z'6b6b6b', kind=c_int32_t) + integer(c_int32_t) :: Gray43 = int(Z'6e6e6e', kind=c_int32_t) + integer(c_int32_t) :: Grey43 = int(Z'6e6e6e', kind=c_int32_t) + integer(c_int32_t) :: Gray44 = int(Z'707070', kind=c_int32_t) + integer(c_int32_t) :: Grey44 = int(Z'707070', kind=c_int32_t) + integer(c_int32_t) :: Gray45 = int(Z'737373', kind=c_int32_t) + integer(c_int32_t) :: Grey45 = int(Z'737373', kind=c_int32_t) + integer(c_int32_t) :: Gray46 = int(Z'757575', kind=c_int32_t) + integer(c_int32_t) :: Grey46 = int(Z'757575', kind=c_int32_t) + integer(c_int32_t) :: Gray47 = int(Z'787878', kind=c_int32_t) + integer(c_int32_t) :: Grey47 = int(Z'787878', kind=c_int32_t) + integer(c_int32_t) :: Gray48 = int(Z'7a7a7a', kind=c_int32_t) + integer(c_int32_t) :: Grey48 = int(Z'7a7a7a', kind=c_int32_t) + integer(c_int32_t) :: Gray49 = int(Z'7d7d7d', kind=c_int32_t) + integer(c_int32_t) :: Grey49 = int(Z'7d7d7d', kind=c_int32_t) + integer(c_int32_t) :: Gray50 = int(Z'7f7f7f', kind=c_int32_t) + integer(c_int32_t) :: Grey50 = int(Z'7f7f7f', kind=c_int32_t) + integer(c_int32_t) :: Gray51 = int(Z'828282', kind=c_int32_t) + integer(c_int32_t) :: Grey51 = int(Z'828282', kind=c_int32_t) + integer(c_int32_t) :: Gray52 = int(Z'858585', kind=c_int32_t) + integer(c_int32_t) :: Grey52 = int(Z'858585', kind=c_int32_t) + integer(c_int32_t) :: Gray53 = int(Z'878787', kind=c_int32_t) + integer(c_int32_t) :: Grey53 = int(Z'878787', kind=c_int32_t) + integer(c_int32_t) :: Gray54 = int(Z'8a8a8a', kind=c_int32_t) + integer(c_int32_t) :: Grey54 = int(Z'8a8a8a', kind=c_int32_t) + integer(c_int32_t) :: Gray55 = int(Z'8c8c8c', kind=c_int32_t) + integer(c_int32_t) :: Grey55 = int(Z'8c8c8c', kind=c_int32_t) + integer(c_int32_t) :: Gray56 = int(Z'8f8f8f', kind=c_int32_t) + integer(c_int32_t) :: Grey56 = int(Z'8f8f8f', kind=c_int32_t) + integer(c_int32_t) :: Gray57 = int(Z'919191', kind=c_int32_t) + integer(c_int32_t) :: Grey57 = int(Z'919191', kind=c_int32_t) + integer(c_int32_t) :: Gray58 = int(Z'949494', kind=c_int32_t) + integer(c_int32_t) :: Grey58 = int(Z'949494', kind=c_int32_t) + integer(c_int32_t) :: Gray59 = int(Z'969696', kind=c_int32_t) + integer(c_int32_t) :: Grey59 = int(Z'969696', kind=c_int32_t) + integer(c_int32_t) :: Gray60 = int(Z'999999', kind=c_int32_t) + integer(c_int32_t) :: Grey60 = int(Z'999999', kind=c_int32_t) + integer(c_int32_t) :: Gray61 = int(Z'9c9c9c', kind=c_int32_t) + integer(c_int32_t) :: Grey61 = int(Z'9c9c9c', kind=c_int32_t) + integer(c_int32_t) :: Gray62 = int(Z'9e9e9e', kind=c_int32_t) + integer(c_int32_t) :: Grey62 = int(Z'9e9e9e', kind=c_int32_t) + integer(c_int32_t) :: Gray63 = int(Z'a1a1a1', kind=c_int32_t) + integer(c_int32_t) :: Grey63 = int(Z'a1a1a1', kind=c_int32_t) + integer(c_int32_t) :: Gray64 = int(Z'a3a3a3', kind=c_int32_t) + integer(c_int32_t) :: Grey64 = int(Z'a3a3a3', kind=c_int32_t) + integer(c_int32_t) :: Gray65 = int(Z'a6a6a6', kind=c_int32_t) + integer(c_int32_t) :: Grey65 = int(Z'a6a6a6', kind=c_int32_t) + integer(c_int32_t) :: Gray66 = int(Z'a8a8a8', kind=c_int32_t) + integer(c_int32_t) :: Grey66 = int(Z'a8a8a8', kind=c_int32_t) + integer(c_int32_t) :: Gray67 = int(Z'ababab', kind=c_int32_t) + integer(c_int32_t) :: Grey67 = int(Z'ababab', kind=c_int32_t) + integer(c_int32_t) :: Gray68 = int(Z'adadad', kind=c_int32_t) + integer(c_int32_t) :: Grey68 = int(Z'adadad', kind=c_int32_t) + integer(c_int32_t) :: Gray69 = int(Z'b0b0b0', kind=c_int32_t) + integer(c_int32_t) :: Grey69 = int(Z'b0b0b0', kind=c_int32_t) + integer(c_int32_t) :: Gray70 = int(Z'b3b3b3', kind=c_int32_t) + integer(c_int32_t) :: Grey70 = int(Z'b3b3b3', kind=c_int32_t) + integer(c_int32_t) :: Gray71 = int(Z'b5b5b5', kind=c_int32_t) + integer(c_int32_t) :: Grey71 = int(Z'b5b5b5', kind=c_int32_t) + integer(c_int32_t) :: Gray72 = int(Z'b8b8b8', kind=c_int32_t) + integer(c_int32_t) :: Grey72 = int(Z'b8b8b8', kind=c_int32_t) + integer(c_int32_t) :: Gray73 = int(Z'bababa', kind=c_int32_t) + integer(c_int32_t) :: Grey73 = int(Z'bababa', kind=c_int32_t) + integer(c_int32_t) :: Gray74 = int(Z'bdbdbd', kind=c_int32_t) + integer(c_int32_t) :: Grey74 = int(Z'bdbdbd', kind=c_int32_t) + integer(c_int32_t) :: Gray75 = int(Z'bfbfbf', kind=c_int32_t) + integer(c_int32_t) :: Grey75 = int(Z'bfbfbf', kind=c_int32_t) + integer(c_int32_t) :: Gray76 = int(Z'c2c2c2', kind=c_int32_t) + integer(c_int32_t) :: Grey76 = int(Z'c2c2c2', kind=c_int32_t) + integer(c_int32_t) :: Gray77 = int(Z'c4c4c4', kind=c_int32_t) + integer(c_int32_t) :: Grey77 = int(Z'c4c4c4', kind=c_int32_t) + integer(c_int32_t) :: Gray78 = int(Z'c7c7c7', kind=c_int32_t) + integer(c_int32_t) :: Grey78 = int(Z'c7c7c7', kind=c_int32_t) + integer(c_int32_t) :: Gray79 = int(Z'c9c9c9', kind=c_int32_t) + integer(c_int32_t) :: Grey79 = int(Z'c9c9c9', kind=c_int32_t) + integer(c_int32_t) :: Gray80 = int(Z'cccccc', kind=c_int32_t) + integer(c_int32_t) :: Grey80 = int(Z'cccccc', kind=c_int32_t) + integer(c_int32_t) :: Gray81 = int(Z'cfcfcf', kind=c_int32_t) + integer(c_int32_t) :: Grey81 = int(Z'cfcfcf', kind=c_int32_t) + integer(c_int32_t) :: Gray82 = int(Z'd1d1d1', kind=c_int32_t) + integer(c_int32_t) :: Grey82 = int(Z'd1d1d1', kind=c_int32_t) + integer(c_int32_t) :: Gray83 = int(Z'd4d4d4', kind=c_int32_t) + integer(c_int32_t) :: Grey83 = int(Z'd4d4d4', kind=c_int32_t) + integer(c_int32_t) :: Gray84 = int(Z'd6d6d6', kind=c_int32_t) + integer(c_int32_t) :: Grey84 = int(Z'd6d6d6', kind=c_int32_t) + integer(c_int32_t) :: Gray85 = int(Z'd9d9d9', kind=c_int32_t) + integer(c_int32_t) :: Grey85 = int(Z'd9d9d9', kind=c_int32_t) + integer(c_int32_t) :: Gray86 = int(Z'dbdbdb', kind=c_int32_t) + integer(c_int32_t) :: Grey86 = int(Z'dbdbdb', kind=c_int32_t) + integer(c_int32_t) :: Gray87 = int(Z'dedede', kind=c_int32_t) + integer(c_int32_t) :: Grey87 = int(Z'dedede', kind=c_int32_t) + integer(c_int32_t) :: Gray88 = int(Z'e0e0e0', kind=c_int32_t) + integer(c_int32_t) :: Grey88 = int(Z'e0e0e0', kind=c_int32_t) + integer(c_int32_t) :: Gray89 = int(Z'e3e3e3', kind=c_int32_t) + integer(c_int32_t) :: Grey89 = int(Z'e3e3e3', kind=c_int32_t) + integer(c_int32_t) :: Gray90 = int(Z'e5e5e5', kind=c_int32_t) + integer(c_int32_t) :: Grey90 = int(Z'e5e5e5', kind=c_int32_t) + integer(c_int32_t) :: Gray91 = int(Z'e8e8e8', kind=c_int32_t) + integer(c_int32_t) :: Grey91 = int(Z'e8e8e8', kind=c_int32_t) + integer(c_int32_t) :: Gray92 = int(Z'ebebeb', kind=c_int32_t) + integer(c_int32_t) :: Grey92 = int(Z'ebebeb', kind=c_int32_t) + integer(c_int32_t) :: Gray93 = int(Z'ededed', kind=c_int32_t) + integer(c_int32_t) :: Grey93 = int(Z'ededed', kind=c_int32_t) + integer(c_int32_t) :: Gray94 = int(Z'f0f0f0', kind=c_int32_t) + integer(c_int32_t) :: Grey94 = int(Z'f0f0f0', kind=c_int32_t) + integer(c_int32_t) :: Gray95 = int(Z'f2f2f2', kind=c_int32_t) + integer(c_int32_t) :: Grey95 = int(Z'f2f2f2', kind=c_int32_t) + integer(c_int32_t) :: Gray96 = int(Z'f5f5f5', kind=c_int32_t) + integer(c_int32_t) :: Grey96 = int(Z'f5f5f5', kind=c_int32_t) + integer(c_int32_t) :: Gray97 = int(Z'f7f7f7', kind=c_int32_t) + integer(c_int32_t) :: Grey97 = int(Z'f7f7f7', kind=c_int32_t) + integer(c_int32_t) :: Gray98 = int(Z'fafafa', kind=c_int32_t) + integer(c_int32_t) :: Grey98 = int(Z'fafafa', kind=c_int32_t) + integer(c_int32_t) :: Gray99 = int(Z'fcfcfc', kind=c_int32_t) + integer(c_int32_t) :: Grey99 = int(Z'fcfcfc', kind=c_int32_t) + integer(c_int32_t) :: Gray100 = int(Z'ffffff', kind=c_int32_t) + integer(c_int32_t) :: Grey100 = int(Z'ffffff', kind=c_int32_t) + integer(c_int32_t) :: DarkGrey = int(Z'a9a9a9', kind=c_int32_t) + integer(c_int32_t) :: DarkGray = int(Z'a9a9a9', kind=c_int32_t) + integer(c_int32_t) :: DarkBlue = int(Z'00008b', kind=c_int32_t) + integer(c_int32_t) :: DarkCyan = int(Z'008b8b', kind=c_int32_t) + integer(c_int32_t) :: DarkMagenta = int(Z'8b008b', kind=c_int32_t) + integer(c_int32_t) :: DarkRed = int(Z'8b0000', kind=c_int32_t) + integer(c_int32_t) :: LightGreen = int(Z'90ee90', kind=c_int32_t) + integer(c_int32_t) :: Crimson = int(Z'dc143c', kind=c_int32_t) + integer(c_int32_t) :: Indigo = int(Z'4b0082', kind=c_int32_t) + integer(c_int32_t) :: Olive = int(Z'808000', kind=c_int32_t) + integer(c_int32_t) :: RebeccaPurple = int(Z'663399', kind=c_int32_t) + integer(c_int32_t) :: Silver = int(Z'c0c0c0', kind=c_int32_t) + integer(c_int32_t) :: Teal = int(Z'008080', kind=c_int32_t) + end type + + interface + subroutine impl_tracy_set_thread_name(name) bind(C, name="___tracy_set_thread_name") + import + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_set_thread_name + end interface + + type, bind(C) :: tracy_source_location_data + type(c_ptr) :: name + type(c_ptr) :: function + type(c_ptr) :: file + integer(c_int32_t) :: line + integer(c_int32_t) :: color + end type + + type, bind(C) :: tracy_zone_context + integer(c_int32_t) :: id + integer(c_int32_t) :: active + end type + + type, bind(C) :: tracy_gpu_time_data + integer(c_int64_t) :: gpuTime + integer(c_int16_t) :: queryId + integer(c_int8_t) :: context + end type + + type, bind(C) :: tracy_gpu_zone_begin_data + integer(c_int64_t) :: srcloc + integer(c_int16_t) :: queryId + integer(c_int8_t) :: context + end type + + type, bind(C) :: tracy_gpu_zone_begin_callstack_data + integer(c_int64_t) :: srcloc + integer(c_int32_t) :: depth + integer(c_int16_t) :: queryId + integer(c_int8_t) :: context + end type + + type, bind(C) :: tracy_gpu_zone_end_data + integer(c_int16_t) :: queryId + integer(c_int8_t) :: context + end type + + type, bind(C) :: tracy_gpu_new_context_data + integer(c_int64_t) :: gpuTime + real(c_float) :: period + integer(c_int8_t) :: context + integer(c_int8_t) :: flags + integer(c_int8_t) :: type + end type + + type, bind(C) :: tracy_gpu_context_name_data + integer(c_int8_t) :: context + type(c_ptr) :: name + integer(c_int16_t) :: len + end type + + type, bind(C) :: tracy_gpu_calibration_data + integer(c_int64_t) :: gpuTime + integer(c_int64_t) :: cpuDelta + integer(c_int8_t) :: context + end type + + type, bind(C) :: tracy_gpu_time_sync_data + integer(c_int64_t) :: gpuTime + integer(c_int8_t) :: context + end type + + ! tracy_lockable_context_data and related stuff is missed since Fortran does not have support of mutexes + + interface + subroutine tracy_startup_profiler() bind(C, name="___tracy_startup_profiler") + end subroutine tracy_startup_profiler + subroutine tracy_shutdown_profiler() bind(C, name="___tracy_shutdown_profiler") + end subroutine tracy_shutdown_profiler + function impl_tracy_profiler_started() bind(C, name="___tracy_profiler_started") + import + integer(c_int32_t) :: impl_tracy_profiler_started + end function impl_tracy_profiler_started + end interface + + interface + function impl_tracy_alloc_srcloc(line, source, sourceSz, function_name, functionSz, color) & + bind(C, name="___tracy_alloc_srcloc") + import + integer(c_int64_t) :: impl_tracy_alloc_srcloc + integer(c_int32_t), intent(in), value :: line + type(c_ptr), intent(in), value :: source + integer(c_size_t), intent(in), value :: sourceSz + type(c_ptr), intent(in), value :: function_name + integer(c_size_t), intent(in), value :: functionSz + integer(c_int32_t), intent(in), value :: color + end function impl_tracy_alloc_srcloc + function impl_tracy_alloc_srcloc_name(line, source, sourceSz, function_name, functionSz, zone_name, nameSz, color) & + bind(C, name="___tracy_alloc_srcloc_name") + import + integer(c_int64_t) :: impl_tracy_alloc_srcloc_name + integer(c_int32_t), intent(in), value :: line + type(c_ptr), intent(in), value :: source + integer(c_size_t), intent(in), value :: sourceSz + type(c_ptr), intent(in), value :: function_name + integer(c_size_t), intent(in), value :: functionSz + type(c_ptr), intent(in), value :: zone_name + integer(c_size_t), intent(in), value :: nameSz + integer(c_int32_t), intent(in), value :: color + end function impl_tracy_alloc_srcloc_name + end interface + + interface + type(tracy_zone_context) function impl_tracy_emit_zone_begin_callstack(srcloc, depth, active) & + bind(C, name="___tracy_emit_zone_begin_callstack") + import + type(tracy_source_location_data), intent(in) :: srcloc + integer(c_int32_t), intent(in), value :: depth + integer(c_int32_t), intent(in), value :: active + end function impl_tracy_emit_zone_begin_callstack + type(tracy_zone_context) function impl_tracy_emit_zone_begin_alloc_callstack(srcloc, depth, active) & + bind(C, name="___tracy_emit_zone_begin_alloc_callstack") + import + integer(c_int64_t), intent(in), value :: srcloc + integer(c_int32_t), intent(in), value :: depth + integer(c_int32_t), intent(in), value :: active + end function impl_tracy_emit_zone_begin_alloc_callstack + end interface + interface tracy_zone_begin + module procedure tracy_emit_zone_begin_id, tracy_emit_zone_begin_type + end interface tracy_zone_begin + + interface + subroutine tracy_zone_end(ctx) bind(C, name="___tracy_emit_zone_end") + import + type(tracy_zone_context), intent(in), value :: ctx + end subroutine tracy_zone_end + end interface + + interface + subroutine tracy_emit_zone_text(ctx, txt, size) bind(C, name="___tracy_emit_zone_text") + import + type(tracy_zone_context), intent(in), value :: ctx + type(c_ptr), intent(in), value :: txt + integer(c_size_t), intent(in), value :: size + end subroutine tracy_emit_zone_text + subroutine tracy_emit_zone_name(ctx, txt, size) bind(C, name="___tracy_emit_zone_name") + import + type(tracy_zone_context), intent(in), value :: ctx + type(c_ptr), intent(in), value :: txt + integer(c_size_t), intent(in), value :: size + end subroutine tracy_emit_zone_name + subroutine tracy_emit_zone_color(ctx, color) bind(C, name="___tracy_emit_zone_color") + import + type(tracy_zone_context), intent(in), value :: ctx + integer(c_int32_t), intent(in), value :: color + end subroutine tracy_emit_zone_color + subroutine tracy_emit_zone_value(ctx, value) bind(C, name="___tracy_emit_zone_value") + import + type(tracy_zone_context), intent(in), value :: ctx + integer(c_int64_t), intent(in), value :: value + end subroutine tracy_emit_zone_value + end interface + + ! GPU is not supported yet + + interface + function impl_tracy_connected() bind(C, name="___tracy_connected") + import + integer(c_int32_t) :: impl_tracy_connected + end function impl_tracy_connected + end interface + + interface + subroutine impl_tracy_emit_memory_alloc_callstack(ptr, size, depth) & + bind(C, name="___tracy_emit_memory_alloc_callstack") + import + type(c_ptr), intent(in), value :: ptr + integer(c_size_t), intent(in), value :: size + integer(c_int32_t), intent(in), value :: depth + end subroutine impl_tracy_emit_memory_alloc_callstack + subroutine impl_tracy_emit_memory_alloc_callstack_named(ptr, size, depth, name) & + bind(C, name="___tracy_emit_memory_alloc_callstack_named") + import + type(c_ptr), intent(in), value :: ptr + integer(c_size_t), intent(in), value :: size + integer(c_int32_t), intent(in), value :: depth + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_emit_memory_alloc_callstack_named + subroutine impl_tracy_emit_memory_free_callstack(ptr, depth) & + bind(C, name="___tracy_emit_memory_free_callstack") + import + type(c_ptr), intent(in), value :: ptr + integer(c_int32_t), intent(in), value :: depth + end subroutine impl_tracy_emit_memory_free_callstack + subroutine impl_tracy_emit_memory_free_callstack_named(ptr, depth, name) & + bind(C, name="___tracy_emit_memory_free_callstack_named") + import + type(c_ptr), intent(in), value :: ptr + integer(c_int32_t), intent(in), value :: depth + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_emit_memory_free_callstack_named + subroutine impl_tracy_emit_memory_discard_callstack(name, depth) & + bind(C, name="___tracy_emit_memory_discard_callstack") + import + type(c_ptr), intent(in), value :: name + integer(c_int32_t), intent(in), value :: depth + end subroutine impl_tracy_emit_memory_discard_callstack + end interface + + interface + subroutine impl_tracy_emit_message(txt, size, depth) & + bind(C, name="___tracy_emit_message") + import + type(c_ptr), intent(in), value :: txt + integer(c_size_t), value :: size + integer(c_int32_t), value :: depth + end subroutine impl_tracy_emit_message + subroutine impl_tracy_emit_messageC(txt, size, color, depth) & + bind(C, name="___tracy_emit_messageC") + import + type(c_ptr), intent(in), value :: txt + integer(c_size_t), value :: size + integer(c_int32_t), value :: color + integer(c_int32_t), value :: depth + end subroutine impl_tracy_emit_messageC + subroutine impl_tracy_emit_message_appinfo(txt, size) & + bind(C, name="___tracy_emit_message_appinfo") + import + type(c_ptr), intent(in), value :: txt + integer(c_size_t), value :: size + end subroutine impl_tracy_emit_message_appinfo + end interface + + interface + subroutine impl_tracy_emit_frame_mark(name) & + bind(C, name="___tracy_emit_frame_mark") + import + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_emit_frame_mark + subroutine impl_tracy_emit_frame_mark_start(name) & + bind(C, name="___tracy_emit_frame_mark_start") + import + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_emit_frame_mark_start + subroutine impl_tracy_emit_frame_mark_end(name) & + bind(C, name="___tracy_emit_frame_mark_end") + import + type(c_ptr), intent(in), value :: name + end subroutine impl_tracy_emit_frame_mark_end + end interface + + interface + subroutine impl_tracy_emit_frame_image(image, w, h, offset, flip) & + bind(C, name="___tracy_emit_frame_image") + import + type(c_ptr), intent(in), value :: image + integer(c_int16_t), intent(in), value :: w + integer(c_int16_t), intent(in), value :: h + integer(c_int8_t), intent(in), value :: offset + integer(c_int32_t), intent(in), value :: flip + end subroutine impl_tracy_emit_frame_image + end interface + + interface + subroutine impl_tracy_emit_plot_int8(name, val) & + bind(C, name="___tracy_emit_plot_int") + import + type(c_ptr), intent(in), value :: name + integer(c_int64_t), value :: val + end subroutine impl_tracy_emit_plot_int8 + subroutine impl_tracy_emit_plot_real4(name, val) & + bind(C, name="___tracy_emit_plot_float") + import + type(c_ptr), intent(in), value :: name + real(c_float), value :: val + end subroutine impl_tracy_emit_plot_real4 + subroutine impl_tracy_emit_plot_real8(name, val) & + bind(C, name="___tracy_emit_plot") + import + type(c_ptr), intent(in), value :: name + real(c_double), value :: val + end subroutine impl_tracy_emit_plot_real8 + end interface + interface tracy_plot + module procedure tracy_plot_int8, tracy_plot_real4, tracy_plot_real8 + end interface tracy_plot + interface + subroutine impl_tracy_emit_plot_config(name, type, step, fill, color) & + bind(C, name="___tracy_emit_plot_config") + import + type(c_ptr), intent(in), value :: name + integer(c_int32_t), intent(in), value :: type + integer(c_int32_t), intent(in), value :: step + integer(c_int32_t), intent(in), value :: fill + integer(c_int32_t), intent(in), value :: color + end subroutine impl_tracy_emit_plot_config + end interface + +#ifdef TRACY_FIBERS + interface + subroutine impl_tracy_fiber_enter(fiber_name) & + bind(C, name="___tracy_fiber_enter") + import + type(c_ptr), intent(in), value :: fiber_name + end subroutine impl_tracy_fiber_enter + subroutine tracy_fiber_leave() & + bind(C, name="___tracy_fiber_leave") + end subroutine tracy_fiber_leave + end interface +#endif + ! + public :: tracy_zone_context + public :: tracy_source_location_data + ! +#ifndef __SUNPRO_F90 + type(TracyColors_t), public, parameter :: TracyColors = TracyColors_t() +#endif + ! + public :: tracy_set_thread_name + public :: tracy_startup_profiler, tracy_shutdown_profiler, tracy_profiler_started + public :: tracy_connected + public :: tracy_appinfo + public :: tracy_alloc_srcloc + public :: tracy_zone_begin, tracy_zone_end + public :: tracy_zone_set_properties + public :: tracy_frame_mark, tracy_frame_start, tracy_frame_end + public :: tracy_memory_alloc, tracy_memory_free, tracy_memory_discard + public :: tracy_message + public :: tracy_image + public :: tracy_plot_config, tracy_plot +#ifdef TRACY_FIBERS + public :: tracy_fiber_enter, tracy_fiber_leave +#endif +contains + subroutine tracy_set_thread_name(name) + character(kind=c_char, len=*), intent(in) :: name + character(kind=c_char, len=:), allocatable, target :: alloc_name + allocate (character(kind=c_char, len=len(name) + 1) :: alloc_name) + alloc_name = name//c_null_char + call impl_tracy_set_thread_name(c_loc(alloc_name)) + end subroutine tracy_set_thread_name + + logical(1) function tracy_profiler_started() + tracy_profiler_started = impl_tracy_profiler_started() /= 0_c_int + end function tracy_profiler_started + + integer(c_int64_t) function tracy_alloc_srcloc(line, source, function_name, zone_name, color) + integer(c_int32_t), intent(in) :: line + character(kind=c_char, len=*), target, intent(in) :: source, function_name + character(kind=c_char, len=*), target, intent(in), optional :: zone_name + integer(c_int32_t), intent(in), optional :: color + ! + integer(c_int32_t) :: color_ + ! + color_ = 0_c_int32_t + if (present(color)) color_ = color + if (present(zone_name)) then + tracy_alloc_srcloc = impl_tracy_alloc_srcloc_name(line, & + c_loc(source), len(source, kind=c_size_t), & + c_loc(function_name), len(function_name, kind=c_size_t), & + c_loc(zone_name), len(zone_name, kind=c_size_t), & + color_) + else + tracy_alloc_srcloc = impl_tracy_alloc_srcloc(line, & + c_loc(source), len(source, kind=c_size_t), & + c_loc(function_name), len(function_name, kind=c_size_t), & + color_) + end if + end function tracy_alloc_srcloc + + type(tracy_zone_context) function tracy_emit_zone_begin_id(srcloc, depth, active) + integer(c_int64_t), intent(inout) :: srcloc + integer(c_int32_t), intent(in), optional :: depth + logical(1), intent(in), optional :: active + ! + integer(c_int32_t) :: depth_ + integer(c_int32_t) :: active_ + active_ = 1_c_int32_t + depth_ = 0_c_int32_t + if (present(active)) then + if (active) then + active_ = 1_c_int32_t + else + active_ = 0_c_int32_t + end if + end if + if (present(depth)) depth_ = depth + tracy_emit_zone_begin_id = impl_tracy_emit_zone_begin_alloc_callstack(srcloc, depth_, active_) + srcloc = 0_c_int64_t + end function tracy_emit_zone_begin_id + type(tracy_zone_context) function tracy_emit_zone_begin_type(srcloc, depth, active) + type(tracy_source_location_data), intent(inout) :: srcloc + integer(c_int32_t), intent(in), optional :: depth + logical(1), intent(in), optional :: active + ! + integer(c_int32_t) :: depth_ + integer(c_int32_t) :: active_ + active_ = 1_c_int32_t + depth_ = 0_c_int32_t + if (present(active)) then + if (active) then + active_ = 1_c_int32_t + else + active_ = 0_c_int32_t + end if + end if + if (present(depth)) depth_ = depth + tracy_emit_zone_begin_type = impl_tracy_emit_zone_begin_callstack(srcloc, depth_, active_) + srcloc = tracy_source_location_data(c_null_ptr, c_null_ptr, c_null_ptr, 0_c_int32_t, 0_c_int32_t) + end function tracy_emit_zone_begin_type + + subroutine tracy_zone_set_properties(ctx, text, name, color, value) + type(tracy_zone_context), intent(in), value :: ctx + character(kind=c_char, len=*), target, intent(in), optional :: text + character(kind=c_char, len=*), target, intent(in), optional :: name + integer(c_int32_t), target, intent(in), optional :: color + integer(c_int64_t), target, intent(in), optional :: value + if (present(text)) then + call tracy_emit_zone_text(ctx, c_loc(text), len(text, kind=c_size_t)) + end if + if (present(name)) then + call tracy_emit_zone_name(ctx, c_loc(name), len(name, kind=c_size_t)) + end if + if (present(color)) then + call tracy_emit_zone_color(ctx, color) + end if + if (present(value)) then + call tracy_emit_zone_value(ctx, value) + end if + end subroutine tracy_zone_set_properties + + logical(1) function tracy_connected() + tracy_connected = impl_tracy_connected() /= 0_c_int32_t + end function tracy_connected + + subroutine tracy_memory_alloc(ptr, size, name, depth) + type(c_ptr), intent(in) :: ptr + integer(c_size_t), intent(in) :: size + character(kind=c_char, len=*), target, intent(in), optional :: name + integer(c_int32_t), intent(in), optional :: depth + ! + integer(c_int32_t) :: depth_ + depth_ = 0_c_int32_t + if (present(depth)) depth_ = depth + if (present(name)) then + call impl_tracy_emit_memory_alloc_callstack_named(ptr, size, depth_, c_loc(name)) + else + call impl_tracy_emit_memory_alloc_callstack(ptr, size, depth_) + end if + end subroutine tracy_memory_alloc + subroutine tracy_memory_free(ptr, name, depth) + type(c_ptr), intent(in) :: ptr + character(kind=c_char, len=*), target, intent(in), optional :: name + integer(c_int32_t), intent(in), optional :: depth + ! + integer(c_int32_t) :: depth_ + depth_ = 0_c_int32_t + if (present(depth)) depth_ = depth + if (present(name)) then + call impl_tracy_emit_memory_free_callstack_named(ptr, depth_, c_loc(name)) + else + call impl_tracy_emit_memory_free_callstack(ptr, depth_) + end if + end subroutine tracy_memory_free + subroutine tracy_memory_discard(name, depth) + character(kind=c_char, len=*), target, intent(in) :: name + integer(c_int32_t), intent(in), optional :: depth + ! + integer(c_int32_t) :: depth_ + depth_ = 0_c_int32_t + if (present(depth)) depth_ = depth + call impl_tracy_emit_memory_discard_callstack(c_loc(name), depth_) + end subroutine tracy_memory_discard + + subroutine tracy_message(msg, color, depth) + character(kind=c_char, len=*), target, intent(in) :: msg + integer(c_int32_t), intent(in), optional :: color + integer(c_int32_t), intent(in), optional :: depth + ! + integer(c_int32_t) :: depth_ + depth_ = 0_c_int32_t + if (present(depth)) depth_ = depth + if (present(color)) then + call impl_tracy_emit_messageC(c_loc(msg), len(msg, kind=c_size_t), color, depth_) + else + call impl_tracy_emit_message(c_loc(msg), len(msg, kind=c_size_t), depth_) + end if + end subroutine tracy_message + + subroutine tracy_appinfo(info) + character(kind=c_char, len=*), target, intent(in) :: info + call impl_tracy_emit_message_appinfo(c_loc(info), len(info, kind=c_size_t)) + end subroutine tracy_appinfo + + subroutine tracy_frame_mark(name) + character(kind=c_char, len=*), target, intent(in), optional :: name + if (present(name)) then + call impl_tracy_emit_frame_mark(c_loc(name)) + else + call impl_tracy_emit_frame_mark(c_null_ptr) + end if + end subroutine tracy_frame_mark + subroutine tracy_frame_start(name) + character(kind=c_char, len=*), target, intent(in), optional :: name + if (present(name)) then + call impl_tracy_emit_frame_mark_start(c_loc(name)) + else + call impl_tracy_emit_frame_mark_start(c_null_ptr) + end if + end subroutine tracy_frame_start + subroutine tracy_frame_end(name) + character(kind=c_char, len=*), target, intent(in), optional :: name + if (present(name)) then + call impl_tracy_emit_frame_mark_end(c_loc(name)) + else + call impl_tracy_emit_frame_mark_end(c_null_ptr) + end if + end subroutine tracy_frame_end + + subroutine tracy_image(image, w, h, offset, flip) + type(c_ptr), intent(in) :: image + integer(c_int16_t), intent(in) :: w, h + integer(c_int8_t), intent(in), optional :: offset + logical(1), intent(in), optional :: flip + ! + integer(c_int32_t) :: flip_ + integer(c_int8_t) :: offset_ + flip_ = 0_c_int32_t + offset_ = 0_c_int8_t + if (present(flip)) then + if (flip) flip_ = 1_c_int32_t + end if + if (present(offset)) offset_ = offset + call impl_tracy_emit_frame_image(image, w, h, offset_, flip_) + end subroutine tracy_image + + subroutine tracy_plot_int8(name, val) + character(kind=c_char, len=*), target, intent(in) :: name + integer(c_int64_t) :: val + call impl_tracy_emit_plot_int8(c_loc(name), val) + end subroutine tracy_plot_int8 + subroutine tracy_plot_real4(name, val) + character(kind=c_char, len=*), target, intent(in) :: name + real(c_float) :: val + call impl_tracy_emit_plot_real4(c_loc(name), val) + end subroutine tracy_plot_real4 + subroutine tracy_plot_real8(name, val) + character(kind=c_char, len=*), target, intent(in) :: name + real(c_double) :: val + call impl_tracy_emit_plot_real8(c_loc(name), val) + end subroutine tracy_plot_real8 + + subroutine tracy_plot_config(name, type, step, fill, color) + character(kind=c_char, len=*), target, intent(in) :: name + integer(c_int32_t), intent(in), optional :: type + logical(1), intent(in), optional :: step + logical(1), intent(in), optional :: fill + integer(c_int32_t), intent(in), optional :: color + ! + integer(c_int32_t) :: type_, step_, fill_, color_ + type_ = 0_c_int32_t + step_ = 0_c_int32_t + fill_ = 1_c_int32_t + color_ = 0_c_int32_t + if (present(type)) type_ = type + if (present(step)) then + if (step) step_ = 1_c_int32_t + end if + if (present(fill)) then + if (.not. fill) fill_ = 0_c_int32_t + end if + if (present(color)) color_ = color + call impl_tracy_emit_plot_config(c_loc(name), type_, step_, fill_, color_) + end subroutine tracy_plot_config + +#ifdef TRACY_FIBERS + subroutine tracy_fiber_enter(fiber_name) + character(kind=c_char, len=*), target, intent(in) :: fiber_name + call impl_tracy_fiber_enter(c_loc(fiber_name)) + end subroutine tracy_fiber_enter +#endif +end module tracy diff --git a/libs/tracy/TracyClient.cpp b/libs/tracy/TracyClient.cpp index 8e66975..d814a89 100644 --- a/libs/tracy/TracyClient.cpp +++ b/libs/tracy/TracyClient.cpp @@ -26,7 +26,9 @@ #include "client/TracySysTime.cpp" #include "client/TracySysTrace.cpp" #include "common/TracySocket.cpp" +#ifndef TRACY_HAS_CUSTOM_ALLOCATOR #include "client/tracy_rpmalloc.cpp" +#endif #include "client/TracyDxt1.cpp" #include "client/TracyAlloc.cpp" #include "client/TracyOverride.cpp" diff --git a/libs/tracy/client/TracyAlloc.cpp b/libs/tracy/client/TracyAlloc.cpp index c675b6d..6acb19e 100644 --- a/libs/tracy/client/TracyAlloc.cpp +++ b/libs/tracy/client/TracyAlloc.cpp @@ -1,6 +1,6 @@ #include "../common/TracyAlloc.hpp" -#ifdef TRACY_USE_RPMALLOC +#if defined TRACY_USE_RPMALLOC || defined TRACY_HAS_CUSTOM_ALLOCATOR #include @@ -14,7 +14,7 @@ extern thread_local bool RpThreadInitDone; extern std::atomic RpInitDone; extern std::atomic RpInitLock; -tracy_no_inline static void InitRpmallocPlumbing() +tracy_no_inline static void InitAllocatorPlumbing() { const auto done = RpInitDone.load( std::memory_order_acquire ); if( !done ) @@ -24,18 +24,26 @@ tracy_no_inline static void InitRpmallocPlumbing() const auto done = RpInitDone.load( std::memory_order_acquire ); if( !done ) { +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + PlatformAllocatorInit(); +#else rpmalloc_initialize(); +#endif RpInitDone.store( 1, std::memory_order_release ); } RpInitLock.store( 0, std::memory_order_release ); } +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + PlatformAllocatorThreadInit(); +#else rpmalloc_thread_initialize(); +#endif RpThreadInitDone = true; } -TRACY_API void InitRpmalloc() +TRACY_API void InitAllocator() { - if( !RpThreadInitDone ) InitRpmallocPlumbing(); + if( !RpThreadInitDone ) InitAllocatorPlumbing(); } } diff --git a/libs/tracy/client/TracyArmCpuTable.hpp b/libs/tracy/client/TracyArmCpuTable.hpp index 2b47c3a..1090e47 100644 --- a/libs/tracy/client/TracyArmCpuTable.hpp +++ b/libs/tracy/client/TracyArmCpuTable.hpp @@ -26,6 +26,8 @@ static const char* DecodeArmImplementer( uint32_t v ) case 0x66: return "Faraday"; case 0x68: return "HXT"; case 0x69: return "Intel"; + case 0x6d: return "Microsoft"; + case 0x70: return "Phytium"; case 0xc0: return "Ampere Computing"; default: break; } @@ -90,20 +92,44 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0xd0e: return " Cortex-A76AE"; case 0xd0f: return " AEMv8"; case 0xd13: return " Cortex-R52"; + case 0xd14: return " Cortex-R82AE"; + case 0xd15: return " Cortex-R82"; + case 0xd16: return " Cortex-R52+"; case 0xd20: return " Cortex-M23"; case 0xd21: return " Cortex-M33"; case 0xd22: return " Cortex-M55"; + case 0xd23: return " Cortex-M85"; + case 0xd24: return " Cortex-M52"; case 0xd40: return " Neoverse V1"; case 0xd41: return " Cortex-A78"; case 0xd42: return " Cortex-A78AE"; case 0xd43: return " Cortex-A65AE"; case 0xd44: return " Cortex-X1"; + case 0xd46: return " Cortex-A510"; case 0xd47: return " Cortex-A710"; case 0xd48: return " Cortex-X2"; case 0xd49: return " Neoverse N2"; case 0xd4a: return " Neoverse E1"; case 0xd4b: return " Cortex-A78C"; case 0xd4c: return " Cortex-X1C"; + case 0xd4d: return " Cortex-A715"; + case 0xd4e: return " Cortex-X3"; + case 0xd4f: return " Neoverse-V2"; + case 0xd80: return " Cortex-A520"; + case 0xd81: return " Cortex-A720"; + case 0xd82: return " Cortex-X4"; + case 0xd83: return " Neoverse-V3AE"; + case 0xd84: return " Neoverse-V3"; + case 0xd85: return " Cortex-X925"; + case 0xd87: return " Cortex-A725"; + case 0xd88: return " Cortex-A520AE"; + case 0xd89: return " Cortex-A720AE"; + case 0xd8a: return " C1-Nano"; + case 0xd8b: return " C1-Pro"; + case 0xd8c: return " C1-Ultra"; + case 0xd8e: return " Neoverse-N3"; + case 0xd8f: return " Cortex-A320"; + case 0xd90: return " C1-Premium"; default: break; } case 0x42: // Broadcom @@ -143,13 +169,17 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) switch( part ) { case 0x1: return " A64FX"; + case 0x3: return " MONAKA"; default: break; } case 0x48: // HiSilicon switch( part ) { - case 0xd01: return " TSV100"; - case 0xd40: return " Kirin 980"; + case 0xd01: return " TaiShan-v110"; + case 0xd02: return " TaiShan-v120"; + case 0xd06: return " hip12"; + case 0xd40: return " Cortex-A76"; + case 0xd41: return " Cortex-A77"; default: break; } case 0x4e: // Nvidia @@ -158,6 +188,8 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0x0: return " Denver"; case 0x3: return " Denver 2"; case 0x4: return " Carmel"; + case 0x10: return " Olympus"; + case 0x11: return " Rigel"; default: break; } case 0x50: // Applied Micro @@ -169,6 +201,7 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0x51: // Qualcomm switch( part ) { + case 0x1: return " Oryon"; case 0xf: return " Scorpion"; case 0x2d: return " Scorpion"; case 0x4d: return " Krait"; @@ -213,6 +246,7 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0x61: // Apple switch( part ) { + case 0x0: return " Swift"; case 0x1: return " Cyclone"; case 0x2: return " Typhoon"; case 0x3: return " Typhoon/Capri"; @@ -220,12 +254,52 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0x5: return " Twister/Elba/Malta"; case 0x6: return " Hurricane"; case 0x7: return " Hurricane/Myst"; + case 0x8: return " Monsoon"; + case 0x9: return " Mistral"; + case 0xb: return " Vortex"; + case 0xc: return " Tempest"; + case 0xf: return " Tempest-M9"; + case 0x10: return " Vortex/Aruba"; + case 0x11: return " Tempest/Aruba"; + case 0x12: return " Lightning"; + case 0x13: return " Thunder"; + case 0x20: return " A14 Icestorm"; + case 0x21: return " A14 Firestorm"; case 0x22: return " M1 Icestorm"; case 0x23: return " M1 Firestorm"; case 0x24: return " M1 Icestorm Pro"; case 0x25: return " M1 Firestorm Pro"; + case 0x26: return " M10 Thunder"; case 0x28: return " M1 Icestorm Max"; case 0x29: return " M1 Firestorm Max"; + case 0x30: return " A15 Blizzard"; + case 0x31: return " A15 Avalanche"; + case 0x32: return " M2 Blizzard"; + case 0x33: return " M2 Avalanche"; + case 0x34: return " M2 Pro Blizzard"; + case 0x35: return " M2 Pro Avalanche"; + case 0x36: return " A16 Sawtooth"; + case 0x37: return " A16 Everest"; + case 0x38: return " M2 Max Blizzard"; + case 0x39: return " M2 Max Avalanche"; + case 0x42: return " M3"; + case 0x43: return " M3"; + case 0x44: return " M3"; + case 0x45: return " M3"; + case 0x48: return " M3"; + case 0x49: return " M3"; + case 0x52: return " M4"; + case 0x53: return " M4"; + case 0x54: return " M4"; + case 0x55: return " M4"; + case 0x58: return " M4"; + case 0x59: return " M4"; + case 0x62: return " M5"; + case 0x63: return " M5"; + case 0x64: return " M5"; + case 0x65: return " M5"; + case 0x68: return " M5"; + case 0x69: return " M5"; default: break; } case 0x66: // Faraday @@ -241,10 +315,55 @@ static const char* DecodeArmPart( uint32_t impl, uint32_t part ) case 0x0: return " Phecda"; default: break; } + case 0x69: // Intel + switch( part ) + { + case 0x200: return " i80200"; + case 0x210: return " PXA250A"; + case 0x212: return " PXA210A"; + case 0x242: return " i80321-400"; + case 0x243: return " i80321-600"; + case 0x290: return " PXA250B/PXA26x"; + case 0x292: return " PXA210B"; + case 0x2c2: return " i80321-400-B0"; + case 0x2c3: return " i80321-600-B0"; + case 0x2d0: return " PXA250C/PXA255/PXA26x"; + case 0x2d2: return " PXA210C"; + case 0x411: return " PXA27x"; + case 0x41c: return " IPX425-533"; + case 0x41d: return " IPX425-400"; + case 0x41f: return " IPX425-266"; + case 0x682: return " PXA32x"; + case 0x683: return " PXA930/PXA935"; + case 0x688: return " PXA30x"; + case 0x689: return " PXA31x"; + case 0xb11: return " SA1110"; + case 0xc12: return " IPX1200"; + default: break; + } + case 0x6d: // Microsoft + switch( part ) + { + case 0xd49: return " Azure-Cobalt-100"; + default: break; + } + case 0x70: // Phytium + switch( part ) + { + case 0x303: return " FTC310"; + case 0x660: return " FTC660"; + case 0x661: return " FTC661"; + case 0x662: return " FTC662"; + case 0x663: return " FTC663"; + case 0x664: return " FTC664"; + case 0x862: return " FTC862"; + default: break; + } case 0xc0: // Ampere Computing switch( part ) { - case 0xac3: return " Ampere1"; + case 0xac3: return " Ampere-1"; + case 0xac4: return " Ampere-1a"; default: break; } default: break; @@ -313,6 +432,16 @@ static const char* DecodeIosDevice( const char* id ) "iPhone15,5", "iPhone 15 Plus", "iPhone16,1", "iPhone 15 Pro", "iPhone16,2", "iPhone 15 Pro Max", + "iPhone17,1", "iPhone 16 Pro", + "iPhone17,2", "iPhone 16 Pro Max", + "iPhone17,3", "iPhone 16", + "iPhone17,4", "iPhone 16 Plus", + "iPhone17,5", "iPhone 16e", + "iPhone18,1", "iPhone 17 Pro", + "iPhone18,2", "iPhone 17 Pro Max", + "iPhone18,3", "iPhone 17", + "iPhone18,4", "iPhone Air", + "iPhone18,5", "iPhone 17e", "iPad1,1", "iPad (A1219/A1337)", "iPad2,1", "iPad 2 (A1395)", "iPad2,2", "iPad 2 (A1396)", @@ -387,14 +516,34 @@ static const char* DecodeIosDevice( const char* id ) "iPad13,11", "iPad Pro 12.9\" 5th gen", "iPad13,16", "iPad Air 5th Gen (WiFi)", "iPad13,17", "iPad Air 5th Gen (WiFi+Cellular)", - "iPad13,18", "iPad 10th Gen", - "iPad13,19", "iPad 10th Gen", + "iPad13,18", "iPad 10th Gen (WiFi)", + "iPad13,19", "iPad 10th Gen (WiFi+Cellular)", "iPad14,1", "iPad mini 6th Gen (WiFi)", "iPad14,2", "iPad mini 6th Gen (WiFi+Cellular)", - "iPad14,3", "iPad Pro 11\" 4th Gen", - "iPad14,4", "iPad Pro 11\" 4th Gen", - "iPad14,5", "iPad Pro 12.9\" 6th Gen", - "iPad14,6", "iPad Pro 12.9\" 6th Gen", + "iPad14,3", "iPad Pro 11\" 4th Gen (WiFi)", + "iPad14,4", "iPad Pro 11\" 4th Gen (WiFi+Cellular)", + "iPad14,5", "iPad Pro 12.9\" 6th Gen (WiFi)", + "iPad14,6", "iPad Pro 12.9\" 6th Gen (WiFi+Cellular)", + "iPad14,8", "iPad Air 11\" 6th Gen (WiFi)", + "iPad14,9", "iPad Air 11\" 6th Gen (WiFi+Cellular)", + "iPad14,10", "iPad Air 13\" 6th Gen (WiFi)", + "iPad14,11", "iPad Air 13\" 6th Gen (WiFi+Cellular)", + "iPad15,3", "iPad Air 11\" 7th Gen (WiFi)", + "iPad15,4", "iPad Air 11\" 7th Gen (WiFi+Cellular)", + "iPad15,5", "iPad Air 13\" 7th Gen (WiFi)", + "iPad15,6", "iPad Air 13\" 7th Gen (WiFi+Cellular)", + "iPad15,7", "iPad 11th Gen (WiFi)", + "iPad15,8", "iPad 11th Gen (WiFi+Cellular)", + "iPad16,1", "iPad mini 7th Gen (WiFi)", + "iPad16,2", "iPad mini 7th Gen (WiFi+Cellular)", + "iPad16,3", "iPad Pro 11\" 5th Gen (WiFi)", + "iPad16,4", "iPad Pro 11\" 5th Gen (WiFi+Cellular)", + "iPad16,5", "iPad Pro 12.9\" 7th Gen (WiFi)", + "iPad16,6", "iPad Pro 12.9\" 7th Gen (WiFi+Cellular)", + "iPad16,8", "iPad Air 11\" 8th Gen (WiFi)", + "iPad16,9", "iPad Air 11\" 8th Gen (WiFi+Cellular)", + "iPad16,10", "iPad Air 13\" 8th Gen (WiFi)", + "iPad16,11", "iPad Air 13\" 8th Gen (WiFi+Cellular)", "iPod1,1", "iPod Touch", "iPod2,1", "iPod Touch 2nd gen", "iPod3,1", "iPod Touch 3rd gen", diff --git a/libs/tracy/client/TracyCallstack.cpp b/libs/tracy/client/TracyCallstack.cpp index 7ab6b1c..e33181b 100644 --- a/libs/tracy/client/TracyCallstack.cpp +++ b/libs/tracy/client/TracyCallstack.cpp @@ -2,11 +2,14 @@ #include #include #include +#include #include "TracyCallstack.hpp" #include "TracyDebug.hpp" #include "TracyFastVector.hpp" #include "TracyStringHelpers.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" +#include "../common/TracyString.hpp" #include "../common/TracySystem.hpp" @@ -36,6 +39,10 @@ # include # include +# ifdef __linux__ +# include "TracyElf.hpp" +# endif + // Implementation files # include "../libbacktrace/alloc.cpp" # include "../libbacktrace/dwarf.cpp" @@ -232,9 +239,18 @@ class ImageCacheDlIteratePhdr : public ImageCache if( cache->ContainsImage( startAddress ) ) return 0; const uint32_t headerCount = info->dlpi_phnum; - assert( headerCount > 0); - const auto endAddress = static_cast( info->dlpi_addr + - info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); + TRACY_ASSERT( headerCount > 0 ); + + // headers aren't guaranteed to be in address order; find the max + uint64_t endAddress = startAddress; + for( uint32_t i=0; idlpi_phdr[i]; + if( phdr.p_type != PT_LOAD ) continue; + + const auto phdrEnd = static_cast( info->dlpi_addr + phdr.p_vaddr + phdr.p_memsz ); + endAddress = std::max( phdrEnd, endAddress ); + } ImageEntry image{}; image.m_startAddress = startAddress; @@ -279,9 +295,7 @@ class ImageCacheDlIteratePhdr : public ImageCache { if( dlInfo.dli_fname ) { - size_t sz = strlen( dlInfo.dli_fname ) + 1; - entry.m_name = (char*)tracy_malloc( sz ); - memcpy( entry.m_name, dlInfo.dli_fname, sz ); + entry.m_name = CopyString( dlInfo.dli_fname ); } } @@ -308,7 +322,7 @@ static ImageCache* s_krnlCache; void CreateImageCaches() { - assert( s_imageCache == nullptr && s_krnlCache == nullptr ); + TRACY_ASSERT( s_imageCache == nullptr && s_krnlCache == nullptr ); s_imageCache = new ( tracy_malloc( sizeof( UserlandImageCache ) ) ) UserlandImageCache(); s_krnlCache = new ( tracy_malloc( sizeof( ImageCache ) ) ) ImageCache(); } @@ -332,6 +346,434 @@ void DestroyImageCaches() } +#ifdef TRACY_HAS_EXTERNAL_TARGET +# include +# include +# include +# include +# include +# include + +static constexpr uint32_t ExtPT_LOAD = 1; + +struct ExternalImageEntry +{ + uint64_t startAddress; + uint64_t endAddress; + uint64_t loadBias; + uint64_t mapsOffset; + char* path; + backtrace_state* btState; + bool btAttempted; +}; + +static FastVector* s_extImages = nullptr; +static pid_t s_externalTargetPid = 0; +static char s_externalTargetName[64] = {}; +static uint64_t s_externalTargetExeMtime = 0; +// Wall-clock second of the last /proc//maps re-parse. Used to rate-limit +// refreshes so addresses that never resolve (JIT, vDSO, stack) do not trigger +// a full re-parse on every symbolization. +static int64_t s_lastMapsRefresh = 0; + +static int MakeExternalTargetPath( char* buf, size_t bufSize, const char* targetPath ) +{ + const int n = snprintf( buf, bufSize, "/proc/%d/root%s", (int)s_externalTargetPid, targetPath ); + return ( n < 0 || (size_t)n >= bufSize ) ? -1 : n; +} + +static int OpenExternalImageFile( const char* path, uint64_t mapStart, uint64_t mapEnd ) +{ + char rootPath[4096]; + if( MakeExternalTargetPath( rootPath, sizeof( rootPath ), path ) >= 0 ) + { + const int fd = open( rootPath, O_RDONLY ); + if( fd >= 0 ) return fd; + } + char mfPath[80]; + snprintf( mfPath, sizeof( mfPath ), "/proc/%d/map_files/%lx-%lx", (int)s_externalTargetPid, (unsigned long)mapStart, (unsigned long)mapEnd ); + return open( mfPath, O_RDONLY ); +} + +static uint64_t ReadElfMinLoadVaddr( const char* path, uint64_t mapStart, uint64_t mapEnd ) +{ + const int fd = OpenExternalImageFile( path, mapStart, mapEnd ); + if( fd < 0 ) return UINT64_MAX; + + elf_ehdr ehdr; + if( read( fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) ) + { + close( fd ); + return UINT64_MAX; + } + + if( ehdr.e_ident[0] != 0x7f || ehdr.e_ident[1] != 'E' || + ehdr.e_ident[2] != 'L' || ehdr.e_ident[3] != 'F' ) + { + close( fd ); + return UINT64_MAX; + } + + if( ehdr.e_phoff == 0 || ehdr.e_phnum == 0 ) + { + close( fd ); + return UINT64_MAX; + } + + if( lseek( fd, ehdr.e_phoff, SEEK_SET ) == (off_t)-1 ) + { + close( fd ); + return UINT64_MAX; + } + + uint64_t minVaddr = UINT64_MAX; + for( uint16_t i = 0; i < ehdr.e_phnum; i++ ) + { + elf_phdr phdr; + if( read( fd, &phdr, sizeof( phdr ) ) != sizeof( phdr ) ) break; + if( phdr.p_type == ExtPT_LOAD ) minVaddr = std::min( minVaddr, static_cast(phdr.p_vaddr) ); + } + + close( fd ); + return minVaddr; +} + +static uint64_t ReadElfSegmentLoadBias( const char* path, uint64_t start, uint64_t end, uint64_t offset, uint64_t pageSize ) +{ + const int fd = OpenExternalImageFile( path, start, end ); + if( fd < 0 ) return UINT64_MAX; + + elf_ehdr ehdr; + if( read( fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) || + ehdr.e_ident[0] != 0x7f || ehdr.e_ident[1] != 'E' || + ehdr.e_ident[2] != 'L' || ehdr.e_ident[3] != 'F' || + ehdr.e_ident[4] != 2 || + ehdr.e_phoff == 0 || ehdr.e_phnum == 0 ) + { + close( fd ); + return UINT64_MAX; + } + + if( lseek( fd, ehdr.e_phoff, SEEK_SET ) == (off_t)-1 ) + { + close( fd ); + return UINT64_MAX; + } + + uint64_t loadBias = UINT64_MAX; + for( uint16_t i = 0; i < ehdr.e_phnum; i++ ) + { + elf_phdr phdr; + if( read( fd, &phdr, sizeof( phdr ) ) != sizeof( phdr ) ) break; + if( phdr.p_type != ExtPT_LOAD ) continue; + const uint64_t vaddr = static_cast( phdr.p_vaddr ); + if( static_cast( phdr.p_offset ) - ( vaddr & ( pageSize - 1 ) ) == offset ) + { + loadBias = start - ( vaddr & ~( pageSize - 1 ) ); + break; + } + } + + close( fd ); + return loadBias; +} + +static void ParseExternalProcMaps( pid_t pid ) +{ + char mapPath[64]; + snprintf( mapPath, sizeof( mapPath ), "/proc/%d/maps", (int)pid ); + FILE* f = fopen( mapPath, "r" ); + if( !f ) return; + + FastVector fresh( 64 ); + + char line[1024]; + while( fgets( line, sizeof( line ), f ) ) + { + uint64_t start, end, offset; + uint32_t devMaj, devMin; + uint64_t inode; + char perms[8]; + int consumed = 0; + + if( sscanf( line, "%lx-%lx %7s %lx %x:%x %lu %n", &start, &end, perms, &offset, &devMaj, &devMin, &inode, &consumed ) < 7 ) continue; + if( !strchr( perms, 'x' ) ) continue; + + char* pathname = line + consumed; + while( *pathname == ' ' || *pathname == '\t' ) pathname++; + size_t plen = strlen( pathname ); + while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--; + if( plen >= 10 && strncmp( pathname + plen - 10, " (deleted)", 10 ) == 0 ) plen -= 10; + pathname[plen] = '\0'; + + if( plen == 0 || pathname[0] != '/' ) continue; + + // list is sorted by start address + auto it = std::lower_bound( s_extImages->begin(), s_extImages->end(), start, + []( const ExternalImageEntry& e, uint64_t a ) { return e.startAddress > a; } ); + if( it != s_extImages->end() && it->startAddress == start + && it->endAddress == end && it->mapsOffset == offset + && strcmp( it->path, pathname ) == 0 ) + { + fresh.push_next()[0] = *it; + continue; + } + + uint64_t pageSize = sysconf( _SC_PAGESIZE ); + uint64_t loadBias = ReadElfSegmentLoadBias( pathname, start, end, offset, pageSize ); + if( loadBias == UINT64_MAX ) + { + uint64_t minVaddr = ReadElfMinLoadVaddr( pathname, start, end ); + loadBias = ( minVaddr == UINT64_MAX ) ? start : start - ( minVaddr & ~( pageSize - 1 ) ) - offset; + } + + ExternalImageEntry entry = { + .startAddress = start, + .endAddress = end, + .loadBias = loadBias, + .mapsOffset = offset, + .path = (char*)tracy_malloc( plen + 1 ), + .btState = nullptr, + .btAttempted = false + }; + memcpy( entry.path, pathname, plen + 1 ); + fresh.push_next()[0] = entry; + } + + fclose( f ); + + std::sort( fresh.begin(), fresh.end(), + []( const ExternalImageEntry& a, const ExternalImageEntry& b ) { return a.startAddress > b.startAddress; } ); + s_extImages->swap( fresh ); +} + +static const ExternalImageEntry* FindExternalImage( uint64_t address ) +{ + if( !s_extImages || s_extImages->empty() ) return nullptr; + + auto it = std::lower_bound( s_extImages->begin(), s_extImages->end(), address, + []( const ExternalImageEntry& e, uint64_t a ) { return e.startAddress > a; } ); + + if( it != s_extImages->end() && address >= it->startAddress && address < it->endAddress ) + { + return &*it; + } + return nullptr; +} + +static const ExternalImageEntry* FindExternalImageRefresh( uint64_t address ) +{ + auto entry = FindExternalImage( address ); + if( entry ) return entry; + + if( s_externalTargetPid != 0 ) + { + const int64_t now = (int64_t)time( nullptr ); + if( now != s_lastMapsRefresh ) + { + s_lastMapsRefresh = now; + ParseExternalProcMaps( s_externalTargetPid ); + return FindExternalImage( address ); + } + } + return nullptr; +} + +static void ExternalBacktraceErrorCb( void* data, const char* msg, int errnum ) +{ +} + +static backtrace_state* GetExternalBtState( const ExternalImageEntry* entry ) +{ + auto* e = const_cast( entry ); + if( e->btAttempted ) return e->btState; + e->btAttempted = true; + const size_t rootPathSize = strlen( e->path ) + 32; + char* rootPath = (char*)tracy_malloc( rootPathSize ); + const char* statePath = nullptr; + char mfPath[80]; + if( MakeExternalTargetPath( rootPath, rootPathSize, e->path ) >= 0 ) + { + const int probe = open( rootPath, O_RDONLY ); + if( probe >= 0 ) + { + close( probe ); + statePath = rootPath; + } + } + if( !statePath ) + { + snprintf( mfPath, sizeof( mfPath ), "/proc/%d/map_files/%lx-%lx", (int)s_externalTargetPid, (unsigned long)e->startAddress, (unsigned long)e->endAddress ); + const int probe = open( mfPath, O_RDONLY ); + if( probe >= 0 ) + { + close( probe ); + statePath = mfPath; + } + } + if( statePath ) + { + e->btState = backtrace_create_state_for_file( statePath, 0, ExternalBacktraceErrorCb, nullptr ); + } + tracy_free( rootPath ); + return e->btState; +} + + +struct ExternalSymInfoData +{ + const char* symname; + uintptr_t symval; + uintptr_t symsize; +}; + +static void ExternalSymInfoCb( void* data, uintptr_t pc, const char* symname, uintptr_t symval, uintptr_t symsize ) +{ + auto& sd = *(ExternalSymInfoData*)data; + sd.symname = symname; + sd.symval = symval; + sd.symsize = symsize; +} + +bool InitExternalTarget( pid_t targetPid ) +{ + if( kill( targetPid, 0 ) != 0 ) + { + fprintf( stderr, "Tracy: cannot profile pid %d: %s\n", (int)targetPid, strerror( errno ) ); + return false; + } + + char path[64]; + snprintf( path, sizeof( path ), "/proc/%d/comm", (int)targetPid ); + FILE* f = fopen( path, "r" ); + if( !f ) + { + fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) ); + return false; + } + char comm[64] = {}; + if( !fgets( comm, sizeof( comm ), f ) ) + { + fclose( f ); + fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) ); + return false; + } + fclose( f ); + size_t len = strlen( comm ); + while( len > 0 && ( comm[len-1] == '\n' || comm[len-1] == '\r' ) ) len--; + if( len >= sizeof( s_externalTargetName ) ) len = sizeof( s_externalTargetName ) - 1; + memcpy( s_externalTargetName, comm, len ); + s_externalTargetName[len] = '\0'; + + snprintf( path, sizeof( path ), "/proc/%d/exe", (int)targetPid ); + { + const int exeFd = open( path, O_RDONLY ); + if( exeFd < 0 ) + { + fprintf( stderr, "Tracy: cannot read %s: %s\n", path, strerror( errno ) ); + return false; + } + struct stat exeSt; + if( fstat( exeFd, &exeSt ) == 0 ) s_externalTargetExeMtime = (uint64_t)exeSt.st_mtime; + close( exeFd ); + } + + s_externalTargetPid = targetPid; + if( !s_extImages ) + { + s_extImages = (FastVector*)tracy_malloc( sizeof( FastVector ) ); + new (s_extImages) FastVector( 64 ); + } + ParseExternalProcMaps( targetPid ); + return true; +} + +uint32_t GetExternalTargetPid() +{ + return (uint32_t)s_externalTargetPid; +} + +const char* GetExternalTargetName() +{ + return s_externalTargetName; +} + +uint64_t GetExternalTargetExeTime() +{ + return s_externalTargetExeMtime; +} + +static bool FindExternalMapping( pid_t pid, uint64_t addr, uint64_t& mapStart, uint64_t& mapEnd, uint64_t& fileOff, char* path, size_t pathSize ) +{ + char mapPath[64]; + snprintf( mapPath, sizeof( mapPath ), "/proc/%d/maps", (int)pid ); + FILE* f = fopen( mapPath, "r" ); + if( !f ) return false; + + bool found = false; + char line[1024]; + while( fgets( line, sizeof( line ), f ) ) + { + uint64_t start, end, offset; + uint32_t devMaj, devMin; + uint64_t inode; + char perms[8]; + int consumed = 0; + + if( sscanf( line, "%lx-%lx %7s %lx %x:%x %lu %n", &start, &end, perms, &offset, &devMaj, &devMin, &inode, &consumed ) < 7 ) continue; + if( !strchr( perms, 'x' ) ) continue; + if( addr < start || addr >= end ) continue; + + char* pathname = line + consumed; + while( *pathname == ' ' || *pathname == '\t' ) pathname++; + size_t plen = strlen( pathname ); + while( plen > 0 && ( pathname[plen-1] == '\n' || pathname[plen-1] == '\r' ) ) plen--; + pathname[plen] = '\0'; + if( plen >= 10 && strncmp( pathname + plen - 10, " (deleted)", 10 ) == 0 ) plen -= 10; + if( plen == 0 || pathname[0] != '/' ) continue; + if( plen >= pathSize ) plen = pathSize - 1; + memcpy( path, pathname, plen ); + path[plen] = '\0'; + + mapStart = start; + mapEnd = end; + fileOff = offset + ( addr - start ); + found = true; + break; + } + + fclose( f ); + return found; +} + +size_t ReadExternalTargetMemory( uint64_t addr, uint32_t size, char* buf ) +{ + const auto pid = (pid_t)GetExternalTargetPid(); + if( pid == 0 || size == 0 ) return 0; + + struct iovec local = { buf, size }; + struct iovec remote = { (void*)addr, size }; + if( process_vm_readv( pid, &local, 1, &remote, 1, 0 ) == (ssize_t)size ) return size; + + uint64_t mapStart = 0, mapEnd = 0, fileOff = 0; + char path[1024] = {}; + if( FindExternalMapping( pid, addr, mapStart, mapEnd, fileOff, path, sizeof( path ) ) && addr + size <= mapEnd ) + { + const int fd = OpenExternalImageFile( path, mapStart, mapEnd ); + if( fd >= 0 ) + { + const ssize_t rd = pread( fd, buf, size, (off_t)fileOff ); + close( fd ); + if( rd == (ssize_t)size ) return size; + } + } + + return 0; +} + +#endif // TRACY_HAS_EXTERNAL_TARGET + + // when "TRACY_SYMBOL_OFFLINE_RESOLVE" is set, instead of fully resolving symbols at runtime, // simply resolve the offset and image name (which will be enough the resolving to be done offline) #ifdef TRACY_SYMBOL_OFFLINE_RESOLVE @@ -347,8 +789,8 @@ bool ShouldResolveSymbolsOffline() #if TRACY_HAS_CALLSTACK == 1 -enum { MaxCbTrace = 64 }; -enum { MaxNameSize = 8*1024 }; +constexpr size_t MaxCbTrace = 64; +constexpr size_t MaxNameSize = 8*1024; int cb_num; CallstackEntry cb_data[MaxCbTrace]; @@ -378,6 +820,22 @@ void InitCallstackCritical() ___tracy_RtlWalkFrameChainPtr = (___tracy_t_RtlWalkFrameChain)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlWalkFrameChain" ); } +static void SymError( const char* function, DWORD code ) { + char message[1024] = {}; + int written = snprintf( message, sizeof( message ), "ERROR: %s FAILED with code %u (0x%x) | ", function, code, code ); + written += FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + code, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (LPSTR)&message[written], + sizeof(message) - written, + NULL + ); + fprintf( stderr, "%s\n", message ); + OutputDebugStringA( message ); +} + void DbgHelpInit() { if( s_shouldResolveSymbolsOffline ) return; @@ -392,8 +850,40 @@ void DbgHelpInit() DBGHELP_LOCK; #endif - SymInitialize( GetCurrentProcess(), nullptr, true ); - SymSetOptions( SYMOPT_LOAD_LINES ); + // append executable path to the _NT_SYMBOL_PATH environment variable + char buffer [32767]; // max env var length on Windows (including null-terminator) + DWORD length = GetEnvironmentVariableA( "_NT_SYMBOL_PATH", buffer, sizeof( buffer ) ); + if( length > sizeof( buffer ) ) SymError( "GetEnvironmentVariableA", GetLastError() ); + else if( length + 1 >= sizeof( buffer ) ) SymError( "_TracyAppendEnvironmentVariable", ERROR_INSUFFICIENT_BUFFER ); + else + { + buffer[length] = ';'; + buffer[++length] = '\0'; + length += GetModuleFileNameA( NULL, &buffer[length], sizeof( buffer ) - length ); + if( length >= sizeof( buffer ) && GetLastError() == ERROR_INSUFFICIENT_BUFFER ) + { + SymError( "GetModuleFileNameA", GetLastError() ); + } + else + { + while( length > 0 && buffer[--length] != '\\' ) + buffer[length] = '\0'; + } + } + + TRACY_ASSERT( length < sizeof( buffer ) ); + if( SetEnvironmentVariableA( "_NT_SYMBOL_PATH", buffer ) == FALSE ) SymError( "SetEnvironmentVariableA", GetLastError() ); + + SymSetOptions( SymGetOptions() | SYMOPT_LOAD_LINES ); + if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) == FALSE ) + { + SymError( "SymInitialize", GetLastError() ); + } + else if( GetModuleHandleA( "SymSrv.dll" ) == NULL ) + { + TracyDebug( "SymSrv.dll was not loaded, it needs to be near a matching version of DbgHelp.dll. Symbol resolution may fail as symbol servers will not be used. See https://learn.microsoft.com/en-us/windows/win32/debug/calling-the-dbghelp-library" ); + } + #ifdef TRACY_DBGHELP_LOCK DBGHELP_UNLOCK; @@ -532,7 +1022,7 @@ void InitCallstack() #endif //#ifndef TRACY_SYMBOL_OFFLINE_RESOLVE if( s_shouldResolveSymbolsOffline ) { - TracyDebug("TRACY: enabling offline symbol resolving!\n"); + TracyDebug( "TRACY: enabling offline symbol resolving!" ); } CreateImageCaches(); @@ -551,7 +1041,7 @@ void InitCallstack() const bool initTimeModuleLoad = !( noInitLoadEnv && noInitLoadEnv[0] == '1' ); if ( !initTimeModuleLoad ) { - TracyDebug("TRACY: skipping init time dbghelper module load\n"); + TracyDebug( "TRACY: skipping init time dbghelper module load" ); } else { @@ -601,7 +1091,7 @@ const char* DecodeCallstackPtrFast( uint64_t ptr ) const char* GetKernelModulePath( uint64_t addr ) { - assert( IsKernelAddress( addr ) ); + TRACY_ASSERT( IsKernelAddress( addr ) ); if( !s_krnlCache ) return nullptr; const ImageEntry* imageEntry = s_krnlCache->GetImageForAddress( addr ); if( imageEntry ) return imageEntry->m_path; @@ -632,7 +1122,7 @@ ModuleNameAndBaseAddress GetModuleNameAndPrepareSymbols( uint64_t addr ) constexpr DWORD flag = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; HMODULE mod = NULL; - InitRpmalloc(); + InitAllocator(); if( GetModuleHandleExA( flag, (char*)addr, &mod ) != 0 ) { MODULEINFO info; @@ -693,13 +1183,25 @@ CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) return sym; } +static CallstackEntryData MakeUnresolvedCallstackEntryData( uint64_t ptr, ModuleNameAndBaseAddress moduleNameAndBaseAddress ) +{ + cb_data[0].symAddr = ptr - moduleNameAndBaseAddress.baseAddr; + cb_data[0].symLen = 0; + + cb_data[0].name = CopyStringFast( "[unresolved]" ); + cb_data[0].file = CopyStringFast( "[unknown]" ); + cb_data[0].line = 0; + + return { cb_data, 1, moduleNameAndBaseAddress.name }; +} + CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { #ifdef TRACY_DBGHELP_LOCK DBGHELP_LOCK; #endif - InitRpmalloc(); + InitAllocator(); const ModuleNameAndBaseAddress moduleNameAndAddress = GetModuleNameAndPrepareSymbols( ptr ); @@ -708,15 +1210,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) #ifdef TRACY_DBGHELP_LOCK DBGHELP_UNLOCK; #endif - - cb_data[0].symAddr = ptr - moduleNameAndAddress.baseAddr; - cb_data[0].symLen = 0; - - cb_data[0].name = CopyStringFast("[unresolved]"); - cb_data[0].file = CopyStringFast("[unknown]"); - cb_data[0].line = 0; - - return { cb_data, 1, moduleNameAndAddress.name }; + return MakeUnresolvedCallstackEntryData( ptr, moduleNameAndAddress ); } int write; @@ -829,7 +1323,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) #elif defined(TRACY_USE_LIBBACKTRACE) -enum { MaxCbTrace = 64 }; +constexpr size_t MaxCbTrace = 64; struct backtrace_state* cb_bts = nullptr; @@ -855,7 +1349,7 @@ static FastVector* s_di_known; struct KernelSymbol { uint64_t addr; - uint32_t size; + uint64_t endAddr; const char* name; const char* mod; }; @@ -866,7 +1360,11 @@ size_t s_kernelSymCnt; static void InitKernelSymbols() { FILE* f = fopen( "/proc/kallsyms", "rb" ); - if( !f ) return; + if( !f ) + { + TracyDebug( "Failed to read /proc/kallsyms, kernel symbols will be unavailable." ); + return; + } tracy::FastVector tmpSym( 512 * 1024 ); size_t linelen = 16 * 1024; // linelen must be big enough to prevent reallocs in getline() auto linebuf = (char*)tracy_malloc( linelen ); @@ -895,9 +1393,9 @@ static void InitKernelSymbols() } else { - assert( false ); + TRACY_ASSERT( false ); } - assert( ( v & ~0xF ) == 0 ); + TRACY_ASSERT( ( v & ~0xF ) == 0 ); addr <<= 4; addr |= v; ptr++; @@ -926,21 +1424,17 @@ static void InitKernelSymbols() { validCnt++; - strname = (char*)tracy_malloc_fast( nameend - namestart + 1 ); - memcpy( strname, namestart, nameend - namestart ); - strname[nameend-namestart] = '\0'; + strname = CopyStringFast( namestart, nameend - namestart ); if( modstart ) { - strmod = (char*)tracy_malloc_fast( modend - modstart + 1 ); - memcpy( strmod, modstart, modend - modstart ); - strmod[modend-modstart] = '\0'; + strmod = CopyStringFast( modstart, modend - modstart ); } } auto sym = tmpSym.push_next(); sym->addr = addr; - sym->size = 0; + sym->endAddr = addr; sym->name = strname; sym->mod = strmod; } @@ -951,7 +1445,7 @@ static void InitKernelSymbols() std::sort( tmpSym.begin(), tmpSym.end(), []( const KernelSymbol& lhs, const KernelSymbol& rhs ) { return lhs.addr < rhs.addr; } ); for( size_t i=0; i res && *back != '/' ) back--; - rsz = back - res; + if( rsz > 0 ) + { + const char* back = res + rsz - 1; + while( back > res && *back != '/' ) back--; + rsz = back - res; + } ptr = next + 1; continue; } @@ -1028,7 +1525,7 @@ void InitCallstackCritical() void InitCallstack() { - InitRpmalloc(); + InitAllocator(); #ifdef TRACY_HAS_DL_ITERATE_PHDR_TO_REFRESH_IMAGE_CACHE CreateImageCaches(); @@ -1040,7 +1537,7 @@ void InitCallstack() if( s_shouldResolveSymbolsOffline ) { cb_bts = nullptr; // disable use of libbacktrace calls - TracyDebug("TRACY: enabling offline symbol resolving!\n"); + TracyDebug( "TRACY: enabling offline symbol resolving!" ); } else { @@ -1100,13 +1597,13 @@ int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const it->filename = (char*)tracy_malloc( fnsz ); memcpy( it->filename, filename, fnsz ); it->fd = fd >= 0 ? fd : -1; - TracyDebug( "DebugInfo descriptor query: %i, fn: %s\n", fd, filename ); + TracyDebug( "DebugInfo descriptor query: %i, fn: %s", fd, filename ); return it->fd; } const uint8_t* GetBuildIdForImage( const char* image, size_t& size ) { - assert( image ); + TRACY_ASSERT( image ); for( auto& v : *s_di_known ) { if( strcmp( image, v.filename ) == 0 ) @@ -1141,11 +1638,52 @@ void EndCallstack() #endif } +#ifdef TRACY_HAS_EXTERNAL_TARGET +static const char* DecodeCallstackPtrFastExternal( uint64_t ptr ) +{ + static char ret[1024]; + auto vptr = (void*)ptr; + const char* symname = nullptr; + + const auto* extImg = FindExternalImageRefresh( ptr ); + if( extImg ) + { + auto* bts = GetExternalBtState( extImg ); + if( bts ) + { + auto elfVaddr = (uintptr_t)( ptr - extImg->loadBias ); + ExternalSymInfoData sid = {}; + backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid ); + if( sid.symname ) + { + const char* demangled = ___tracy_demangle( sid.symname ); + symname = demangled ? demangled : sid.symname; + } + } + } + if( symname ) + { + strzcpy( ret, symname, sizeof( ret ) ); + } + else + { + *ret = '\0'; + } + return ret; +} +#endif + const char* DecodeCallstackPtrFast( uint64_t ptr ) { static char ret[1024]; + +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( s_externalTargetPid != 0 && s_extImages ) return DecodeCallstackPtrFastExternal( ptr ); +#endif + auto vptr = (void*)ptr; const char* symname = nullptr; + Dl_info dlinfo; if( dladdr( vptr, &dlinfo ) && dlinfo.dli_sname ) { @@ -1153,7 +1691,7 @@ const char* DecodeCallstackPtrFast( uint64_t ptr ) } if( symname ) { - strcpy( ret, symname ); + strzcpy( ret, symname, sizeof( ret ) ); } else { @@ -1190,9 +1728,34 @@ static void SymbolAddressErrorCb( void* data, const char* /*msg*/, int /*errnum* sym.needFree = false; } +#ifdef TRACY_HAS_EXTERNAL_TARGET +static CallstackSymbolData DecodeSymbolAddressExternal( uint64_t ptr ) +{ + CallstackSymbolData sym; + const auto* extImg = FindExternalImageRefresh( ptr ); + if( extImg ) + { + auto* bts = GetExternalBtState( extImg ); + if( bts ) + { + auto elfVaddr = (uintptr_t)( ptr - extImg->loadBias ); + backtrace_pcinfo( bts, elfVaddr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); + return sym; + } + } + SymbolAddressErrorCb( &sym, nullptr, 0 ); + return sym; +} +#endif + CallstackSymbolData DecodeSymbolAddress( uint64_t ptr ) { CallstackSymbolData sym; + +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( s_externalTargetPid != 0 && s_extImages ) return DecodeSymbolAddressExternal( ptr ); +#endif + if( cb_bts ) { backtrace_pcinfo( cb_bts, ptr, SymbolAddressDataCb, SymbolAddressErrorCb, &sym ); @@ -1315,11 +1878,147 @@ void GetSymbolForOfflineResolve(void* address, uint64_t imageBaseAddress, Callst cbEntry.line = 0; } +#ifdef TRACY_HAS_EXTERNAL_TARGET +static int ExternalCallstackDataCb( void* data, uintptr_t /*pc*/, uintptr_t lowaddr, const char* fn, int lineno, const char* function ) +{ + auto* img = (const ExternalImageEntry*)data; + + cb_data[cb_num].symLen = 0; + cb_data[cb_num].symAddr = (uint64_t)img->loadBias + (uint64_t)lowaddr; + + if( !fn && !function ) + { + // no symbol from pcinfo: name stays null, repaired from the symtab by ResolveExternalCallstack + cb_data[cb_num].name = nullptr; + cb_data[cb_num].file = nullptr; + cb_data[cb_num].line = 0; + } + else + { + if( !fn ) fn = "[unknown]"; + if( !function ) + { + function = "[unknown]"; + } + else + { + const char* demangled = ___tracy_demangle( function ); + if( demangled ) function = demangled; + } + + const auto len = std::min( strlen( function ), std::numeric_limits::max() ); + cb_data[cb_num].name = CopyStringFast( function, len ); + cb_data[cb_num].file = NormalizePath( fn ); + if( !cb_data[cb_num].file ) cb_data[cb_num].file = CopyStringFast( fn ); + cb_data[cb_num].line = lineno; + } + + if( ++cb_num >= MaxCbTrace ) + { + return 1; + } + else + { + return 0; + } +} + +static void ExternalPcinfoErrorCb( void* /*data*/, const char* /*msg*/, int /*errnum*/ ) +{ + for( int i=0; ipath ? img->path : "[unknown]"; + + const auto elfVaddr = (uintptr_t)( vma - img->loadBias ); + auto* bts = GetExternalBtState( img ); + + if( bts ) + { + cb_num = 0; + backtrace_pcinfo( bts, elfVaddr, ExternalCallstackDataCb, ExternalPcinfoErrorCb, const_cast( img ) ); + + if( cb_num > 0 ) + { + ExternalSymInfoData sid = {}; + backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid ); + if( sid.symname ) + { + cb_data[cb_num-1].symLen = (uint32_t)sid.symsize; + cb_data[cb_num-1].symAddr = (uint64_t)img->loadBias + (uint64_t)sid.symval; + if( !cb_data[cb_num-1].name ) + { + const char* demangled = ___tracy_demangle( sid.symname ); + cb_data[cb_num-1].name = CopyStringFast( demangled ? demangled : sid.symname ); + cb_data[cb_num-1].file = CopyStringFast( imageName ); + } + } + else if( !cb_data[cb_num-1].name ) + { + cb_data[cb_num-1].name = CopyStringFast( "[unresolved]" ); + cb_data[cb_num-1].file = CopyStringFast( imageName ); + cb_data[cb_num-1].symLen = 0; + cb_data[cb_num-1].symAddr = vma; + } + return { cb_data, uint8_t( cb_num ), imageName }; + } + + ExternalSymInfoData sid = {}; + backtrace_syminfo( bts, elfVaddr, ExternalSymInfoCb, ExternalBacktraceErrorCb, &sid ); + if( sid.symname ) + { + cb_num = 1; + const char* demangled = ___tracy_demangle( sid.symname ); + cb_data[0].name = CopyStringFast( demangled ? demangled : sid.symname ); + cb_data[0].file = CopyStringFast( imageName ); + cb_data[0].line = 0; + cb_data[0].symLen = (uint32_t)sid.symsize; + cb_data[0].symAddr = (uint64_t)img->loadBias + (uint64_t)sid.symval; + return { cb_data, 1, imageName }; + } + } + + cb_num = 1; + cb_data[0].name = CopyStringFast( "[unresolved]" ); + cb_data[0].file = CopyStringFast( imageName ); + cb_data[0].line = 0; + cb_data[0].symLen = 0; + cb_data[0].symAddr = vma; + return { cb_data, 1, imageName }; +} + +CallstackEntryData DecodeCallstackPtrExternal( uint64_t ptr ) +{ + const auto* extImg = FindExternalImageRefresh( ptr ); + if( extImg ) return ResolveExternalCallstack( extImg, ptr ); + + // Address doesn't belong to any known mapping + cb_num = 1; + cb_data[0].name = CopyStringFast( "[unknown]" ); + cb_data[0].file = CopyStringFast( "[unknown]" ); + cb_data[0].line = 0; + cb_data[0].symLen = 0; + cb_data[0].symAddr = ptr; + return { cb_data, 1, "[unknown]" }; +} +#endif + CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { - InitRpmalloc(); - if ( !IsKernelAddress( ptr ) ) + InitAllocator(); + if( !IsKernelAddress( ptr ) ) { +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( s_externalTargetPid != 0 && s_extImages ) return DecodeCallstackPtrExternal( ptr ); +#endif + const char* imageName = nullptr; uint64_t imageBaseAddress = 0x0; @@ -1348,7 +2047,7 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) { cb_num = 0; backtrace_pcinfo( cb_bts, ptr, CallstackDataCb, CallstackErrorCb, nullptr ); - assert( cb_num > 0 ); + TRACY_ASSERT( cb_num > 0 ); backtrace_syminfo( cb_bts, ptr, SymInfoCallback, SymInfoError, nullptr ); } @@ -1358,13 +2057,13 @@ CallstackEntryData DecodeCallstackPtr( uint64_t ptr ) #ifdef __linux else if( s_kernelSym ) { - auto it = std::lower_bound( s_kernelSym, s_kernelSym + s_kernelSymCnt, ptr, []( const KernelSymbol& lhs, const uint64_t& rhs ) { return lhs.addr + lhs.size < rhs; } ); + auto it = std::lower_bound( s_kernelSym, s_kernelSym + s_kernelSymCnt, ptr, []( const KernelSymbol& lhs, const uint64_t& rhs ) { return lhs.endAddr < rhs; } ); if( it != s_kernelSym + s_kernelSymCnt ) { cb_data[0].name = CopyStringFast( it->name ); cb_data[0].file = CopyStringFast( "" ); cb_data[0].line = 0; - cb_data[0].symLen = it->size; + cb_data[0].symLen = it->endAddr - it->addr; cb_data[0].symAddr = it->addr; return { cb_data, 1, it->mod ? it->mod : "" }; } @@ -1407,7 +2106,7 @@ const char* DecodeCallstackPtrFast( uint64_t ptr ) } if( symname ) { - strcpy( ret, symname ); + strzcpy( ret, symname, sizeof( ret ) ); } else { diff --git a/libs/tracy/client/TracyCallstack.h b/libs/tracy/client/TracyCallstack.h index 2df1542..a0ad6b2 100644 --- a/libs/tracy/client/TracyCallstack.h +++ b/libs/tracy/client/TracyCallstack.h @@ -3,10 +3,6 @@ #ifndef TRACY_NO_CALLSTACK -# if !defined _WIN32 -# include -# endif - # if defined _WIN32 # include "../common/TracyWinFamily.hpp" # if !defined TRACY_WIN32_NO_DESKTOP @@ -26,7 +22,7 @@ # endif # elif defined __APPLE__ # define TRACY_HAS_CALLSTACK 4 -# elif defined BSD +# elif defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ # define TRACY_HAS_CALLSTACK 6 # endif diff --git a/libs/tracy/client/TracyCallstack.hpp b/libs/tracy/client/TracyCallstack.hpp index 7d8ed6e..55aa7cc 100644 --- a/libs/tracy/client/TracyCallstack.hpp +++ b/libs/tracy/client/TracyCallstack.hpp @@ -7,6 +7,13 @@ #include "../common/TracyForceInline.hpp" #include "TracyCallstack.h" +// External target API: desktop Linux only. The tracy-monitor that drives +// it builds for Linux x86_64/aarch64 only, and Android, although __linux__, +// lacks process_vm_readv on 32-bit ABIs. +#if defined(__linux__) && !defined(__ANDROID__) && defined(TRACY_HAS_CALLSTACK) +# define TRACY_HAS_EXTERNAL_TARGET +#endif + namespace tracy { @@ -46,10 +53,10 @@ static tracy_force_inline void* Callstack( int32_t /*depth*/ ) { return nullptr; # include #endif -#include #include #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" namespace tracy { @@ -88,6 +95,15 @@ void InitCallstackCritical(); void EndCallstack(); const char* GetKernelModulePath( uint64_t addr ); +#ifdef TRACY_HAS_EXTERNAL_TARGET +bool InitExternalTarget( pid_t targetPid ); +uint32_t GetExternalTargetPid(); +const char* GetExternalTargetName(); +uint64_t GetExternalTargetExeTime(); + +size_t ReadExternalTargetMemory( uint64_t addr, uint32_t size, char* buf ); +#endif + #ifdef TRACY_DEBUGINFOD const uint8_t* GetBuildIdForImage( const char* image, size_t& size ); debuginfod_client* GetDebuginfodClient(); @@ -102,7 +118,7 @@ extern "C" static tracy_force_inline void* Callstack( int32_t depth ) { - assert( depth >= 1 && depth < 63 ); + TRACY_ASSERT( depth >= 1 && depth < 63 ); auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) ); const auto num = ___tracy_RtlWalkFrameChain( (void**)( trace + 1 ), depth, 0 ); *trace = num; @@ -131,7 +147,7 @@ static _Unwind_Reason_Code tracy_unwind_callback( struct _Unwind_Context* ctx, v static tracy_force_inline void* Callstack( int32_t depth ) { - assert( depth >= 1 && depth < 63 ); + TRACY_ASSERT( depth >= 1 && depth < 63 ); auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) ); BacktraceState state = { (void**)(trace+1), (void**)(trace+1+depth) }; @@ -146,7 +162,7 @@ static tracy_force_inline void* Callstack( int32_t depth ) static tracy_force_inline void* Callstack( int32_t depth ) { - assert( depth >= 1 ); + TRACY_ASSERT( depth >= 1 ); auto trace = (uintptr_t*)tracy_malloc( ( 1 + (size_t)depth ) * sizeof( uintptr_t ) ); diff --git a/libs/tracy/client/TracyDebug.hpp b/libs/tracy/client/TracyDebug.hpp index 8723356..e34d021 100644 --- a/libs/tracy/client/TracyDebug.hpp +++ b/libs/tracy/client/TracyDebug.hpp @@ -1,9 +1,34 @@ -#ifndef __TRACYPRINT_HPP__ -#define __TRACYPRINT_HPP__ +#ifndef __TRACYDEBUG_HPP__ +#define __TRACYDEBUG_HPP__ + +#include "TracyMangle.hpp" + +#ifdef TRACY_ON_DEMAND +# define TRACY_VERBOSE_EARLY_OUT_COND if( !GetProfiler().IsConnected() ) break +#else +// Skip rather than assert: TracyDebug can fire during Profiler member +// construction (e.g. SysPower ctor scanning intel-rapl), before s_instance is +// set, so ProfilerAvailable() is legitimately false. +# define TRACY_VERBOSE_EARLY_OUT_COND if( !tracy::ProfilerAvailable() ) break +#endif + +#define TracyInternalMessage( severity, ... ) \ + do { \ + TRACY_VERBOSE_EARLY_OUT_COND; \ + char buffer[4096]; \ + snprintf( buffer, sizeof(buffer), __VA_ARGS__ ); \ + tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, severity, 0, TRACY_CALLSTACK, strlen( buffer ), buffer ); \ + } while( 0 ) #ifdef TRACY_VERBOSE # include -# define TracyDebug(...) fprintf( stderr, __VA_ARGS__ ); +# define TracyDebug(...) do { fprintf( stderr, __VA_ARGS__ ); fputc( '\n', stderr ); } while( 0 ) +// Note: We can't use LogString when using TRACY_DELAYED_INIT due to a deadlock in the init code. +// This is caused by `GetProfilerData` triggering ProfileData ctor, which itself will call `GetProfilerData` and deadlock. +// TRACY_MANUAL_LIFETIME does not have this issue since StartupProfiler sets s_profilerData before calling the constructor. +// In general, this also means we can only call TracyDebug after and the first logging is after queue initialization and critical init (such as InitCallstackCritical). +#elif !defined(TRACY_NO_INTERNAL_MESSAGE) && (!defined(TRACY_DELAYED_INIT) || defined(TRACY_MANUAL_LIFETIME)) +# define TracyDebug(...) TracyInternalMessage( tracy::MessageSeverity::Debug, __VA_ARGS__ ) #else # define TracyDebug(...) #endif diff --git a/libs/tracy/client/TracyDxt1.cpp b/libs/tracy/client/TracyDxt1.cpp index 930d098..4c96a4f 100644 --- a/libs/tracy/client/TracyDxt1.cpp +++ b/libs/tracy/client/TracyDxt1.cpp @@ -1,7 +1,7 @@ #include "TracyDxt1.hpp" +#include "../common/TracyAssert.hpp" #include "../common/TracyForceInline.hpp" -#include #include #include @@ -584,7 +584,7 @@ static tracy_force_inline void ProcessRGB_AVX( const uint8_t* src, char*& dst ) void CompressImageDxt1( const char* src, char* dst, int w, int h ) { - assert( (w % 4) == 0 && (h % 4) == 0 ); + TRACY_ASSERT( (w % 4) == 0 && (h % 4) == 0 ); #ifdef __AVX2__ if( w%8 == 0 ) diff --git a/libs/tracy/client/TracyElf.hpp b/libs/tracy/client/TracyElf.hpp new file mode 100644 index 0000000..1e2a301 --- /dev/null +++ b/libs/tracy/client/TracyElf.hpp @@ -0,0 +1,55 @@ +#ifndef __TRACYELF_HPP__ +#define __TRACYELF_HPP__ + +#include + +namespace tracy +{ + +using elf_half = uint16_t; +using elf_word = uint32_t; +using elf_sword = int32_t; + +#if __SIZEOF_POINTER__ == 8 + using elf_addr = uint64_t; + using elf_off = uint64_t; + using elf_xword = uint64_t; +#else + using elf_addr = uint32_t; + using elf_off = uint32_t; + using elf_xword = uint32_t; +#endif + +struct elf_ehdr +{ + unsigned char e_ident[16]; + elf_half e_type; + elf_half e_machine; + elf_word e_version; + elf_addr e_entry; + elf_off e_phoff; + elf_off e_shoff; + elf_word e_flags; + elf_half e_ehsize; + elf_half e_phentsize; + elf_half e_phnum; + elf_half e_shentsize; + elf_half e_shnum; + elf_half e_shstrndx; +}; + +struct elf_phdr +{ + elf_word p_type; + elf_word p_flags; + elf_off p_offset; + elf_addr p_vaddr; + elf_addr p_paddr; + elf_xword p_filesz; + elf_xword p_memsz; + uint64_t p_align; // include 32-bit-only flags field for 32-bit compatibility +}; + +} + +#endif diff --git a/libs/tracy/client/TracyFastVector.hpp b/libs/tracy/client/TracyFastVector.hpp index 38accc9..b94716e 100644 --- a/libs/tracy/client/TracyFastVector.hpp +++ b/libs/tracy/client/TracyFastVector.hpp @@ -1,10 +1,10 @@ #ifndef __TRACYFASTVECTOR_HPP__ #define __TRACYFASTVECTOR_HPP__ -#include #include #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" #include "../common/TracyForceInline.hpp" namespace tracy @@ -22,7 +22,7 @@ class FastVector , m_write( m_ptr ) , m_end( m_ptr + capacity ) { - assert( capacity != 0 ); + TRACY_ASSERT( capacity != 0 ); } FastVector( const FastVector& ) = delete; @@ -47,11 +47,11 @@ class FastVector T* end() { return m_write; } const T* end() const { return m_write; } - T& front() { assert( !empty() ); return m_ptr[0]; } - const T& front() const { assert( !empty() ); return m_ptr[0]; } + T& front() { TRACY_ASSERT( !empty() ); return m_ptr[0]; } + const T& front() const { TRACY_ASSERT( !empty() ); return m_ptr[0]; } - T& back() { assert( !empty() ); return m_write[-1]; } - const T& back() const { assert( !empty() ); return m_write[-1]; } + T& back() { TRACY_ASSERT( !empty() ); return m_write[-1]; } + const T& back() const { TRACY_ASSERT( !empty() ); return m_write[-1]; } T& operator[]( size_t idx ) { return m_ptr[idx]; } const T& operator[]( size_t idx ) const { return m_ptr[idx]; } diff --git a/libs/tracy/client/TracyKCore.cpp b/libs/tracy/client/TracyKCore.cpp index 09d51d1..21d36f2 100644 --- a/libs/tracy/client/TracyKCore.cpp +++ b/libs/tracy/client/TracyKCore.cpp @@ -1,67 +1,20 @@ #ifdef __linux__ #include -#include #include #include #include #include "TracyDebug.hpp" +#include "TracyElf.hpp" #include "TracyKCore.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" -#if !defined(__GLIBC__) && !defined(__WORDSIZE) -// include __WORDSIZE headers for musl -# include -#endif namespace tracy { -using elf_half = uint16_t; -using elf_word = uint32_t; -using elf_sword = int32_t; - -#if __WORDSIZE == 32 - using elf_addr = uint32_t; - using elf_off = uint32_t; - using elf_xword = uint32_t; -#else - using elf_addr = uint64_t; - using elf_off = uint64_t; - using elf_xword = uint64_t; -#endif - -struct elf_ehdr -{ - unsigned char e_ident[16]; - elf_half e_type; - elf_half e_machine; - elf_word e_version; - elf_addr e_entry; - elf_off e_phoff; - elf_off e_shoff; - elf_word e_flags; - elf_half e_ehsize; - elf_half e_phentsize; - elf_half e_phnum; - elf_half e_shentsize; - elf_half e_shnum; - elf_half e_shstrndx; -}; - -struct elf_phdr -{ - elf_word p_type; - elf_word p_flags; - elf_off p_offset; - elf_addr p_vaddr; - elf_addr p_paddr; - elf_xword p_filesz; - elf_xword p_memsz; - uint64_t p_align; // include 32-bit-only flags field for 32-bit compatibility -}; - KCore::KCore() : m_offsets( 16 ) { @@ -71,7 +24,7 @@ KCore::KCore() elf_ehdr ehdr; if( read( m_fd, &ehdr, sizeof( ehdr ) ) != sizeof( ehdr ) ) goto err; - assert( ehdr.e_phentsize == sizeof( elf_phdr ) ); + TRACY_ASSERT( ehdr.e_phentsize == sizeof( elf_phdr ) ); for( elf_half i=0; i::max)() ); + TRACY_ASSERT( m_id != (std::numeric_limits::max)() ); auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::LockAnnounce ); @@ -154,7 +155,7 @@ class LockableCtx tracy_force_inline void CustomName( const char* name, size_t size ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, name, size ); auto item = Profiler::QueueSerial(); @@ -236,7 +237,7 @@ class SharedLockableCtx , m_active( false ) #endif { - assert( m_id != (std::numeric_limits::max)() ); + TRACY_ASSERT( m_id != (std::numeric_limits::max)() ); auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::LockAnnounce ); @@ -451,7 +452,7 @@ class SharedLockableCtx tracy_force_inline void CustomName( const char* name, size_t size ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, name, size ); auto item = Profiler::QueueSerial(); diff --git a/libs/tracy/client/TracyMangle.hpp b/libs/tracy/client/TracyMangle.hpp new file mode 100644 index 0000000..5a04755 --- /dev/null +++ b/libs/tracy/client/TracyMangle.hpp @@ -0,0 +1,75 @@ +#ifndef __TRACYMANGLE_HPP__ +#define __TRACYMANGLE_HPP__ + +// TRACY_DELAYED_INIT is an internal implementation switch, not a user-facing +// option: Apple platforms require it, and TRACY_MANUAL_LIFETIME builds on top +// of it. It is derived here so that every translation unit, and the client +// implementation, see the same effective value before any config-dependent +// symbol name is computed. +#if defined(TRACY_MANUAL_LIFETIME) || defined(__APPLE__) +# ifndef TRACY_DELAYED_INIT +# define TRACY_DELAYED_INIT +# endif +#endif + +#ifdef TRACY_ENABLE +#define TRACY_ENABLE_MANGLE _E1 +#else +#define TRACY_ENABLE_MANGLE _E0 +#endif + +#ifdef TRACY_ON_DEMAND +#define TRACY_ON_DEMAND_MANGLE _OD1 +#else +#define TRACY_ON_DEMAND_MANGLE _OD0 +#endif + +#ifdef TRACY_DELAYED_INIT +#define TRACY_DELAYED_INIT_MANGLE _DI1 +#else +#define TRACY_DELAYED_INIT_MANGLE _DI0 +#endif + +#ifdef TRACY_MANUAL_LIFETIME +#define TRACY_MANUAL_LIFETIME_MANGLE _ML1 +#else +#define TRACY_MANUAL_LIFETIME_MANGLE _ML0 +#endif + +#ifdef TRACY_FIBERS +#define TRACY_FIBERS_MANGLE _F1 +#else +#define TRACY_FIBERS_MANGLE _F0 +#endif + +#ifdef TRACY_DISALLOW_HW_TIMER +#define TRACY_DISALLOW_HW_TIMER_MANGLE _DHT1 +#else +#define TRACY_DISALLOW_HW_TIMER_MANGLE _DHT0 +#endif + +#ifdef TRACY_TIMER_FALLBACK +#define TRACY_TIMER_FALLBACK_MANGLE _TF1 +#else +#define TRACY_TIMER_FALLBACK_MANGLE _TF0 +#endif + +#ifdef TRACY_PLATFORM_HEADER +#define TRACY_PLATFORM_HEADER_MANGLE _PH1 +#else +#define TRACY_PLATFORM_HEADER_MANGLE _PH0 +#endif + +#define MANGLED_NAME_BASED_ON_CONFIG(base) \ + TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat( \ + base##_CFG, \ + TRACY_ENABLE_MANGLE), \ + TRACY_ON_DEMAND_MANGLE), \ + TRACY_DELAYED_INIT_MANGLE), \ + TRACY_MANUAL_LIFETIME_MANGLE), \ + TRACY_FIBERS_MANGLE), \ + TRACY_DISALLOW_HW_TIMER_MANGLE), \ + TRACY_TIMER_FALLBACK_MANGLE), \ + TRACY_PLATFORM_HEADER_MANGLE) + +#endif // __TRACYMANGLE_HPP__ diff --git a/libs/tracy/client/TracyOverride.cpp b/libs/tracy/client/TracyOverride.cpp index 591508a..07064bc 100644 --- a/libs/tracy/client/TracyOverride.cpp +++ b/libs/tracy/client/TracyOverride.cpp @@ -12,11 +12,11 @@ extern "C" int dlclose( void* hnd ) struct link_map* lm; if( dlinfo( hnd, RTLD_DI_LINKMAP, &lm ) == 0 ) { - TracyDebug( "Overriding dlclose for %s\n", lm->l_name ); + TracyDebug( "Overriding dlclose for %s", lm->l_name ); } else { - TracyDebug( "Overriding dlclose for unknown object (%s)\n", dlerror() ); + TracyDebug( "Overriding dlclose for unknown object (%s)", dlerror() ); } #endif return 0; diff --git a/libs/tracy/client/TracyProfiler.cpp b/libs/tracy/client/TracyProfiler.cpp index b4413cb..8fcd999 100644 --- a/libs/tracy/client/TracyProfiler.cpp +++ b/libs/tracy/client/TracyProfiler.cpp @@ -10,12 +10,13 @@ # include # include # include "../common/TracyWinFamily.hpp" -# ifndef _MSC_VER +# if defined(_MSC_VER) // https://devblogs.microsoft.com/oldnewthing/20200730-00/?p=104021/ +# define fileno _fileno +# else # include # endif #else # include -# include #endif #ifdef _GNU_SOURCE @@ -29,7 +30,7 @@ # include #endif -#if defined __APPLE__ || defined BSD +#if defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ # include # include #endif @@ -43,13 +44,11 @@ # include # include # include -# include # include # include #endif #ifdef __QNX__ -# include # include # include # include @@ -57,11 +56,11 @@ #endif #include -#include #include #include #include #include +#include #include #include #include @@ -69,11 +68,11 @@ #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" #include "../common/TracySocket.hpp" #include "../common/TracySystem.hpp" #include "../common/TracyYield.hpp" #include "../common/tracy_lz4.hpp" -#include "tracy_rpmalloc.hpp" #include "TracyCallstack.hpp" #include "TracyDebug.hpp" #include "TracyDxt1.hpp" @@ -84,10 +83,6 @@ #include "TracySysTrace.hpp" #include "../tracy/TracyC.h" -#if defined TRACY_MANUAL_LIFETIME && !defined(TRACY_DELAYED_INIT) -# error "TRACY_MANUAL_LIFETIME requires enabled TRACY_DELAYED_INIT" -#endif - #ifdef TRACY_PORT # ifndef TRACY_DATA_PORT # define TRACY_DATA_PORT TRACY_PORT @@ -97,11 +92,7 @@ # endif #endif -#ifdef __APPLE__ -# ifndef TRACY_DELAYED_INIT -# define TRACY_DELAYED_INIT -# endif -#else +#ifndef __APPLE__ # ifdef __GNUC__ # define init_order( val ) __attribute__ ((init_priority(val))) # else @@ -282,6 +273,9 @@ static bool EnsureReadable( uintptr_t address ) return mapping && EnsureReadable( *mapping ); } #elif defined WIN32 +#ifdef TRACY_HAS_CNTVCT +static_assert( TRACY_WINARM64_CNTVCT_EL0 == ARM64_CNTVCT_EL0, "ARM64_CNTVCT_EL0 mismatch" ); +#endif static bool EnsureReadable( uintptr_t address ) { MEMORY_BASIC_INFORMATION memInfo; @@ -424,6 +418,10 @@ static int64_t SetupHwTimer() static const char* GetProcessName() { +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( GetExternalTargetPid() != 0 ) return GetExternalTargetName(); +#endif + const char* processName = "unknown"; #ifdef _WIN32 static char buf[_MAX_PATH]; @@ -440,7 +438,7 @@ static const char* GetProcessName() # endif #elif defined __linux__ && defined _GNU_SOURCE if( program_invocation_short_name ) processName = program_invocation_short_name; -#elif defined __APPLE__ || defined BSD +#elif defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ auto buf = getprogname(); if( buf ) processName = buf; #elif defined __QNX__ @@ -517,7 +515,7 @@ static const char* GetHostInfo() auto ptr = buf; #if defined _WIN32 # if defined TRACY_WIN32_NO_DESKTOP - auto GetVersion = &::GetVersionEx; + auto GetVersion = &::GetVersionExW; # else auto GetVersion = (t_RtlGetVersion)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "RtlGetVersion" ); # endif @@ -595,40 +593,20 @@ static const char* GetHostInfo() #if defined _WIN32 InitWinSock(); +#endif - char hostname[512]; - gethostname( hostname, 512 ); - -# if defined TRACY_WIN32_NO_DESKTOP - const char* user = ""; -# else - DWORD userSz = UNLEN+1; - char user[UNLEN+1]; - GetUserNameA( user, &userSz ); -# endif - - ptr += sprintf( ptr, "User: %s@%s\n", user, hostname ); + const char* user = GetUserLogin(); + char hostname[512] = {}; +#if defined TRACY_HAS_CUSTOM_USER_INFO + PlatformGetHostname( hostname, sizeof( hostname ) ); #else - char hostname[_POSIX_HOST_NAME_MAX]{}; - char user[_POSIX_LOGIN_NAME_MAX]{}; - - gethostname( hostname, _POSIX_HOST_NAME_MAX ); -# if defined __ANDROID__ - const auto login = getlogin(); - if( login ) - { - strcpy( user, login ); - } - else - { - memcpy( user, "(?)", 4 ); - } -# else - getlogin_r( user, _POSIX_LOGIN_NAME_MAX ); -# endif - - ptr += sprintf( ptr, "User: %s@%s\n", user, hostname ); + gethostname( hostname, sizeof( hostname ) ); #endif + ptr += sprintf( ptr, "User: %s@%s", user, hostname ); + + const char* fullName = GetUserFullName(); + if( fullName ) ptr += sprintf( ptr, " (%s)", fullName ); + ptr += sprintf( ptr, "\n" ); #if defined __i386 || defined _M_IX86 ptr += sprintf( ptr, "Arch: x86\n" ); @@ -658,7 +636,7 @@ static const char* GetHostInfo() FILE* fcpuinfo = fopen( "/proc/cpuinfo", "rb" ); if( fcpuinfo ) { - enum { BufSize = 4*1024 }; + constexpr size_t BufSize = 4*1024; char buf[BufSize]; const auto sz = fread( buf, 1, BufSize, fcpuinfo ); fclose( fcpuinfo ); @@ -742,7 +720,7 @@ static const char* GetHostInfo() size_t sz = sizeof( memSize ); sysctlbyname( "hw.memsize", &memSize, &sz, nullptr, 0 ); ptr += sprintf( ptr, "RAM: %zu MB\n", memSize / 1024 / 1024 ); -#elif defined BSD +#elif defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ size_t memSize; size_t sz = sizeof( memSize ); sysctlbyname( "hw.physmem", &memSize, &sz, nullptr, 0 ); @@ -771,6 +749,11 @@ static const char* GetHostInfo() static uint64_t GetPid() { +#ifdef TRACY_HAS_EXTERNAL_TARGET + const auto externalPid = GetExternalTargetPid(); + if( externalPid != 0 ) return uint64_t( externalPid ); +#endif + #if defined _WIN32 return uint64_t( GetCurrentProcessId() ); #else @@ -869,7 +852,8 @@ LONG WINAPI CrashFilter( PEXCEPTION_POINTERS pExp ) } { - GetProfiler().SendCallstack( 60, "KiUserExceptionDispatcher" ); + const char* remove[] = { "KiUserExceptionDispatcher", nullptr }; + GetProfiler().SendCallstack( 60, remove ); TracyQueuePrepare( QueueType::CrashReport ); item->crashReport.time = Profiler::GetTime(); @@ -935,7 +919,8 @@ static Thread* s_symbolThread; std::atomic s_symbolThreadGone { false }; #endif #ifdef TRACY_HAS_SYSTEM_TRACING -static Thread* s_sysTraceThread = nullptr; +static std::atomic s_sysTraceThread(nullptr); +static std::atomic s_sysTraceStartFailed(false); #endif #if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER @@ -979,7 +964,7 @@ static inline void HexPrint( char*& ptr, uint64_t val ) while( bptr != buf ); } -static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) +TRACY_API void TracyCrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) { bool expected = false; if( !s_alreadyCrashed.compare_exchange_strong( expected, true ) ) ThreadFreezer( signal ); @@ -1131,7 +1116,12 @@ static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) } { - GetProfiler().SendCallstack( 60, "__kernel_rt_sigreturn" ); + const char* remove[] = { + "__kernel_rt_sigreturn", + "TracyCrashHandler", + nullptr + }; + GetProfiler().SendCallstack( 60, remove ); TracyQueuePrepare( QueueType::CrashReport ); item->crashReport.time = Profiler::GetTime(); @@ -1174,32 +1164,39 @@ static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ ) #ifdef TRACY_HAS_SYSTEM_TRACING static void StartSystemTracing( int64_t& samplingPeriod ) { - assert( s_sysTraceThread == nullptr ); + TRACY_ASSERT( s_sysTraceThread == nullptr ); // use TRACY_NO_SYS_TRACE=1 to force disabling sys tracing (even if available in the underlying system) // as it can have significant impact on the size of the traces const char* noSysTrace = GetEnvVar( "TRACY_NO_SYS_TRACE" ); - const bool disableSystrace = (noSysTrace && noSysTrace[0] == '1'); + const bool disableSystrace = ( noSysTrace && noSysTrace[0] == '1' ); if( disableSystrace ) { - TracyDebug("TRACY: Sys Trace was disabled by 'TRACY_NO_SYS_TRACE=1'\n"); + TracyDebug( "TRACY: Sys Trace was disabled by 'TRACY_NO_SYS_TRACE=1'" ); } else if( SysTraceStart( samplingPeriod ) ) { - s_sysTraceThread = (Thread*)tracy_malloc( sizeof( Thread ) ); - new(s_sysTraceThread) Thread( SysTraceWorker, nullptr ); + s_sysTraceStartFailed.store( false, std::memory_order_release ); + Thread* sysTraceThread = (Thread*)tracy_malloc( sizeof( Thread ) ); + new( sysTraceThread ) Thread( SysTraceWorker, nullptr ); + Thread* prev = s_sysTraceThread.exchange( sysTraceThread ); + TRACY_ASSERT( prev == nullptr ); std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) ); } + else + { + s_sysTraceStartFailed.store( true, std::memory_order_release ); + } } static void StopSystemTracing() { - if( s_sysTraceThread ) + Thread* sysTraceThread = s_sysTraceThread.exchange( nullptr ); + if( sysTraceThread ) { SysTraceStop(); - s_sysTraceThread->~Thread(); - tracy_free( s_sysTraceThread ); - s_sysTraceThread = nullptr; + sysTraceThread->~Thread(); + tracy_free( sysTraceThread ); } } #endif @@ -1220,7 +1217,7 @@ void Profiler::EndSamplingProfiling() #endif } -enum { QueuePrealloc = 256 * 1024 }; +constexpr size_t QueuePrealloc = 256 * 1024; TRACY_API int64_t GetFrequencyQpc() { @@ -1243,7 +1240,7 @@ struct ProfilerData moodycamel::ConcurrentQueue queue; Profiler profiler; std::atomic lockCounter { 0 }; - std::atomic gpuCtxCounter { 0 }; + std::atomic gpuCtxCounter { 0 }; std::atomic threadNameData { nullptr }; }; @@ -1283,7 +1280,7 @@ TRACY_API void StartupProfiler() } static ProfilerData& GetProfilerData() { - assert( s_profilerData ); + TRACY_ASSERT( s_profilerData ); return *s_profilerData; } TRACY_API void ShutdownProfiler() @@ -1292,7 +1289,11 @@ TRACY_API void ShutdownProfiler() s_profilerData->~ProfilerData(); tracy_free( s_profilerData ); s_profilerData = nullptr; +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + PlatformAllocatorFinalize(); +#elif defined TRACY_USE_RPMALLOC rpmalloc_finalize(); +#endif RpThreadInitDone = false; RpInitDone.store( 0, std::memory_order_release ); } @@ -1337,13 +1338,13 @@ struct ProfilerThreadDataKey { int val = pthread_key_create(&m_key, sDestructor); static_cast(val); // unused - assert(val == 0); + TRACY_ASSERT(val == 0); } ~ProfilerThreadDataKey() { int val = pthread_key_delete(m_key); static_cast(val); // unused - assert(val == 0); + TRACY_ASSERT(val == 0); } ProfilerThreadData& get() { @@ -1379,12 +1380,12 @@ static ProfilerThreadData& GetProfilerThreadData() } #endif -TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken() { return GetProfilerThreadData().token.ptr; } -TRACY_API Profiler& GetProfiler() { return GetProfilerData().profiler; } +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* MANGLED_NAME_BASED_ON_CONFIG(GetToken)() { return GetProfilerThreadData().token.ptr; } +TRACY_API Profiler& MANGLED_NAME_BASED_ON_CONFIG(GetProfiler)() { return GetProfilerData().profiler; } TRACY_API moodycamel::ConcurrentQueue& GetQueue() { return GetProfilerData().queue; } TRACY_API int64_t GetInitTime() { return GetProfilerData().initTime; } TRACY_API std::atomic& GetLockCounter() { return GetProfilerData().lockCounter; } -TRACY_API std::atomic& GetGpuCtxCounter() { return GetProfilerData().gpuCtxCounter; } +TRACY_API std::atomic& GetGpuCtxCounter() { return GetProfilerData().gpuCtxCounter; } TRACY_API GpuCtxWrapper& GetGpuCtx() { return GetProfilerThreadData().gpuCtx; } TRACY_API uint32_t GetThreadHandle() { return detail::GetThreadHandleImpl(); } std::atomic& GetThreadNameData() { return GetProfilerData().threadNameData; } @@ -1407,9 +1408,30 @@ namespace // 1a. But s_queue is needed for initialization of variables in point 2. extern moodycamel::ConcurrentQueue s_queue; +// A producer token may be created before s_initTime is constructed (the dynamic loader +// runs shared object initializers before any of the executable's constructors, and such +// an initializer may emit a zone). Remember the time of such an early token creation, so +// that the init time can be backdated accordingly and no event timestamp precedes the +// trace epoch. +static std::atomic s_earlyTokenTime { 0 }; +static bool s_initTimeConstructed = false; + // 2. If these variables would be in the .CRT$XCB section, they would be initialized only in main thread. thread_local moodycamel::ProducerToken init_order(107) s_token_detail( s_queue ); -thread_local ProducerWrapper init_order(108) s_token { s_queue.get_explicit_producer( s_token_detail ) }; + +static moodycamel::ConcurrentQueue::ExplicitProducer* CreateProducerToken() +{ + auto ptr = s_queue.get_explicit_producer( s_token_detail ); + if( !s_initTimeConstructed ) + { + const auto t = Profiler::GetTime(); + auto e = s_earlyTokenTime.load( std::memory_order_relaxed ); + while( ( e == 0 || t < e ) && !s_earlyTokenTime.compare_exchange_weak( e, t, std::memory_order_relaxed ) ) {} + } + return ptr; +} + +thread_local ProducerWrapper init_order(108) s_token { CreateProducerToken() }; thread_local ThreadHandleWrapper init_order(104) s_threadHandle { detail::GetThreadHandleImpl() }; # ifdef _MSC_VER @@ -1418,14 +1440,38 @@ thread_local ThreadHandleWrapper init_order(104) s_threadHandle { detail::GetThr # pragma init_seg( ".CRT$XCB" ) # endif -static InitTimeWrapper init_order(101) s_initTime { SetupHwTimer() }; +static int64_t GetInitTimeImpl() +{ + auto t = SetupHwTimer(); + const auto e = s_earlyTokenTime.load( std::memory_order_relaxed ); + if( e != 0 && e < t ) t = e; + s_initTimeConstructed = true; + return t; +} +static InitTimeWrapper init_order(101) s_initTime { GetInitTimeImpl() }; std::atomic init_order(102) RpInitDone( 0 ); std::atomic init_order(102) RpInitLock( 0 ); thread_local bool RpThreadInitDone = false; thread_local bool RpThreadShutdown = false; moodycamel::ConcurrentQueue init_order(103) s_queue( QueuePrealloc ); + +# ifndef _MSC_VER +// An instrumented shared object may emit zones from its static initializers, which the +// dynamic loader runs before any of the executable's constructors, including the +// priority-ordered constructor of s_queue above. The main thread producer token (s_token) +// is then lazily created against the zero-initialized queue memory, and the queue +// constructor subsequently orphans it, making all zones emitted on the main thread +// invisible to the consumer. Re-adopt such a producer here. If no zones were emitted up +// to this point, this only triggers construction of s_token, which is a no-op repair. +struct EarlyMainThreadTokenRepair +{ + EarlyMainThreadTokenRepair() { if( s_token.ptr ) s_queue.readopt_orphaned_producer( s_token.ptr ); } +}; +static EarlyMainThreadTokenRepair init_order(104) s_earlyMainThreadTokenRepair; +# endif + std::atomic init_order(104) s_lockCounter( 0 ); -std::atomic init_order(104) s_gpuCtxCounter( 0 ); +std::atomic init_order(104) s_gpuCtxCounter( 0 ); thread_local GpuCtxWrapper init_order(104) s_gpuCtx { nullptr }; @@ -1434,17 +1480,17 @@ static std::atomic init_order(104) s_threadNameDataInstance( nu std::atomic& s_threadNameData = s_threadNameDataInstance; # ifdef TRACY_ON_DEMAND -thread_local LuaZoneState init_order(104) s_luaZoneState { 0, false }; +thread_local LuaZoneState init_order(104) s_luaZoneState; # endif static Profiler init_order(105) s_profiler; -TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken() { return s_token.ptr; } -TRACY_API Profiler& GetProfiler() { return s_profiler; } +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* MANGLED_NAME_BASED_ON_CONFIG(GetToken)() { return s_token.ptr; } +TRACY_API Profiler& MANGLED_NAME_BASED_ON_CONFIG(GetProfiler)() { return s_profiler; } TRACY_API moodycamel::ConcurrentQueue& GetQueue() { return s_queue; } TRACY_API int64_t GetInitTime() { return s_initTime.val; } TRACY_API std::atomic& GetLockCounter() { return s_lockCounter; } -TRACY_API std::atomic& GetGpuCtxCounter() { return s_gpuCtxCounter; } +TRACY_API std::atomic& GetGpuCtxCounter() { return s_gpuCtxCounter; } TRACY_API GpuCtxWrapper& GetGpuCtx() { return s_gpuCtx; } TRACY_API uint32_t GetThreadHandle() { return s_threadHandle.val; } @@ -1455,11 +1501,43 @@ TRACY_API LuaZoneState& GetLuaZoneState() { return s_luaZoneState; } # endif #endif +TRACY_API int32_t NextGpuContextId() +{ + const auto id = GetGpuCtxCounter().fetch_add( 1, std::memory_order_relaxed ); + if( id > UINT8_MAX ) + { + Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Error, 0, 0, "Tracy: more than 256 GPU contexts in this process; gpu context ids are 8-bit" ); + TRACY_ASSERT( false ); + return InvalidGpuContextId; + } + return int32_t( id ); +} + TRACY_API bool ProfilerAvailable() { return s_instance != nullptr; } TRACY_API bool ProfilerAllocatorAvailable() { return !RpThreadShutdown; } TRACY_API bool BeginSamplingProfiling() { return GetProfiler().BeginSamplingProfiling(); } TRACY_API void EndSamplingProfiling() { return GetProfiler().EndSamplingProfiling(); } +TRACY_API bool IsSystemTracingFailed() +{ +#if defined(TRACY_HAS_SYSTEM_TRACING) + return s_sysTraceStartFailed.load( std::memory_order_acquire ); +#else + return false; +#endif +} + +static std::atomic s_reservedListenFd( -1 ); + +TRACY_API void SetReservedListenSocket( int fd ) +{ + TRACY_ASSERT( fd >= 0 ); + s_reservedListenFd.store( fd, std::memory_order_release ); +} + +static std::atomic s_dataPortListening(false); + +TRACY_API bool IsDataPortListening() { return s_dataPortListening.load( std::memory_order_acquire ); } constexpr static size_t SafeSendBufferSize = 65536; @@ -1474,7 +1552,9 @@ Profiler::Profiler() , m_broadcast( nullptr ) , m_noExit( false ) , m_userPort( 0 ) + , m_dataPort( 0 ) , m_zoneId( 1 ) + , m_sectionId( 1 ) , m_samplingPeriod( 0 ) , m_stream( LZ4_createStream() ) , m_buffer( (char*)tracy_malloc( TargetFrameSize*3 ) ) @@ -1502,7 +1582,7 @@ Profiler::Profiler() , m_crashHandlerInstalled( false ) , m_programName( nullptr ) { - assert( !s_instance ); + TRACY_ASSERT( !s_instance ); s_instance = this; #ifndef TRACY_DELAYED_INIT @@ -1539,9 +1619,9 @@ Profiler::Profiler() m_safeSendBuffer = (char*)tracy_malloc( SafeSendBufferSize ); -#ifndef _WIN32 +#if !defined _WIN32 && !defined TRACY_HAS_CUSTOM_SAFE_COPY pipe(m_pipe); -# if defined __APPLE__ || defined BSD +# if defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ // FreeBSD/XNU don't have F_SETPIPE_SZ, so use the default m_pipeBufSize = 16384; # else @@ -1566,7 +1646,7 @@ void Profiler::InstallCrashHandler() sigaction( TRACY_CRASH_SIGNAL, &threadFreezer, &m_prevSignal.pwr ); struct sigaction crashHandler = {}; - crashHandler.sa_sigaction = CrashHandler; + crashHandler.sa_sigaction = TracyCrashHandler; crashHandler.sa_flags = SA_SIGINFO; sigaction( SIGILL, &crashHandler, &m_prevSignal.ill ); sigaction( SIGFPE, &crashHandler, &m_prevSignal.fpe ); @@ -1604,7 +1684,7 @@ void Profiler::RemoveCrashHandler() auto restore = []( int signum, struct sigaction* prev ) { struct sigaction old; sigaction( signum, prev, &old ); - if( old.sa_sigaction != CrashHandler ) sigaction( signum, &old, nullptr ); // A different signal handler was installed over ours => put it back + if( old.sa_sigaction != TracyCrashHandler ) sigaction( signum, &old, nullptr ); // A different signal handler was installed over ours => put it back }; restore( TRACY_CRASH_SIGNAL, &m_prevSignal.pwr ); restore( SIGILL, &m_prevSignal.ill ); @@ -1683,7 +1763,7 @@ Profiler::~Profiler() tracy_free( m_kcore ); #endif -#ifndef _WIN32 +#if !defined _WIN32 && !defined TRACY_HAS_CUSTOM_SAFE_COPY close( m_pipe[0] ); close( m_pipe[1] ); #endif @@ -1705,7 +1785,7 @@ Profiler::~Profiler() tracy_free( m_broadcast ); } - assert( s_instance ); + TRACY_ASSERT( s_instance ); s_instance = nullptr; } @@ -1744,13 +1824,22 @@ void Profiler::Worker() #endif m_exectime = 0; - const auto execname = GetProcessExecutablePath(); - if( execname ) +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( GetExternalTargetPid() != 0 ) { - struct stat st; - if( stat( execname, &st ) == 0 ) + m_exectime = GetExternalTargetExeTime(); + } + else +#endif + { + const auto execname = GetProcessExecutablePath(); + if( execname ) { - m_exectime = (uint64_t)st.st_mtime; + struct stat st; + if( stat( execname, &st ) == 0 ) + { + m_exectime = (uint64_t)st.st_mtime; + } } } @@ -1767,8 +1856,8 @@ void Profiler::Worker() #ifdef TRACY_ON_DEMAND flags |= WelcomeFlag::OnDemand; #endif -#ifdef __APPLE__ - flags |= WelcomeFlag::IsApple; +#if defined TRACY_IGNORE_MEMORY_FAULTS || defined __APPLE__ + flags |= WelcomeFlag::IgnoreMemFaults; #endif #ifndef TRACY_NO_CODE_TRANSFER flags |= WelcomeFlag::CodeTransfer; @@ -1829,7 +1918,13 @@ void Profiler::Worker() ListenSocket listen; bool isListening = false; - if( !dataPortSearch ) + const int reservedFd = s_reservedListenFd.exchange( -1, std::memory_order_acquire ); + if( reservedFd != -1 ) + { + listen.Adopt( reservedFd ); + isListening = true; + } + else if( !dataPortSearch ) { isListening = listen.Listen( dataPort, 4 ); } @@ -1845,6 +1940,9 @@ void Profiler::Worker() } } } + if( isListening ) dataPort = listen.LocalPort(); + + s_dataPortListening.store( isListening, std::memory_order_release ); if( !isListening ) { for(;;) @@ -1859,6 +1957,7 @@ void Profiler::Worker() std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); } } + m_dataPort.store( dataPort, std::memory_order_release ); #ifndef TRACY_NO_BROADCAST m_broadcast = (UdpBroadcast*)tracy_malloc( sizeof( UdpBroadcast ) ); @@ -1931,7 +2030,7 @@ void Profiler::Worker() lastBroadcast = t; const auto ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); broadcastMsg.activeTime = int32_t( ts - m_epoch ); - assert( broadcastMsg.activeTime >= 0 ); + TRACY_ASSERT( broadcastMsg.activeTime >= 0 ); m_broadcast->Send( broadcastPort, &broadcastMsg, broadcastLen ); } } @@ -1978,8 +2077,12 @@ void Profiler::Worker() } #ifdef TRACY_ON_DEMAND +# ifdef TRACY_HAS_CALLSTACK + // Only wait on m_symbolsBusy if the symbol worker thread exists; otherwise nobody + // ever resets the flag and we'd spin forever on the second connection. while( m_symbolsBusy.load( std::memory_order_acquire ) ) { YieldThread(); } m_symbolsBusy.store( true, std::memory_order_release ); +# endif const auto currentTime = GetTime(); ClearQueues( token ); m_connectionId.fetch_add( 1, std::memory_order_release ); @@ -2010,22 +2113,27 @@ void Profiler::Worker() { uint64_t ptr; uint16_t size; - const auto idx = MemRead( &item.hdr.idx ); + const auto idx = MemRead( &item.hdr.idx ); switch( (QueueType)idx ) { case QueueType::MessageAppInfo: - ptr = MemRead( &item.messageFat.text ); - size = MemRead( &item.messageFat.size ); + ptr = MemRead( &item.messageFat.textAndMetadata ).GetAddress(); + size = MemRead( &item.messageFat.size ); SendSingleString( (const char*)ptr, size ); break; case QueueType::LockName: - ptr = MemRead( &item.lockNameFat.name ); - size = MemRead( &item.lockNameFat.size ); + ptr = MemRead( &item.lockNameFat.name ); + size = MemRead( &item.lockNameFat.size ); SendSingleString( (const char*)ptr, size ); break; case QueueType::GpuContextName: - ptr = MemRead( &item.gpuContextNameFat.ptr ); - size = MemRead( &item.gpuContextNameFat.size ); + ptr = MemRead( &item.gpuContextNameFat.ptr ); + size = MemRead( &item.gpuContextNameFat.size ); + SendSingleString( (const char*)ptr, size ); + break; + case QueueType::SectionSetup: + ptr = MemRead( &item.sectionSetupFat.text ); + size = MemRead( &item.sectionSetupFat.size ); SendSingleString( (const char*)ptr, size ); break; default: @@ -2082,6 +2190,7 @@ void Profiler::Worker() connActive = HandleServerQuery(); if( !connActive ) break; } + if ( !m_symbolQueue.empty() ) m_symbolQueueSignal.notify_one(); if( !connActive || ShouldExit() ) break; } if( ShouldExit() ) break; @@ -2148,9 +2257,15 @@ void Profiler::Worker() while( s_symbolThreadGone.load() == false ) { YieldThread(); } #endif - // Client is exiting. #ifdef TRACY_HAS_SYSTEM_TRACING - // Stop filling queues with new data. + // On a typical shutdown scenario, the (global) Profiler object is destroyed by + // the C++ runtime when the client program returns from "main", and ~Profiler() + // takes care of calling StopSystemTracing(). However, a client may decide to + // manually RequestShutdown(), in which case ~Profile() may not execute before + // this Worker() thread goes through its teardown stages and reaches this point. + // To ensure that system tracing does not keep pushing data to the worker queue + // indefinitely (thus preventing this worker from terminating), we have to call + // StopSystemTracing() here as well to be safe: StopSystemTracing(); #endif @@ -2278,12 +2393,12 @@ void Profiler::CompressWorker() const auto w = fi->w; const auto h = fi->h; const auto csz = size_t( w * h / 2 ); - auto etc1buf = (char*)tracy_malloc( csz ); - CompressImageDxt1( (const char*)fi->image, etc1buf, w, h ); + auto texbuf = (char*)tracy_malloc( csz ); + CompressImageDxt1( (const char*)fi->image, texbuf, w, h ); tracy_free( fi->image ); TracyLfqPrepare( QueueType::FrameImage ); - MemWrite( &item->frameImageFat.image, (uint64_t)etc1buf ); + MemWrite( &item->frameImageFat.image, (uint64_t)texbuf ); MemWrite( &item->frameImageFat.frame, fi->frame ); MemWrite( &item->frameImageFat.w, w ); MemWrite( &item->frameImageFat.h, h ); @@ -2317,12 +2432,12 @@ static void FreeAssociatedMemory( const QueueItem& item ) { case QueueType::ZoneText: case QueueType::ZoneName: - ptr = MemRead( &item.zoneTextFat.text ); + ptr = MemRead( &item.zoneTextFat.text ); tracy_free( (void*)ptr ); break; case QueueType::MessageColor: case QueueType::MessageColorCallstack: - ptr = MemRead( &item.messageColorFat.text ); + ptr = MemRead( &item.messageColorFat.textAndMetadata ).GetAddress(); tracy_free( (void*)ptr ); break; case QueueType::Message: @@ -2330,47 +2445,47 @@ static void FreeAssociatedMemory( const QueueItem& item ) #ifndef TRACY_ON_DEMAND case QueueType::MessageAppInfo: #endif - ptr = MemRead( &item.messageFat.text ); + ptr = MemRead( &item.messageFat.textAndMetadata ).GetAddress(); tracy_free( (void*)ptr ); break; case QueueType::ZoneBeginAllocSrcLoc: case QueueType::ZoneBeginAllocSrcLocCallstack: - ptr = MemRead( &item.zoneBegin.srcloc ); + ptr = MemRead( &item.zoneBegin.srcloc ); tracy_free( (void*)ptr ); break; case QueueType::GpuZoneBeginAllocSrcLoc: case QueueType::GpuZoneBeginAllocSrcLocCallstack: case QueueType::GpuZoneBeginAllocSrcLocSerial: case QueueType::GpuZoneBeginAllocSrcLocCallstackSerial: - ptr = MemRead( &item.gpuZoneBegin.srcloc ); + ptr = MemRead( &item.gpuZoneBegin.srcloc ); tracy_free( (void*)ptr ); break; case QueueType::CallstackSerial: case QueueType::Callstack: - ptr = MemRead( &item.callstackFat.ptr ); + ptr = MemRead( &item.callstackFat.ptr ); tracy_free( (void*)ptr ); break; case QueueType::CallstackAlloc: - ptr = MemRead( &item.callstackAllocFat.nativePtr ); + ptr = MemRead( &item.callstackAllocFat.nativePtr ); tracy_free( (void*)ptr ); - ptr = MemRead( &item.callstackAllocFat.ptr ); + ptr = MemRead( &item.callstackAllocFat.ptr ); tracy_free( (void*)ptr ); break; case QueueType::CallstackSample: case QueueType::CallstackSampleContextSwitch: - ptr = MemRead( &item.callstackSampleFat.ptr ); + ptr = MemRead( &item.callstackSampleFat.ptr ); tracy_free( (void*)ptr ); break; case QueueType::FrameImage: - ptr = MemRead( &item.frameImageFat.image ); + ptr = MemRead( &item.frameImageFat.image ); tracy_free( (void*)ptr ); break; #ifdef TRACY_HAS_CALLSTACK case QueueType::CallstackFrameSize: { - InitRpmalloc(); - auto size = MemRead( &item.callstackFrameSizeFat.size ); - auto data = (const CallstackEntry*)MemRead( &item.callstackFrameSizeFat.data ); + InitAllocator(); + auto size = MemRead( &item.callstackFrameSizeFat.size ); + auto data = (const CallstackEntry*)MemRead( &item.callstackFrameSizeFat.data ); for( uint8_t i=0; i( &item.symbolInformationFat.needFree ); + uint8_t needFree = MemRead( &item.symbolInformationFat.needFree ); if( needFree ) { - ptr = MemRead( &item.symbolInformationFat.fileString ); + ptr = MemRead( &item.symbolInformationFat.fileString ); tracy_free( (void*)ptr ); } break; } case QueueType::SymbolCodeMetadata: - ptr = MemRead( &item.symbolCodeMetadata.ptr ); + ptr = MemRead( &item.symbolCodeMetadata.ptr ); tracy_free( (void*)ptr ); break; #endif #ifndef TRACY_ON_DEMAND case QueueType::LockName: - ptr = MemRead( &item.lockNameFat.name ); + ptr = MemRead( &item.lockNameFat.name ); tracy_free( (void*)ptr ); break; case QueueType::GpuContextName: - ptr = MemRead( &item.gpuContextNameFat.ptr ); + ptr = MemRead( &item.gpuContextNameFat.ptr ); tracy_free( (void*)ptr ); break; #endif case QueueType::GpuAnnotationName: - ptr = MemRead( &item.gpuAnnotationNameFat.ptr ); + ptr = MemRead( &item.gpuAnnotationNameFat.ptr ); tracy_free( (void*)ptr ); break; #ifdef TRACY_ON_DEMAND case QueueType::MessageAppInfo: case QueueType::GpuContextName: + case QueueType::SectionSetup: // Don't free memory associated with deferred messages. break; #endif #ifdef TRACY_HAS_SYSTEM_TRACING case QueueType::ExternalNameMetadata: - ptr = MemRead( &item.externalNameMetadata.name ); + ptr = MemRead( &item.externalNameMetadata.name ); tracy_free( (void*)ptr ); - ptr = MemRead( &item.externalNameMetadata.threadName ); + ptr = MemRead( &item.externalNameMetadata.threadName ); tracy_free_fast( (void*)ptr ); break; #endif case QueueType::SourceCodeMetadata: - ptr = MemRead( &item.sourceCodeMetadata.ptr ); + ptr = MemRead( &item.sourceCodeMetadata.ptr ); + tracy_free( (void*)ptr ); + break; + case QueueType::SectionEnter: + ptr = MemRead( &item.sectionEnterFat.text ); tracy_free( (void*)ptr ); break; +#ifndef TRACY_ON_DEMAND + case QueueType::SectionSetup: + ptr = MemRead( &item.sectionSetupFat.text ); + tracy_free( (void*)ptr ); + break; +#endif default: break; } @@ -2436,7 +2562,7 @@ void Profiler::ClearQueues( moodycamel::ConsumerToken& token ) { for(;;) { - const auto sz = GetQueue().try_dequeue_bulk_single( token, [](const uint64_t&){}, []( QueueItem* item, size_t sz ) { assert( sz > 0 ); while( sz-- > 0 ) FreeAssociatedMemory( *item++ ); } ); + const auto sz = GetQueue().try_dequeue_bulk_single( token, [](const uint64_t&){}, []( QueueItem* item, size_t sz ) { TRACY_ASSERT( sz > 0 ); while( sz-- > 0 ) FreeAssociatedMemory( *item++ ); } ); if( sz == 0 ) break; } @@ -2476,8 +2602,8 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) [this, &connectionLost] ( QueueItem* item, size_t sz ) { if( connectionLost ) return; - InitRpmalloc(); - assert( sz > 0 ); + InitAllocator(); + TRACY_ASSERT( sz > 0 ); int64_t refThread = m_refTimeThread; int64_t refCtx = m_refTimeCtx; int64_t refGpu = m_refTimeGpu; @@ -2485,35 +2611,41 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) { uint64_t ptr; uint16_t size; - auto idx = MemRead( &item->hdr.idx ); + auto idx = MemRead( &item->hdr.idx ); if( idx < (int)QueueType::Terminate ) { switch( (QueueType)idx ) { case QueueType::ZoneText: case QueueType::ZoneName: - ptr = MemRead( &item->zoneTextFat.text ); - size = MemRead( &item->zoneTextFat.size ); + ptr = MemRead( &item->zoneTextFat.text ); + size = MemRead( &item->zoneTextFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; case QueueType::Message: case QueueType::MessageCallstack: - ptr = MemRead( &item->messageFat.text ); - size = MemRead( &item->messageFat.size ); + { + TaggedUserlandAddress taggedPtr = MemRead( &item->messageFat.textAndMetadata ); + ptr = taggedPtr.GetAddress(); + size = MemRead( &item->messageFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; + } case QueueType::MessageColor: case QueueType::MessageColorCallstack: - ptr = MemRead( &item->messageColorFat.text ); - size = MemRead( &item->messageColorFat.size ); + { + TaggedUserlandAddress taggedPtr = MemRead( &item->messageColorFat.textAndMetadata ); + ptr = taggedPtr.GetAddress(); + size = MemRead( &item->messageColorFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; + } case QueueType::MessageAppInfo: - ptr = MemRead( &item->messageFat.text ); - size = MemRead( &item->messageFat.size ); + ptr = MemRead( &item->messageFat.textAndMetadata ).GetAddress(); + size = MemRead( &item->messageFat.size ); SendSingleString( (const char*)ptr, size ); #ifndef TRACY_ON_DEMAND tracy_free_fast( (void*)ptr ); @@ -2522,49 +2654,68 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) case QueueType::ZoneBeginAllocSrcLoc: case QueueType::ZoneBeginAllocSrcLocCallstack: { - int64_t t = MemRead( &item->zoneBegin.time ); + int64_t t = MemRead( &item->zoneBegin.time ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->zoneBegin.time, dt ); - ptr = MemRead( &item->zoneBegin.srcloc ); + ptr = MemRead( &item->zoneBegin.srcloc ); SendSourceLocationPayload( ptr ); tracy_free_fast( (void*)ptr ); break; } case QueueType::Callstack: - ptr = MemRead( &item->callstackFat.ptr ); + ptr = MemRead( &item->callstackFat.ptr ); SendCallstackPayload( ptr ); tracy_free_fast( (void*)ptr ); break; case QueueType::CallstackAlloc: - ptr = MemRead( &item->callstackAllocFat.nativePtr ); + ptr = MemRead( &item->callstackAllocFat.nativePtr ); if( ptr != 0 ) { - CutCallstack( (void*)ptr, "lua_pcall" ); + const char* remove[] = { "lua_pcall", nullptr }; + CutCallstack( (void*)ptr, remove ); SendCallstackPayload( ptr ); tracy_free_fast( (void*)ptr ); } - ptr = MemRead( &item->callstackAllocFat.ptr ); + ptr = MemRead( &item->callstackAllocFat.ptr ); SendCallstackAlloc( ptr ); tracy_free_fast( (void*)ptr ); break; case QueueType::CallstackSample: case QueueType::CallstackSampleContextSwitch: { - ptr = MemRead( &item->callstackSampleFat.ptr ); + ptr = MemRead( &item->callstackSampleFat.ptr ); SendCallstackPayload64( ptr ); tracy_free_fast( (void*)ptr ); - int64_t t = MemRead( &item->callstackSampleFat.time ); + int64_t t = MemRead( &item->callstackSampleFat.time ); int64_t dt = t - refCtx; refCtx = t; + if( dt >= 0 ) + { + if( dt < ProtocolOffset16Bit ) + { + idx = QueueType( idx ) == QueueType::CallstackSample ? (int)QueueType::CallstackSample16 : (int)QueueType::CallstackSampleContextSwitch16; + MemWrite( &item->hdr.idx, idx ); + } + else if( dt < ProtocolOffset32Bit ) + { + dt -= ProtocolOffset16Bit; + idx = QueueType( idx ) == QueueType::CallstackSample ? (int)QueueType::CallstackSample32 : (int)QueueType::CallstackSampleContextSwitch32; + MemWrite( &item->hdr.idx, idx ); + } + else + { + dt -= ProtocolOffset32Bit; + } + } MemWrite( &item->callstackSampleFat.time, dt ); break; } case QueueType::FrameImage: { - ptr = MemRead( &item->frameImageFat.image ); - const auto w = MemRead( &item->frameImageFat.w ); - const auto h = MemRead( &item->frameImageFat.h ); + ptr = MemRead( &item->frameImageFat.image ); + const auto w = MemRead( &item->frameImageFat.w ); + const auto h = MemRead( &item->frameImageFat.h ); const auto csz = size_t( w * h / 2 ); SendLongString( ptr, (const char*)ptr, csz, QueueType::FrameImageData ); tracy_free_fast( (void*)ptr ); @@ -2573,24 +2724,68 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) case QueueType::ZoneBegin: case QueueType::ZoneBeginCallstack: { - int64_t t = MemRead( &item->zoneBegin.time ); + int64_t t = MemRead( &item->zoneBegin.time ); int64_t dt = t - refThread; refThread = t; + if( dt >= 0 ) + { + if( dt < ProtocolOffset16Bit ) + { + const uint64_t srcloc = MemRead( &item->zoneBegin.srcloc ); + idx = QueueType( idx ) == QueueType::ZoneBegin ? (int)QueueType::ZoneBegin16 : (int)QueueType::ZoneBeginCallstack16; + MemWrite( &item->hdr.idx, idx ); + MemWrite( &item->zoneBegin16.time, uint16_t( dt ) ); + MemWrite( &item->zoneBegin16.srcloc, srcloc ); + break; + } + else if( dt < ProtocolOffset32Bit ) + { + dt -= ProtocolOffset16Bit; + const uint64_t srcloc = MemRead( &item->zoneBegin.srcloc ); + idx = QueueType( idx ) == QueueType::ZoneBegin ? (int)QueueType::ZoneBegin32 : (int)QueueType::ZoneBeginCallstack32; + MemWrite( &item->hdr.idx, idx ); + MemWrite( &item->zoneBegin32.time, uint32_t( dt ) ); + MemWrite( &item->zoneBegin32.srcloc, srcloc ); + break; + } + else + { + dt -= ProtocolOffset32Bit; + } + } MemWrite( &item->zoneBegin.time, dt ); break; } case QueueType::ZoneEnd: { - int64_t t = MemRead( &item->zoneEnd.time ); + int64_t t = MemRead( &item->zoneEnd.time ); int64_t dt = t - refThread; refThread = t; + if( dt >= 0 ) + { + if( dt < ProtocolOffset16Bit ) + { + idx = (int)QueueType::ZoneEnd16; + MemWrite( &item->hdr.idx, idx ); + } + else if( dt < ProtocolOffset32Bit ) + { + dt -= ProtocolOffset16Bit; + idx = (int)QueueType::ZoneEnd32; + MemWrite( &item->hdr.idx, idx ); + } + else + { + dt -= ProtocolOffset32Bit; + } + } MemWrite( &item->zoneEnd.time, dt ); break; } case QueueType::GpuZoneBegin: case QueueType::GpuZoneBeginCallstack: { - int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->gpuZoneBegin.cpuTime, dt ); @@ -2599,34 +2794,34 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) case QueueType::GpuZoneBeginAllocSrcLoc: case QueueType::GpuZoneBeginAllocSrcLocCallstack: { - int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->gpuZoneBegin.cpuTime, dt ); - ptr = MemRead( &item->gpuZoneBegin.srcloc ); + ptr = MemRead( &item->gpuZoneBegin.srcloc ); SendSourceLocationPayload( ptr ); tracy_free_fast( (void*)ptr ); break; } case QueueType::GpuZoneEnd: { - int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); + int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->gpuZoneEnd.cpuTime, dt ); break; } case QueueType::GpuContextName: - ptr = MemRead( &item->gpuContextNameFat.ptr ); - size = MemRead( &item->gpuContextNameFat.size ); + ptr = MemRead( &item->gpuContextNameFat.ptr ); + size = MemRead( &item->gpuContextNameFat.size ); SendSingleString( (const char*)ptr, size ); #ifndef TRACY_ON_DEMAND tracy_free_fast( (void*)ptr ); #endif break; case QueueType::GpuAnnotationName: - ptr = MemRead( &item->gpuAnnotationNameFat.ptr ); - size = MemRead( &item->gpuAnnotationNameFat.size ); + ptr = MemRead( &item->gpuAnnotationNameFat.ptr ); + size = MemRead( &item->gpuAnnotationNameFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; @@ -2634,7 +2829,7 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) case QueueType::PlotDataFloat: case QueueType::PlotDataDouble: { - int64_t t = MemRead( &item->plotDataInt.time ); + int64_t t = MemRead( &item->plotDataInt.time ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->plotDataInt.time, dt ); @@ -2642,7 +2837,7 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) } case QueueType::ContextSwitch: { - int64_t t = MemRead( &item->contextSwitch.time ); + int64_t t = MemRead( &item->contextSwitch.time ); int64_t dt = t - refCtx; refCtx = t; MemWrite( &item->contextSwitch.time, dt ); @@ -2650,7 +2845,7 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) } case QueueType::ThreadWakeup: { - int64_t t = MemRead( &item->threadWakeup.time ); + int64_t t = MemRead( &item->threadWakeup.time ); int64_t dt = t - refCtx; refCtx = t; MemWrite( &item->threadWakeup.time, dt ); @@ -2658,7 +2853,7 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) } case QueueType::GpuTime: { - int64_t t = MemRead( &item->gpuTime.gpuTime ); + int64_t t = MemRead( &item->gpuTime.gpuTime ); int64_t dt = t - refGpu; refGpu = t; MemWrite( &item->gpuTime.gpuTime, dt ); @@ -2667,9 +2862,9 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) #ifdef TRACY_HAS_CALLSTACK case QueueType::CallstackFrameSize: { - auto data = (const CallstackEntry*)MemRead( &item->callstackFrameSizeFat.data ); - auto datasz = MemRead( &item->callstackFrameSizeFat.size ); - auto imageName = (const char*)MemRead( &item->callstackFrameSizeFat.imageName ); + auto data = (const CallstackEntry*)MemRead( &item->callstackFrameSizeFat.data ); + auto datasz = MemRead( &item->callstackFrameSizeFat.size ); + auto imageName = (const char*)MemRead( &item->callstackFrameSizeFat.imageName ); SendSingleString( imageName ); AppendData( item++, QueueDataSize[idx] ); @@ -2696,17 +2891,17 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) } case QueueType::SymbolInformation: { - auto fileString = (const char*)MemRead( &item->symbolInformationFat.fileString ); - auto needFree = MemRead( &item->symbolInformationFat.needFree ); + auto fileString = (const char*)MemRead( &item->symbolInformationFat.fileString ); + auto needFree = MemRead( &item->symbolInformationFat.needFree ); SendSingleString( fileString ); if( needFree ) tracy_free_fast( (void*)fileString ); break; } case QueueType::SymbolCodeMetadata: { - auto symbol = MemRead( &item->symbolCodeMetadata.symbol ); - auto ptr = (const char*)MemRead( &item->symbolCodeMetadata.ptr ); - auto size = MemRead( &item->symbolCodeMetadata.size ); + auto symbol = MemRead( &item->symbolCodeMetadata.symbol ); + auto ptr = (const char*)MemRead( &item->symbolCodeMetadata.ptr ); + auto size = MemRead( &item->symbolCodeMetadata.size ); SendLongString( symbol, ptr, size, QueueType::SymbolCode ); tracy_free_fast( (void*)ptr ); ++item; @@ -2716,9 +2911,9 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) #ifdef TRACY_HAS_SYSTEM_TRACING case QueueType::ExternalNameMetadata: { - auto thread = MemRead( &item->externalNameMetadata.thread ); - auto name = (const char*)MemRead( &item->externalNameMetadata.name ); - auto threadName = (const char*)MemRead( &item->externalNameMetadata.threadName ); + auto thread = MemRead( &item->externalNameMetadata.thread ); + auto name = (const char*)MemRead( &item->externalNameMetadata.name ); + auto threadName = (const char*)MemRead( &item->externalNameMetadata.threadName ); SendString( thread, threadName, QueueType::ExternalThreadName ); SendString( thread, name, QueueType::ExternalName ); tracy_free_fast( (void*)threadName ); @@ -2729,16 +2924,44 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) #endif case QueueType::SourceCodeMetadata: { - auto ptr = (const char*)MemRead( &item->sourceCodeMetadata.ptr ); - auto size = MemRead( &item->sourceCodeMetadata.size ); - auto id = MemRead( &item->sourceCodeMetadata.id ); + auto ptr = (const char*)MemRead( &item->sourceCodeMetadata.ptr ); + auto size = MemRead( &item->sourceCodeMetadata.size ); + auto id = MemRead( &item->sourceCodeMetadata.id ); SendLongString( (uint64_t)id, ptr, size, QueueType::SourceCode ); tracy_free_fast( (void*)ptr ); ++item; continue; } + case QueueType::SectionEnter: + { + int64_t t = MemRead( &item->sectionEnter.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->sectionEnter.time, dt ); + ptr = MemRead( &item->sectionEnterFat.text ); + size = MemRead( &item->sectionEnterFat.size ); + SendSingleString( (const char*)ptr, size ); + tracy_free_fast( (void*)ptr ); + break; + } + case QueueType::SectionLeave: + { + int64_t t = MemRead( &item->sectionLeave.time ); + int64_t dt = t - refThread; + refThread = t; + MemWrite( &item->sectionLeave.time, dt ); + break; + } + case QueueType::SectionSetup: + ptr = MemRead( &item->sectionSetupFat.text ); + size = MemRead( &item->sectionSetupFat.size ); + SendSingleString( (const char*)ptr, size ); +#ifndef TRACY_ON_DEMAND + tracy_free_fast( (void*)ptr ); +#endif + break; default: - assert( false ); + TRACY_ASSERT( false ); break; } } @@ -2765,16 +2988,16 @@ Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::Con const auto sz = GetQueue().try_dequeue_bulk_single( token, [] ( const uint64_t& ) {}, [this, &timeStop] ( QueueItem* item, size_t sz ) { - assert( sz > 0 ); + TRACY_ASSERT( sz > 0 ); int64_t refCtx = m_refTimeCtx; while( sz-- > 0 ) { FreeAssociatedMemory( *item ); if( timeStop < 0 ) return; - const auto idx = MemRead( &item->hdr.idx ); + const auto idx = MemRead( &item->hdr.idx ); if( idx == (uint8_t)QueueType::ContextSwitch ) { - const auto csTime = MemRead( &item->contextSwitch.time ); + const auto csTime = MemRead( &item->contextSwitch.time ); if( csTime > timeStop ) { timeStop = -1; @@ -2793,7 +3016,7 @@ Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::Con } else if( idx == (uint8_t)QueueType::ThreadWakeup ) { - const auto csTime = MemRead( &item->threadWakeup.time ); + const auto csTime = MemRead( &item->threadWakeup.time ); if( csTime > timeStop ) { timeStop = -1; @@ -2821,13 +3044,13 @@ Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::Con } #define ThreadCtxCheckSerial( _name ) \ - uint32_t thread = MemRead( &item->_name.thread ); \ + uint32_t thread = MemRead( &item->_name.thread ); \ switch( ThreadCtxCheck( thread ) ) \ { \ case ThreadCtxStatus::Same: break; \ - case ThreadCtxStatus::Changed: assert( m_refTimeThread == 0 ); refThread = 0; break; \ + case ThreadCtxStatus::Changed: TRACY_ASSERT( m_refTimeThread == 0 ); refThread = 0; break; \ case ThreadCtxStatus::ConnectionLost: return DequeueStatus::ConnectionLost; \ - default: assert( false ); break; \ + default: TRACY_ASSERT( false ); break; \ } Profiler::DequeueStatus Profiler::DequeueSerial() @@ -2856,7 +3079,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() { dequeueStatus = DequeueStatus::DataDequeued; - InitRpmalloc(); + InitAllocator(); int64_t refSerial = m_refTimeSerial; int64_t refGpu = m_refTimeGpu; #ifdef TRACY_FIBERS @@ -2867,20 +3090,20 @@ Profiler::DequeueStatus Profiler::DequeueSerial() while( item != end ) { uint64_t ptr; - auto idx = MemRead( &item->hdr.idx ); + auto idx = MemRead( &item->hdr.idx ); if( idx < (int)QueueType::Terminate ) { switch( (QueueType)idx ) { case QueueType::CallstackSerial: - ptr = MemRead( &item->callstackFat.ptr ); + ptr = MemRead( &item->callstackFat.ptr ); SendCallstackPayload( ptr ); tracy_free_fast( (void*)ptr ); break; case QueueType::LockWait: case QueueType::LockSharedWait: { - int64_t t = MemRead( &item->lockWait.time ); + int64_t t = MemRead( &item->lockWait.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->lockWait.time, dt ); @@ -2889,7 +3112,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::LockObtain: case QueueType::LockSharedObtain: { - int64_t t = MemRead( &item->lockObtain.time ); + int64_t t = MemRead( &item->lockObtain.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->lockObtain.time, dt ); @@ -2898,7 +3121,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::LockRelease: case QueueType::LockSharedRelease: { - int64_t t = MemRead( &item->lockRelease.time ); + int64_t t = MemRead( &item->lockRelease.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->lockRelease.time, dt ); @@ -2906,8 +3129,8 @@ Profiler::DequeueStatus Profiler::DequeueSerial() } case QueueType::LockName: { - ptr = MemRead( &item->lockNameFat.name ); - uint16_t size = MemRead( &item->lockNameFat.size ); + ptr = MemRead( &item->lockNameFat.name ); + uint16_t size = MemRead( &item->lockNameFat.size ); SendSingleString( (const char*)ptr, size ); #ifndef TRACY_ON_DEMAND tracy_free_fast( (void*)ptr ); @@ -2919,7 +3142,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::MemAllocCallstack: case QueueType::MemAllocCallstackNamed: { - int64_t t = MemRead( &item->memAlloc.time ); + int64_t t = MemRead( &item->memAlloc.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->memAlloc.time, dt ); @@ -2930,7 +3153,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::MemFreeCallstack: case QueueType::MemFreeCallstackNamed: { - int64_t t = MemRead( &item->memFree.time ); + int64_t t = MemRead( &item->memFree.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->memFree.time, dt ); @@ -2939,7 +3162,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::MemDiscard: case QueueType::MemDiscardCallstack: { - int64_t t = MemRead( &item->memDiscard.time ); + int64_t t = MemRead( &item->memDiscard.time ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->memDiscard.time, dt ); @@ -2948,7 +3171,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::GpuZoneBeginSerial: case QueueType::GpuZoneBeginCallstackSerial: { - int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->gpuZoneBegin.cpuTime, dt ); @@ -2957,18 +3180,18 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::GpuZoneBeginAllocSrcLocSerial: case QueueType::GpuZoneBeginAllocSrcLocCallstackSerial: { - int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); + int64_t t = MemRead( &item->gpuZoneBegin.cpuTime ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->gpuZoneBegin.cpuTime, dt ); - ptr = MemRead( &item->gpuZoneBegin.srcloc ); + ptr = MemRead( &item->gpuZoneBegin.srcloc ); SendSourceLocationPayload( ptr ); tracy_free_fast( (void*)ptr ); break; } case QueueType::GpuZoneEndSerial: { - int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); + int64_t t = MemRead( &item->gpuZoneEnd.cpuTime ); int64_t dt = t - refSerial; refSerial = t; MemWrite( &item->gpuZoneEnd.cpuTime, dt ); @@ -2976,7 +3199,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() } case QueueType::GpuTime: { - int64_t t = MemRead( &item->gpuTime.gpuTime ); + int64_t t = MemRead( &item->gpuTime.gpuTime ); int64_t dt = t - refGpu; refGpu = t; MemWrite( &item->gpuTime.gpuTime, dt ); @@ -2984,8 +3207,8 @@ Profiler::DequeueStatus Profiler::DequeueSerial() } case QueueType::GpuContextName: { - ptr = MemRead( &item->gpuContextNameFat.ptr ); - uint16_t size = MemRead( &item->gpuContextNameFat.size ); + ptr = MemRead( &item->gpuContextNameFat.ptr ); + uint16_t size = MemRead( &item->gpuContextNameFat.size ); SendSingleString( (const char*)ptr, size ); #ifndef TRACY_ON_DEMAND tracy_free_fast( (void*)ptr ); @@ -2994,8 +3217,8 @@ Profiler::DequeueStatus Profiler::DequeueSerial() } case QueueType::GpuAnnotationName: { - ptr = MemRead( &item->gpuAnnotationNameFat.ptr ); - uint16_t size = MemRead( &item->gpuAnnotationNameFat.size ); + ptr = MemRead( &item->gpuAnnotationNameFat.ptr ); + uint16_t size = MemRead( &item->gpuAnnotationNameFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; @@ -3005,9 +3228,35 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::ZoneBeginCallstack: { ThreadCtxCheckSerial( zoneBeginThread ); - int64_t t = MemRead( &item->zoneBegin.time ); + int64_t t = MemRead( &item->zoneBegin.time ); int64_t dt = t - refThread; refThread = t; + if( dt >= 0 ) + { + if( dt < ProtocolOffset16Bit ) + { + const uint64_t srcloc = MemRead( &item->zoneBegin.srcloc ); + idx = QueueType( idx ) == QueueType::ZoneBegin ? (int)QueueType::ZoneBegin16 : (int)QueueType::ZoneBeginCallstack16; + MemWrite( &item->hdr.idx, idx ); + MemWrite( &item->zoneBegin16.time, uint16_t( dt ) ); + MemWrite( &item->zoneBegin16.srcloc, srcloc ); + break; + } + else if( dt < ProtocolOffset32Bit ) + { + dt -= ProtocolOffset16Bit; + const uint64_t srcloc = MemRead( &item->zoneBegin.srcloc ); + idx = QueueType( idx ) == QueueType::ZoneBegin ? (int)QueueType::ZoneBegin32 : (int)QueueType::ZoneBeginCallstack32; + MemWrite( &item->hdr.idx, idx ); + MemWrite( &item->zoneBegin32.time, uint32_t( dt ) ); + MemWrite( &item->zoneBegin32.srcloc, srcloc ); + break; + } + else + { + dt -= ProtocolOffset32Bit; + } + } MemWrite( &item->zoneBegin.time, dt ); break; } @@ -3015,11 +3264,11 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::ZoneBeginAllocSrcLocCallstack: { ThreadCtxCheckSerial( zoneBeginThread ); - int64_t t = MemRead( &item->zoneBegin.time ); + int64_t t = MemRead( &item->zoneBegin.time ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->zoneBegin.time, dt ); - ptr = MemRead( &item->zoneBegin.srcloc ); + ptr = MemRead( &item->zoneBegin.srcloc ); SendSourceLocationPayload( ptr ); tracy_free_fast( (void*)ptr ); break; @@ -3027,9 +3276,27 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::ZoneEnd: { ThreadCtxCheckSerial( zoneEndThread ); - int64_t t = MemRead( &item->zoneEnd.time ); + int64_t t = MemRead( &item->zoneEnd.time ); int64_t dt = t - refThread; refThread = t; + if( dt >= 0 ) + { + if( dt < ProtocolOffset16Bit ) + { + idx = (int)QueueType::ZoneEnd16; + MemWrite( &item->hdr.idx, idx ); + } + else if( dt < ProtocolOffset32Bit ) + { + dt -= ProtocolOffset16Bit; + idx = (int)QueueType::ZoneEnd32; + MemWrite( &item->hdr.idx, idx ); + } + else + { + dt -= ProtocolOffset32Bit; + } + } MemWrite( &item->zoneEnd.time, dt ); break; } @@ -3037,8 +3304,8 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::ZoneName: { ThreadCtxCheckSerial( zoneTextFatThread ); - ptr = MemRead( &item->zoneTextFat.text ); - uint16_t size = MemRead( &item->zoneTextFat.size ); + ptr = MemRead( &item->zoneTextFat.text ); + uint16_t size = MemRead( &item->zoneTextFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; @@ -3047,8 +3314,9 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::MessageCallstack: { ThreadCtxCheckSerial( messageFatThread ); - ptr = MemRead( &item->messageFat.text ); - uint16_t size = MemRead( &item->messageFat.size ); + TaggedUserlandAddress taggedPtr = MemRead( &item->messageFat.textAndMetadata ); + ptr = taggedPtr.GetAddress(); + uint16_t size = MemRead( &item->messageFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; @@ -3057,8 +3325,9 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::MessageColorCallstack: { ThreadCtxCheckSerial( messageColorFatThread ); - ptr = MemRead( &item->messageColorFat.text ); - uint16_t size = MemRead( &item->messageColorFat.size ); + TaggedUserlandAddress taggedPtr = MemRead( &item->messageColorFat.textAndMetadata ); + ptr = taggedPtr.GetAddress(); + uint16_t size = MemRead( &item->messageColorFat.size ); SendSingleString( (const char*)ptr, size ); tracy_free_fast( (void*)ptr ); break; @@ -3066,7 +3335,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::Callstack: { ThreadCtxCheckSerial( callstackFatThread ); - ptr = MemRead( &item->callstackFat.ptr ); + ptr = MemRead( &item->callstackFat.ptr ); SendCallstackPayload( ptr ); tracy_free_fast( (void*)ptr ); break; @@ -3074,14 +3343,15 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::CallstackAlloc: { ThreadCtxCheckSerial( callstackAllocFatThread ); - ptr = MemRead( &item->callstackAllocFat.nativePtr ); + ptr = MemRead( &item->callstackAllocFat.nativePtr ); if( ptr != 0 ) { - CutCallstack( (void*)ptr, "lua_pcall" ); + const char* remove[] = { "lua_pcall", nullptr }; + CutCallstack( (void*)ptr, remove ); SendCallstackPayload( ptr ); tracy_free_fast( (void*)ptr ); } - ptr = MemRead( &item->callstackAllocFat.ptr ); + ptr = MemRead( &item->callstackAllocFat.ptr ); SendCallstackAlloc( ptr ); tracy_free_fast( (void*)ptr ); break; @@ -3089,7 +3359,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::FiberEnter: { ThreadCtxCheckSerial( fiberEnter ); - int64_t t = MemRead( &item->fiberEnter.time ); + int64_t t = MemRead( &item->fiberEnter.time ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->fiberEnter.time, dt ); @@ -3098,7 +3368,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() case QueueType::FiberLeave: { ThreadCtxCheckSerial( fiberLeave ); - int64_t t = MemRead( &item->fiberLeave.time ); + int64_t t = MemRead( &item->fiberLeave.time ); int64_t dt = t - refThread; refThread = t; MemWrite( &item->fiberLeave.time, dt ); @@ -3106,7 +3376,7 @@ Profiler::DequeueStatus Profiler::DequeueSerial() } #endif default: - assert( false ); + TRACY_ASSERT( false ); break; } } @@ -3193,12 +3463,14 @@ char* Profiler::SafeCopyProlog( const char* data, size_t size ) bool success = true; char* buf = m_safeSendBuffer; #ifndef NDEBUG - assert( !m_inUse.exchange(true) ); + TRACY_ASSERT( !m_inUse.exchange(true) ); #endif if( size > SafeSendBufferSize ) buf = (char*)tracy_malloc( size ); -#ifdef _WIN32 +#if defined TRACY_HAS_CUSTOM_SAFE_COPY + success = PlatformSafeMemcpy( buf, data, size ); +#elif defined _WIN32 # ifdef _MSC_VER __try { @@ -3257,7 +3529,7 @@ bool Profiler::SendData( const char* data, size_t len ) void Profiler::SendString( uint64_t str, const char* ptr, size_t len, QueueType type ) { - assert( type == QueueType::StringData || + TRACY_ASSERT( type == QueueType::StringData || type == QueueType::ThreadName || type == QueueType::PlotName || type == QueueType::FrameName || @@ -3269,7 +3541,7 @@ void Profiler::SendString( uint64_t str, const char* ptr, size_t len, QueueType MemWrite( &item.hdr.type, type ); MemWrite( &item.stringTransfer.ptr, str ); - assert( len <= std::numeric_limits::max() ); + TRACY_ASSERT( len <= std::numeric_limits::max() ); auto l16 = uint16_t( len ); NeedDataSize( QueueDataSize[(int)type] + sizeof( l16 ) + l16 ); @@ -3279,39 +3551,71 @@ void Profiler::SendString( uint64_t str, const char* ptr, size_t len, QueueType AppendDataUnsafe( ptr, l16 ); } -void Profiler::SendSingleString( const char* ptr, size_t len ) +void Profiler::SendSingleString8( const char* ptr, size_t len ) +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SingleStringData8 ); + + TRACY_ASSERT( len <= std::numeric_limits::max() ); + auto l8 = uint8_t( len ); + + NeedDataSize( QueueDataSize[(int)QueueType::SingleStringData8] + sizeof( l8 ) + len ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SingleStringData8] ); + AppendDataUnsafe( &l8, sizeof( l8 ) ); + AppendDataUnsafe( ptr, len ); +} + +void Profiler::SendSingleString16( const char* ptr, size_t len ) { QueueItem item; MemWrite( &item.hdr.type, QueueType::SingleStringData ); - assert( len <= std::numeric_limits::max() ); - auto l16 = uint16_t( len ); + TRACY_ASSERT( len > std::numeric_limits::max() ); + TRACY_ASSERT( len <= ProtocolOffset8Bit + std::numeric_limits::max() ); + auto l16 = uint16_t( len - ProtocolOffset8Bit ); - NeedDataSize( QueueDataSize[(int)QueueType::SingleStringData] + sizeof( l16 ) + l16 ); + NeedDataSize( QueueDataSize[(int)QueueType::SingleStringData] + sizeof( l16 ) + len ); AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SingleStringData] ); AppendDataUnsafe( &l16, sizeof( l16 ) ); - AppendDataUnsafe( ptr, l16 ); + AppendDataUnsafe( ptr, len ); } -void Profiler::SendSecondString( const char* ptr, size_t len ) +void Profiler::SendSecondString8( const char* ptr, size_t len ) +{ + QueueItem item; + MemWrite( &item.hdr.type, QueueType::SecondStringData8 ); + + TRACY_ASSERT( len <= std::numeric_limits::max() ); + auto l8 = uint8_t( len ); + + NeedDataSize( QueueDataSize[(int)QueueType::SecondStringData8] + sizeof( l8 ) + len ); + + AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SecondStringData8] ); + AppendDataUnsafe( &l8, sizeof( l8 ) ); + AppendDataUnsafe( ptr, len ); +} + +void Profiler::SendSecondString16( const char* ptr, size_t len ) { QueueItem item; MemWrite( &item.hdr.type, QueueType::SecondStringData ); - assert( len <= std::numeric_limits::max() ); - auto l16 = uint16_t( len ); + TRACY_ASSERT( len > std::numeric_limits::max() ); + TRACY_ASSERT( len <= ProtocolOffset8Bit + std::numeric_limits::max() ); + auto l16 = uint16_t( len - ProtocolOffset8Bit ); - NeedDataSize( QueueDataSize[(int)QueueType::SecondStringData] + sizeof( l16 ) + l16 ); + NeedDataSize( QueueDataSize[(int)QueueType::SecondStringData] + sizeof( l16 ) + len ); AppendDataUnsafe( &item, QueueDataSize[(int)QueueType::SecondStringData] ); AppendDataUnsafe( &l16, sizeof( l16 ) ); - AppendDataUnsafe( ptr, l16 ); + AppendDataUnsafe( ptr, len ); } void Profiler::SendLongString( uint64_t str, const char* ptr, size_t len, QueueType type ) { - assert( type == QueueType::FrameImageData || + TRACY_ASSERT( type == QueueType::FrameImageData || type == QueueType::SymbolCode || type == QueueType::SourceCode ); @@ -3319,8 +3623,8 @@ void Profiler::SendLongString( uint64_t str, const char* ptr, size_t len, QueueT MemWrite( &item.hdr.type, type ); MemWrite( &item.stringTransfer.ptr, str ); - assert( len <= std::numeric_limits::max() ); - assert( QueueDataSize[(int)type] + sizeof( uint32_t ) + len <= TargetFrameSize ); + TRACY_ASSERT( len <= std::numeric_limits::max() ); + TRACY_ASSERT( QueueDataSize[(int)type] + sizeof( uint32_t ) + len <= TargetFrameSize ); auto l32 = uint32_t( len ); NeedDataSize( QueueDataSize[(int)type] + sizeof( l32 ) + l32 ); @@ -3355,7 +3659,7 @@ void Profiler::SendSourceLocationPayload( uint64_t _ptr ) uint16_t len; memcpy( &len, ptr, sizeof( len ) ); - assert( len > 2 ); + TRACY_ASSERT( len > 2 ); len -= 2; ptr += 2; @@ -3453,7 +3757,7 @@ void Profiler::QueueSymbolQuery( uint64_t symbol ) SendSingleString( "" ); QueueItem item; MemWrite( &item.hdr.type, QueueType::SymbolInformation ); - MemWrite( &item.symbolInformation.line, 0 ); + MemWrite( &item.symbolInformation.line, uint32_t( 0 ) ); MemWrite( &item.symbolInformation.symAddr, symbol ); AppendData( &item, QueueDataSize[(int)QueueType::SymbolInformation] ); } @@ -3475,7 +3779,7 @@ void Profiler::QueueExternalName( uint64_t ptr ) void Profiler::QueueKernelCode( uint64_t symbol, uint32_t size ) { - assert( symbol >> 63 != 0 ); + TRACY_ASSERT( symbol >> 63 != 0 ); #ifdef TRACY_HAS_CALLSTACK m_symbolQueue.emplace( SymbolQueueItem { SymbolQueueItemType::KernelCode, symbol, size } ); #else @@ -3485,8 +3789,7 @@ void Profiler::QueueKernelCode( uint64_t symbol, uint32_t size ) void Profiler::QueueSourceCodeQuery( uint32_t id ) { - assert( m_exectime != 0 ); - assert( m_queryData ); + TRACY_ASSERT( m_queryData ); m_symbolQueue.emplace( SymbolQueueItem { SymbolQueueItemType::SourceCode, uint64_t( m_queryData ), uint64_t( m_queryImage ), id } ); m_queryData = nullptr; m_queryImage = nullptr; @@ -3594,7 +3897,7 @@ void Profiler::HandleSymbolQueueItem( const SymbolQueueItem& si ) HandleSourceCodeQuery( (char*)si.ptr, (char*)si.extra, si.id ); break; default: - assert( false ); + TRACY_ASSERT( false ); break; } } @@ -3607,9 +3910,7 @@ void Profiler::SymbolWorker() ThreadExitHandler threadExitHandler; SetThreadName( "Tracy Symbol Worker" ); -#ifdef TRACY_USE_RPMALLOC - InitRpmalloc(); -#endif + InitAllocator(); InitCallstack(); while( m_timeBegin.load( std::memory_order_relaxed ) == 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); @@ -3643,7 +3944,10 @@ void Profiler::SymbolWorker() s_symbolThreadGone.store( true, std::memory_order_release ); return; } - std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) ); + // Symbol Worker is idle: wait for Profiler Worker to enqueue more symbol queries + // (having a timeout ensures progress even if notifications are missed) + std::unique_lock lock( m_symbolQueueMutex ); + m_symbolQueueSignal.wait_for( lock, std::chrono::milliseconds( 20 ), [this]() { return !m_symbolQueue.empty(); } ); } } } @@ -3723,12 +4027,19 @@ bool Profiler::HandleServerQuery() break; #endif case ServerQuerySourceCode: +#ifdef TRACY_HAS_CALLSTACK QueueSourceCodeQuery( uint32_t( ptr ) ); +#else + TRACY_ASSERT( m_queryData ); + HandleSourceCodeQuery( m_queryData, m_queryImage, uint32_t( ptr ) ); + m_queryData = nullptr; + m_queryImage = nullptr; +#endif break; case ServerQueryDataTransfer: if( m_queryData ) { - assert( !m_queryImage ); + TRACY_ASSERT( !m_queryImage ); m_queryImage = m_queryData; } m_queryDataPtr = m_queryData = (char*)tracy_malloc( ptr + 11 ); @@ -3746,7 +4057,7 @@ bool Profiler::HandleServerQuery() break; #endif default: - assert( false ); + TRACY_ASSERT( false ); break; } @@ -3910,7 +4221,7 @@ void Profiler::ReportTopology() { packageInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( psz ); auto res = _GetLogicalProcessorInformationEx( RelationProcessorPackage, packageInfo, &psz ); - assert( res ); + TRACY_ASSERT( res ); } else { @@ -3923,7 +4234,7 @@ void Profiler::ReportTopology() { dieInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( dsz ); auto res = _GetLogicalProcessorInformationEx( RelationProcessorDie, dieInfo, &dsz ); - assert( res ); + TRACY_ASSERT( res ); } else { @@ -3936,7 +4247,7 @@ void Profiler::ReportTopology() { coreInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)tracy_malloc( csz ); auto res = _GetLogicalProcessorInformationEx( RelationProcessorCore, coreInfo, &csz ); - assert( res ); + TRACY_ASSERT( res ); } else { @@ -3955,7 +4266,7 @@ void Profiler::ReportTopology() auto ptr = packageInfo; while( (char*)ptr < ((char*)packageInfo) + psz ) { - assert( ptr->Relationship == RelationProcessorPackage ); + TRACY_ASSERT( ptr->Relationship == RelationProcessorPackage ); // FIXME account for GroupCount auto mask = ptr->Processor.GroupMask[0].Mask; int core = 0; @@ -3973,7 +4284,7 @@ void Profiler::ReportTopology() ptr = dieInfo; while( (char*)ptr < ((char*)dieInfo) + dsz ) { - assert( ptr->Relationship == RelationProcessorDie ); + TRACY_ASSERT( ptr->Relationship == RelationProcessorDie ); // FIXME account for GroupCount auto mask = ptr->Processor.GroupMask[0].Mask; int core = 0; @@ -3991,7 +4302,7 @@ void Profiler::ReportTopology() ptr = coreInfo; while( (char*)ptr < ((char*)coreInfo) + csz ) { - assert( ptr->Relationship == RelationProcessorCore ); + TRACY_ASSERT( ptr->Relationship == RelationProcessorCore ); // FIXME account for GroupCount auto mask = ptr->Processor.GroupMask[0].Mask; int core = 0; @@ -4091,7 +4402,7 @@ void Profiler::ReportTopology() #endif } -void Profiler::SendCallstack( int32_t depth, const char* skipBefore ) +void Profiler::SendCallstack( int32_t depth, const char** skipBefore ) { #ifdef TRACY_HAS_CALLSTACK auto ptr = Callstack( depth ); @@ -4103,23 +4414,29 @@ void Profiler::SendCallstack( int32_t depth, const char* skipBefore ) #endif } -void Profiler::CutCallstack( void* callstack, const char* skipBefore ) +void Profiler::CutCallstack( void* callstack, const char** skipBefore ) { #ifdef TRACY_HAS_CALLSTACK auto data = (uintptr_t*)callstack; const auto sz = *data++; uintptr_t i; - for( i=0; i> 32 ); const auto val = int32_t( payload & 0xFFFFFFFF ); m_paramCallback( m_paramCallbackData, idx, val ); @@ -4163,21 +4480,38 @@ void Profiler::HandleSymbolCodeQuery( uint64_t symbol, uint32_t size ) if( symbol >> 63 != 0 ) { QueueKernelCode( symbol, size ); + return; } - else - { - auto&& lambda = [ this, symbol ]( const char* buf, size_t size ) { - SendLongString( symbol, buf, size, QueueType::SymbolCode ); - }; - // 'symbol' may have come from a module that has since unloaded, perform a safe copy before sending - if( !WithSafeCopy( (const char*)symbol, size, lambda ) ) AckSymbolCodeNotAvailable(); + auto&& lambda = [ this, symbol ]( const char* buf, size_t size ) { + SendLongString( symbol, buf, size, QueueType::SymbolCode ); + }; + +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( GetExternalTargetPid() != 0 ) + { + auto buf = (char*)tracy_malloc_fast( size ); + if( ReadExternalTargetMemory( symbol, size, buf ) == size ) + { + lambda( buf, size ); + } + else + { + AckSymbolCodeNotAvailable(); + } + tracy_free_fast( buf ); + return; } +#endif + + // 'symbol' may have come from a module that has since unloaded, perform a safe copy before sending + if( !WithSafeCopy( (const char*)symbol, size, lambda ) ) AckSymbolCodeNotAvailable(); } void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) { bool ok = false; +#ifndef TRACY_NO_CODE_TRANSFER FILE* f = fopen( data, "rb" ); if( f ) { @@ -4203,7 +4537,7 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) fclose( f ); } -#ifdef TRACY_DEBUGINFOD +# ifdef TRACY_DEBUGINFOD else if( image && data[0] == '/' ) { size_t size; @@ -4211,7 +4545,7 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) if( buildid ) { auto d = debuginfod_find_source( GetDebuginfodClient(), buildid, size, data, nullptr ); - TracyDebug( "DebugInfo source query: %s, fn: %s, image: %s\n", d >= 0 ? " ok " : "fail", data, image ); + TracyDebug( "DebugInfo source query: %s, fn: %s, image: %s", d >= 0 ? " ok " : "fail", data, image ); if( d >= 0 ) { struct stat st; @@ -4241,9 +4575,9 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) } else { - TracyDebug( "DebugInfo invalid query fn: %s, image: %s\n", data, image ); + TracyDebug( "DebugInfo invalid query fn: %s, image: %s", data, image ); } -#endif +# endif if( !ok && m_sourceCallback ) { @@ -4266,11 +4600,12 @@ void Profiler::HandleSourceCodeQuery( char* data, char* image, uint32_t id ) } } } +#endif if( !ok ) { TracyLfqPrepare( QueueType::AckSourceCodeNotAvailable ); - MemWrite( &item->sourceCodeNotAvailable, id ); + MemWrite( &item->sourceCodeNotAvailable.id, id ); TracyLfqCommit; } @@ -4302,6 +4637,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin( const struct ___tracy_source_l ctx.active = active; #endif if( !ctx.active ) return ctx; +#ifdef TRACY_ON_DEMAND + ctx.connectionId = tracy::GetProfiler().ConnectionId(); +#endif const auto id = tracy::GetProfiler().GetNextZoneId(); ctx.id = id; @@ -4330,6 +4668,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_callstack( const struct ___trac ctx.active = active; #endif if( !ctx.active ) return ctx; +#ifdef TRACY_ON_DEMAND + ctx.connectionId = tracy::GetProfiler().ConnectionId(); +#endif const auto id = tracy::GetProfiler().GetNextZoneId(); ctx.id = id; @@ -4367,6 +4708,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc( uint64_t srcloc, int32_t tracy::tracy_free( (void*)srcloc ); return ctx; } +#ifdef TRACY_ON_DEMAND + ctx.connectionId = tracy::GetProfiler().ConnectionId(); +#endif const auto id = tracy::GetProfiler().GetNextZoneId(); ctx.id = id; @@ -4399,6 +4743,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc_callstack( uint64_t srclo tracy::tracy_free( (void*)srcloc ); return ctx; } +#ifdef TRACY_ON_DEMAND + ctx.connectionId = tracy::GetProfiler().ConnectionId(); +#endif const auto id = tracy::GetProfiler().GetNextZoneId(); ctx.id = id; @@ -4426,6 +4773,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc_callstack( uint64_t srclo TRACY_API void ___tracy_emit_zone_end( TracyCZoneCtx ctx ) { if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif #ifndef TRACY_NO_VERIFY { TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); @@ -4442,8 +4792,11 @@ TRACY_API void ___tracy_emit_zone_end( TracyCZoneCtx ctx ) TRACY_API void ___tracy_emit_zone_text( TracyCZoneCtx ctx, const char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + TRACY_ASSERT( size < std::numeric_limits::max() ); if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif auto ptr = (char*)tracy::tracy_malloc( size ); memcpy( ptr, txt, size ); #ifndef TRACY_NO_VERIFY @@ -4461,10 +4814,44 @@ TRACY_API void ___tracy_emit_zone_text( TracyCZoneCtx ctx, const char* txt, size } } +TRACY_API void ___tracy_emit_zone_text_fmt( TracyCZoneCtx ctx, const char* fmt, ... ) +{ + if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return; + TRACY_ASSERT( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy::tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + TracyQueuePrepareC( tracy::QueueType::ZoneText ); + tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommitC( zoneTextFatThread ); +} + TRACY_API void ___tracy_emit_zone_name( TracyCZoneCtx ctx, const char* txt, size_t size ) { - assert( size < std::numeric_limits::max() ); + TRACY_ASSERT( size < std::numeric_limits::max() ); if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif auto ptr = (char*)tracy::tracy_malloc( size ); memcpy( ptr, txt, size ); #ifndef TRACY_NO_VERIFY @@ -4482,8 +4869,42 @@ TRACY_API void ___tracy_emit_zone_name( TracyCZoneCtx ctx, const char* txt, size } } +TRACY_API void ___tracy_emit_zone_name_fmt( TracyCZoneCtx ctx, const char* fmt, ... ) +{ + if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return; + TRACY_ASSERT( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy::tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + +#ifndef TRACY_NO_VERIFY + { + TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); + tracy::MemWrite( &item->zoneValidation.id, ctx.id ); + TracyQueueCommitC( zoneValidationThread ); + } +#endif + TracyQueuePrepareC( tracy::QueueType::ZoneName ); + tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr ); + tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size ); + TracyQueueCommitC( zoneTextFatThread ); +} + TRACY_API void ___tracy_emit_zone_color( TracyCZoneCtx ctx, uint32_t color ) { if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif #ifndef TRACY_NO_VERIFY { TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); @@ -4503,6 +4924,9 @@ TRACY_API void ___tracy_emit_zone_color( TracyCZoneCtx ctx, uint32_t color ) { TRACY_API void ___tracy_emit_zone_value( TracyCZoneCtx ctx, uint64_t value ) { if( !ctx.active ) return; +#ifdef TRACY_ON_DEMAND + if( tracy::GetProfiler().ConnectionId() != ctx.connectionId ) return; +#endif #ifndef TRACY_NO_VERIFY { TracyQueuePrepareC( tracy::QueueType::ZoneValidation ); @@ -4517,64 +4941,64 @@ TRACY_API void ___tracy_emit_zone_value( TracyCZoneCtx ctx, uint64_t value ) } } -TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size, int32_t secure ) { tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int32_t depth, int32_t secure ) +TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size ) { tracy::Profiler::MemAlloc( ptr, size ); } +TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int32_t depth ) { if( depth > 0 && tracy::has_callstack() ) { - tracy::Profiler::MemAllocCallstack( ptr, size, depth, secure != 0 ); + tracy::Profiler::MemAllocCallstack( ptr, size, depth ); } else { - tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); + tracy::Profiler::MemAlloc( ptr, size ); } } -TRACY_API void ___tracy_emit_memory_free( const void* ptr, int32_t secure ) { tracy::Profiler::MemFree( ptr, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int32_t depth, int32_t secure ) +TRACY_API void ___tracy_emit_memory_free( const void* ptr ) { tracy::Profiler::MemFree( ptr ); } +TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int32_t depth ) { if( depth > 0 && tracy::has_callstack() ) { - tracy::Profiler::MemFreeCallstack( ptr, depth, secure != 0 ); + tracy::Profiler::MemFreeCallstack( ptr, depth ); } else { - tracy::Profiler::MemFree( ptr, secure != 0 ); + tracy::Profiler::MemFree( ptr ); } } -TRACY_API void ___tracy_emit_memory_discard( const char* name, int32_t secure ) { tracy::Profiler::MemDiscard( name, secure != 0 ); } -TRACY_API void ___tracy_emit_memory_discard_callstack( const char* name, int32_t secure, int32_t depth ) +TRACY_API void ___tracy_emit_memory_discard( const char* name ) { tracy::Profiler::MemDiscard( name ); } +TRACY_API void ___tracy_emit_memory_discard_callstack( const char* name, int32_t depth ) { if( depth > 0 && tracy::has_callstack() ) { - tracy::Profiler::MemDiscardCallstack( name, secure != 0, depth ); + tracy::Profiler::MemDiscardCallstack( name, depth ); } else { - tracy::Profiler::MemDiscard( name, secure != 0 ); + tracy::Profiler::MemDiscard( name ); } } -TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, int32_t secure, const char* name ) { tracy::Profiler::MemAllocNamed( ptr, size, secure != 0, name ); } -TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int32_t depth, int32_t secure, const char* name ) +TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, const char* name ) { tracy::Profiler::MemAllocNamed( ptr, size, name ); } +TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int32_t depth, const char* name ) { if( depth > 0 && tracy::has_callstack() ) { - tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, secure != 0, name ); + tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, name ); } else { - tracy::Profiler::MemAllocNamed( ptr, size, secure != 0, name ); + tracy::Profiler::MemAllocNamed( ptr, size, name ); } } -TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, int32_t secure, const char* name ) { tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); } -TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int32_t depth, int32_t secure, const char* name ) +TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, const char* name ) { tracy::Profiler::MemFreeNamed( ptr, name ); } +TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int32_t depth, const char* name ) { if( depth > 0 && tracy::has_callstack() ) { - tracy::Profiler::MemFreeCallstackNamed( ptr, depth, secure != 0, name ); + tracy::Profiler::MemFreeCallstackNamed( ptr, depth, name ); } else { - tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); + tracy::Profiler::MemFreeNamed( ptr, name ); } } TRACY_API void ___tracy_emit_frame_mark( const char* name ) { tracy::Profiler::SendFrameMark( name ); } @@ -4585,10 +5009,16 @@ TRACY_API void ___tracy_emit_plot( const char* name, double val ) { tracy::Profi TRACY_API void ___tracy_emit_plot_float( const char* name, float val ) { tracy::Profiler::PlotData( name, val ); } TRACY_API void ___tracy_emit_plot_int( const char* name, int64_t val ) { tracy::Profiler::PlotData( name, val ); } TRACY_API void ___tracy_emit_plot_config( const char* name, int32_t type, int32_t step, int32_t fill, uint32_t color ) { tracy::Profiler::ConfigurePlot( name, tracy::PlotFormatType(type), step != 0, fill != 0, color ); } -TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int32_t callstack_depth ) { tracy::Profiler::Message( txt, size, callstack_depth ); } -TRACY_API void ___tracy_emit_messageL( const char* txt, int32_t callstack_depth ) { tracy::Profiler::Message( txt, callstack_depth ); } -TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int32_t callstack_depth ) { tracy::Profiler::MessageColor( txt, size, color, callstack_depth ); } -TRACY_API void ___tracy_emit_messageLC( const char* txt, uint32_t color, int32_t callstack_depth ) { tracy::Profiler::MessageColor( txt, color, callstack_depth ); } + +static_assert( TracyMessageSeverityTrace == int(tracy::MessageSeverity::Trace), "Mismatch between C and C++ versions of message severity" ); +static_assert( TracyMessageSeverityDebug == int(tracy::MessageSeverity::Debug), "Mismatch between C and C++ versions of message severity" ); +static_assert( TracyMessageSeverityInfo == int(tracy::MessageSeverity::Info), "Mismatch between C and C++ versions of message severity" ); +static_assert( TracyMessageSeverityWarning == int(tracy::MessageSeverity::Warning), "Mismatch between C and C++ versions of message severity" ); +static_assert( TracyMessageSeverityError == int(tracy::MessageSeverity::Error), "Mismatch between C and C++ versions of message severity" ); +static_assert( TracyMessageSeverityFatal == int(tracy::MessageSeverity::Fatal), "Mismatch between C and C++ versions of message severity" ); + +TRACY_API void ___tracy_emit_logString( int8_t severity, int32_t color, int32_t callstack_depth, size_t size, const char* txt ) { tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity(severity), color, callstack_depth, size, txt ); } +TRACY_API void ___tracy_emit_logStringL( int8_t severity, int32_t color, int32_t callstack_depth, const char* txt ) { tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity(severity), color, callstack_depth, txt ); } TRACY_API void ___tracy_emit_message_appinfo( const char* txt, size_t size ) { tracy::Profiler::MessageAppInfo( txt, size ); } TRACY_API uint64_t ___tracy_alloc_srcloc( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, uint32_t color ) { @@ -4601,6 +5031,9 @@ TRACY_API uint64_t ___tracy_alloc_srcloc_name( uint32_t line, const char* source TRACY_API void ___tracy_emit_gpu_zone_begin( const struct ___tracy_gpu_zone_begin_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif TracyLfqPrepareC( tracy::QueueType::GpuZoneBegin ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); @@ -4612,6 +5045,9 @@ TRACY_API void ___tracy_emit_gpu_zone_begin( const struct ___tracy_gpu_zone_begi TRACY_API void ___tracy_emit_gpu_zone_begin_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif tracy::GetProfiler().SendCallstack( data.depth ); TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginCallstack ); tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); @@ -4624,6 +5060,13 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_callstack( const struct ___tracy_gpu TRACY_API void ___tracy_emit_gpu_zone_begin_alloc( const struct ___tracy_gpu_zone_begin_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) + { + tracy::tracy_free( (void*)data.srcloc ); + return; + } +#endif TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLoc ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); @@ -4635,6 +5078,13 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_alloc( const struct ___tracy_gpu_zon TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) + { + tracy::tracy_free( (void*)data.srcloc ); + return; + } +#endif tracy::GetProfiler().SendCallstack( data.depth ); TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLocCallstack ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); @@ -4647,6 +5097,9 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack( const struct ___tra TRACY_API void ___tracy_emit_gpu_time( const struct ___tracy_gpu_time_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif TracyLfqPrepareC( tracy::QueueType::GpuTime ); tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime ); tracy::MemWrite( &item->gpuTime.queryId, data.queryId ); @@ -4656,6 +5109,9 @@ TRACY_API void ___tracy_emit_gpu_time( const struct ___tracy_gpu_time_data data TRACY_API void ___tracy_emit_gpu_zone_end( const struct ___tracy_gpu_zone_end_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif TracyLfqPrepareC( tracy::QueueType::GpuZoneEnd ); tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() ); memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) ); @@ -4672,8 +5128,8 @@ TRACY_API void ___tracy_emit_gpu_new_context( ___tracy_gpu_new_context_data data tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime ); tracy::MemWrite( &item->gpuNewContext.period, data.period ); tracy::MemWrite( &item->gpuNewContext.context, data.context ); - tracy::MemWrite( &item->gpuNewContext.flags, data.flags ); - tracy::MemWrite( &item->gpuNewContext.type, data.type ); + tracy::MemWrite( &item->gpuNewContext.flags, tracy::GpuContextFlags( data.flags ) ); + tracy::MemWrite( &item->gpuNewContext.type, tracy::GpuContextType( data.type ) ); #ifdef TRACY_ON_DEMAND tracy::GetProfiler().DeferItem( *item ); @@ -4701,6 +5157,9 @@ TRACY_API void ___tracy_emit_gpu_context_name( const struct ___tracy_gpu_context TRACY_API void ___tracy_emit_gpu_calibration( const struct ___tracy_gpu_calibration_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif TracyLfqPrepareC( tracy::QueueType::GpuCalibration ); tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() ); tracy::MemWrite( &item->gpuCalibration.gpuTime, data.gpuTime ); @@ -4711,6 +5170,9 @@ TRACY_API void ___tracy_emit_gpu_calibration( const struct ___tracy_gpu_calibrat TRACY_API void ___tracy_emit_gpu_time_sync( const struct ___tracy_gpu_time_sync_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif TracyLfqPrepareC( tracy::QueueType::GpuTimeSync ); tracy::MemWrite( &item->gpuTimeSync.cpuTime, tracy::Profiler::GetTime() ); tracy::MemWrite( &item->gpuTimeSync.gpuTime, data.gpuTime ); @@ -4720,6 +5182,9 @@ TRACY_API void ___tracy_emit_gpu_time_sync( const struct ___tracy_gpu_time_sync_ TRACY_API void ___tracy_emit_gpu_zone_begin_serial( const struct ___tracy_gpu_zone_begin_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginSerial ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); @@ -4732,6 +5197,9 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_serial( const struct ___tracy_gpu_zo TRACY_API void ___tracy_emit_gpu_zone_begin_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) ); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginCallstackSerial ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); @@ -4744,6 +5212,13 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_callstack_serial( const struct ___tr TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_serial( const struct ___tracy_gpu_zone_begin_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) + { + tracy::tracy_free( (void*)data.srcloc ); + return; + } +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocSerial ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); @@ -4756,6 +5231,13 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_serial( const struct ___tracy_ TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) + { + tracy::tracy_free( (void*)data.srcloc ); + return; + } +#endif auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) ); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocCallstackSerial ); tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() ); @@ -4768,6 +5250,9 @@ TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack_serial( const struct TRACY_API void ___tracy_emit_gpu_time_serial( const struct ___tracy_gpu_time_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTime ); tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime ); @@ -4778,6 +5263,9 @@ TRACY_API void ___tracy_emit_gpu_time_serial( const struct ___tracy_gpu_time_dat TRACY_API void ___tracy_emit_gpu_zone_end_serial( const struct ___tracy_gpu_zone_end_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneEndSerial ); tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() ); @@ -4796,8 +5284,8 @@ TRACY_API void ___tracy_emit_gpu_new_context_serial( ___tracy_gpu_new_context_da tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime ); tracy::MemWrite( &item->gpuNewContext.period, data.period ); tracy::MemWrite( &item->gpuNewContext.context, data.context ); - tracy::MemWrite( &item->gpuNewContext.flags, data.flags ); - tracy::MemWrite( &item->gpuNewContext.type, data.type ); + tracy::MemWrite( &item->gpuNewContext.flags, tracy::GpuContextFlags( data.flags ) ); + tracy::MemWrite( &item->gpuNewContext.type, tracy::GpuContextType( data.type ) ); #ifdef TRACY_ON_DEMAND tracy::GetProfiler().DeferItem( *item ); @@ -4826,6 +5314,9 @@ TRACY_API void ___tracy_emit_gpu_context_name_serial( const struct ___tracy_gpu_ TRACY_API void ___tracy_emit_gpu_calibration_serial( const struct ___tracy_gpu_calibration_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuCalibration ); tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() ); @@ -4837,6 +5328,9 @@ TRACY_API void ___tracy_emit_gpu_calibration_serial( const struct ___tracy_gpu_c TRACY_API void ___tracy_emit_gpu_time_sync_serial( const struct ___tracy_gpu_time_sync_data data ) { +#ifdef TRACY_ON_DEMAND + if( !tracy::GetProfiler().IsConnected() ) return; +#endif auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTimeSync ); tracy::MemWrite( &item->gpuTimeSync.cpuTime, tracy::Profiler::GetTime() ); @@ -4862,7 +5356,7 @@ TRACY_API struct __tracy_lockable_context_data* ___tracy_announce_lockable_ctx( new(&lockdata->m_lockCount) std::atomic( 0 ); new(&lockdata->m_active) std::atomic( false ); #endif - assert( lockdata->m_id != (std::numeric_limits::max)() ); + TRACY_ASSERT( lockdata->m_id != (std::numeric_limits::max)() ); auto item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockAnnounce ); @@ -5000,7 +5494,7 @@ TRACY_API void ___tracy_mark_lockable_ctx( struct __tracy_lockable_context_data* TRACY_API void ___tracy_custom_name_lockable_ctx( struct __tracy_lockable_context_data* lockdata, const char* name, size_t nameSz ) { - assert( nameSz < (std::numeric_limits::max)() ); + TRACY_ASSERT( nameSz < (std::numeric_limits::max)() ); auto ptr = (char*)tracy::tracy_malloc( nameSz ); memcpy( ptr, name, nameSz ); auto item = tracy::Profiler::QueueSerial(); @@ -5014,6 +5508,164 @@ TRACY_API void ___tracy_custom_name_lockable_ctx( struct __tracy_lockable_contex tracy::Profiler::QueueSerialFinish(); } +struct __tracy_shared_lockable_context_data { + struct __tracy_lockable_context_data m_base; +}; + +TRACY_API struct __tracy_shared_lockable_context_data* ___tracy_announce_shared_lockable_ctx( const struct ___tracy_source_location_data* srcloc ) +{ + struct __tracy_shared_lockable_context_data *lockdata = (__tracy_shared_lockable_context_data*)tracy::tracy_malloc( sizeof( __tracy_shared_lockable_context_data ) ); + lockdata->m_base.m_id = tracy::GetLockCounter().fetch_add( 1, std::memory_order_relaxed ); +#ifdef TRACY_ON_DEMAND + new(&lockdata->m_base.m_lockCount) std::atomic( 0 ); + new(&lockdata->m_base.m_active) std::atomic( false ); +#endif + TRACY_ASSERT( lockdata->m_base.m_id != (std::numeric_limits::max)() ); + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockAnnounce ); + tracy::MemWrite( &item->lockAnnounce.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockAnnounce.time, tracy::Profiler::GetTime() ); + tracy::MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc ); + tracy::MemWrite( &item->lockAnnounce.type, tracy::LockType::SharedLockable ); +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + tracy::Profiler::QueueSerialFinish(); + + return lockdata; +} + +TRACY_API void ___tracy_terminate_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockTerminate ); + tracy::MemWrite( &item->lockTerminate.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockTerminate.time, tracy::Profiler::GetTime() ); +#ifdef TRACY_ON_DEMAND + tracy::GetProfiler().DeferItem( *item ); +#endif + tracy::Profiler::QueueSerialFinish(); + +#ifdef TRACY_ON_DEMAND + lockdata->m_base.m_lockCount.~atomic(); + lockdata->m_base.m_active.~atomic(); +#endif + tracy::tracy_free((void*)lockdata); +} + +TRACY_API int32_t ___tracy_before_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ + return ___tracy_before_lock_lockable_ctx( &lockdata->m_base ); +} + +TRACY_API void ___tracy_after_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ + ___tracy_after_lock_lockable_ctx( &lockdata->m_base ); +} + +TRACY_API void ___tracy_after_unlock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ + ___tracy_after_unlock_lockable_ctx( &lockdata->m_base ); +} + +TRACY_API void ___tracy_after_try_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired ) +{ + ___tracy_after_try_lock_lockable_ctx( &lockdata->m_base, acquired ); +} + +TRACY_API int32_t ___tracy_before_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ +#ifdef TRACY_ON_DEMAND + bool queue = false; + const auto locks = lockdata->m_base.m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = lockdata->m_base.m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = tracy::GetProfiler().IsConnected(); + if( active != connected ) lockdata->m_base.m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return static_cast(false); +#endif + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockSharedWait ); + tracy::MemWrite( &item->lockWait.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockWait.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockWait.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); + return static_cast(true); +} + +TRACY_API void ___tracy_after_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockSharedObtain ); + tracy::MemWrite( &item->lockObtain.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockObtain.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockObtain.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_after_unlock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ) +{ +#ifdef TRACY_ON_DEMAND + lockdata->m_base.m_lockCount.fetch_sub( 1, std::memory_order_relaxed ); + if( !lockdata->m_base.m_active.load( std::memory_order_relaxed ) ) return; + if( !tracy::GetProfiler().IsConnected() ) + { + lockdata->m_base.m_active.store( false, std::memory_order_relaxed ); + return; + } +#endif + + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockSharedRelease ); + tracy::MemWrite( &item->lockReleaseShared.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockReleaseShared.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockReleaseShared.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); +} + +TRACY_API void ___tracy_after_try_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired ) +{ +#ifdef TRACY_ON_DEMAND + if( !acquired ) return; + + bool queue = false; + const auto locks = lockdata->m_base.m_lockCount.fetch_add( 1, std::memory_order_relaxed ); + const auto active = lockdata->m_base.m_active.load( std::memory_order_relaxed ); + if( locks == 0 || active ) + { + const bool connected = tracy::GetProfiler().IsConnected(); + if( active != connected ) lockdata->m_base.m_active.store( connected, std::memory_order_relaxed ); + if( connected ) queue = true; + } + if( !queue ) return; +#endif + + if( acquired ) + { + auto item = tracy::Profiler::QueueSerial(); + tracy::MemWrite( &item->hdr.type, tracy::QueueType::LockObtain ); + tracy::MemWrite( &item->lockObtain.thread, tracy::GetThreadHandle() ); + tracy::MemWrite( &item->lockObtain.id, lockdata->m_base.m_id ); + tracy::MemWrite( &item->lockObtain.time, tracy::Profiler::GetTime() ); + tracy::Profiler::QueueSerialFinish(); + } +} + +TRACY_API void ___tracy_mark_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, const struct ___tracy_source_location_data* srcloc ) +{ + ___tracy_mark_lockable_ctx( &lockdata->m_base, srcloc ); +} + +TRACY_API void ___tracy_custom_name_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, const char* name, size_t nameSz ) +{ + ___tracy_custom_name_lockable_ctx( &lockdata->m_base, name, nameSz ); +} + TRACY_API int32_t ___tracy_connected( void ) { return static_cast( tracy::GetProfiler().IsConnected() ); @@ -5049,6 +5701,11 @@ TRACY_API void ___tracy_end_sampling_profiling( void ) { tracy::EndSamplingProfiling(); } +TRACY_API int64_t ___tracy_get_time( void ) +{ + return tracy::Profiler::GetTime(); +} + #ifdef __cplusplus } #endif diff --git a/libs/tracy/client/TracyProfiler.hpp b/libs/tracy/client/TracyProfiler.hpp index aacfa16..215741b 100644 --- a/libs/tracy/client/TracyProfiler.hpp +++ b/libs/tracy/client/TracyProfiler.hpp @@ -1,8 +1,9 @@ #ifndef __TRACYPROFILER_HPP__ #define __TRACYPROFILER_HPP__ -#include #include +#include +#include #include #include #include @@ -11,15 +12,22 @@ #include "tracy_SPSCQueue.h" #include "TracyCallstack.hpp" #include "TracyKCore.hpp" +#include "TracyMangle.hpp" #include "TracySysPower.hpp" #include "TracySysTime.hpp" #include "TracyFastVector.hpp" #include "../common/TracyQueue.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" +#include "../common/TracyFormat.h" #include "../common/TracyMutex.hpp" #include "../common/TracyProtocol.hpp" +#ifdef TRACY_PLATFORM_HEADER +# include TRACY_PLATFORM_HEADER +#endif + #if defined _WIN32 # include #endif @@ -28,8 +36,20 @@ # include #endif -#if ( (defined _WIN32 && !(defined _M_ARM64 || defined _M_ARM)) || ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) || ( defined TARGET_OS_IOS && TARGET_OS_IOS == 1 ) ) -# define TRACY_HW_TIMER +#if ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) +# define TRACY_HAS_RDTSC +#elif defined _WIN32 && defined _M_ARM64 +# define TRACY_HAS_CNTVCT +#elif defined __APPLE__ && defined __MACH__ && TARGET_CPU_ARM64 // For now only supported on Apple devices +# define TRACY_HAS_CNTVCT +#endif + +#if !defined TRACY_DISALLOW_HW_TIMER +# if ( defined TRACY_HAS_RDTSC || defined TRACY_HAS_CNTVCT ) +# define TRACY_HW_TIMER +# elif defined TARGET_OS_IOS && TARGET_OS_IOS == 1 // For now, !defined(TRACY_HW_TIMER) implies TRACY_TIMER_FALLBACK, so define TRACY_HW_TIMER to use mach_absolute_time() on iOS +# define TRACY_HW_TIMER +# endif #endif #ifdef __linux__ @@ -60,6 +80,11 @@ TRACY_API bool IsProfilerStarted(); TRACY_API bool BeginSamplingProfiling(); TRACY_API void EndSamplingProfiling(); +TRACY_API bool IsSystemTracingFailed(); + +TRACY_API void SetReservedListenSocket( int fd ); + +TRACY_API bool IsDataPortListening(); class GpuCtx; class Profiler; @@ -71,17 +96,20 @@ struct GpuCtxWrapper GpuCtx* ptr; }; -TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* GetToken(); -TRACY_API Profiler& GetProfiler(); +TRACY_API moodycamel::ConcurrentQueue::ExplicitProducer* MANGLED_NAME_BASED_ON_CONFIG(GetToken)(); +tracy_force_inline moodycamel::ConcurrentQueue::ExplicitProducer* GetToken() { return MANGLED_NAME_BASED_ON_CONFIG(GetToken)(); } +TRACY_API Profiler& MANGLED_NAME_BASED_ON_CONFIG(GetProfiler)(); +tracy_force_inline Profiler& GetProfiler() { return MANGLED_NAME_BASED_ON_CONFIG(GetProfiler)(); } TRACY_API std::atomic& GetLockCounter(); -TRACY_API std::atomic& GetGpuCtxCounter(); +TRACY_API std::atomic& GetGpuCtxCounter(); +TRACY_API int32_t NextGpuContextId(); TRACY_API GpuCtxWrapper& GetGpuCtx(); TRACY_API uint32_t GetThreadHandle(); TRACY_API bool ProfilerAvailable(); TRACY_API bool ProfilerAllocatorAvailable(); TRACY_API int64_t GetFrequencyQpc(); -#if defined TRACY_TIMER_FALLBACK && defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) +#if defined TRACY_TIMER_FALLBACK && defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC TRACY_API bool HardwareSupportsInvariantTSC(); // check, if we need fallback scenario #else # if defined TRACY_HW_TIMER @@ -110,8 +138,38 @@ struct SourceLocationData #ifdef TRACY_ON_DEMAND struct LuaZoneState { - uint32_t counter; - bool active; + LuaZoneState() = default; + ~LuaZoneState() { if( stack ) tracy_free( stack ); } + LuaZoneState( const LuaZoneState& ) = delete; + LuaZoneState& operator=( const LuaZoneState& ) = delete; + + tracy_force_inline void Push( uint64_t connectionId ) + { + if( size == capacity ) + { + const auto newCapacity = capacity ? capacity * 2 : 16; + stack = (uint64_t*)tracy_realloc( stack, newCapacity * sizeof( uint64_t ) ); + capacity = newCapacity; + } + stack[size] = connectionId; + size++; + } + + tracy_force_inline uint64_t Pop() + { + TRACY_ASSERT( size > 0 ); + return stack[--size]; + } + + tracy_force_inline uint64_t Top() + { + return size ? stack[size - 1] : 0; + } + +private: + uint64_t* stack = nullptr; + uint32_t size = 0; + uint32_t capacity = 0; }; #endif @@ -161,6 +219,27 @@ struct LuaZoneState typedef void(*ParameterCallback)( void* data, uint32_t idx, int32_t val ); typedef char*(*SourceContentsCallback)( void* data, const char* filename, size_t& size ); +#if defined _WIN32 && defined TRACY_HAS_CNTVCT +// NOTE: implementing timestamp_win_arm64_cntvct_el0() requires ARM64_CNTVCT_EL0, +// which in turn would require including "Windows.h" here... instead, just bring +// what's needed from "winnt.h" +# ifdef ARM64_CNTVCT_EL0 +# define TRACY_WINARM64_CNTVCT_EL0 ARM64_CNTVCT_EL0 +# else +# define TRACY_WINARM64_SYSREG( op0, op1, crn, crm, op2 ) \ + ( ( ( op0 & 1 ) << 14 ) | \ + ( ( op1 & 7 ) << 11 ) | \ + ( ( crn & 15 ) << 7 ) | \ + ( ( crm & 15 ) << 3 ) | \ + ( ( op2 & 7 ) << 0 ) ) +# define TRACY_WINARM64_CNTVCT_EL0 TRACY_WINARM64_SYSREG( 3, 3, 14, 0, 2 ) +# endif +tracy_force_inline int64_t timestamp_win_arm64_cntvct_el0() +{ + return _ReadStatusReg( TRACY_WINARM64_CNTVCT_EL0 ); +} +#endif + class Profiler { struct FrameImageQueueItem @@ -200,10 +279,25 @@ class Profiler #ifdef TRACY_HW_TIMER # if defined TARGET_OS_IOS && TARGET_OS_IOS == 1 if( HardwareSupportsInvariantTSC() ) return mach_absolute_time(); +# elif defined __APPLE__ && defined __MACH__ && TARGET_CPU_ARM64 + if( HardwareSupportsInvariantTSC() ) + { + uint64_t value; + __asm__ __volatile__( + //"isb \n" // ommitting "Instruction Synchronization Barrier" + "mrs %0, CNTVCT_EL0" + : "=r" (value) // Output: write 'register %0' to 'value' + : // No inputs + : "memory" // Clobber list: memory (e.g., compiler barrier) + ); + return value; + } # elif defined _WIN32 # ifdef TRACY_TIMER_QPC return GetTimeQpc(); -# else +# elif defined TRACY_HAS_CNTVCT + return timestamp_win_arm64_cntvct_el0(); +# elif defined TRACY_HAS_RDTSC if( HardwareSupportsInvariantTSC() ) return int64_t( __rdtsc() ); # endif # elif defined __i386 || defined _M_IX86 @@ -263,6 +357,11 @@ class Profiler return m_zoneId.fetch_add( 1, std::memory_order_relaxed ); } + tracy_force_inline uint32_t GetNextSectionId() + { + return m_sectionId.fetch_add( 1, std::memory_order_relaxed ); + } + static tracy_force_inline QueueItem* QueueSerial() { auto& p = GetProfiler(); @@ -300,7 +399,7 @@ class Profiler static tracy_force_inline void SendFrameMark( const char* name, QueueType type ) { - assert( type == QueueType::FrameMarkMsgStart || type == QueueType::FrameMarkMsgEnd ); + TRACY_ASSERT( type == QueueType::FrameMarkMsgStart || type == QueueType::FrameMarkMsgEnd ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -315,7 +414,7 @@ class Profiler { #ifndef TRACY_NO_FRAME_IMAGE auto& profiler = GetProfiler(); - assert( profiler.m_frameCount.load( std::memory_order_relaxed ) < (std::numeric_limits::max)() ); + TRACY_ASSERT( profiler.m_frameCount.load( std::memory_order_relaxed ) < (std::numeric_limits::max)() ); # ifdef TRACY_ON_DEMAND if( !profiler.IsConnected() ) return; # endif @@ -393,9 +492,9 @@ class Profiler TracyLfqCommit; } - static tracy_force_inline void Message( const char* txt, size_t size, int32_t callstack_depth ) + static tracy_force_inline void LogString( MessageSourceType source, MessageSeverity severity, uint32_t color, int32_t callstack_depth, size_t txtLength, const char* txt ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( txtLength < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -404,35 +503,33 @@ class Profiler tracy::GetProfiler().SendCallstack( callstack_depth ); } - auto ptr = (char*)tracy_malloc( size ); - memcpy( ptr, txt, size ); + auto ptr = (char*)tracy_malloc( txtLength ); + memcpy( ptr, txt, txtLength ); + TaggedUserlandAddress taggedPtr{ (uint64_t)ptr, MakeMessageMetadata( source, severity ) }; - TracyQueuePrepare( callstack_depth == 0 ? QueueType::Message : QueueType::MessageCallstack ); - MemWrite( &item->messageFat.time, GetTime() ); - MemWrite( &item->messageFat.text, (uint64_t)ptr ); - MemWrite( &item->messageFat.size, (uint16_t)size ); - TracyQueueCommit( messageFatThread ); - } - - static tracy_force_inline void Message( const char* txt, int32_t callstack_depth ) - { -#ifdef TRACY_ON_DEMAND - if( !GetProfiler().IsConnected() ) return; -#endif - if( callstack_depth != 0 && has_callstack() ) + if( color != 0 ) { - tracy::GetProfiler().SendCallstack( callstack_depth ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); + MemWrite( &item->messageColorFat.time, GetTime() ); + MemWrite( &item->messageColorFat.textAndMetadata, taggedPtr ); + MemWrite( &item->messageColorFat.b, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorFat.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + MemWrite( &item->messageColorFat.r, uint8_t( ( color >> 16 ) & 0xFF ) ); + MemWrite( &item->messageColorFat.size, (uint16_t)txtLength ); + TracyQueueCommit( messageColorFatThread ); + } + else + { + TracyQueuePrepare( callstack_depth == 0 ? QueueType::Message : QueueType::MessageCallstack ); + MemWrite( &item->messageFat.time, GetTime() ); + MemWrite( &item->messageFat.textAndMetadata, taggedPtr ); + MemWrite( &item->messageFat.size, (uint16_t)txtLength ); + TracyQueueCommit( messageFatThread ); } - - TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteral : QueueType::MessageLiteralCallstack ); - MemWrite( &item->messageLiteral.time, GetTime() ); - MemWrite( &item->messageLiteral.text, (uint64_t)txt ); - TracyQueueCommit( messageLiteralThread ); } - static tracy_force_inline void MessageColor( const char* txt, size_t size, uint32_t color, int32_t callstack_depth ) + static tracy_force_inline void LogString( MessageSourceType source, MessageSeverity severity, uint32_t color, int32_t callstack_depth, const char* txt ) { - assert( size < (std::numeric_limits::max)() ); #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -441,46 +538,38 @@ class Profiler tracy::GetProfiler().SendCallstack( callstack_depth ); } - auto ptr = (char*)tracy_malloc( size ); - memcpy( ptr, txt, size ); - - TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack ); - MemWrite( &item->messageColorFat.time, GetTime() ); - MemWrite( &item->messageColorFat.text, (uint64_t)ptr ); - MemWrite( &item->messageColorFat.b, uint8_t( ( color ) & 0xFF ) ); - MemWrite( &item->messageColorFat.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - MemWrite( &item->messageColorFat.r, uint8_t( ( color >> 16 ) & 0xFF ) ); - MemWrite( &item->messageColorFat.size, (uint16_t)size ); - TracyQueueCommit( messageColorFatThread ); - } + TaggedUserlandAddress taggedPtr{ (uint64_t)txt, MakeMessageMetadata( source, severity ) }; - static tracy_force_inline void MessageColor( const char* txt, uint32_t color, int32_t callstack_depth ) - { -#ifdef TRACY_ON_DEMAND - if( !GetProfiler().IsConnected() ) return; -#endif - if( callstack_depth != 0 && has_callstack() ) + if( color != 0 ) { - tracy::GetProfiler().SendCallstack( callstack_depth ); + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); + MemWrite( &item->messageColorLiteral.time, GetTime() ); + MemWrite( &item->messageColorLiteral.textAndMetadata, taggedPtr ); + MemWrite( &item->messageColorLiteral.b, uint8_t( ( color ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.g, uint8_t( ( color >> 8 ) & 0xFF ) ); + MemWrite( &item->messageColorLiteral.r, uint8_t( ( color >> 16 ) & 0xFF ) ); + TracyQueueCommit( messageColorLiteralThread ); + } + else + { + TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteral : QueueType::MessageLiteralCallstack ); + MemWrite( &item->messageLiteral.time, GetTime() ); + MemWrite( &item->messageLiteral.textAndMetadata, taggedPtr ); + TracyQueueCommit( messageLiteralThread ); } - - TracyQueuePrepare( callstack_depth == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack ); - MemWrite( &item->messageColorLiteral.time, GetTime() ); - MemWrite( &item->messageColorLiteral.text, (uint64_t)txt ); - MemWrite( &item->messageColorLiteral.b, uint8_t( ( color ) & 0xFF ) ); - MemWrite( &item->messageColorLiteral.g, uint8_t( ( color >> 8 ) & 0xFF ) ); - MemWrite( &item->messageColorLiteral.r, uint8_t( ( color >> 16 ) & 0xFF ) ); - TracyQueueCommit( messageColorLiteralThread ); } + static tracy_force_inline void MessageAppInfo( const char* txt, size_t size ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); + TaggedUserlandAddress taggedPtr{ (uint64_t)ptr, MakeMessageMetadata( MessageSourceType::User, MessageSeverity::Info ) }; + TracyLfqPrepare( QueueType::MessageAppInfo ); MemWrite( &item->messageFat.time, GetTime() ); - MemWrite( &item->messageFat.text, (uint64_t)ptr ); + MemWrite( &item->messageFat.textAndMetadata, taggedPtr ); MemWrite( &item->messageFat.size, (uint16_t)size ); #ifdef TRACY_ON_DEMAND @@ -490,9 +579,9 @@ class Profiler TracyLfqCommit; } - static tracy_force_inline void MemAlloc( const void* ptr, size_t size, bool secure ) + static tracy_force_inline void MemAlloc( const void* ptr, size_t size ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -503,9 +592,9 @@ class Profiler GetProfiler().m_serialLock.unlock(); } - static tracy_force_inline void MemFree( const void* ptr, bool secure ) + static tracy_force_inline void MemFree( const void* ptr ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -516,9 +605,9 @@ class Profiler GetProfiler().m_serialLock.unlock(); } - static tracy_force_inline void MemAllocCallstack( const void* ptr, size_t size, int32_t depth, bool secure ) + static tracy_force_inline void MemAllocCallstack( const void* ptr, size_t size, int32_t depth ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; if( depth > 0 && has_callstack() ) { auto& profiler = GetProfiler(); @@ -536,16 +625,16 @@ class Profiler } else { - MemAlloc( ptr, size, secure ); + MemAlloc( ptr, size ); } } - static tracy_force_inline void MemFreeCallstack( const void* ptr, int32_t depth, bool secure ) + static tracy_force_inline void MemFreeCallstack( const void* ptr, int32_t depth ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; if( !ProfilerAllocatorAvailable() ) { - MemFree( ptr, secure ); + MemFree( ptr ); return; } if( depth > 0 && has_callstack() ) @@ -565,13 +654,13 @@ class Profiler } else { - MemFree( ptr, secure ); + MemFree( ptr ); } } - static tracy_force_inline void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name ) + static tracy_force_inline void MemAllocNamed( const void* ptr, size_t size, const char* name ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -583,9 +672,9 @@ class Profiler GetProfiler().m_serialLock.unlock(); } - static tracy_force_inline void MemFreeNamed( const void* ptr, bool secure, const char* name ) + static tracy_force_inline void MemFreeNamed( const void* ptr, const char* name ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -597,9 +686,9 @@ class Profiler GetProfiler().m_serialLock.unlock(); } - static tracy_force_inline void MemAllocCallstackNamed( const void* ptr, size_t size, int32_t depth, bool secure, const char* name ) + static tracy_force_inline void MemAllocCallstackNamed( const void* ptr, size_t size, int32_t depth, const char* name ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; if( depth > 0 && has_callstack() ) { auto& profiler = GetProfiler(); @@ -618,13 +707,13 @@ class Profiler } else { - MemAllocNamed( ptr, size, secure, name ); + MemAllocNamed( ptr, size, name ); } } - static tracy_force_inline void MemFreeCallstackNamed( const void* ptr, int32_t depth, bool secure, const char* name ) + static tracy_force_inline void MemFreeCallstackNamed( const void* ptr, int32_t depth, const char* name ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; if( depth > 0 && has_callstack() ) { auto& profiler = GetProfiler(); @@ -643,13 +732,13 @@ class Profiler } else { - MemFreeNamed( ptr, secure, name ); + MemFreeNamed( ptr, name ); } } - static tracy_force_inline void MemDiscard( const char* name, bool secure ) + static tracy_force_inline void MemDiscard( const char* name ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) return; #endif @@ -660,9 +749,9 @@ class Profiler GetProfiler().m_serialLock.unlock(); } - static tracy_force_inline void MemDiscardCallstack( const char* name, bool secure, int32_t depth ) + static tracy_force_inline void MemDiscardCallstack( const char* name, int32_t depth ) { - if( secure && !ProfilerAvailable() ) return; + if( !ProfilerAvailable() ) return; if( depth > 0 && has_callstack() ) { # ifdef TRACY_ON_DEMAND @@ -674,12 +763,12 @@ class Profiler GetProfiler().m_serialLock.lock(); SendCallstackSerial( callstack ); - SendMemDiscard( QueueType::MemDiscard, thread, name ); + SendMemDiscard( QueueType::MemDiscardCallstack, thread, name ); GetProfiler().m_serialLock.unlock(); } else { - MemDiscard( name, secure ); + MemDiscard( name ); } } @@ -701,12 +790,13 @@ class Profiler profiler.m_paramCallbackData = data; } - static tracy_force_inline void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val ) + static tracy_force_inline void ParameterSetup( uint32_t idx, const char* name, uint8_t type, int32_t val ) { + TRACY_ASSERT( type >= 0 && type <= 2 ); TracyLfqPrepare( QueueType::ParamSetup ); tracy::MemWrite( &item->paramSetup.idx, idx ); tracy::MemWrite( &item->paramSetup.name, (uint64_t)name ); - tracy::MemWrite( &item->paramSetup.isBool, (uint8_t)isBool ); + tracy::MemWrite( &item->paramSetup.type, type ); tracy::MemWrite( &item->paramSetup.val, val ); #ifdef TRACY_ON_DEMAND @@ -747,8 +837,75 @@ class Profiler } #endif - void SendCallstack( int32_t depth, const char* skipBefore ); - static void CutCallstack( void* callstack, const char* skipBefore ); + static uint32_t SectionEnter( uint16_t category, const char* fmt, ... ) TRACY_ATTRIBUTE_FORMAT_PRINTF( 2, 3 ) + { + auto& profiler = GetProfiler(); +#ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) return 0; +#endif + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return 0; + TRACY_ASSERT( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + + const auto id = profiler.GetNextSectionId(); + TracyLfqPrepare( QueueType::SectionEnter ); + MemWrite( &item->sectionEnterFat.time, GetTime() ); + MemWrite( &item->sectionEnterFat.id, id ); + MemWrite( &item->sectionEnterFat.category, category ); + MemWrite( &item->sectionEnterFat.text, (uint64_t)ptr ); + MemWrite( &item->sectionEnterFat.size, (uint16_t)size ); + TracyLfqCommit; + return id; + } + + static tracy_force_inline void SectionLeave( uint32_t id ) + { + if( id == 0 ) return; +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + TracyLfqPrepare( QueueType::SectionLeave ); + MemWrite( &item->sectionLeave.time, GetTime() ); + MemWrite( &item->sectionLeave.id, id ); + TracyLfqCommit; + } + + static void SectionSetup( uint16_t category, const char* fmt, ... ) TRACY_ATTRIBUTE_FORMAT_PRINTF( 2, 3 ) + { + va_list args; + va_start( args, fmt ); + auto size = vsnprintf( nullptr, 0, fmt, args ); + va_end( args ); + if( size < 0 ) return; + TRACY_ASSERT( size < (std::numeric_limits::max)() ); + + char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); + va_start( args, fmt ); + vsnprintf( ptr, size_t( size ) + 1, fmt, args ); + va_end( args ); + + TracyLfqPrepare( QueueType::SectionSetup ); + MemWrite( &item->sectionSetupFat.category, category ); + MemWrite( &item->sectionSetupFat.text, (uint64_t)ptr ); + MemWrite( &item->sectionSetupFat.size, (uint16_t)size ); + +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif + + TracyLfqCommit; + } + + void SendCallstack( int32_t depth, const char** skipBefore ); + static void CutCallstack( void* callstack, const char** skipBefore ); static bool ShouldExit(); @@ -757,6 +914,12 @@ class Profiler return m_isConnected.load( std::memory_order_acquire ); } + // Returns 0 until the listen socket is bound + tracy_force_inline uint32_t GetPort() const + { + return m_dataPort.load( std::memory_order_acquire ); + } + tracy_force_inline void SetProgramName( const char* name ) { m_programNameLock.lock(); @@ -782,12 +945,12 @@ class Profiler void RequestShutdown() { m_shutdown.store( true, std::memory_order_relaxed ); m_shutdownManual.store( true, std::memory_order_relaxed ); } bool HasShutdownFinished() const { return m_shutdownFinished.load( std::memory_order_relaxed ); } - void SendString( uint64_t str, const char* ptr, QueueType type ) { SendString( str, ptr, strlen( ptr ), type ); } + tracy_force_inline void SendString( uint64_t str, const char* ptr, QueueType type ) { SendString( str, ptr, strlen( ptr ), type ); } void SendString( uint64_t str, const char* ptr, size_t len, QueueType type ); - void SendSingleString( const char* ptr ) { SendSingleString( ptr, strlen( ptr ) ); } - void SendSingleString( const char* ptr, size_t len ); - void SendSecondString( const char* ptr ) { SendSecondString( ptr, strlen( ptr ) ); } - void SendSecondString( const char* ptr, size_t len ); + tracy_force_inline void SendSingleString( const char* ptr ) { SendSingleString( ptr, strlen( ptr ) ); } + tracy_force_inline void SendSingleString( const char* ptr, size_t len ) { len <= 255 ? SendSingleString8( ptr, len ) : SendSingleString16( ptr, len ); } + tracy_force_inline void SendSecondString( const char* ptr ) { SendSecondString( ptr, strlen( ptr ) ); } + tracy_force_inline void SendSecondString( const char* ptr, size_t len ) { len <= 255 ? SendSecondString8( ptr, len ) : SendSecondString16( ptr, len ); } // Allocated source location data layout: @@ -818,7 +981,7 @@ class Profiler static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, uint32_t color = 0 ) { const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz ); - assert( sz32 <= (std::numeric_limits::max)() ); + TRACY_ASSERT( sz32 <= (std::numeric_limits::max)() ); const auto sz = uint16_t( sz32 ); auto ptr = (char*)tracy_malloc( sz ); memcpy( ptr, &sz, 2 ); @@ -873,9 +1036,9 @@ class Profiler tracy_force_inline bool NeedDataSize( size_t len ) { - assert( len <= TargetFrameSize ); + TRACY_ASSERT( len <= TargetFrameSize ); bool ret = true; - if( m_bufferOffset - m_bufferStart + (int)len > TargetFrameSize ) + if( m_bufferOffset - m_bufferStart + (int)len > (int)TargetFrameSize ) { ret = CommitData(); } @@ -930,6 +1093,11 @@ class Profiler void CalibrateDelay(); void ReportTopology(); + void SendSingleString8( const char* ptr, size_t len ); + void SendSingleString16( const char* ptr, size_t len ); + void SendSecondString8( const char* ptr, size_t len ); + void SendSecondString16( const char* ptr, size_t len ); + static tracy_force_inline void SendCallstackSerial( void* ptr ) { if( has_callstack() ) @@ -943,7 +1111,7 @@ class Profiler static tracy_force_inline void SendMemAlloc( QueueType type, const uint32_t thread, const void* ptr, size_t size ) { - assert( type == QueueType::MemAlloc || type == QueueType::MemAllocCallstack || type == QueueType::MemAllocNamed || type == QueueType::MemAllocCallstackNamed ); + TRACY_ASSERT( type == QueueType::MemAlloc || type == QueueType::MemAllocCallstack || type == QueueType::MemAllocNamed || type == QueueType::MemAllocCallstackNamed ); auto item = GetProfiler().m_serialQueue.prepare_next(); MemWrite( &item->hdr.type, type ); @@ -957,7 +1125,7 @@ class Profiler } else { - assert( sizeof( size ) == 8 ); + TRACY_ASSERT( sizeof( size ) == 8 ); memcpy( &item->memAlloc.size, &size, 4 ); memcpy( ((char*)&item->memAlloc.size)+4, ((char*)&size)+4, 2 ); } @@ -966,7 +1134,7 @@ class Profiler static tracy_force_inline void SendMemFree( QueueType type, const uint32_t thread, const void* ptr ) { - assert( type == QueueType::MemFree || type == QueueType::MemFreeCallstack || type == QueueType::MemFreeNamed || type == QueueType::MemFreeCallstackNamed ); + TRACY_ASSERT( type == QueueType::MemFree || type == QueueType::MemFreeCallstack || type == QueueType::MemFreeNamed || type == QueueType::MemFreeCallstackNamed ); auto item = GetProfiler().m_serialQueue.prepare_next(); MemWrite( &item->hdr.type, type ); @@ -978,7 +1146,7 @@ class Profiler static tracy_force_inline void SendMemDiscard( QueueType type, const uint32_t thread, const char* name ) { - assert( type == QueueType::MemDiscard || type == QueueType::MemDiscardCallstack ); + TRACY_ASSERT( type == QueueType::MemDiscard || type == QueueType::MemDiscardCallstack ); auto item = GetProfiler().m_serialQueue.prepare_next(); MemWrite( &item->hdr.type, type ); @@ -990,7 +1158,7 @@ class Profiler static tracy_force_inline void SendMemName( const char* name ) { - assert( name ); + TRACY_ASSERT( name ); auto item = GetProfiler().m_serialQueue.prepare_next(); MemWrite( &item->hdr.type, QueueType::MemNamePayload ); MemWrite( &item->memName.name, (uint64_t)name ); @@ -1013,7 +1181,9 @@ class Profiler UdpBroadcast* m_broadcast; bool m_noExit; uint32_t m_userPort; + std::atomic m_dataPort; std::atomic m_zoneId; + std::atomic m_sectionId; int64_t m_samplingPeriod; uint32_t m_threadCtx; @@ -1038,6 +1208,8 @@ class Profiler #endif SPSCQueue m_symbolQueue; + std::condition_variable m_symbolQueueSignal; + std::mutex m_symbolQueueMutex; std::atomic m_frameCount; std::atomic m_isConnected; @@ -1079,7 +1251,7 @@ class Profiler #if defined _WIN32 void* m_prevHandler; -#else +#elif !defined TRACY_HAS_CUSTOM_SAFE_COPY int m_pipe[2]; int m_pipeBufSize; #endif diff --git a/libs/tracy/client/TracyRingBuffer.hpp b/libs/tracy/client/TracyRingBuffer.hpp index e9100e2..4ac1e43 100644 --- a/libs/tracy/client/TracyRingBuffer.hpp +++ b/libs/tracy/client/TracyRingBuffer.hpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -9,6 +8,8 @@ #include #include "TracyDebug.hpp" +#include "../common/TracyAssert.hpp" +#include "../common/TracyForceInline.hpp" namespace tracy { @@ -18,25 +19,26 @@ class RingBuffer public: RingBuffer( unsigned int size, int fd, int id, int cpu = -1 ) : m_size( size ) + , m_mask( size - 1 ) , m_id( id ) , m_cpu( cpu ) , m_fd( fd ) { const auto pageSize = uint32_t( getpagesize() ); - assert( size >= pageSize ); - assert( __builtin_popcount( size ) == 1 ); + TRACY_ASSERT( size >= pageSize ); + TRACY_ASSERT( __builtin_popcount( size ) == 1 ); m_mapSize = size + pageSize; auto mapAddr = mmap( nullptr, m_mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if( mapAddr == MAP_FAILED ) { - TracyDebug( "mmap failed: errno %i (%s)\n", errno, strerror( errno ) ); + TracyDebug( "mmap failed: errno %i (%s)", errno, strerror( errno ) ); m_fd = 0; m_metadata = nullptr; close( fd ); return; } m_metadata = (perf_event_mmap_page*)mapAddr; - assert( m_metadata->data_offset == pageSize ); + TRACY_ASSERT( m_metadata->data_offset == pageSize ); m_buffer = ((char*)mapAddr) + pageSize; m_tail = m_metadata->data_tail; } @@ -50,20 +52,8 @@ class RingBuffer RingBuffer( const RingBuffer& ) = delete; RingBuffer& operator=( const RingBuffer& ) = delete; - RingBuffer( RingBuffer&& other ) - { - memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) ); - m_metadata = nullptr; - m_fd = 0; - } - - RingBuffer& operator=( RingBuffer&& other ) - { - memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) ); - m_metadata = nullptr; - m_fd = 0; - return *this; - } + RingBuffer( RingBuffer&& other ) = delete; + RingBuffer& operator=( RingBuffer&& other ) = delete; bool IsValid() const { return m_metadata != nullptr; } int GetId() const { return m_id; } @@ -74,10 +64,10 @@ class RingBuffer ioctl( m_fd, PERF_EVENT_IOC_ENABLE, 0 ); } - void Read( void* dst, uint64_t offset, uint64_t cnt ) + tracy_force_inline void Read( void* dst, uint64_t offset, uint64_t cnt ) { const auto size = m_size; - auto src = ( m_tail + offset ) % size; + auto src = ( m_tail + offset ) & m_mask; if( src + cnt <= size ) { memcpy( dst, m_buffer + src, cnt ); @@ -128,6 +118,7 @@ class RingBuffer } unsigned int m_size; + unsigned int m_mask; uint64_t m_tail; char* m_buffer; int m_id; diff --git a/libs/tracy/client/TracyRocprof.cpp b/libs/tracy/client/TracyRocprof.cpp index 370e42e..b8f9b29 100644 --- a/libs/tracy/client/TracyRocprof.cpp +++ b/libs/tracy/client/TracyRocprof.cpp @@ -1,3 +1,4 @@ +#include "../common/TracyAssert.hpp" #include "../server/tracy_robin_hood.h" #include "TracyProfiler.hpp" #include "TracyThread.hpp" @@ -79,14 +80,9 @@ uint8_t gpu_context_allocate( ToolData* data ) float timestamp_period = 1.0f; data->previous_cpu_time = cpu_timestamp; - // Allocate the process-unique GPU context ID. There's a max of 255 available; - // if we are recreating devices a lot we may exceed that. Don't do that, or - // wrap around and get weird (but probably still usable) numbers. - uint8_t context_id = tracy::GetGpuCtxCounter().fetch_add( 1, std::memory_order_relaxed ); - if( context_id >= 255 ) - { - context_id %= 255; - } + // Allocate the process-unique GPU context ID. There's a max of 256 available + // (ids are 8-bit); if we are recreating devices a lot we may exceed that. + uint8_t context_id = uint8_t( tracy::NextGpuContextId() ); uint8_t context_flags = 0; #ifdef TRACY_ROCPROF_CALIBRATION @@ -98,13 +94,16 @@ uint8_t gpu_context_allocate( ToolData* data ) { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuNewContext ); - tracy::MemWrite( &item->gpuNewContext.cpuTime, cpu_timestamp ); - tracy::MemWrite( &item->gpuNewContext.gpuTime, gpu_timestamp ); + tracy::MemWrite( &item->gpuNewContext.cpuTime, int64_t( cpu_timestamp ) ); + tracy::MemWrite( &item->gpuNewContext.gpuTime, int64_t( gpu_timestamp ) ); memset( &item->gpuNewContext.thread, 0, sizeof( item->gpuNewContext.thread ) ); tracy::MemWrite( &item->gpuNewContext.period, timestamp_period ); tracy::MemWrite( &item->gpuNewContext.context, context_id ); - tracy::MemWrite( &item->gpuNewContext.flags, context_flags ); + tracy::MemWrite( &item->gpuNewContext.flags, GpuContextFlags( context_flags ) ); tracy::MemWrite( &item->gpuNewContext.type, tracy::GpuContextType::Rocprof ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif tracy::Profiler::QueueSerialFinish(); } @@ -119,8 +118,11 @@ uint8_t gpu_context_allocate( ToolData* data ) auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuContextName ); tracy::MemWrite( &item->gpuContextNameFat.context, context_id ); - tracy::MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)cloned_name ); - tracy::MemWrite( &item->gpuContextNameFat.size, name_length ); + tracy::MemWrite( &item->gpuContextNameFat.ptr, uint64_t( cloned_name ) ); + tracy::MemWrite( &item->gpuContextNameFat.size, uint16_t( name_length ) ); +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem( *item ); +#endif tracy::Profiler::QueueSerialFinish(); } @@ -181,8 +183,8 @@ void record_interval( ToolData* data, rocprofiler_timestamp_t start_timestamp, r { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocSerial ); - tracy::MemWrite( &item->gpuZoneBegin.cpuTime, cpu_start_time ); - tracy::MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)src_loc ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, int64_t( cpu_start_time ) ); + tracy::MemWrite( &item->gpuZoneBegin.srcloc, src_loc ); tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); tracy::MemWrite( &item->gpuZoneBegin.queryId, query_id ); tracy::MemWrite( &item->gpuZoneBegin.context, context_id ); @@ -195,7 +197,7 @@ void record_interval( ToolData* data, rocprofiler_timestamp_t start_timestamp, r { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginSerial ); - tracy::MemWrite( &item->gpuZoneBegin.cpuTime, cpu_start_time ); + tracy::MemWrite( &item->gpuZoneBegin.cpuTime, int64_t( cpu_start_time ) ); tracy::MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)&src_loc ); tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() ); tracy::MemWrite( &item->gpuZoneBegin.queryId, query_id ); @@ -207,7 +209,7 @@ void record_interval( ToolData* data, rocprofiler_timestamp_t start_timestamp, r { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTime ); - tracy::MemWrite( &item->gpuTime.gpuTime, start_timestamp ); + tracy::MemWrite( &item->gpuTime.gpuTime, int64_t( start_timestamp ) ); tracy::MemWrite( &item->gpuTime.queryId, query_id ); tracy::MemWrite( &item->gpuTime.context, context_id ); tracy::Profiler::QueueSerialFinish(); @@ -216,7 +218,7 @@ void record_interval( ToolData* data, rocprofiler_timestamp_t start_timestamp, r { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneEndSerial ); - tracy::MemWrite( &item->gpuZoneEnd.cpuTime, cpu_end_time ); + tracy::MemWrite( &item->gpuZoneEnd.cpuTime, int64_t( cpu_end_time ) ); tracy::MemWrite( &item->gpuZoneEnd.thread, tracy::GetThreadHandle() ); tracy::MemWrite( &item->gpuZoneEnd.queryId, query_id ); tracy::MemWrite( &item->gpuZoneEnd.context, context_id ); @@ -226,7 +228,7 @@ void record_interval( ToolData* data, rocprofiler_timestamp_t start_timestamp, r { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTime ); - tracy::MemWrite( &item->gpuTime.gpuTime, end_timestamp ); + tracy::MemWrite( &item->gpuTime.gpuTime, int64_t( end_timestamp ) ); tracy::MemWrite( &item->gpuTime.queryId, query_id ); tracy::MemWrite( &item->gpuTime.context, context_id ); tracy::Profiler::QueueSerialFinish(); @@ -237,7 +239,7 @@ void record_callback( rocprofiler_dispatch_counting_service_data_t dispatch_data rocprofiler_record_counter_t* record_data, size_t record_count, rocprofiler_user_data_t /*user_data*/, void* callback_data ) { - assert( callback_data != nullptr ); + TRACY_ASSERT( callback_data != nullptr ); ToolData* data = static_cast( callback_data ); if( !data->init ) return; @@ -256,7 +258,7 @@ void record_callback( rocprofiler_dispatch_counting_service_data_t dispatch_data auto _lk = std::unique_lock{ data->mut }; // An assumption is made here that the counter values are supplied after the dispatch // complete callback. - assert( data->dispatch_data.count( dispatch_data.dispatch_info.dispatch_id ) ); + TRACY_ASSERT( data->dispatch_data.count( dispatch_data.dispatch_info.dispatch_id ) ); DispatchData& ddata = data->dispatch_data[dispatch_data.dispatch_info.dispatch_id]; query_id = ddata.query_id; thread_id = ddata.thread_id; @@ -266,7 +268,7 @@ void record_callback( rocprofiler_dispatch_counting_service_data_t dispatch_data { auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneAnnotation ); - tracy::MemWrite( &item->zoneAnnotation.noteId, p.first ); + tracy::MemWrite( &item->zoneAnnotation.noteId, int64_t( p.first ) ); tracy::MemWrite( &item->zoneAnnotation.queryId, query_id ); tracy::MemWrite( &item->zoneAnnotation.thread, thread_id ); tracy::MemWrite( &item->zoneAnnotation.value, p.second ); @@ -284,7 +286,7 @@ void dispatch_callback( rocprofiler_dispatch_counting_service_data_t dispatch_da rocprofiler_profile_config_id_t* config, rocprofiler_user_data_t* /*user_data*/, void* callback_data ) { - assert( callback_data != nullptr ); + TRACY_ASSERT( callback_data != nullptr ); ToolData* data = static_cast( callback_data ); if( !data->init ) return; @@ -356,9 +358,9 @@ void dispatch_callback( rocprofiler_dispatch_counting_service_data_t dispatch_da auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuAnnotationName ); tracy::MemWrite( &item->gpuAnnotationNameFat.context, data->context_id ); - tracy::MemWrite( &item->gpuAnnotationNameFat.noteId, counter.handle ); - tracy::MemWrite( &item->gpuAnnotationNameFat.ptr, (uint64_t)cloned_name ); - tracy::MemWrite( &item->gpuAnnotationNameFat.size, name_length ); + tracy::MemWrite( &item->gpuAnnotationNameFat.noteId, int64_t( counter.handle ) ); + tracy::MemWrite( &item->gpuAnnotationNameFat.ptr, uint64_t( cloned_name ) ); + tracy::MemWrite( &item->gpuAnnotationNameFat.size, uint16_t( name_length ) ); tracy::Profiler::QueueSerialFinish(); } } @@ -378,10 +380,12 @@ void dispatch_callback( rocprofiler_dispatch_counting_service_data_t dispatch_da void tool_callback_tracing_callback( rocprofiler_callback_tracing_record_t record, rocprofiler_user_data_t* user_data, void* callback_data ) { - assert( callback_data != nullptr ); + TRACY_ASSERT( callback_data != nullptr ); ToolData* data = static_cast( callback_data ); - if( !data->init ) return; + // Kernel symbol registrations happen at HIP init time, before any Tracy + // client connects (and before data->init is set). Record them regardless + // of init state so that kernel names are available when profiling starts. if( record.kind == ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT && record.operation == ROCPROFILER_CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER ) { @@ -398,7 +402,13 @@ void tool_callback_tracing_callback( rocprofiler_callback_tracing_record_t recor data->client_kernels.erase( sym_data->kernel_id ); } } - else if( record.kind == ROCPROFILER_CALLBACK_TRACING_KERNEL_DISPATCH ) + + // Gate dispatch and memory-copy recording on data->init, which is set + // once the GPU context is allocated (under TRACY_ON_DEMAND this waits + // for a client connection). + if( !data->init ) return; + + if( record.kind == ROCPROFILER_CALLBACK_TRACING_KERNEL_DISPATCH ) { auto* rdata = static_cast( record.payload ); if( record.operation == ROCPROFILER_KERNEL_DISPATCH_ENQUEUE ) diff --git a/libs/tracy/client/TracyScoped.hpp b/libs/tracy/client/TracyScoped.hpp index c2f7eda..34611ac 100644 --- a/libs/tracy/client/TracyScoped.hpp +++ b/libs/tracy/client/TracyScoped.hpp @@ -9,15 +9,11 @@ #include "../common/TracySystem.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" +#include "../common/TracyFormat.h" #include "TracyProfiler.hpp" #include "TracyCallstack.hpp" -#if (defined(__GNUC__) || defined(__clang__)) -# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) \ - __attribute__((format(printf, fmt_idx, arg_idx))) -#else -# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) -#endif namespace tracy { @@ -92,7 +88,7 @@ class ScopedZone tracy_force_inline void Text( const char* txt, size_t size ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -116,7 +112,7 @@ class ScopedZone auto size = vsnprintf( nullptr, 0, fmt, args ); va_end( args ); if( size < 0 ) return; - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); va_start( args, fmt ); @@ -131,7 +127,7 @@ class ScopedZone tracy_force_inline void Name( const char* txt, size_t size ) { - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); if( !m_active ) return; #ifdef TRACY_ON_DEMAND if( GetProfiler().ConnectionId() != m_connectionId ) return; @@ -155,7 +151,7 @@ class ScopedZone auto size = vsnprintf( nullptr, 0, fmt, args ); va_end( args ); if( size < 0 ) return; - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); char* ptr = (char*)tracy_malloc( size_t( size ) + 1 ); va_start( args, fmt ); diff --git a/libs/tracy/client/TracySysPower.cpp b/libs/tracy/client/TracySysPower.cpp index 6ad1d64..ed04b8b 100644 --- a/libs/tracy/client/TracySysPower.cpp +++ b/libs/tracy/client/TracySysPower.cpp @@ -138,7 +138,7 @@ void SysPower::ScanDirectory( const char* path, int parent ) domain->overflow = maxRange; domain->handle = handle; domain->name = name; - TracyDebug( "Power domain id %i, %s found at %s\n", parent, name, path ); + TracyDebug( "Power domain id %i, %s found at %s", parent, name, path ); } else { diff --git a/libs/tracy/client/TracySysTime.cpp b/libs/tracy/client/TracySysTime.cpp index cf7dd9b..0a38500 100644 --- a/libs/tracy/client/TracySysTime.cpp +++ b/libs/tracy/client/TracySysTime.cpp @@ -11,7 +11,7 @@ # elif defined __APPLE__ # include # include -# elif defined BSD +# elif defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ # include # include # endif @@ -79,7 +79,7 @@ void SysTime::ReadTimes() idle = info.cpu_ticks[CPU_STATE_IDLE]; } -# elif defined BSD +# elif defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ void SysTime::ReadTimes() { @@ -109,7 +109,7 @@ float SysTime::Get() #if defined _WIN32 return diffUsed == 0 ? -1 : ( diffUsed - diffIdle ) * 100.f / diffUsed; -#elif defined __linux__ || defined __APPLE__ || defined BSD +#elif defined __linux__ || defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ const auto total = diffUsed + diffIdle; return total == 0 ? -1 : diffUsed * 100.f / total; #endif diff --git a/libs/tracy/client/TracySysTime.hpp b/libs/tracy/client/TracySysTime.hpp index cb5ebe7..77dbd28 100644 --- a/libs/tracy/client/TracySysTime.hpp +++ b/libs/tracy/client/TracySysTime.hpp @@ -1,13 +1,7 @@ #ifndef __TRACYSYSTIME_HPP__ #define __TRACYSYSTIME_HPP__ -#if defined _WIN32 || defined __linux__ || defined __APPLE__ -# define TRACY_HAS_SYSTIME -#else -# include -#endif - -#ifdef BSD +#if defined _WIN32 || defined __linux__ || defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ # define TRACY_HAS_SYSTIME #endif diff --git a/libs/tracy/client/TracySysTrace.cpp b/libs/tracy/client/TracySysTrace.cpp index e6bb356..0788dc0 100644 --- a/libs/tracy/client/TracySysTrace.cpp +++ b/libs/tracy/client/TracySysTrace.cpp @@ -1,6 +1,7 @@ #include "TracyDebug.hpp" #include "TracyStringHelpers.hpp" #include "TracySysTrace.hpp" +#include "../common/TracyAssert.hpp" #include "../common/TracySystem.hpp" #ifdef TRACY_HAS_SYSTEM_TRACING @@ -10,6 +11,8 @@ # define TRACY_SAMPLING_HZ 8000 # elif defined __linux__ # define TRACY_SAMPLING_HZ 10000 +# elif defined __APPLE__ +# define TRACY_SAMPLING_HZ 1000 # endif #endif @@ -34,9 +37,9 @@ static int GetSamplingFrequency() #endif } -static int GetSamplingPeriod() +static int SamplingFrequencyToPeriodNs( int samplingHz ) { - return 1000000000 / GetSamplingFrequency(); + return 1000000000 / samplingHz; } } @@ -48,12 +51,9 @@ static int GetSamplingPeriod() # endif # define INITGUID -# include # include # include # include -# include -# include # include # include @@ -61,85 +61,14 @@ static int GetSamplingPeriod() # include "../common/TracySystem.hpp" # include "TracyProfiler.hpp" # include "TracyThread.hpp" +# include "windows/TracyETW_compat.h" +# include "windows/TracyETW.cpp" namespace tracy { -static const GUID PerfInfoGuid = { 0xce1dbfb4, 0x137e, 0x4da6, { 0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc } }; -static const GUID DxgKrnlGuid = { 0x802ec45a, 0x1e99, 0x4b83, { 0x99, 0x20, 0x87, 0xc9, 0x82, 0x77, 0xba, 0x9d } }; -static const GUID ThreadV2Guid = { 0x3d6fa8d1, 0xfe05, 0x11d0, { 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c } }; - - -static TRACEHANDLE s_traceHandle; -static TRACEHANDLE s_traceHandle2; -static EVENT_TRACE_PROPERTIES* s_prop; static DWORD s_pid; -static EVENT_TRACE_PROPERTIES* s_propVsync; -static TRACEHANDLE s_traceHandleVsync; -static TRACEHANDLE s_traceHandleVsync2; -Thread* s_threadVsync = nullptr; - -struct CSwitch -{ - uint32_t newThreadId; - uint32_t oldThreadId; - int8_t newThreadPriority; - int8_t oldThreadPriority; - uint8_t previousCState; - int8_t spareByte; - int8_t oldThreadWaitReason; - int8_t oldThreadWaitMode; - int8_t oldThreadState; - int8_t oldThreadWaitIdealProcessor; - uint32_t newThreadWaitTime; - uint32_t reserved; -}; - -struct ReadyThread -{ - uint32_t threadId; - int8_t adjustReason; - int8_t adjustIncrement; - int8_t flag; - int8_t reserverd; -}; - -struct ThreadTrace -{ - uint32_t processId; - uint32_t threadId; - uint32_t stackBase; - uint32_t stackLimit; - uint32_t userStackBase; - uint32_t userStackLimit; - uint32_t startAddr; - uint32_t win32StartAddr; - uint32_t tebBase; - uint32_t subProcessTag; -}; - -struct StackWalkEvent -{ - uint64_t eventTimeStamp; - uint32_t stackProcess; - uint32_t stackThread; - uint64_t stack[192]; -}; - -struct VSyncInfo -{ - void* dxgAdapter; - uint32_t vidPnTargetId; - uint64_t scannedPhysicalAddress; - uint32_t vidPnSourceId; - uint32_t frameNumber; - int64_t frameQpcTime; - void* hFlipDevice; - uint32_t flipType; - uint64_t flipFenceId; -}; - extern "C" typedef NTSTATUS (WINAPI *t_NtQueryInformationThread)( HANDLE, THREADINFOCLASS, PVOID, ULONG, PULONG ); extern "C" typedef BOOL (WINAPI *t_EnumProcessModules)( HANDLE, HMODULE*, DWORD, LPDWORD ); extern "C" typedef BOOL (WINAPI *t_GetModuleInformation)( HANDLE, HMODULE, LPMODULEINFO, DWORD ); @@ -161,28 +90,32 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) #endif const auto& hdr = record->EventHeader; + // WARN: doing a fast switch-match below with the top 32 bits of the GUID + // (Data1 is the leading 32bit word of the 128bit GUID). + // Ideally, we should be using 'IsEqualGUID()' inside each case match to be + // inequivocally sure we are dealing the correct event provider. switch( hdr.ProviderId.Data1 ) { - case 0x3d6fa8d1: // Thread Guid - if( hdr.EventDescriptor.Opcode == 36 ) + case etw::ThreadGuid.Data1: + if( hdr.EventDescriptor.Opcode == etw::CSwitch::Opcode ) { - const auto cswitch = (const CSwitch*)record->UserData; + const auto cswitch = (const etw::CSwitch*)record->UserData; TracyLfqPrepare( QueueType::ContextSwitch ); MemWrite( &item->contextSwitch.time, hdr.TimeStamp.QuadPart ); MemWrite( &item->contextSwitch.oldThread, cswitch->oldThreadId ); MemWrite( &item->contextSwitch.newThread, cswitch->newThreadId ); MemWrite( &item->contextSwitch.cpu, record->BufferContext.ProcessorNumber ); - MemWrite( &item->contextSwitch.oldThreadWaitReason, cswitch->oldThreadWaitReason ); - MemWrite( &item->contextSwitch.oldThreadState, cswitch->oldThreadState ); + MemWrite( &item->contextSwitch.oldThreadWaitReason, uint8_t( cswitch->oldThreadWaitReason ) ); + MemWrite( &item->contextSwitch.oldThreadState, uint8_t( cswitch->oldThreadState ) ); MemWrite( &item->contextSwitch.newThreadPriority, cswitch->newThreadPriority ); MemWrite( &item->contextSwitch.oldThreadPriority, cswitch->oldThreadPriority ); MemWrite( &item->contextSwitch.previousCState, cswitch->previousCState ); TracyLfqCommit; } - else if( hdr.EventDescriptor.Opcode == 50 ) + else if( hdr.EventDescriptor.Opcode == etw::ReadyThread::Opcode ) { - const auto rt = (const ReadyThread*)record->UserData; + const auto rt = (const etw::ReadyThread*)record->UserData; TracyLfqPrepare( QueueType::ThreadWakeup ); MemWrite( &item->threadWakeup.time, hdr.TimeStamp.QuadPart ); @@ -192,23 +125,23 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) MemWrite( &item->threadWakeup.adjustIncrement, rt->adjustIncrement ); TracyLfqCommit; } - else if( hdr.EventDescriptor.Opcode == 1 || hdr.EventDescriptor.Opcode == 3 ) + else if( hdr.EventDescriptor.Opcode == etw::ThreadStart::Opcode || hdr.EventDescriptor.Opcode == etw::ThreadDCStart::Opcode ) { - const auto tt = (const ThreadTrace*)record->UserData; + const auto ti = (const etw::ThreadInfo*)record->UserData; - uint64_t tid = tt->threadId; + uint64_t tid = ti->threadId; if( tid == 0 ) return; - uint64_t pid = tt->processId; + uint64_t pid = ti->processId; TracyLfqPrepare( QueueType::TidToPid ); MemWrite( &item->tidToPid.tid, tid ); MemWrite( &item->tidToPid.pid, pid ); TracyLfqCommit; } break; - case 0xdef2fe46: // StackWalk Guid - if( hdr.EventDescriptor.Opcode == 32 ) + case etw::StackWalkGuid.Data1: + if( hdr.EventDescriptor.Opcode == etw::StackWalkEvent::Opcode ) { - const auto sw = (const StackWalkEvent*)record->UserData; + const auto sw = (const etw::StackWalkEvent*)record->UserData; if( sw->stackProcess == s_pid ) { const uint64_t sz = ( record->UserDataLength - 16 ) / 8; @@ -218,128 +151,34 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) memcpy( trace, &sz, sizeof( uint64_t ) ); memcpy( trace+1, sw->stack, sizeof( uint64_t ) * sz ); TracyLfqPrepare( QueueType::CallstackSample ); - MemWrite( &item->callstackSampleFat.time, sw->eventTimeStamp ); + MemWrite( &item->callstackSampleFat.time, int64_t( sw->eventTimeStamp ) ); MemWrite( &item->callstackSampleFat.thread, sw->stackThread ); - MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); + MemWrite( &item->callstackSampleFat.ptr, uint64_t( trace ) ); TracyLfqCommit; } } } break; + case etw::DxgKrnlGuid.Data1: + TRACY_ASSERT( hdr.EventDescriptor.Id == etw::VSyncDPC::EventId ); + { + const auto vs = (const etw::VSyncDPC*)record->UserData; + TracyLfqPrepare( QueueType::FrameVsync ); + MemWrite( &item->frameVsync.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->frameVsync.id, vs->vidPnTargetId ); + TracyLfqCommit; + } + break; default: break; } } -void WINAPI EventRecordCallbackVsync( PEVENT_RECORD record ) -{ -#ifdef TRACY_ON_DEMAND - if( !GetProfiler().IsConnected() ) return; -#endif - - const auto& hdr = record->EventHeader; - assert( hdr.ProviderId.Data1 == 0x802EC45A ); - assert( hdr.EventDescriptor.Id == 0x0011 ); - - const auto vs = (const VSyncInfo*)record->UserData; - - TracyLfqPrepare( QueueType::FrameVsync ); - MemWrite( &item->frameVsync.time, hdr.TimeStamp.QuadPart ); - MemWrite( &item->frameVsync.id, vs->vidPnTargetId ); - TracyLfqCommit; -} - -static void SetupVsync() -{ -#if _WIN32_WINNT >= _WIN32_WINNT_WINBLUE && !defined(__MINGW32__) - const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + MAX_PATH; - s_propVsync = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); - memset( s_propVsync, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); - s_propVsync->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; - s_propVsync->Wnode.BufferSize = psz; -#ifdef TRACY_TIMER_QPC - s_propVsync->Wnode.ClientContext = 1; -#else - s_propVsync->Wnode.ClientContext = 3; -#endif - s_propVsync->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); - strcpy( ((char*)s_propVsync) + sizeof( EVENT_TRACE_PROPERTIES ), "TracyVsync" ); - - auto backup = tracy_malloc( psz ); - memcpy( backup, s_propVsync, psz ); - - const auto controlStatus = ControlTraceA( 0, "TracyVsync", s_propVsync, EVENT_TRACE_CONTROL_STOP ); - if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) - { - tracy_free( backup ); - tracy_free( s_propVsync ); - return; - } - - memcpy( s_propVsync, backup, psz ); - tracy_free( backup ); - - const auto startStatus = StartTraceA( &s_traceHandleVsync, "TracyVsync", s_propVsync ); - if( startStatus != ERROR_SUCCESS ) - { - tracy_free( s_propVsync ); - return; - } - - EVENT_FILTER_EVENT_ID fe = {}; - fe.FilterIn = TRUE; - fe.Count = 1; - fe.Events[0] = 0x0011; // VSyncDPC_Info - - EVENT_FILTER_DESCRIPTOR desc = {}; - desc.Ptr = (ULONGLONG)&fe; - desc.Size = sizeof( fe ); - desc.Type = EVENT_FILTER_TYPE_EVENT_ID; - - ENABLE_TRACE_PARAMETERS params = {}; - params.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; - params.EnableProperty = EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0; - params.SourceId = s_propVsync->Wnode.Guid; - params.EnableFilterDesc = &desc; - params.FilterDescCount = 1; - - uint64_t mask = 0x4000000000000001; // Microsoft_Windows_DxgKrnl_Performance | Base - if( EnableTraceEx2( s_traceHandleVsync, &DxgKrnlGuid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, mask, mask, 0, ¶ms ) != ERROR_SUCCESS ) - { - tracy_free( s_propVsync ); - return; - } - - char loggerName[MAX_PATH]; - strcpy( loggerName, "TracyVsync" ); - - EVENT_TRACE_LOGFILEA log = {}; - log.LoggerName = loggerName; - log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; - log.EventRecordCallback = EventRecordCallbackVsync; - - s_traceHandleVsync2 = OpenTraceA( &log ); - if( s_traceHandleVsync2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) - { - CloseTrace( s_traceHandleVsync ); - tracy_free( s_propVsync ); - return; - } - - s_threadVsync = (Thread*)tracy_malloc( sizeof( Thread ) ); - new(s_threadVsync) Thread( [] (void*) { - ThreadExitHandler threadExitHandler; - SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); - SetThreadName( "Tracy Vsync" ); - ProcessTrace( &s_traceHandleVsync2, 1, nullptr, nullptr ); - }, nullptr ); -#endif -} - -static int GetSamplingInterval() -{ - return GetSamplingPeriod() / 100; -} +static etw::Session session_kernel = {}; +static etw::Session session_vsync = {}; +static PROCESSTRACE_HANDLE consumer_kernel = INVALID_PROCESSTRACE_HANDLE; +static PROCESSTRACE_HANDLE consumer_vsync = INVALID_PROCESSTRACE_HANDLE; +static Thread* s_threadVsync = nullptr; bool SysTraceStart( int64_t& samplingPeriod ) { @@ -347,121 +186,59 @@ bool SysTraceStart( int64_t& samplingPeriod ) s_pid = GetCurrentProcessId(); -#if defined _WIN64 - constexpr bool isOs64Bit = true; -#else - BOOL _iswow64; - IsWow64Process( GetCurrentProcess(), &_iswow64 ); - const bool isOs64Bit = _iswow64; -#endif - - TOKEN_PRIVILEGES priv = {}; - priv.PrivilegeCount = 1; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if( LookupPrivilegeValue( nullptr, SE_SYSTEM_PROFILE_NAME, &priv.Privileges[0].Luid ) == 0 ) return false; - - HANDLE pt; - if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &pt ) == 0 ) return false; - const auto adjust = AdjustTokenPrivileges( pt, FALSE, &priv, 0, nullptr, nullptr ); - CloseHandle( pt ); - if( adjust == 0 ) return false; - const auto status = GetLastError(); - if( status != ERROR_SUCCESS ) return false; + if( !etw::CheckAdminPrivilege() ) + return false; - if( isOs64Bit ) - { - TRACE_PROFILE_INTERVAL interval = {}; - interval.Interval = GetSamplingInterval(); - const auto intervalStatus = TraceSetInformation( 0, TraceSampledProfileIntervalInfo, &interval, sizeof( interval ) ); - if( intervalStatus != ERROR_SUCCESS ) return false; - samplingPeriod = GetSamplingPeriod(); - } + session_kernel = etw::StartSingletonKernelLoggerSession( 0 ); + if( session_kernel.handle == 0 ) + return false; - const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + sizeof( KERNEL_LOGGER_NAME ); - s_prop = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); - memset( s_prop, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); - ULONG flags = 0; #ifndef TRACY_NO_CONTEXT_SWITCH - flags = EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_DISPATCHER | EVENT_TRACE_FLAG_THREAD; -#endif -#ifndef TRACY_NO_SAMPLING - if( isOs64Bit ) flags |= EVENT_TRACE_FLAG_PROFILE; -#endif - s_prop->EnableFlags = flags; - s_prop->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; - s_prop->Wnode.BufferSize = psz; - s_prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; -#ifdef TRACY_TIMER_QPC - s_prop->Wnode.ClientContext = 1; +#ifdef TRACY_NO_WAIT_STACKS + const bool noWaitStacks = true; #else - s_prop->Wnode.ClientContext = 3; + const char* noWaitStacksEnv = GetEnvVar( "TRACY_NO_WAIT_STACKS" ); + const bool noWaitStacks = noWaitStacksEnv && noWaitStacksEnv[0] == '1'; +#endif + if( etw::EnableProcessAndThreadMonitoring( session_kernel ) != ERROR_SUCCESS ) + return etw::StopSession( session_kernel ), false; + if( etw::EnableContextSwitchMonitoring( session_kernel, !noWaitStacks ) != ERROR_SUCCESS ) + return etw::StopSession( session_kernel ), false; #endif - s_prop->Wnode.Guid = SystemTraceControlGuid; - s_prop->BufferSize = 1024; - s_prop->MinimumBuffers = std::thread::hardware_concurrency() * 4; - s_prop->MaximumBuffers = std::thread::hardware_concurrency() * 6; - s_prop->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); - memcpy( ((char*)s_prop) + sizeof( EVENT_TRACE_PROPERTIES ), KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); - - auto backup = tracy_malloc( psz ); - memcpy( backup, s_prop, psz ); - - const auto controlStatus = ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); - if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) - { - tracy_free( backup ); - tracy_free( s_prop ); - return false; - } - memcpy( s_prop, backup, psz ); - tracy_free( backup ); - - const auto startStatus = StartTrace( &s_traceHandle, KERNEL_LOGGER_NAME, s_prop ); - if( startStatus != ERROR_SUCCESS ) - { - tracy_free( s_prop ); - return false; - } #ifndef TRACY_NO_SAMPLING - if( isOs64Bit ) - { - CLASSIC_EVENT_ID stackId[2] = {}; - stackId[0].EventGuid = PerfInfoGuid; - stackId[0].Type = 46; - stackId[1].EventGuid = ThreadV2Guid; - stackId[1].Type = 36; - const auto stackStatus = TraceSetInformation( s_traceHandle, TraceStackTracingInfo, &stackId, sizeof( stackId ) ); - if( stackStatus != ERROR_SUCCESS ) - { - tracy_free( s_prop ); - return false; - } - } + samplingPeriod = SamplingFrequencyToPeriodNs( GetSamplingFrequency() ); + const int microseconds = samplingPeriod / 1000; + if( etw::EnableCPUProfiling( session_kernel, microseconds ) != ERROR_SUCCESS ) + return etw::StopSession( session_kernel ), false; #endif -#ifdef UNICODE - WCHAR KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; -#else - char KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; -#endif - memcpy( KernelLoggerName, KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); - EVENT_TRACE_LOGFILE log = {}; - log.LoggerName = KernelLoggerName; - log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; - log.EventRecordCallback = EventRecordCallback; - - s_traceHandle2 = OpenTrace( &log ); - if( s_traceHandle2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) - { - CloseTrace( s_traceHandle ); - tracy_free( s_prop ); - return false; - } + consumer_kernel = etw::SetupEventConsumer( session_kernel, EventRecordCallback ); + if( consumer_kernel == INVALID_PROCESSTRACE_HANDLE ) + return etw::StopSession( session_kernel ), false; #ifndef TRACY_NO_VSYNC_CAPTURE - SetupVsync(); + session_vsync = etw::StartUserSession( "TracyVsync" ); + if( session_vsync.handle != 0 ) + { + if( etw::EnableVSyncMonitoring( session_vsync ) != ERROR_SUCCESS ) + etw::StopSession( session_vsync ); + else + { + consumer_vsync = etw::SetupEventConsumer( session_vsync, EventRecordCallback ); + if( consumer_vsync != INVALID_PROCESSTRACE_HANDLE ) + { + s_threadVsync = (Thread*)tracy_malloc( sizeof( Thread ) ); + new(s_threadVsync) Thread( [] (void*) { + ThreadExitHandler threadExitHandler; + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + SetThreadName( "Tracy Vsync (ETW)" ); + etw::EventConsumerLoop( consumer_vsync ); + }, nullptr ); + } + } + } #endif return true; @@ -471,24 +248,21 @@ void SysTraceStop() { if( s_threadVsync ) { - CloseTrace( s_traceHandleVsync2 ); - CloseTrace( s_traceHandleVsync ); + etw::StopEventConsumer( consumer_vsync ); + etw::StopSession( session_vsync ); s_threadVsync->~Thread(); tracy_free( s_threadVsync ); } - - CloseTrace( s_traceHandle2 ); - CloseTrace( s_traceHandle ); + etw::StopEventConsumer( consumer_kernel ); + etw::StopSession( session_kernel ); } void SysTraceWorker( void* ptr ) { ThreadExitHandler threadExitHandler; SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); - SetThreadName( "Tracy SysTrace" ); - ProcessTrace( &s_traceHandle2, 1, 0, 0 ); - ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); - tracy_free( s_prop ); + SetThreadName( "Tracy SysTrace (ETW)" ); + etw::EventConsumerLoop( consumer_kernel ); } void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const char*& name ) @@ -607,9 +381,11 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch # include # include # include +# include # include # include # include +# include # include # include # include @@ -622,11 +398,13 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch # include # include # include +# include # if defined __i386 || defined __x86_64__ # include "TracyCpuid.hpp" # endif +# include "TracyCallstack.hpp" # include "TracyProfiler.hpp" # include "TracyRingBuffer.hpp" # include "TracyThread.hpp" @@ -638,9 +416,57 @@ static std::atomic traceActive { false }; static int s_numCpus = 0; static int s_numBuffers = 0; static int s_ctxBufferIdx = 0; +static bool s_ctxSwitchCallchain = false; static RingBuffer* s_ring = nullptr; + +struct PerfIterTarget +{ + pid_t pid; + int cpu; +}; + +// Read /proc//task/ and return the list of tids. Caller owns the buffer +// (tracy_free). Returns 0 and sets *out = nullptr on failure. +static int EnumerateTaskTids( pid_t pid, uint32_t** out ) +{ + char path[64]; + snprintf( path, sizeof( path ), "/proc/%d/task", (int)pid ); + DIR* dir = opendir( path ); + if( !dir ) + { + *out = nullptr; + return 0; + } + size_t capacity = 32; + uint32_t* tids = (uint32_t*)tracy_malloc( sizeof( uint32_t ) * capacity ); + size_t count = 0; + struct dirent* entry; + while( ( entry = readdir( dir ) ) != nullptr ) + { + if( entry->d_name[0] == '.' ) continue; + char* endp; + unsigned long tid = strtoul( entry->d_name, &endp, 10 ); + if( *endp != '\0' || tid == 0 ) continue; + if( count >= capacity ) + { + capacity *= 2; + tids = (uint32_t*)tracy_realloc( tids, sizeof( uint32_t ) * capacity ); + } + tids[count++] = (uint32_t)tid; + } + closedir( dir ); + if( count == 0 ) + { + tracy_free( tids ); + *out = nullptr; + return 0; + } + *out = tids; + return (int)count; +} + static const int ThreadHashSize = 4 * 1024; static uint32_t s_threadHash[ThreadHashSize] = {}; @@ -652,7 +478,17 @@ static bool CurrentProcOwnsThread( uint32_t tid ) if( hv == -tid ) return false; char path[256]; - sprintf( path, "/proc/self/task/%d", tid ); +#ifdef TRACY_HAS_EXTERNAL_TARGET + const auto externalPid = GetExternalTargetPid(); + if( externalPid != 0 ) + { + sprintf( path, "/proc/%" PRIu32 "/task/%" PRIu32, externalPid, tid ); + } + else +#endif + { + sprintf( path, "/proc/self/task/%" PRIu32, tid ); + } struct stat st; if( stat( path, &st ) == 0 ) { @@ -685,6 +521,36 @@ enum TraceEventId EventWaking, }; +static void ProbePreciseIp( perf_event_attr& pe, pid_t pid ); + +static bool OpenSampleEvent( const PerfIterTarget& tgt, const perf_event_attr& inPe, int eventId ) +{ + static bool noKernelAccessLogged = false; + perf_event_attr pe = inPe; + int fd = perf_event_open( &pe, tgt.pid, tgt.cpu, -1, PERF_FLAG_FD_CLOEXEC ); + if( fd == -1 ) + { + pe.exclude_kernel = 1; + pe.exclude_callchain_kernel = 1; + ProbePreciseIp( pe, tgt.pid ); + fd = perf_event_open( &pe, tgt.pid, tgt.cpu, -1, PERF_FLAG_FD_CLOEXEC ); + if( fd != -1 && !noKernelAccessLogged ) + { + noKernelAccessLogged = true; + TracyDebug( " No access to kernel samples; user-space only (perf_event_paranoid / capabilities)" ); + } + } + if( fd == -1 ) + { + TracyDebug( " Failed to setup!" ); + return false; + } + new( s_ring + s_numBuffers ) RingBuffer( 64 * 1024, fd, eventId ); + if( !s_ring[s_numBuffers].IsValid() ) return false; + s_numBuffers++; + return true; +} + static void ProbePreciseIp( perf_event_attr& pe, unsigned long long config0, unsigned long long config1, pid_t pid ) { pe.config = config1; @@ -710,7 +576,7 @@ static void ProbePreciseIp( perf_event_attr& pe, unsigned long long config0, uns } pe.precise_ip--; } - TracyDebug( " Probed precise_ip: %i\n", pe.precise_ip ); + TracyDebug( " Probed precise_ip: %i", pe.precise_ip ); } static void ProbePreciseIp( perf_event_attr& pe, pid_t pid ) @@ -726,7 +592,7 @@ static void ProbePreciseIp( perf_event_attr& pe, pid_t pid ) } pe.precise_ip--; } - TracyDebug( " Probed precise_ip: %i\n", pe.precise_ip ); + TracyDebug( " Probed precise_ip: %i", pe.precise_ip ); } static bool IsGenuineIntel() @@ -774,45 +640,59 @@ static const char* ReadFile( const char* base, const char* path ) static char* GetTraceFsPath() { - int fd = open( "/proc/mounts", O_RDONLY ); - if( fd < 0 ) return nullptr; - - constexpr size_t BufSize = 64 * 1024; - auto tmp = (char*)tracy_malloc( BufSize ); - const auto cnt = read( fd, tmp, BufSize-1 ); - close( fd ); - if( cnt < 0 ) - { - tracy_free( tmp ); - return nullptr; - } - tmp[cnt] = '\0'; + auto f = setmntent( "/proc/mounts", "r" ); + if( !f ) return nullptr; - auto ptr = tmp; - while( *ptr ) + char* ret = nullptr; + while( auto ent = getmntent( f ) ) { - if( strncmp( ptr, "tracefs ", 8 ) == 0 ) + if( strcmp( ent->mnt_type, "tracefs" ) == 0 ) { - ptr += 8; - auto end = ptr; - while( *end && *end != ' ' ) end++; - if( !*end ) - { - tracy_free( tmp ); - return nullptr; - } - const auto len = end - ptr; - auto ret = (char*)tracy_malloc( len+1 ); - memcpy( ret, ptr, len ); + auto len = strlen( ent->mnt_dir ); + // ret may be != nullptr if we already saw a debugfs entry + ret = (char*)tracy_realloc( ret, len + 1 ); + memcpy( ret, ent->mnt_dir, len ); ret[len] = '\0'; - return ret; + break; + } + else if( !ret && strcmp( ent->mnt_type, "debugfs" ) == 0 ) + { + const char* tracingDirName = "tracing"; + const size_t tracingDirNameLen = strlen( tracingDirName ); + auto debugFsPathLen = strlen( ent->mnt_dir ); + ret = (char*)tracy_malloc( debugFsPathLen + 1 + tracingDirNameLen + 1 ); + memcpy( ret, ent->mnt_dir, debugFsPathLen ); + ret[debugFsPathLen] = '/'; + memcpy( ret + debugFsPathLen + 1, tracingDirName, tracingDirNameLen ); + ret[debugFsPathLen + 1 + tracingDirNameLen] = '\0'; + // Don't break to allow for tracefs to be found later as it is the preferred path } - while( *ptr && *ptr != '\n' ) ptr++; - if( *ptr ) ptr++; } + endmntent( f ); + return ret; +} - tracy_free( tmp ); - return nullptr; +// Categories of the running kernel's perf_event_open() ABI, defined by the +// perf_event_attr fields Tracy uses. use_clockid/clockid exist since Linux +// 4.1 (commit 34f439278c), sample_max_stack since Linux 4.8 (commit +// 97c79a38cd); older kernels reject the fields (EINVAL / E2BIG) and all of +// system tracing dies. Kernels whose version cannot be determined are +// treated as the least capable. +enum PerfKernelAbi +{ + PerfAbiPre41, // < 4.1: no use_clockid, no sample_max_stack + PerfAbi41To47, // 4.1-4.7: use_clockid, no sample_max_stack + PerfAbi48AndNewer, // >= 4.8: use_clockid, sample_max_stack +}; + +static PerfKernelAbi ClassifyPerfKernelAbi( const char* release ) +{ + int major, minor; + if( sscanf( release, "%d.%d", &major, &minor ) != 2 ) return PerfAbiPre41; + const int version = KERNEL_VERSION( major, minor, 0 ); + if( version < KERNEL_VERSION( 4, 1, 0 ) ) return PerfAbiPre41; + if( version < KERNEL_VERSION( 4, 8, 0 ) ) return PerfAbi41To47; + return PerfAbi48AndNewer; } bool SysTraceStart( int64_t& samplingPeriod ) @@ -822,16 +702,22 @@ bool SysTraceStart( int64_t& samplingPeriod ) #endif const auto paranoidLevelStr = ReadFile( "/proc/sys/kernel/perf_event_paranoid" ); - if( !paranoidLevelStr ) return false; -#ifdef TRACY_VERBOSE - int paranoidLevel = 2; - paranoidLevel = atoi( paranoidLevelStr ); - TracyDebug( "perf_event_paranoid: %i\n", paranoidLevel ); -#endif + if( !paranoidLevelStr ) + { + TracyDebug( "Failed to read perf_event_paranoid, cannot setup system tracing." ); + return false; + } + + const int paranoidLevel = atoi( paranoidLevelStr ); + TracyDebug( "perf_event_paranoid: %i", paranoidLevel ); auto traceFsPath = GetTraceFsPath(); - if( !traceFsPath ) return false; - TracyDebug( "tracefs path: %s\n", traceFsPath ); + if( !traceFsPath ) + { + TracyDebug( "Failed to get tracefs path, cannot setup system tracing." ); + return false; + } + TracyDebug( "tracefs path: %s", traceFsPath ); int switchId = -1, wakingId = -1, vsyncId = -1; const auto switchIdStr = ReadFile( traceFsPath, "/events/sched/sched_switch/id" ); @@ -843,9 +729,27 @@ bool SysTraceStart( int64_t& samplingPeriod ) tracy_free( traceFsPath ); - TracyDebug( "sched_switch id: %i\n", switchId ); - TracyDebug( "sched_waking id: %i\n", wakingId ); - TracyDebug( "drm_vblank_event id: %i\n", vsyncId ); + TracyDebug( "sched_switch id: %i", switchId ); + TracyDebug( "sched_waking id: %i", wakingId ); + TracyDebug( "drm_vblank_event id: %i", vsyncId ); + + struct utsname kernelInfo; + const bool gotKernelInfo = uname( &kernelInfo ) == 0; + const PerfKernelAbi perfAbi = gotKernelInfo ? ClassifyPerfKernelAbi( kernelInfo.release ) : PerfAbiPre41; + + bool useMonotonicClockRaw = !HardwareSupportsInvariantTSC(); +#if !defined TRACY_HW_TIMER || !defined TRACY_HAS_RDTSC + useMonotonicClockRaw = true; +#endif + if( useMonotonicClockRaw && perfAbi < PerfAbi41To47 ) + { + TracyDebug( "Kernel %s: perf_event_open() ABI predates 4.1, use_clockid not supported, using the default event clock.", gotKernelInfo ? kernelInfo.release : "version unknown" ); + useMonotonicClockRaw = false; + } + if( useMonotonicClockRaw ) + { + TracyDebug( "Using CLOCK_MONOTONIC_RAW for Linux perf events." ); + } #ifdef TRACY_NO_SAMPLING const bool noSoftwareSampling = true; @@ -889,16 +793,83 @@ bool SysTraceStart( int64_t& samplingPeriod ) const bool noVsync = noVsyncEnv && noVsyncEnv[0] == '1'; #endif - samplingPeriod = GetSamplingPeriod(); - uint32_t currentPid = (uint32_t)getpid(); +#ifdef TRACY_NO_WAIT_STACKS + const bool noWaitStacks = true; +#else + const char* noWaitStacksEnv = GetEnvVar( "TRACY_NO_WAIT_STACKS" ); + const bool noWaitStacks = noWaitStacksEnv && noWaitStacksEnv[0] == '1'; +#endif + + int samplingFrequency = GetSamplingFrequency(); + if( samplingFrequency > 0 ) + { + const auto maxSampleRateStr = ReadFile( "/proc/sys/kernel/perf_event_max_sample_rate" ); + if( maxSampleRateStr ) + { + const int sysMax = atoi( maxSampleRateStr ); + if( sysMax > 0 && sysMax < samplingFrequency ) + { + TracyDebug( "Requested sampling frequency %d Hz is higher than system maximum of %d Hz, reducing to system maximum.", samplingFrequency, sysMax ); + samplingFrequency = sysMax; + } + } + } + samplingPeriod = SamplingFrequencyToPeriodNs( samplingFrequency ); +#ifdef TRACY_HAS_EXTERNAL_TARGET + const auto externalPid = GetExternalTargetPid(); +#else + const uint32_t externalPid = 0; +#endif + uint32_t currentPid = externalPid != 0 ? externalPid : (uint32_t)getpid(); s_numCpus = (int)std::thread::hardware_concurrency(); - const auto maxNumBuffers = s_numCpus * ( + PerfIterTarget* iter; + int numIter; +#ifdef TRACY_HAS_EXTERNAL_TARGET + if( externalPid != 0 ) + { + uint32_t* tids = nullptr; + const int numTids = EnumerateTaskTids( (pid_t)currentPid, &tids ); + if( numTids == 0 ) + { + TracyDebug( "Failed to enumerate threads of pid %u; target may have exited.", currentPid ); + return false; + } + if( numTids == 1 ) + { + iter = (PerfIterTarget*)tracy_malloc( sizeof( PerfIterTarget ) * s_numCpus ); + for( int i=0; i= KERNEL_VERSION( 4, 8, 0 ) - pe.sample_max_stack = 127; + if( perfAbi >= PerfAbi48AndNewer ) pe.sample_max_stack = 127; #endif pe.disabled = 1; pe.freq = 1; pe.inherit = 1; -#if !defined TRACY_HW_TIMER || !( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) - pe.use_clockid = 1; - pe.clockid = CLOCK_MONOTONIC_RAW; -#endif + if( useMonotonicClockRaw ) + { + pe.use_clockid = 1; + pe.clockid = CLOCK_MONOTONIC_RAW; + } if( !noSoftwareSampling ) { - TracyDebug( "Setup software sampling\n" ); + TracyDebug( "Setup software sampling" ); ProbePreciseIp( pe, currentPid ); - for( int i=0; i= KERNEL_VERSION( 4, 8, 0 ) - pe.sample_max_stack = 127; + if( perfAbi >= PerfAbi48AndNewer ) pe.sample_max_stack = 127; #endif + } pe.disabled = 1; pe.inherit = 1; pe.config = switchId; -#if !defined TRACY_HW_TIMER || !( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) - pe.use_clockid = 1; - pe.clockid = CLOCK_MONOTONIC_RAW; -#endif + if( useMonotonicClockRaw ) + { + pe.use_clockid = 1; + pe.clockid = CLOCK_MONOTONIC_RAW; + } - TracyDebug( "Setup context switch capture\n" ); + TracyDebug( "Setup context switch capture" ); for( int i=0; i tail ); + TRACY_ASSERT( head > tail ); hadData = true; const auto id = ring.GetId(); - assert( id != EventContextSwitch ); + TRACY_ASSERT( id != EventContextSwitch ); const auto end = head - tail; uint64_t pos = 0; if( id == EventCallstack ) @@ -1286,37 +1206,42 @@ void SysTraceWorker( void* ptr ) { auto offset = pos + sizeof( perf_event_header ); - // Layout: - // u32 pid, tid - // u64 time - // u64 cnt - // u64 ip[cnt] - - uint32_t tid; - uint64_t t0; - uint64_t cnt; - - offset += sizeof( uint32_t ); - ring.Read( &tid, offset, sizeof( uint32_t ) ); - offset += sizeof( uint32_t ); - ring.Read( &t0, offset, sizeof( uint64_t ) ); - offset += sizeof( uint64_t ); - ring.Read( &cnt, offset, sizeof( uint64_t ) ); - offset += sizeof( uint64_t ); - - if( cnt > 0 ) + // field order matches PERF_SAMPLE_IP | TID | TIME | CALLCHAIN (then buf.cnt ips) + +#pragma pack( push, 1 ) + struct { -#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) - t0 = ring.ConvertTimeToTsc( t0 ); -#endif - auto trace = GetCallstackBlock( cnt, ring, offset ); + uint64_t ip; + uint32_t pid; + uint32_t tid; + uint64_t t0; + uint64_t cnt; + } buf; +#pragma pack( pop ) - TracyLfqPrepare( QueueType::CallstackSample ); - MemWrite( &item->callstackSampleFat.time, t0 ); - MemWrite( &item->callstackSampleFat.thread, tid ); - MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); - TracyLfqCommit; + ring.Read( &buf, offset, sizeof( buf ) ); + offset += sizeof( buf ); + + uint64_t* trace; + if( buf.cnt > 0 ) + { + trace = GetCallstackBlock( buf.cnt, ring, offset ); + } + else + { + trace = (uint64_t*)tracy_malloc_fast( 2 * sizeof( uint64_t ) ); + trace[0] = 1; + trace[1] = buf.ip; } + +#if defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC + buf.t0 = ring.ConvertTimeToTsc( buf.t0 ); +#endif + TracyLfqPrepare( QueueType::CallstackSample ); + MemWrite( &item->callstackSampleFat.time, int64_t( buf.t0 ) ); + MemWrite( &item->callstackSampleFat.thread, buf.tid ); + MemWrite( &item->callstackSampleFat.ptr, uint64_t( trace ) ); + TracyLfqCommit; } pos += hdr.size; } @@ -1335,13 +1260,15 @@ void SysTraceWorker( void* ptr ) // u64 ip // u64 time - uint64_t ip, t0; - ring.Read( &ip, offset, sizeof( uint64_t ) ); - offset += sizeof( uint64_t ); - ring.Read( &t0, offset, sizeof( uint64_t ) ); + struct + { + uint64_t ip, t0; + } buf; + + ring.Read( &buf, offset, sizeof( buf ) ); -#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) - t0 = ring.ConvertTimeToTsc( t0 ); +#if defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC + buf.t0 = ring.ConvertTimeToTsc( buf.t0 ); #endif QueueType type; switch( id ) @@ -1369,14 +1296,14 @@ void SysTraceWorker( void* ptr ) } TracyLfqPrepare( type ); - MemWrite( &item->hwSample.ip, ip ); - MemWrite( &item->hwSample.time, t0 ); + MemWrite( &item->hwSample.ip, buf.ip ); + MemWrite( &item->hwSample.time, int64_t( buf.t0 ) ); TracyLfqCommit; } pos += hdr.size; } } - assert( pos == end ); + TRACY_ASSERT( pos == end ); ring.Advance( end ); } if( !traceActive.load( std::memory_order_relaxed ) ) break; @@ -1389,19 +1316,39 @@ void SysTraceWorker( void* ptr ) uint16_t active[512]; uint32_t end[512]; uint32_t pos[512]; + int64_t time[512]; + + auto PrimeNext = [&pos, &end, &time]( int idx, RingBuffer& ring ) { + while( pos[idx] < end[idx] ) + { + perf_event_header hdr; + ring.Read( &hdr, pos[idx], sizeof( hdr ) ); + if( hdr.type == PERF_RECORD_SAMPLE ) + { + ring.Read( time + idx, pos[idx] + sizeof( hdr ), sizeof( int64_t ) ); + return true; + } + TRACY_ASSERT( hdr.size > 0 ); + pos[idx] += hdr.size; + } + return false; + }; + for( int i=0; i= 0 ) { + TRACY_ASSERT( pos[sel] < end[sel] ); + auto& ring = ringArray[ctxBufferIdx + sel]; auto rbPos = pos[sel]; auto offset = rbPos; perf_event_header hdr; ring.Read( &hdr, offset, sizeof( perf_event_header ) ); -#if defined TRACY_HW_TIMER && ( defined __i386 || defined _M_IX86 || defined __x86_64__ || defined _M_X64 ) +#if defined TRACY_HW_TIMER && defined TRACY_HAS_RDTSC t0 = ring.ConvertTimeToTsc( t0 ); #endif @@ -1469,8 +1394,8 @@ void SysTraceWorker( void* ptr ) { // Layout: See /sys/kernel/debug/tracing/events/sched/sched_switch/format // u64 time // PERF_SAMPLE_TIME - // u64 cnt // PERF_SAMPLE_CALLCHAIN - // u64 ip[cnt] // PERF_SAMPLE_CALLCHAIN + // u64 cnt // PERF_SAMPLE_CALLCHAIN, if enabled + // u64 ip[cnt] // PERF_SAMPLE_CALLCHAIN, if enabled // u32 size // u8 data[size] // Data (not ABI stable, but has not changed since it was added, in 2009): @@ -1485,58 +1410,59 @@ void SysTraceWorker( void* ptr ) offset += sizeof( perf_event_header ) + sizeof( uint64_t ); - uint64_t cnt; - ring.Read( &cnt, offset, sizeof( uint64_t ) ); - offset += sizeof( uint64_t ); - const auto traceOffset = offset; - offset += sizeof( uint64_t ) * cnt + sizeof( uint32_t ) + 8 + 16; - - uint32_t prev_pid, prev_prio; - uint32_t next_pid, next_prio; - long prev_state; - - ring.Read( &prev_pid, offset, sizeof( uint32_t ) ); - offset += sizeof( uint32_t ); - ring.Read( &prev_prio, offset, sizeof( uint32_t ) ); - offset += sizeof( uint32_t ); - ring.Read( &prev_state, offset, sizeof( long ) ); - offset += sizeof( long ) + 16; - ring.Read( &next_pid, offset, sizeof( uint32_t ) ); - offset += sizeof( uint32_t ); - ring.Read( &next_prio, offset, sizeof( uint32_t ) ); + uint64_t cnt = 0; + uint64_t traceOffset = 0; + if( s_ctxSwitchCallchain ) + { + ring.Read( &cnt, offset, sizeof( uint64_t ) ); + offset += sizeof( uint64_t ); + traceOffset = offset; + offset += sizeof( uint64_t ) * cnt; + } + offset += sizeof( uint32_t ) + 8 + 16; + + struct + { + uint32_t prev_pid, prev_prio; + long prev_state; + char next_comm[16]; + uint32_t next_pid, next_prio; + } buf; + + ring.Read( &buf, offset, sizeof( buf ) ); uint8_t oldThreadWaitReason = 100; uint8_t oldThreadState; - if( prev_state & 0x0001 ) oldThreadState = 104; - else if( prev_state & 0x0002 ) oldThreadState = 101; - else if( prev_state & 0x0004 ) oldThreadState = 105; - else if( prev_state & 0x0008 ) oldThreadState = 106; - else if( prev_state & 0x0010 ) oldThreadState = 108; - else if( prev_state & 0x0020 ) oldThreadState = 109; - else if( prev_state & 0x0040 ) oldThreadState = 110; - else if( prev_state & 0x0080 ) oldThreadState = 102; + if( buf.prev_state & 0x0001 ) oldThreadState = 104; + else if( buf.prev_state & 0x0002 ) oldThreadState = 101; + else if( buf.prev_state & 0x0004 ) oldThreadState = 105; + else if( buf.prev_state & 0x0008 ) oldThreadState = 106; + else if( buf.prev_state & 0x0010 ) oldThreadState = 108; + else if( buf.prev_state & 0x0020 ) oldThreadState = 109; + else if( buf.prev_state & 0x0040 ) oldThreadState = 110; + else if( buf.prev_state & 0x0080 ) oldThreadState = 102; else oldThreadState = 103; TracyLfqPrepare( QueueType::ContextSwitch ); MemWrite( &item->contextSwitch.time, t0 ); - MemWrite( &item->contextSwitch.oldThread, prev_pid ); - MemWrite( &item->contextSwitch.newThread, next_pid ); + MemWrite( &item->contextSwitch.oldThread, buf.prev_pid ); + MemWrite( &item->contextSwitch.newThread, buf.next_pid ); MemWrite( &item->contextSwitch.cpu, uint8_t( ring.GetCpu() ) ); MemWrite( &item->contextSwitch.oldThreadWaitReason, oldThreadWaitReason ); MemWrite( &item->contextSwitch.oldThreadState, oldThreadState ); MemWrite( &item->contextSwitch.previousCState, uint8_t( 0 ) ); - MemWrite( &item->contextSwitch.newThreadPriority, int8_t( next_prio ) ); - MemWrite( &item->contextSwitch.oldThreadPriority, int8_t( prev_prio ) ); + MemWrite( &item->contextSwitch.newThreadPriority, int8_t( buf.next_prio ) ); + MemWrite( &item->contextSwitch.oldThreadPriority, int8_t( buf.prev_prio ) ); TracyLfqCommit; - if( cnt > 0 && prev_pid != 0 && CurrentProcOwnsThread( prev_pid ) ) + if( cnt > 0 && buf.prev_pid != 0 && CurrentProcOwnsThread( buf.prev_pid ) ) { auto trace = GetCallstackBlock( cnt, ring, traceOffset ); TracyLfqPrepare( QueueType::CallstackSampleContextSwitch ); MemWrite( &item->callstackSampleFat.time, t0 ); - MemWrite( &item->callstackSampleFat.thread, prev_pid ); + MemWrite( &item->callstackSampleFat.thread, buf.prev_pid ); MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); TracyLfqCommit; } @@ -1572,7 +1498,7 @@ void SysTraceWorker( void* ptr ) } else { - assert( rid == EventVsync ); + TRACY_ASSERT( rid == EventVsync ); // Layout: // u64 time // u32 size @@ -1600,21 +1526,18 @@ void SysTraceWorker( void* ptr ) #endif TracyLfqPrepare( QueueType::FrameVsync ); - MemWrite( &item->frameVsync.id, crtc ); + MemWrite( &item->frameVsync.id, uint32_t( crtc ) ); MemWrite( &item->frameVsync.time, t0 ); TracyLfqCommit; } rbPos += hdr.size; - if( rbPos == end[sel] ) + pos[sel] = rbPos; + if( !PrimeNext( sel, ring ) ) { - memmove( active+selPos, active+selPos+1, sizeof(*active) * ( activeNum - selPos - 1 ) ); + active[selPos] = active[activeNum - 1]; activeNum--; } - else - { - pos[sel] = rbPos; - } } } for( int i=0; i 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0'; - threadName = CopyString( buf ); + if( sz > 0 ) + { + threadName = CopyString( buf ); + } + else + { + threadName = CopyString( "???", 3 ); + } fclose( f ); } else @@ -1657,15 +1587,22 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch f = fopen( fn, "rb" ); if( f ) { - char* tmp = (char*)tracy_malloc_fast( 8*1024 ); + char* tmp = (char*)tracy_malloc_fast( 8*1024 + 1 ); const auto fsz = (ptrdiff_t)fread( tmp, 1, 8*1024, f ); fclose( f ); + if( fsz <= 0 ) + { + tracy_free_fast( tmp ); + name = CopyStringFast( "???", 3 ); + return; + } + tmp[fsz] = '\0'; int pid = -1; auto line = tmp; for(;;) { - if( memcmp( "Tgid:\t", line, 6 ) == 0 ) + if( line - tmp + 6 <= fsz && memcmp( "Tgid:\t", line, 6 ) == 0 ) { pid = atoi( line + 6 ); break; @@ -1689,10 +1626,10 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch f = fopen( fn, "rb" ); if( f ) { - char buf[256]; + char buf[256] = {}; const auto sz = fread( buf, 1, 256, f ); if( sz > 0 && buf[sz-1] == '\n' ) buf[sz-1] = '\0'; - name = CopyStringFast( buf ); + name = sz > 0 ? CopyStringFast( buf ) : CopyStringFast( "???", 3 ); fclose( f ); return; } @@ -1703,6 +1640,10 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch } +# elif defined __APPLE__ + +# include "apple/TracyMach.cpp" + # endif #endif diff --git a/libs/tracy/client/TracySysTrace.hpp b/libs/tracy/client/TracySysTrace.hpp index 2a28e8b..fe06481 100644 --- a/libs/tracy/client/TracySysTrace.hpp +++ b/libs/tracy/client/TracySysTrace.hpp @@ -1,9 +1,13 @@ #ifndef __TRACYSYSTRACE_HPP__ #define __TRACYSYSTRACE_HPP__ -#if !defined TRACY_NO_SYSTEM_TRACING && ( defined _WIN32 || defined __linux__ ) -# include "../common/TracyWinFamily.hpp" -# if !defined TRACY_WIN32_NO_DESKTOP +#if !defined TRACY_NO_SYSTEM_TRACING +# if defined _WIN32 || defined __linux__ +# include "../common/TracyWinFamily.hpp" +# if !defined TRACY_WIN32_NO_DESKTOP +# define TRACY_HAS_SYSTEM_TRACING +# endif +# elif defined __APPLE__ # define TRACY_HAS_SYSTEM_TRACING # endif #endif diff --git a/libs/tracy/client/TracyThread.hpp b/libs/tracy/client/TracyThread.hpp index 5638756..f95627d 100644 --- a/libs/tracy/client/TracyThread.hpp +++ b/libs/tracy/client/TracyThread.hpp @@ -8,7 +8,7 @@ #endif #ifdef TRACY_MANUAL_LIFETIME -# include "tracy_rpmalloc.hpp" +# include "../common/TracyAlloc.hpp" #endif namespace tracy @@ -24,7 +24,11 @@ class ThreadExitHandler ~ThreadExitHandler() { #ifdef TRACY_MANUAL_LIFETIME +# if defined TRACY_HAS_CUSTOM_ALLOCATOR + PlatformAllocatorThreadFinalize(); +# elif defined TRACY_USE_RPMALLOC rpmalloc_thread_finalize( 1 ); +# endif RpThreadInitDone = false; #endif } diff --git a/libs/tracy/client/apple/TracyMach.cpp b/libs/tracy/client/apple/TracyMach.cpp new file mode 100644 index 0000000..e102461 --- /dev/null +++ b/libs/tracy/client/apple/TracyMach.cpp @@ -0,0 +1,257 @@ +// (this file gets included by TracySysTrace.cpp) + +#ifndef __APPLE__ +#error this file can only be compiled for Apple targets +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../TracyProfiler.hpp" +#include "../TracyStringHelpers.hpp" +#include "../TracyThread.hpp" + +namespace tracy +{ + +struct SysTraceApple +{ + std::atomic active { false }; + int samplingHz = 1000; + static SysTraceApple& Get() + { + static SysTraceApple systrace = {}; + return systrace; + } +}; + +static void SysTraceEmitCallstackSample( uint32_t threadId, int64_t timestamp, const uint64_t* frames, int depth ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return; +#endif + + auto trace = (uint64_t*)tracy_malloc( ( 1 + depth ) * sizeof( uint64_t ) ); + trace[0] = (uint64_t)depth; + memcpy( trace + 1, frames, depth * sizeof( uint64_t ) ); + + TracyLfqPrepare( QueueType::CallstackSample ); + MemWrite( &item->callstackSampleFat.time, timestamp ); + MemWrite( &item->callstackSampleFat.thread, threadId ); + MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace ); + TracyLfqCommit; +} + +static int SysTraceBacktrace( uint64_t* frames, int maxDepth, uint64_t pc, uint64_t fp ) +{ + int depth = 0; + frames[depth++] = pc; + + // NOTE: frame-pointer walk for now... It should be fine since the ABI + // mandates it on ARM64 (and on x64 Apple clang preserves it by default) + auto framePtr = (const uint64_t*)fp; + while( framePtr && depth < maxDepth ) + { + if( (uintptr_t)framePtr & (sizeof(uint64_t) - 1) ) break; // misaligned — stop walk + // [framePtr + 0] = saved frame pointer (previous frame) + // [framePtr + 1] = return address, may be PAC-signed on ARM64 + frames[depth++] = (uint64_t)ptrauth_strip( (void*)framePtr[1], ptrauth_key_return_address ); + framePtr = (const uint64_t*)framePtr[0]; + } + + return depth; +} + +static void SysTraceSampleThread( mach_port_t tid ) +{ + const int64_t t0 = Profiler::GetTime(); + if( thread_suspend( tid ) != KERN_SUCCESS ) return; + const int64_t t1 = Profiler::GetTime(); + const int64_t timestamp = t0 + ( t1 - t0 ) / 2; + +#if defined(__aarch64__) + arm_thread_state64_t state; + mach_msg_type_number_t stateCount = ARM_THREAD_STATE64_COUNT; + const kern_return_t kr = thread_get_state( tid, ARM_THREAD_STATE64, (thread_state_t)&state, &stateCount ); +#elif defined(__x86_64__) + x86_thread_state64_t state; + mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; + const kern_return_t kr = thread_get_state( tid, x86_THREAD_STATE64, (thread_state_t)&state, &stateCount ); +#else + #error "unsupported architecture" +#endif + + if( kr != KERN_SUCCESS ) + { + thread_resume( tid ); + return; + } + + constexpr int MaxDepth = 192; + uint64_t frames [MaxDepth]; + +#if defined(__aarch64__) + const int depth = SysTraceBacktrace( frames, MaxDepth, state.__pc, state.__fp ); +#elif defined(__x86_64__) + const int depth = SysTraceBacktrace( frames, MaxDepth, state.__rip, state.__rbp ); +#endif + + thread_resume( tid ); + + SysTraceEmitCallstackSample( (uint32_t)tid, timestamp, frames, depth ); +} + +static void SysTraceWait( uint64_t deadline ) +{ + mach_wait_until( deadline ); +} + +static uint64_t SysTraceRngInit() +{ + uint64_t seed = mach_absolute_time(); + seed ^= (uint64_t)(uintptr_t)&seed; + return seed; +} + +static uint32_t SysTraceRngNext( uint64_t& rng, uint32_t range ) +{ + rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; + return (uint32_t)( rng % range ); +} + +static void SysTraceWatch() +{ + auto& systrace = SysTraceApple::Get(); + + const mach_port_t selfThread = mach_thread_self(); + mach_timebase_info_data_t timebase; + mach_timebase_info( &timebase ); + const uint64_t samplingPeriodNs = 1000000000ULL / systrace.samplingHz; + const uint64_t periodMach = samplingPeriodNs * timebase.denom / timebase.numer; + + std::vector runningThreads; + std::vector waitingThreads; + + uint64_t rng = SysTraceRngInit(); + + uint64_t deadline = mach_absolute_time(); + while( systrace.active.load( std::memory_order_relaxed ) ) + { + SysTraceWait(deadline); + deadline = mach_absolute_time() + periodMach; + +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) continue; +#endif + + thread_act_array_t threads; + mach_msg_type_number_t threadCount; + if( task_threads( mach_task_self(), &threads, &threadCount ) != KERN_SUCCESS ) continue; + + runningThreads.clear(); + waitingThreads.clear(); + + for( mach_msg_type_number_t i = 0; i < threadCount; i++ ) + { + const mach_port_t tid = threads[i]; + if( tid == selfThread ) continue; + + thread_basic_info_data_t info; + mach_msg_type_number_t infoCount = THREAD_BASIC_INFO_COUNT; + if( thread_info( tid, THREAD_BASIC_INFO, (thread_info_t)&info, &infoCount ) != KERN_SUCCESS ) continue; + if( info.flags & TH_FLAGS_IDLE ) continue; // kernel idle thread, not user code + + if( info.run_state == TH_STATE_RUNNING ) + runningThreads.push_back( tid ); + else + waitingThreads.push_back( tid ); + } + + for( const mach_port_t tid : runningThreads ) + SysTraceSampleThread( tid ); + + while( !waitingThreads.empty() ) + { + if( mach_absolute_time() >= deadline ) break; + const uint32_t idx = SysTraceRngNext( rng, (uint32_t)waitingThreads.size() ); + SysTraceSampleThread( waitingThreads[idx] ); + std::swap( waitingThreads[idx], waitingThreads.back() ); + waitingThreads.pop_back(); + } + + for( mach_msg_type_number_t i = 0; i < threadCount; i++ ) + mach_port_deallocate( mach_task_self(), threads[i] ); + vm_deallocate( mach_task_self(), (vm_address_t)threads, sizeof(thread_t) * threadCount ); + } + mach_port_deallocate( mach_task_self(), selfThread ); +} + +void SysTraceWorker( void* ) +{ + ThreadExitHandler threadExitHandler; + SetThreadName( "Tracy Mach Watchdog" ); + InitAllocator(); + SysTraceWatch(); +} + +bool SysTraceStart( int64_t& samplingPeriod ) +{ + // check for elevated privileges + // (technically, since this is a software-based user-mode sampling, elevated + // privileges are unnecessary, but doing so keeps the behavior consistent with + // the system tracing in other platforms) + if( geteuid() != 0 ) return false; + + auto& systrace = SysTraceApple::Get(); + + bool expected = false; + if( !systrace.active.compare_exchange_strong( expected, true, std::memory_order_relaxed ) ) + return false; + + systrace.samplingHz = GetSamplingFrequency(); + samplingPeriod = SamplingFrequencyToPeriodNs( systrace.samplingHz ); + return true; +} + +void SysTraceStop() +{ + auto& systrace = SysTraceApple::Get(); + systrace.active.store( false, std::memory_order_relaxed ); +} + +void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const char*& name ) +{ + // Resolve pthread handle from the Mach port so we can query the thread name. + const mach_port_t mach_tid = (mach_port_t)thread; + thread_identifier_info_data_t idInfo; + mach_msg_type_number_t idInfoCount = THREAD_IDENTIFIER_INFO_COUNT; + if( thread_info( mach_tid, THREAD_IDENTIFIER_INFO, (thread_info_t)&idInfo, &idInfoCount ) == KERN_SUCCESS ) + { + char buf[64] = {}; + const pthread_t pt = (pthread_t)(uintptr_t)idInfo.thread_handle; + if( pt && pthread_getname_np( pt, buf, sizeof( buf ) ) == 0 && buf[0] != '\0' ) + threadName = CopyString( buf ); + else + threadName = CopyString( "???", 3 ); + + TracyLfqPrepare( QueueType::TidToPid ); + MemWrite( &item->tidToPid.tid, thread ); + MemWrite( &item->tidToPid.pid, (uint64_t)getpid() ); + TracyLfqCommit; + } + else + { + threadName = CopyString( "???", 3 ); + } + + name = CopyStringFast( getprogname() ); +} + +} // namespace tracy diff --git a/libs/tracy/client/tracy_concurrentqueue.h b/libs/tracy/client/tracy_concurrentqueue.h index 4178d39..2ae31cc 100644 --- a/libs/tracy/client/tracy_concurrentqueue.h +++ b/libs/tracy/client/tracy_concurrentqueue.h @@ -171,8 +171,8 @@ struct ConcurrentQueueDefaultTraits #if defined(malloc) || defined(free) // Gah, this is 2015, stop defining macros that break standard code already! // Work around malloc/free being special macros: - static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } - static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* WORKAROUND_malloc(size_t size) { return tracy::tracy_malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return tracy::tracy_free(ptr); } static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } #else @@ -975,7 +975,7 @@ class ConcurrentQueue auto block = this->tailBlock; do { block = block->next; - if (block->ConcurrentQueue::Block::is_empty()) { + if (block->is_empty()) { continue; } @@ -1020,10 +1020,10 @@ class ConcurrentQueue inline void enqueue_begin_alloc(index_t currentTailIndex) { // We reached the end of a block, start a new one - if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::is_empty()) { + if (this->tailBlock != nullptr && this->tailBlock->next->is_empty()) { // We can re-use the block ahead of us, it's empty! this->tailBlock = this->tailBlock->next; - this->tailBlock->ConcurrentQueue::Block::reset_empty(); + this->tailBlock->reset_empty(); // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the // last block from it first -- except instead of removing then adding, we can just overwrite). @@ -1042,7 +1042,7 @@ class ConcurrentQueue // Insert a new block in the circular linked list auto newBlock = this->parent->ConcurrentQueue::requisition_block(); - newBlock->ConcurrentQueue::Block::reset_empty(); + newBlock->reset_empty(); if (this->tailBlock == nullptr) { newBlock->next = newBlock; } @@ -1124,7 +1124,7 @@ class ConcurrentQueue processData( (*block)[index], sz ); index += sz; - block->ConcurrentQueue::Block::set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + block->set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); } while (index != firstIndex + actualCount); @@ -1210,6 +1210,21 @@ class ConcurrentQueue return static_cast(token.producer); } + // If a producer token is created before the constructor of a statically allocated + // queue runs (which may happen due to the undefined order of static initialization + // across module boundaries), the constructor will orphan it by resetting the + // producer list. Such a producer is functional, as producer creation works on the + // zero-initialized queue memory, but the consumer is not able to see the data it + // enqueues. This method links the producer back into the list. + bool readopt_orphaned_producer(ExplicitProducer* producer) + { + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr == static_cast(producer)) return false; + } + add_producer(static_cast(producer)); + return true; + } + private: ////////////////////////////////// diff --git a/libs/tracy/client/tracy_rpmalloc.cpp b/libs/tracy/client/tracy_rpmalloc.cpp index c43b8ca..6004210 100644 --- a/libs/tracy/client/tracy_rpmalloc.cpp +++ b/libs/tracy/client/tracy_rpmalloc.cpp @@ -743,7 +743,7 @@ get_thread_id(void) { __asm__("movq %%fs:0, %0" : "=r" (tid) : : ); # endif # elif defined(__arm__) - __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3" : "=r" (tid)); + tid = (uintptr_t)__builtin_thread_pointer(); # elif defined(__aarch64__) # if defined(__MACH__) // tpidr_el0 likely unused, always return 0 on iOS diff --git a/libs/tracy/client/windows/TracyETW.cpp b/libs/tracy/client/windows/TracyETW.cpp new file mode 100644 index 0000000..83dfb42 --- /dev/null +++ b/libs/tracy/client/windows/TracyETW.cpp @@ -0,0 +1,608 @@ +#include +#include +#include +#include + +#include +#include + +#include "../../common/TracyString.hpp" + +namespace tracy +{ +namespace etw +{ + +constexpr GUID NullGuid = {}; +constexpr GUID ThreadGuid = { 0x3D6FA8D1, 0xFE05, 0x11D0, { 0x9D, 0xDA, 0x00, 0xC0, 0x4F, 0xD7, 0xBA, 0x7C } }; +constexpr GUID PerfInfoGuid = { 0xCE1DBFB4, 0x137E, 0x4DA6, { 0x87, 0xB0, 0x3F, 0x59, 0xAA, 0x10, 0x2C, 0xBC } }; +constexpr GUID StackWalkGuid = { 0xDEF2FE46, 0x7BD6, 0x4B80, { 0xBD, 0x94, 0xF5, 0x7F, 0xE2, 0x0D, 0x0C, 0xE3 } }; +constexpr GUID DxgKrnlGuid = { 0x802EC45A, 0x1E99, 0x4B83, { 0x99, 0x20, 0x87, 0xC9, 0x82, 0x77, 0xBA, 0x9D } }; +constexpr GUID LostEventGuid = { 0x6A399AE0, 0x4BC6, 0x4DE9, { 0x87, 0x0B, 0x36, 0x57, 0xF8, 0x94, 0x7E, 0x7E } }; + +struct Session +{ + EVENT_TRACE_PROPERTIES properties = {}; + CHAR name[64] = {}; + CONTROLTRACE_ID handle = 0; + CLASSIC_EVENT_ID stackwalk[8] = {}; +}; + +// ---- ETW Events ---------- + +struct CSwitch +{ + // V2 fields: + static constexpr UCHAR Opcode = 36; + uint32_t newThreadId; + uint32_t oldThreadId; + int8_t newThreadPriority; + int8_t oldThreadPriority; + uint8_t previousCState; + int8_t spareByte; + int8_t oldThreadWaitReason; + int8_t oldThreadWaitMode; + int8_t oldThreadState; + int8_t oldThreadWaitIdealProcessor; + uint32_t newThreadWaitTime; + uint32_t reserved; +}; +static_assert( sizeof( CSwitch ) == 24, "unexpected CSwitch struct size/alignment" ); + +struct ReadyThread +{ + // V2 fields: + static constexpr UCHAR Opcode = 50; + uint32_t threadId; + int8_t adjustReason; + int8_t adjustIncrement; + int8_t flag; + int8_t reserverd; +}; +static_assert( sizeof( ReadyThread ) == 8, "unexpected ReadyThread struct size/alignment" ); + +struct ThreadInfo +{ + // V0 (Thread_V0_TypeGroup1) fields: + uint32_t processId; + uint32_t threadId; + // NOTE: we only care about PID and TID for now, and these two are "invariant" + // across all revisions (versions) of this event. As such, let's omit the other + // fields since they vary based on the event version; their sizes also vary by + // target architecture (32bit or 64bit), and this is not even mentioned in the + // MSDN documentation, and worse, have not been updated in the official schemas + // either (which ETW Explorer uses), but can be introspected via the TDH API. +}; +static_assert( sizeof( ThreadInfo ) == 8, "unexpected ThreadInfo struct size/alignment" ); + +struct ThreadStart : public ThreadInfo +{ + static constexpr UCHAR Opcode = 1; +}; +static_assert( sizeof( ThreadStart ) == 8, "unexpected ThreadStart struct size/alignment" ); + +// DC: Data Collection (associated with the "rundown" phase) +struct ThreadDCStart : public ThreadInfo +{ + static constexpr UCHAR Opcode = 3; +}; +static_assert( sizeof( ThreadDCStart ) == 8, "unexpected ThreadDCStart struct size/alignment" ); + +struct SampledProfile +{ + static constexpr UCHAR Opcode = 46; + // NOTE: we don't handle SampledProfile events directly; instead, we handle + // the StackWalk event associated with each SampledProfile event. Just like + // ThreadInfo, the data layout varies based on the target architecture, and + // the MSDN documentation and schemas are outdated. + //uint64_t instructionPointer; // 32/64 bits + //uint32_t threadId; + //uint32_t count; // Not used. +}; +static_assert( sizeof( SampledProfile ) == 1, "unexpected SampledProfile struct size/alignment" ); + +struct StackWalkEvent +{ + // V2 fields: + static constexpr UCHAR Opcode = 32; + uint64_t eventTimeStamp; + uint32_t stackProcess; + uint32_t stackThread; + uint64_t stack[192]; // arbitrary upperbound limit; schema stops at [32] +}; +static_assert( offsetof( StackWalkEvent, stackProcess ) == 8, "unexpected StackWalkEvent struct size/alignment" ); +static_assert( offsetof( StackWalkEvent, stackThread ) == 12, "unexpected StackWalkEvent struct size/alignment" ); +static_assert( offsetof( StackWalkEvent, stack ) == 16, "unexpected StackWalkEvent struct size/alignment" ); + +struct VSyncDPC +{ + static constexpr USHORT EventId = 17; // 0x11 + uint64_t dxgAdapter; + uint32_t vidPnTargetId; + uint64_t scannedPhysicalAddress; + uint32_t vidPnSourceId; + uint32_t frameNumber; + int64_t frameQpcTime; + uint64_t hFlipDevice; + uint32_t flipType; + uint64_t flipFenceId; +}; +static_assert( sizeof( VSyncDPC ) == 64, "unexpected VSyncDPC struct size/alignment" ); + +// -------------------------- + +constexpr uint32_t Color_Red4 = 0x8b0000; // TracyColor.hpp + +static void ETWErrorAction( ULONG error_code, const char* message, int length ) +{ +#ifndef TRACY_NO_INTERNAL_MESSAGE +# ifdef TRACY_HAS_CALLSTACK + tracy::InitCallstackCritical(); + tracy::Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Error, Color_Red4, 60, length, message ); +# else + tracy::Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Error, Color_Red4, 0, length, message ); +# endif +#endif +#ifdef __cpp_exceptions + // TODO: should we throw an exception? +#endif +} + +static ULONG ETWError( ULONG result ) +{ + if( result == ERROR_SUCCESS ) + return result; + static constexpr tracy::SourceLocationData srcLocHere{ nullptr, __FUNCTION__, __FILE__, __LINE__, Color_Red4 }; + tracy::ScopedZone ___tracy_scoped_zone( &srcLocHere, 0, true ); + char message[128] = {}; + int written = snprintf( message, sizeof( message ), "ETW Error %u (0x%x): ", result, result ); + written += FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + result, + MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ), + (LPSTR)&message[written], + sizeof( message ) - written, + NULL ); + ETWErrorAction( result, message, written ); + return result; +} + +static bool CheckAdminPrivilege() +{ + HANDLE hToken = NULL; + if( OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ) == FALSE ) + return ETWError( GetLastError() ), false; + TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeDefault; + DWORD ReturnLength = 0; + if( GetTokenInformation( hToken, TokenElevationType, &elevationType, sizeof( elevationType ), &ReturnLength ) == FALSE ) + ETWError( GetLastError() ), false; + CloseHandle( hToken ); + return ( elevationType == TokenElevationTypeFull ); +} + +static DWORD ElevatePrivilege( LPCTSTR PrivilegeName ) +{ + TOKEN_PRIVILEGES tp = {}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if( LookupPrivilegeValue( nullptr, PrivilegeName, &tp.Privileges[0].Luid ) == FALSE ) + return ETWError( GetLastError() ); + HANDLE hToken = {}; + if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken ) == FALSE ) + return ETWError( GetLastError() ); + BOOL adjusted = AdjustTokenPrivileges( hToken, FALSE, &tp, 0, nullptr, nullptr ); + DWORD status = GetLastError(); + CloseHandle( hToken ); // NOTE: skipping error handling for CloseHandle() + return ETWError( status ); +} + +static bool IsOS64Bit() +{ +#if defined _WIN64 + constexpr bool isOs64Bit = true; +#else + BOOL _iswow64; + IsWow64Process( GetCurrentProcess(), &_iswow64 ); + const bool isOs64Bit = _iswow64; +#endif + return isOs64Bit; +} + +static ULONG StopSession( Session& session ) +{ + // Use a copy of the session properties, because ControlTrace() will write stuff to it + Session temp = session; + ULONG status = ControlTraceA( temp.handle, temp.name, &temp.properties, EVENT_TRACE_CONTROL_STOP ); + if( status != ERROR_SUCCESS ) + return ETWError( status ); + // once stopped, the session handle becomes invalid + session.handle = 0; + for( auto&& sw : session.stackwalk ) + sw = {}; + return ERROR_SUCCESS; +} + +static ULONG StartSession( Session& session ) +{ + ULONG status = StartTraceA( &session.handle, session.name, &session.properties ); + if( status == ERROR_ALREADY_EXISTS ) + { + // Session is already running (likely from a previous run that did not terminate + // gracefully). There are two options: take control of the existing session with + // ControlSession(UPDATE), or stop the session and start fresh again. The latter + // is better because it also resets the event providers. + status = StopSession( session ); + if( status != ERROR_SUCCESS ) + return status; + status = StartTraceA( &session.handle, session.name, &session.properties ); + } + return ETWError( status ); +} + +static ULONG CheckProviderSessions( GUID provider, ULONGLONG MatchAnyKeyword ) +{ + MatchAnyKeyword = ( MatchAnyKeyword != 0 ) ? MatchAnyKeyword : ~ULONGLONG( 0 ); + char buffer[4096] = {}; + auto Info = (PTRACE_GUID_INFO)buffer; + ULONG ActualSize = 0; + ULONG result = EnumerateTraceGuidsEx( TraceGuidQueryInfo, &provider, sizeof( provider ), Info, sizeof( buffer ), &ActualSize ); + if( result != ERROR_SUCCESS ) + return ETWError( result ); + TRACE_ENABLE_INFO sessions[8] = {}; + // Info->InstanceCount is typically 1, but can be more when the provider is registered from within a DLL + for( ULONG i = 0, offset = 0; i < Info->InstanceCount; ++i ) + { + auto instance = (PTRACE_PROVIDER_INSTANCE_INFO)&buffer[sizeof( *Info ) + offset]; + auto first = (PTRACE_ENABLE_INFO)&buffer[sizeof( *Info ) + offset + sizeof( *instance )]; + for( ULONG j = 0; j < instance->EnableCount; ++j ) + { + auto session = &first[j]; + for( auto&& entry : sessions ) + { + if( entry.LoggerId == session->LoggerId ) + continue; + if( entry.LoggerId != 0 ) + continue; + if( ( MatchAnyKeyword & session->MatchAnyKeyword ) != 0 ) + entry = *session; + break; + } + } + offset += instance->NextOffset; + } + if( sessions[0].LoggerId == 0 ) + return ERROR_SUCCESS; + int length = snprintf( buffer, sizeof( buffer ), "ETW Warning: provider (0x%08X) already enabled by other session(s); Tracy may miss events.", provider.Data1 ); + ETWErrorAction( 0, buffer, length ); + return ERROR_SUCCESS; +} + +static ULONG EnableProvider( + Session& session, + const GUID& ProviderId, + ULONG ControlCode = EVENT_CONTROL_CODE_ENABLE_PROVIDER, + UCHAR Level = TRACE_LEVEL_INFORMATION, + ULONGLONG MatchAnyKeyword = 0, // NOTE: a MatchAnyKeyword of 0 actually means "all bits set", according to the EnableTraceEx2 docs + ULONGLONG MatchAllKeyword = 0, + ULONG Timeout = 0, + PENABLE_TRACE_PARAMETERS EnableParameters = NULL ) +{ + ULONG status = EnableTraceEx2( session.handle, &ProviderId, ControlCode, Level, MatchAnyKeyword, MatchAllKeyword, Timeout, EnableParameters ); + return ETWError( status ); +} + +static ULONG EnableStackWalk( Session& session, GUID EventGuid, UCHAR Opcode ) +{ + if( !IsOS64Bit() ) + return 0 /* ERROR_SUCCESS */; // TODO: return error instead? + // TraceStackTracingInfo: Turns on stack trace collection for the specified kernel events + // for the specified logger. It also turns off stack tracing for + // all kernel events not on this list, regardless of prior status. + // NOTE: It'd be nice if we could rely on TraceQueryInformation(TraceStackTracingInfo) + // to retrieve the list of the active stack trace event ids, but even though MSDN says + // that it is possible, the query call returns ERROR_NOT_SUPPORTED... + // Instead, we keep our own array of active stack trace event ids in the session object. + for( auto&& sw : session.stackwalk ) + { + if( !IsEqualGUID( sw.EventGuid, {} ) ) + continue; + sw.EventGuid = EventGuid; + sw.Type = Opcode; + size_t count = ( &sw - session.stackwalk ) + 1; + ULONG status = TraceSetInformation( session.handle, TraceStackTracingInfo, session.stackwalk, count * sizeof( CLASSIC_EVENT_ID ) ); + return ETWError( status ); + } + return 0 /* ERROR_SUCCESS */; // TODO: return error instead? +} + +static ULONG SetCPUProfilingInterval( int microseconds ) +{ + if( !IsOS64Bit() ) + return 0 /* ERROR_SUCCESS */; // TODO: fabricate SetLastError(ERROR_NOT_SUPPORTED) instead? + TRACE_PROFILE_INTERVAL interval = {}; + interval.Source = 0; // 0: ProfileTime (from enum KPROFILE_SOURCE in wdm.h) + interval.Interval = ( microseconds * 1000 ) / 100; // in 100's of nanoseconds + CONTROLTRACE_ID TraceId = 0; // must be zero for TraceSampledProfileIntervalInfo + ULONG status = TraceSetInformation( TraceId, TraceSampledProfileIntervalInfo, &interval, sizeof( interval ) ); + return ETWError( status ); +} + +static Session StartSingletonKernelLoggerSession( ULONGLONG EnableFlags ) +{ + Session session = {}; + + strzcpy( session.name, KERNEL_LOGGER_NAMEA, sizeof( session.name ) ); + + auto& props = session.properties; + props.LoggerNameOffset = offsetof( Session, name ); + props.Wnode.BufferSize = sizeof( Session ); + props.Wnode.Guid = SystemTraceControlGuid; +#ifdef TRACY_TIMER_QPC + props.Wnode.ClientContext = 1; // 1: QueryPerformanceCounter +#else + props.Wnode.ClientContext = 3; // 3: CPU Ticks (e.g., rdtsc) +#endif + props.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + props.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + props.EnableFlags = EnableFlags; + + // TODO: should we really be tweaking the buffering parameters? + props.BufferSize = 1024; // in KB + props.MinimumBuffers = std::thread::hardware_concurrency() * 4; + props.MaximumBuffers = std::thread::hardware_concurrency() * 6; + + ULONG status = StartSession( session ); + if( status != ERROR_SUCCESS ) + return {}; + + return session; +} + +static Session StartPrivateKernelSession( const CHAR* name ) +{ + Session session = {}; + + strzcpy( session.name, name, sizeof( session.name ) ); + + auto& props = session.properties; + props.LoggerNameOffset = offsetof( Session, name ); + props.Wnode.BufferSize = sizeof( Session ); + props.Wnode.Guid = NullGuid; +#ifdef TRACY_TIMER_QPC + props.Wnode.ClientContext = 1; // 1: QueryPerformanceCounter +#else + props.Wnode.ClientContext = 3; // 3: CPU Ticks (e.g., rdtsc) +#endif + props.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + props.LogFileMode = 0; + props.LogFileMode |= EVENT_TRACE_SYSTEM_LOGGER_MODE; + props.LogFileMode |= EVENT_TRACE_REAL_TIME_MODE; + + // TODO: should we really be tweaking the buffering parameters? + props.BufferSize = 1024; // in KB + props.MinimumBuffers = std::thread::hardware_concurrency() * 4; + props.MaximumBuffers = std::thread::hardware_concurrency() * 6; + + ULONG status = StartSession( session ); + if( status != ERROR_SUCCESS ) + return {}; + + return session; +} + +static Session StartUserSession( const CHAR* name ) +{ + Session session = {}; + + strzcpy( session.name, name, sizeof( session.name ) ); + + auto& props = session.properties; + props.LoggerNameOffset = offsetof( Session, name ); + props.Wnode.BufferSize = sizeof( Session ); + props.Wnode.Guid = NullGuid; +#ifdef TRACY_TIMER_QPC + props.Wnode.ClientContext = 1; // 1: QueryPerformanceCounter +#else + props.Wnode.ClientContext = 3; // 3: CPU Ticks (e.g., rdtsc) +#endif + //props.Wnode.Flags = WNODE_FLAG_TRACED_GUID; // unnecessary for user sessions, apparently + props.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + + ULONG status = StartSession( session ); + if( status != ERROR_SUCCESS ) + return {}; + + return session; +} + +bool IsSingletonKernelLoggerSession( Session& session ) +{ + bool check = true; + check &= ( session.handle == 0xFFFF ); + check &= ( strncmp( session.name, KERNEL_LOGGER_NAMEA, sizeof( session.name ) ) == 0 ); + return check; +} + +static ULONG UpdateSessionEnableFlags( Session& session, ULONGLONG EnableFlags ) +{ + // Use a copy of the session properties, because ControlTrace(UPDATE) will modify + // LogFileNameOffset and "pad" the rest with zeros, overwriting the session.handle! + Session temp = session; + temp.properties.EnableFlags = EnableFlags; + ULONG status = ControlTraceA( temp.handle, temp.name, &temp.properties, EVENT_TRACE_CONTROL_UPDATE ); + if( status != ERROR_SUCCESS ) + return ETWError( status ); + session.properties.EnableFlags = EnableFlags; + return status; +} + +static ULONG EnableProcessAndThreadMonitoring( Session& session ) +{ + if( IsSingletonKernelLoggerSession( session ) ) + { + ULONGLONG EnableFlags = session.properties.EnableFlags; + EnableFlags |= EVENT_TRACE_FLAG_THREAD; + ULONG status = UpdateSessionEnableFlags( session, EnableFlags ); + return status; + } + + ULONGLONG MatchAnyKeyword = SYSTEM_PROCESS_KW_THREAD; // ThreadStart and ThreadDCStart events + ULONG status = EnableProvider( session, SystemProcessProviderGuid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, MatchAnyKeyword ); + return status; +} + +static ULONG EnableCPUProfiling( Session& session, int microseconds = 125 /* 8KHz = 125us */ ) +{ + if( !IsOS64Bit() ) + return 0 /* ERROR_SUCCESS */; // TODO: fabricate SetLastError(ERROR_NOT_SUPPORTED) instead? + + // CPU Profiling requires special privileges on top of admin privileges + DWORD access = ElevatePrivilege( SE_SYSTEM_PROFILE_NAME ); + if( access != ERROR_SUCCESS ) + return access; + + if( IsSingletonKernelLoggerSession( session ) ) + { + ULONGLONG EnableFlags = session.properties.EnableFlags; + EnableFlags |= EVENT_TRACE_FLAG_PROFILE; + ULONG status = UpdateSessionEnableFlags( session, EnableFlags ); + if( status != ERROR_SUCCESS ) + return status; + } + else + { + CheckProviderSessions( SystemProfileProviderGuid, 0 ); + ULONG status = EnableProvider( session, SystemProfileProviderGuid ); + if( status != ERROR_SUCCESS ) + return status; + } + + ULONG status = SetCPUProfilingInterval( microseconds ); + if( status != ERROR_SUCCESS ) + return status; + + status = EnableStackWalk( session, PerfInfoGuid, SampledProfile::Opcode ); + return status; +} + +static ULONG EnableContextSwitchMonitoring( Session& session, bool waitStacks ) +{ + if( IsSingletonKernelLoggerSession( session ) ) + { + ULONGLONG EnableFlags = session.properties.EnableFlags; + EnableFlags |= EVENT_TRACE_FLAG_CSWITCH; + EnableFlags |= EVENT_TRACE_FLAG_DISPATCHER; + ULONG status = UpdateSessionEnableFlags( session, EnableFlags ); + if( status != ERROR_SUCCESS ) + return status; + } + else + { + ULONGLONG MatchAnyKeyword = 0; + MatchAnyKeyword |= SYSTEM_SCHEDULER_KW_CONTEXT_SWITCH; // CSwitch events + MatchAnyKeyword |= SYSTEM_SCHEDULER_KW_DISPATCHER; // ReadyThread events + CheckProviderSessions( SystemSchedulerProviderGuid, MatchAnyKeyword ); + ULONG status = EnableProvider( session, SystemSchedulerProviderGuid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, MatchAnyKeyword ); + if( status != ERROR_SUCCESS ) + return status; + } + + if( !waitStacks ) + return ERROR_SUCCESS; + + ULONG status = EnableStackWalk( session, ThreadGuid, CSwitch::Opcode ); + return status; +} + +static ULONG EnableVSyncMonitoring( Session& session ) +{ + if( !IsOS64Bit() ) + return 0 /* ERROR_SUCCESS */; // TODO: fabricate SetLastError(ERROR_NOT_SUPPORTED) instead? +// TODO: is this correct? +#if ( _WIN32_WINNT < _WIN32_WINNT_WINBLUE ) + return ETWError( ERROR_NOT_SUPPORTED ); +#endif + + enum Keyword : ULONGLONG + { + DxgKrnlBase = 0x0000000000000001ULL, // Microsoft-Windows-DxgKrnl: Base + DxgKrnlPresent = 0x0000000008000000ULL, // Microsoft-Windows-DxgKrnl: Present + MSFTReserved62 = 0x4000000000000000ULL // winmeta.h: WINEVENT_KEYWORD_RESERVED_62 + // (Microsoft-Windows-DxgKrnl/Performance, according to logman) + }; + // DxgKrnlPresent bit was added in Win11, but we do not want to break Win10, so do not put it in MatchAllKeyword + ULONGLONG MatchAnyKeyword = Keyword::MSFTReserved62 /*| Keyword::DxgKrnlPresent*/ | Keyword::DxgKrnlBase; + ULONGLONG MatchAllKeyword = MatchAnyKeyword; + + EVENT_FILTER_EVENT_ID fe = {}; + fe.FilterIn = TRUE; + fe.Count = 1; + fe.Events[0] = VSyncDPC::EventId; + EVENT_FILTER_DESCRIPTOR desc = {}; + desc.Ptr = (ULONGLONG)&fe; + desc.Size = sizeof( fe ); + desc.Type = EVENT_FILTER_TYPE_EVENT_ID; + ENABLE_TRACE_PARAMETERS EnableParameters = {}; + EnableParameters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; + EnableParameters.EnableProperty = EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0; + EnableParameters.SourceId = DxgKrnlGuid; // or NullGuid? Does it even matter? + EnableParameters.EnableFilterDesc = &desc; + EnableParameters.FilterDescCount = 1; + + CheckProviderSessions( DxgKrnlGuid, MatchAnyKeyword ); + ULONG status = EnableProvider( session, DxgKrnlGuid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, + MatchAnyKeyword, MatchAllKeyword, 0, &EnableParameters ); + return status; +} + +static ULONG WINAPI OnBufferComplete( PEVENT_TRACE_LOGFILEA Buffer ) +{ + if( Buffer->EventsLost > 0 ) + { + char buffer[64] = {}; + int length = snprintf( buffer, sizeof( buffer ), "ETW Warning: %u events have been lost.", Buffer->EventsLost ); + ETWErrorAction( ERROR_BUFFER_OVERFLOW, buffer, length ); + } + return TRUE; // or FALSE to break out of ProcessTrace() +} + +static PROCESSTRACE_HANDLE SetupEventConsumer( const Session& session, PEVENT_RECORD_CALLBACK callback ) +{ + EVENT_TRACE_LOGFILEA trace = {}; + trace.LoggerName = (LPSTR)session.name; + trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME; + trace.ProcessTraceMode |= PROCESS_TRACE_MODE_EVENT_RECORD; // request EVENT_RECORD, not EVENT_TRACE (legacy) + trace.ProcessTraceMode |= PROCESS_TRACE_MODE_RAW_TIMESTAMP; // no timestamp conversions (use whatever the session is using) + trace.EventRecordCallback = callback; + trace.BufferCallback = OnBufferComplete; + + PROCESSTRACE_HANDLE hConsumer = OpenTraceA( &trace ); + if( hConsumer == INVALID_PROCESSTRACE_HANDLE ) + ETWError( GetLastError() ); + + return hConsumer; +} + +static ULONG StopEventConsumer( PROCESSTRACE_HANDLE hEventConsumer ) +{ + ULONG status = CloseTrace( hEventConsumer ); + if( ( status != ERROR_SUCCESS ) && ( status != ERROR_CTX_CLOSE_PENDING ) ) + return ETWError( status ); + return status; +} + +static ULONG EventConsumerLoop( PROCESSTRACE_HANDLE hEventConsumer ) +{ + ULONG status = ProcessTrace( &hEventConsumer, 1, NULL, NULL ); + if( status != ERROR_SUCCESS && status != ERROR_CANCELLED ) + return ETWError( status ); + return status; +} + +} +} diff --git a/libs/tracy/client/windows/TracyETW_compat.h b/libs/tracy/client/windows/TracyETW_compat.h new file mode 100644 index 0000000..dd3a93f --- /dev/null +++ b/libs/tracy/client/windows/TracyETW_compat.h @@ -0,0 +1,57 @@ +#ifndef __TRACY_ETW_COMPAT_H__ +#define __TRACY_ETW_COMPAT_H__ + +// Compatibility definitions for older Windows SDKs and MinGW-w64 which lacks some ETW types +// present in Microsoft's Windows SDK + +#ifdef __MINGW32__ + +// CONTROLTRACE_ID - ETW trace session handle type +typedef ULONG64 CONTROLTRACE_ID; + +// PROCESSTRACE_HANDLE - ETW process trace handle type +// MinGW defines INVALID_PROCESSTRACE_HANDLE but not the type itself +#ifndef PROCESSTRACE_HANDLE +#define PROCESSTRACE_HANDLE TRACEHANDLE +#endif + +// EVENT_FILTER_EVENT_ID struct for event filtering +typedef struct _EVENT_FILTER_EVENT_ID { + BOOLEAN FilterIn; + UCHAR Reserved; + USHORT Count; + USHORT Events[ANYSIZE_ARRAY]; +} EVENT_FILTER_EVENT_ID, *PEVENT_FILTER_EVENT_ID; + +// Event filter type constants +#define EVENT_FILTER_TYPE_EVENT_ID (0x80000200) // Event IDs. + +// Enable property constants +#define EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0 (0x00000010) + +// System provider GUIDs +// MinGW's DEFINE_GUID only declares the GUID, we need to define it with actual storage +// Using GUID format: {l, w1, w2, d1, d2, d3, d4, d5, d6, d7, d8} where d1-d8 are bytes +static const GUID SystemProcessProviderGuid = { 0x151f55dc, 0x467d, 0x471f, { 0x83, 0xb5, 0x5f, 0x88, 0x9d, 0x46, 0xff, 0x66 } }; +static const GUID SystemProfileProviderGuid = { 0xbfeb0324, 0x1cee, 0x496f, { 0xa4, 0x9, 0x2a, 0xc2, 0xb4, 0x8a, 0x63, 0x22 } }; +static const GUID SystemSchedulerProviderGuid = { 0x599a2a76, 0x4d91, 0x4910, { 0x9a, 0xc7, 0x7d, 0x33, 0xf2, 0xe9, 0x7a, 0x6c } }; + +// System provider keyword constants +#define SYSTEM_PROCESS_KW_THREAD (0x0000000000000800) +#define SYSTEM_SCHEDULER_KW_DISPATCHER (0x0000000000000002) +#define SYSTEM_SCHEDULER_KW_CONTEXT_SWITCH (0x0000000000000200) + +#else // __MINGW32__ + +// Backcompat with older sdk versions +// SDK 10.0.26100 introduced those two and marked TRACEHANDLE obsolete +// SDK 10.0.26100 is the first one to define NTDDI_VERSION and WDK_NTDDI_VERSION to NTDDI_WIN11_GE, while older ones will have lower versions and NTDDI_WIN11_GE undefined. +// Just in case we check both definition and value. +#if !(defined NTDDI_WIN11_GE && WDK_NTDDI_VERSION >= NTDDI_WIN11_GE) +typedef ULONG64 PROCESSTRACE_HANDLE; +typedef ULONG64 CONTROLTRACE_ID; +#endif + +#endif // __MINGW32__ + +#endif // __TRACY_ETW_COMPAT_H__ diff --git a/libs/tracy/common/TracyAlign.hpp b/libs/tracy/common/TracyAlign.hpp index c3531ba..e6e854f 100644 --- a/libs/tracy/common/TracyAlign.hpp +++ b/libs/tracy/common/TracyAlign.hpp @@ -2,24 +2,32 @@ #define __TRACYALIGN_HPP__ #include +#include #include "TracyForceInline.hpp" +#if defined _MSC_VER && !defined _M_IX86 +# define TracyUnaligned __unaligned +#else +# define TracyUnaligned +#endif + namespace tracy { template -tracy_force_inline T MemRead( const void* ptr ) +tracy_force_inline T MemRead( const TracyUnaligned T* ptr ) { T val; memcpy( &val, ptr, sizeof( T ) ); return val; } -template -tracy_force_inline void MemWrite( void* ptr, T val ) +template +tracy_force_inline void MemWrite( TracyUnaligned T* ptr, U val ) { - memcpy( ptr, &val, sizeof( T ) ); + static_assert( std::is_same::value, "MemWrite type mismatch" ); + memcpy( (void*)ptr, &val, sizeof( T ) ); } } diff --git a/libs/tracy/common/TracyAlloc.hpp b/libs/tracy/common/TracyAlloc.hpp index ddb0e5d..52ec215 100644 --- a/libs/tracy/common/TracyAlloc.hpp +++ b/libs/tracy/common/TracyAlloc.hpp @@ -3,26 +3,35 @@ #include +#ifdef TRACY_PLATFORM_HEADER +# include TRACY_PLATFORM_HEADER +#endif + #if defined TRACY_ENABLE && !defined __EMSCRIPTEN__ # include "TracyApi.h" # include "TracyForceInline.hpp" -# include "../client/tracy_rpmalloc.hpp" -# define TRACY_USE_RPMALLOC +# if !defined TRACY_HAS_CUSTOM_ALLOCATOR +# include "../client/tracy_rpmalloc.hpp" +# define TRACY_USE_RPMALLOC +# endif #endif namespace tracy { -#ifdef TRACY_USE_RPMALLOC -TRACY_API void InitRpmalloc(); +#if defined TRACY_USE_RPMALLOC || defined TRACY_HAS_CUSTOM_ALLOCATOR +TRACY_API void InitAllocator(); #else -static inline void InitRpmalloc() {} +static inline void InitAllocator() {} #endif static inline void* tracy_malloc( size_t size ) { -#ifdef TRACY_USE_RPMALLOC - InitRpmalloc(); +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + InitAllocator(); + return PlatformMalloc( size ); +#elif defined TRACY_USE_RPMALLOC + InitAllocator(); return rpmalloc( size ); #else return malloc( size ); @@ -31,7 +40,9 @@ static inline void* tracy_malloc( size_t size ) static inline void* tracy_malloc_fast( size_t size ) { -#ifdef TRACY_USE_RPMALLOC +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + return PlatformMalloc( size ); +#elif defined TRACY_USE_RPMALLOC return rpmalloc( size ); #else return malloc( size ); @@ -40,8 +51,11 @@ static inline void* tracy_malloc_fast( size_t size ) static inline void tracy_free( void* ptr ) { -#ifdef TRACY_USE_RPMALLOC - InitRpmalloc(); +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + InitAllocator(); + PlatformFree( ptr ); +#elif defined TRACY_USE_RPMALLOC + InitAllocator(); rpfree( ptr ); #else free( ptr ); @@ -50,7 +64,9 @@ static inline void tracy_free( void* ptr ) static inline void tracy_free_fast( void* ptr ) { -#ifdef TRACY_USE_RPMALLOC +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + PlatformFree( ptr ); +#elif defined TRACY_USE_RPMALLOC rpfree( ptr ); #else free( ptr ); @@ -59,8 +75,11 @@ static inline void tracy_free_fast( void* ptr ) static inline void* tracy_realloc( void* ptr, size_t size ) { -#ifdef TRACY_USE_RPMALLOC - InitRpmalloc(); +#if defined TRACY_HAS_CUSTOM_ALLOCATOR + InitAllocator(); + return PlatformRealloc( ptr, size ); +#elif defined TRACY_USE_RPMALLOC + InitAllocator(); return rprealloc( ptr, size ); #else return realloc( ptr, size ); diff --git a/libs/tracy/common/TracyApi.h b/libs/tracy/common/TracyApi.h index f396ce0..4f92b30 100644 --- a/libs/tracy/common/TracyApi.h +++ b/libs/tracy/common/TracyApi.h @@ -3,11 +3,23 @@ #if defined _WIN32 # if defined TRACY_EXPORTS -# define TRACY_API __declspec(dllexport) +# if defined(__clang__) +# define TRACY_API __declspec(dllexport) __attribute__((visibility("default"))) +# else +# define TRACY_API __declspec(dllexport) +# endif # elif defined TRACY_IMPORTS -# define TRACY_API __declspec(dllimport) +# if defined(__clang__) +# define TRACY_API __declspec(dllimport) __attribute__((visibility("default"))) +# else +# define TRACY_API __declspec(dllimport) +# endif # else -# define TRACY_API +# if defined(__clang__) +# define TRACY_API __attribute__((visibility("default"))) +# else +# define TRACY_API +# endif # endif #else # define TRACY_API __attribute__((visibility("default"))) diff --git a/libs/tracy/common/TracyAssert.hpp b/libs/tracy/common/TracyAssert.hpp new file mode 100644 index 0000000..7421b20 --- /dev/null +++ b/libs/tracy/common/TracyAssert.hpp @@ -0,0 +1,12 @@ +#ifndef __TRACYASSERT_HPP__ +#define __TRACYASSERT_HPP__ + +// Define TRACY_ASSERT(condition) before including any Tracy header to +// route the internal checks through a custom assert implementation. +// Falls back to the standard assert when not defined. +#ifndef TRACY_ASSERT +# include +# define TRACY_ASSERT(x) assert(x) +#endif + +#endif diff --git a/libs/tracy/common/TracyFormat.h b/libs/tracy/common/TracyFormat.h new file mode 100644 index 0000000..03af049 --- /dev/null +++ b/libs/tracy/common/TracyFormat.h @@ -0,0 +1,11 @@ +#ifndef __TRACYFORMAT_H__ +#define __TRACYFORMAT_H__ + +#if (defined(__GNUC__) || defined(__clang__)) +# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) \ + __attribute__((format(printf, fmt_idx, arg_idx))) +#else +# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) +#endif + +#endif diff --git a/libs/tracy/common/TracyProtocol.hpp b/libs/tracy/common/TracyProtocol.hpp index ff38686..f50d5ce 100644 --- a/libs/tracy/common/TracyProtocol.hpp +++ b/libs/tracy/common/TracyProtocol.hpp @@ -2,6 +2,7 @@ #define __TRACYPROTOCOL_HPP__ #include +#include #include namespace tracy @@ -9,17 +10,17 @@ namespace tracy constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; } -enum : uint32_t { ProtocolVersion = 76 }; -enum : uint16_t { BroadcastVersion = 3 }; +constexpr uint32_t ProtocolVersion = 82; +constexpr uint16_t BroadcastVersion = 3; using lz4sz_t = uint32_t; -enum { TargetFrameSize = 256 * 1024 }; -enum { LZ4Size = Lz4CompressBound( TargetFrameSize ) }; +constexpr unsigned TargetFrameSize = 256 * 1024; +constexpr unsigned LZ4Size = Lz4CompressBound( TargetFrameSize ); static_assert( LZ4Size <= (std::numeric_limits::max)(), "LZ4Size greater than lz4sz_t" ); static_assert( TargetFrameSize * 2 >= 64 * 1024, "Not enough space for LZ4 stream buffer" ); -enum { HandshakeShibbolethSize = 8 }; +constexpr size_t HandshakeShibbolethSize = 8; static const char HandshakeShibboleth[HandshakeShibbolethSize] = { 'T', 'r', 'a', 'c', 'y', 'P', 'r', 'f' }; enum HandshakeStatus : uint8_t @@ -31,8 +32,8 @@ enum HandshakeStatus : uint8_t HandshakeDropped }; -enum { WelcomeMessageProgramNameSize = 64 }; -enum { WelcomeMessageHostInfoSize = 1024 }; +constexpr size_t WelcomeMessageHostInfoSize = 1024; +constexpr size_t WelcomeMessageProgramNameSize = 64; #pragma pack( push, 1 ) @@ -65,7 +66,7 @@ struct ServerQueryPacket uint32_t extra; }; -enum { ServerQueryPacketSize = sizeof( ServerQueryPacket ) }; +constexpr size_t ServerQueryPacketSize = sizeof( ServerQueryPacket ); enum CpuArchitecture : uint8_t @@ -83,7 +84,7 @@ struct WelcomeFlag enum _t : uint8_t { OnDemand = 1 << 0, - IsApple = 1 << 1, + IgnoreMemFaults = 1 << 1, CodeTransfer = 1 << 2, CombineSamples = 1 << 3, IdentifySamples = 1 << 4, @@ -108,8 +109,6 @@ struct WelcomeMessage char hostInfo[WelcomeMessageHostInfoSize]; }; -enum { WelcomeMessageSize = sizeof( WelcomeMessage ) }; - struct OnDemandPayloadMessage { @@ -117,8 +116,6 @@ struct OnDemandPayloadMessage uint64_t currentTime; }; -enum { OnDemandPayloadMessageSize = sizeof( OnDemandPayloadMessage ) }; - struct BroadcastMessage { @@ -156,13 +153,12 @@ struct BroadcastMessage_v0 char programName[WelcomeMessageProgramNameSize]; }; -enum { BroadcastMessageSize = sizeof( BroadcastMessage ) }; -enum { BroadcastMessageSize_v2 = sizeof( BroadcastMessage_v2 ) }; -enum { BroadcastMessageSize_v1 = sizeof( BroadcastMessage_v1 ) }; -enum { BroadcastMessageSize_v0 = sizeof( BroadcastMessage_v0 ) }; - #pragma pack( pop ) +constexpr uint64_t ProtocolOffset8Bit = (1ull << 8); +constexpr uint64_t ProtocolOffset16Bit = (1ull << 16); +constexpr uint64_t ProtocolOffset32Bit = (1ull << 16) + (1ull << 32); + } #endif diff --git a/libs/tracy/common/TracyQueue.hpp b/libs/tracy/common/TracyQueue.hpp index 765c83c..db5a70f 100644 --- a/libs/tracy/common/TracyQueue.hpp +++ b/libs/tracy/common/TracyQueue.hpp @@ -3,6 +3,9 @@ #include #include +#include "TracyAssert.hpp" +#include "TracyTaggedUserlandAddress.hpp" +#include "TracyForceInline.hpp" namespace tracy { @@ -22,11 +25,21 @@ enum class QueueType : uint8_t Callstack, CallstackAlloc, CallstackSample, + CallstackSample32, + CallstackSample16, CallstackSampleContextSwitch, + CallstackSampleContextSwitch32, + CallstackSampleContextSwitch16, FrameImage, ZoneBegin, + ZoneBegin32, + ZoneBegin16, ZoneBeginCallstack, + ZoneBeginCallstack32, + ZoneBeginCallstack16, ZoneEnd, + ZoneEnd32, + ZoneEnd16, LockWait, LockObtain, LockRelease, @@ -69,6 +82,9 @@ enum class QueueType : uint8_t SourceCodeMetadata, FiberEnter, FiberLeave, + SectionEnter, + SectionLeave, + SectionSetup, Terminate, KeepAlive, ThreadContext, @@ -110,6 +126,8 @@ enum class QueueType : uint8_t CpuTopology, SingleStringData, SecondStringData, + SingleStringData8, + SecondStringData8, MemNamePayload, ThreadGroupHint, GpuZoneAnnotation, @@ -151,11 +169,33 @@ struct QueueZoneBeginThread : public QueueZoneBegin uint32_t thread; }; +struct QueueZoneBegin32 +{ + uint32_t time; + uint64_t srcloc; +}; + +struct QueueZoneBegin16 +{ + uint16_t time; + uint64_t srcloc; +}; + struct QueueZoneEnd { int64_t time; }; +struct QueueZoneEnd32 +{ + uint32_t time; +}; + +struct QueueZoneEnd16 +{ + uint16_t time; +}; + struct QueueZoneEndThread : public QueueZoneEnd { uint32_t thread; @@ -273,6 +313,36 @@ struct QueueFiberLeave uint32_t thread; }; +struct QueueSectionEnter +{ + int64_t time; + uint32_t id; + uint16_t category; +}; + +struct QueueSectionEnterFat : public QueueSectionEnter +{ + uint64_t text; // ptr + uint16_t size; +}; + +struct QueueSectionLeave +{ + int64_t time; + uint32_t id; +}; + +struct QueueSectionSetup +{ + uint16_t category; +}; + +struct QueueSectionSetupFat : public QueueSectionSetup +{ + uint64_t text; // ptr + uint16_t size; +}; + struct QueueLockTerminate { uint32_t id; @@ -343,6 +413,45 @@ struct QueuePlotDataDouble : public QueuePlotDataBase double val; }; +enum class MessageSourceType : uint8_t +{ + User, + Tracy, + COUNT +}; + +enum class MessageSeverity : uint8_t +{ + Trace, // Broadly track variable states and events in the software program. + Debug, // Describes variable states and details about specific internal events in the software, that are useful for investigations. + Info, // Describes normal events, which inform on the expected progress and state of your software. + Warning, // Describes potentially dangerous situations caused by unexpected events and states. + Error, // Describes the occurrence of unexpected behavior. Does not interrupt the execution of the software. + Fatal, // Describes a critical event that will lead to a software failure/crash. + COUNT +}; + +tracy_force_inline uint8_t MakeMessageMetadata(MessageSourceType source, MessageSeverity severity) +{ + static_assert( (uint8_t)MessageSourceType::COUNT < ( 1 << 4 ), "We use 4 bits for the messages source." ); + static_assert( (uint8_t)MessageSeverity::COUNT < ( 1 << 4 ), "We use 4 bits for the messages severity." ); + return ( (uint8_t)severity ) << 4 | (uint8_t)source; +} + +tracy_force_inline MessageSourceType MessageSourceFromMetadata(uint8_t metadata) +{ + TRACY_ASSERT( ( metadata & 0x0F ) < (uint8_t)MessageSourceType::COUNT ); + return (MessageSourceType)( metadata & 0x0F ); +} + +tracy_force_inline MessageSeverity MessageSeverityFromMetadata(uint8_t metadata) +{ + TRACY_ASSERT( ( ( metadata & 0xF0 ) >> 4 ) < (uint8_t)MessageSeverity::COUNT ); + return (MessageSeverity)( ( metadata & 0xF0 ) >> 4 ); +} + +// QueueMessage*Metadata and QueMessageLiteral* are the only structures sent over the wire +// All other variants are used only internally to dispatch from the thread to the profiler and interpreted by Profiler::Dequeue struct QueueMessage { int64_t time; @@ -355,9 +464,19 @@ struct QueueMessageColor : public QueueMessage uint8_t r; }; +struct QueueMessageMetadata : public QueueMessage +{ + uint8_t metadata; +}; + +struct QueueMessageColorMetadata : public QueueMessageColor +{ + uint8_t metadata; +}; + struct QueueMessageLiteral : public QueueMessage { - uint64_t text; // ptr + TaggedUserlandAddress textAndMetadata; // ptr + log level/channels }; struct QueueMessageLiteralThread : public QueueMessageLiteral @@ -367,7 +486,7 @@ struct QueueMessageLiteralThread : public QueueMessageLiteral struct QueueMessageColorLiteral : public QueueMessageColor { - uint64_t text; // ptr + TaggedUserlandAddress textAndMetadata; // ptr + log level/channels }; struct QueueMessageColorLiteralThread : public QueueMessageColorLiteral @@ -377,7 +496,7 @@ struct QueueMessageColorLiteralThread : public QueueMessageColorLiteral struct QueueMessageFat : public QueueMessage { - uint64_t text; // ptr + TaggedUserlandAddress textAndMetadata; // ptr + log level/channels uint16_t size; }; @@ -388,7 +507,7 @@ struct QueueMessageFatThread : public QueueMessageFat struct QueueMessageColorFat : public QueueMessageColor { - uint64_t text; // ptr + TaggedUserlandAddress textAndMetadata; // ptr + log level/channels uint16_t size; }; @@ -409,7 +528,8 @@ enum class GpuContextType : uint8_t Metal, Custom, CUDA, - Rocprof + Rocprof, + WebGPU }; enum GpuContextFlags : uint8_t @@ -417,6 +537,8 @@ enum GpuContextFlags : uint8_t GpuContextCalibration = 1 << 0 }; +constexpr int32_t InvalidGpuContextId = -1; + struct QueueGpuNewContext { int64_t cpuTime; @@ -559,8 +681,8 @@ struct QueueCallstackAllocFatThread : public QueueCallstackAllocFat struct QueueCallstackSample { - int64_t time; uint32_t thread; + int64_t time; }; struct QueueCallstackSampleFat : public QueueCallstackSample @@ -568,6 +690,18 @@ struct QueueCallstackSampleFat : public QueueCallstackSample uint64_t ptr; }; +struct QueueCallstackSample32 +{ + uint32_t thread; + uint32_t time; +}; + +struct QueueCallstackSample16 +{ + uint32_t thread; + uint16_t time; +}; + struct QueueCallstackFrameSize { uint64_t ptr; @@ -677,7 +811,7 @@ struct QueueParamSetup { uint32_t idx; uint64_t name; // ptr - uint8_t isBool; + uint8_t type; int32_t val; }; @@ -733,7 +867,11 @@ struct QueueItem QueueZoneBegin zoneBegin; QueueZoneBeginLean zoneBeginLean; QueueZoneBeginThread zoneBeginThread; + QueueZoneBegin32 zoneBegin32; + QueueZoneBegin16 zoneBegin16; QueueZoneEnd zoneEnd; + QueueZoneEnd32 zoneEnd32; + QueueZoneEnd16 zoneEnd16; QueueZoneEndThread zoneEndThread; QueueZoneValidation zoneValidation; QueueZoneValidationThread zoneValidationThread; @@ -762,7 +900,9 @@ struct QueueItem QueuePlotDataFloat plotDataFloat; QueuePlotDataDouble plotDataDouble; QueueMessage message; + QueueMessageMetadata messageMetadata; QueueMessageColor messageColor; + QueueMessageColorMetadata messageColorMetadata; QueueMessageLiteral messageLiteral; QueueMessageLiteralThread messageLiteralThread; QueueMessageColorLiteral messageColorLiteral; @@ -793,6 +933,8 @@ struct QueueItem QueueCallstackAllocFatThread callstackAllocFatThread; QueueCallstackSample callstackSample; QueueCallstackSampleFat callstackSampleFat; + QueueCallstackSample32 callstackSample32; + QueueCallstackSample16 callstackSample16; QueueCallstackFrameSize callstackFrameSize; QueueCallstackFrameSizeFat callstackFrameSizeFat; QueueCallstackFrame callstackFrame; @@ -815,21 +957,26 @@ struct QueueItem QueueSourceCodeNotAvailable sourceCodeNotAvailable; QueueFiberEnter fiberEnter; QueueFiberLeave fiberLeave; + QueueSectionEnter sectionEnter; + QueueSectionEnterFat sectionEnterFat; + QueueSectionLeave sectionLeave; + QueueSectionSetup sectionSetup; + QueueSectionSetupFat sectionSetupFat; QueueGpuZoneAnnotation zoneAnnotation; }; }; #pragma pack( pop ) -enum { QueueItemSize = sizeof( QueueItem ) }; +constexpr size_t QueueItemSize = sizeof( QueueItem ); static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ), // zone text sizeof( QueueHeader ), // zone name - sizeof( QueueHeader ) + sizeof( QueueMessage ), - sizeof( QueueHeader ) + sizeof( QueueMessageColor ), - sizeof( QueueHeader ) + sizeof( QueueMessage ), // callstack - sizeof( QueueHeader ) + sizeof( QueueMessageColor ), // callstack + sizeof( QueueHeader ) + sizeof( QueueMessageMetadata ), // Message + sizeof( QueueHeader ) + sizeof( QueueMessageColorMetadata ),// MessageColor + sizeof( QueueHeader ) + sizeof( QueueMessageMetadata ), // MessageCallstack + sizeof( QueueHeader ) + sizeof( QueueMessageColorMetadata ),// MessageColorCallstack sizeof( QueueHeader ) + sizeof( QueueMessage ), // app info sizeof( QueueHeader ) + sizeof( QueueZoneBeginLean ), // allocated source location sizeof( QueueHeader ) + sizeof( QueueZoneBeginLean ), // allocated source location, callstack @@ -837,11 +984,21 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ), // callstack sizeof( QueueHeader ), // callstack alloc sizeof( QueueHeader ) + sizeof( QueueCallstackSample ), - sizeof( QueueHeader ) + sizeof( QueueCallstackSample ), // context switch + sizeof( QueueHeader ) + sizeof( QueueCallstackSample32 ), + sizeof( QueueHeader ) + sizeof( QueueCallstackSample16 ), + sizeof( QueueHeader ) + sizeof( QueueCallstackSample ), // context switch + sizeof( QueueHeader ) + sizeof( QueueCallstackSample32 ), // context switch + sizeof( QueueHeader ) + sizeof( QueueCallstackSample16 ), // context switch sizeof( QueueHeader ) + sizeof( QueueFrameImage ), sizeof( QueueHeader ) + sizeof( QueueZoneBegin ), + sizeof( QueueHeader ) + sizeof( QueueZoneBegin32 ), + sizeof( QueueHeader ) + sizeof( QueueZoneBegin16 ), sizeof( QueueHeader ) + sizeof( QueueZoneBegin ), // callstack + sizeof( QueueHeader ) + sizeof( QueueZoneBegin32 ), // callstack + sizeof( QueueHeader ) + sizeof( QueueZoneBegin16 ), // callstack sizeof( QueueHeader ) + sizeof( QueueZoneEnd ), + sizeof( QueueHeader ) + sizeof( QueueZoneEnd32 ), + sizeof( QueueHeader ) + sizeof( QueueZoneEnd16 ), sizeof( QueueHeader ) + sizeof( QueueLockWait ), sizeof( QueueHeader ) + sizeof( QueueLockObtain ), sizeof( QueueHeader ) + sizeof( QueueLockRelease ), @@ -884,6 +1041,9 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ), // SourceCodeMetadata - not for wire transfer sizeof( QueueHeader ) + sizeof( QueueFiberEnter ), sizeof( QueueHeader ) + sizeof( QueueFiberLeave ), + sizeof( QueueHeader ) + sizeof( QueueSectionEnter ), + sizeof( QueueHeader ) + sizeof( QueueSectionLeave ), + sizeof( QueueHeader ) + sizeof( QueueSectionSetup ), // above items must be first sizeof( QueueHeader ), // terminate sizeof( QueueHeader ), // keep alive @@ -926,6 +1086,8 @@ static constexpr size_t QueueDataSize[] = { sizeof( QueueHeader ) + sizeof( QueueCpuTopology ), sizeof( QueueHeader ), // single string data sizeof( QueueHeader ), // second string data + sizeof( QueueHeader ), // single string data, 8 bit length + sizeof( QueueHeader ), // second string data, 8 bit length sizeof( QueueHeader ) + sizeof( QueueMemNamePayload ), sizeof( QueueHeader ) + sizeof( QueueThreadGroupHint ), sizeof( QueueHeader ) + sizeof( QueueGpuZoneAnnotation ), // GPU zone annotation diff --git a/libs/tracy/common/TracySocket.cpp b/libs/tracy/common/TracySocket.cpp index bdba361..ba36e9b 100644 --- a/libs/tracy/common/TracySocket.cpp +++ b/libs/tracy/common/TracySocket.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -7,6 +6,7 @@ #include #include "TracyAlloc.hpp" +#include "TracyAssert.hpp" #include "TracySocket.hpp" #include "TracySystem.hpp" @@ -27,10 +27,10 @@ #else # include # include -# include # include # include # include +# include # include # include # include @@ -70,7 +70,19 @@ void InitWinSock() #endif -enum { BufSize = 128 * 1024 }; +static void SetNoDelay( int sock ) +{ +#ifdef _WIN32 + unsigned long val = 1; + setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&val, sizeof( val ) ); +#else + int val = 1; + setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof( val ) ); +#endif +} + + +constexpr size_t BufSize = 128 * 1024; Socket::Socket() : m_buf( (char*)tracy_malloc( BufSize ) ) @@ -113,7 +125,7 @@ Socket::~Socket() bool Socket::Connect( const char* addr, uint16_t port ) { - assert( !IsValid() ); + TRACY_ASSERT( !IsValid() ); if( m_ptr ) { @@ -150,6 +162,7 @@ bool Socket::Connect( const char* addr, uint16_t port ) int flags = fcntl( m_connSock, F_GETFL, 0 ); fcntl( m_connSock, F_SETFL, flags & ~O_NONBLOCK ); #endif + SetNoDelay( m_connSock ); m_sock.store( m_connSock, std::memory_order_relaxed ); freeaddrinfo( m_res ); m_ptr = nullptr; @@ -218,49 +231,7 @@ bool Socket::Connect( const char* addr, uint16_t port ) int flags = fcntl( sock, F_GETFL, 0 ); fcntl( sock, F_SETFL, flags & ~O_NONBLOCK ); #endif - - m_sock.store( sock, std::memory_order_relaxed ); - return true; -} - -bool Socket::ConnectBlocking( const char* addr, uint16_t port ) -{ - assert( !IsValid() ); - assert( !m_ptr ); - - struct addrinfo hints; - struct addrinfo *res, *ptr; - - memset( &hints, 0, sizeof( hints ) ); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - char portbuf[32]; - sprintf( portbuf, "%" PRIu16, port ); - - if( getaddrinfo( addr, portbuf, &hints, &res ) != 0 ) return false; - int sock = 0; - for( ptr = res; ptr; ptr = ptr->ai_next ) - { - if( ( sock = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol ) ) == -1 ) continue; -#if defined __APPLE__ - int val = 1; - setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); -#endif - if( connect( sock, ptr->ai_addr, ptr->ai_addrlen ) == -1 ) - { -#ifdef _WIN32 - closesocket( sock ); -#else - close( sock ); -#endif - continue; - } - break; - } - freeaddrinfo( res ); - if( !ptr ) return false; - + SetNoDelay( sock ); m_sock.store( sock, std::memory_order_relaxed ); return true; } @@ -268,7 +239,7 @@ bool Socket::ConnectBlocking( const char* addr, uint16_t port ) void Socket::Close() { const auto sock = m_sock.load( std::memory_order_relaxed ); - assert( sock != -1 ); + TRACY_ASSERT( sock != -1 ); #ifdef _WIN32 closesocket( sock ); #else @@ -281,7 +252,7 @@ int Socket::Send( const void* _buf, int len ) { const auto sock = m_sock.load( std::memory_order_relaxed ); auto buf = (const char*)_buf; - assert( sock != -1 ); + TRACY_ASSERT( sock != -1 ); auto start = buf; while( len > 0 ) { @@ -437,6 +408,11 @@ bool Socket::IsValid() const return m_sock.load( std::memory_order_relaxed ) >= 0; } +bool Socket::IsConnecting() const +{ + return m_ptr != nullptr; +} + ListenSocket::ListenSocket() : m_sock( -1 ) @@ -451,6 +427,21 @@ ListenSocket::~ListenSocket() if( m_sock != -1 ) Close(); } +void ListenSocket::Adopt( int fd ) +{ + TRACY_ASSERT( m_sock == -1 ); + m_sock = fd; +} + +uint16_t ListenSocket::LocalPort() const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof( addr ); + if( getsockname( m_sock, (sockaddr*)&addr, &len ) != 0 ) return 0; + if( addr.ss_family == AF_INET6 ) return ntohs( ((struct sockaddr_in6*)&addr)->sin6_port ); + return ntohs( ((struct sockaddr_in*)&addr)->sin_port ); +} + static int addrinfo_and_socket_for_family( uint16_t port, int ai_family, struct addrinfo** res ) { struct addrinfo hints; @@ -474,7 +465,7 @@ static int addrinfo_and_socket_for_family( uint16_t port, int ai_family, struct bool ListenSocket::Listen( uint16_t port, int backlog ) { - assert( m_sock == -1 ); + TRACY_ASSERT( m_sock == -1 ); struct addrinfo* res = nullptr; @@ -495,7 +486,7 @@ bool ListenSocket::Listen( uint16_t port, int backlog ) #if defined _WIN32 unsigned long val = 0; setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) ); -#elif defined BSD +#elif defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__ int val = 0; setsockopt( m_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&val, sizeof( val ) ); val = 1; @@ -529,6 +520,8 @@ Socket* ListenSocket::Accept() setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof( val ) ); #endif + SetNoDelay( sock ); + auto ptr = (Socket*)tracy_malloc( sizeof( Socket ) ); new(ptr) Socket( sock ); return ptr; @@ -541,7 +534,7 @@ Socket* ListenSocket::Accept() void ListenSocket::Close() { - assert( m_sock != -1 ); + TRACY_ASSERT( m_sock != -1 ); #ifdef _WIN32 closesocket( m_sock ); #else @@ -565,7 +558,7 @@ UdpBroadcast::~UdpBroadcast() bool UdpBroadcast::Open( const char* addr, uint16_t port ) { - assert( m_sock == -1 ); + TRACY_ASSERT( m_sock == -1 ); struct addrinfo hints; struct addrinfo *res, *ptr; @@ -613,7 +606,7 @@ bool UdpBroadcast::Open( const char* addr, uint16_t port ) void UdpBroadcast::Close() { - assert( m_sock != -1 ); + TRACY_ASSERT( m_sock != -1 ); #ifdef _WIN32 closesocket( m_sock ); #else @@ -624,7 +617,7 @@ void UdpBroadcast::Close() int UdpBroadcast::Send( uint16_t port, const void* data, int len ) { - assert( m_sock != -1 ); + TRACY_ASSERT( m_sock != -1 ); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons( port ); @@ -670,7 +663,7 @@ UdpListen::~UdpListen() bool UdpListen::Listen( uint16_t port ) { - assert( m_sock == -1 ); + TRACY_ASSERT( m_sock == -1 ); int sock; if( ( sock = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 ) return false; @@ -723,7 +716,7 @@ bool UdpListen::Listen( uint16_t port ) void UdpListen::Close() { - assert( m_sock != -1 ); + TRACY_ASSERT( m_sock != -1 ); #ifdef _WIN32 closesocket( m_sock ); #else diff --git a/libs/tracy/common/TracySocket.hpp b/libs/tracy/common/TracySocket.hpp index f7713aa..6204797 100644 --- a/libs/tracy/common/TracySocket.hpp +++ b/libs/tracy/common/TracySocket.hpp @@ -23,7 +23,6 @@ class Socket ~Socket(); bool Connect( const char* addr, uint16_t port ); - bool ConnectBlocking( const char* addr, uint16_t port ); void Close(); int Send( const void* buf, int len ); @@ -44,9 +43,29 @@ class Socket return true; } + template + int ReadUpTo( void* buf, int len, int timeout, ShouldExit exitCb ) + { + auto cbuf = (char*)buf; + int rd = 0; + while( len > 0 ) + { + if( exitCb() ) return rd; + char* p = cbuf; + int l = len; + if( !ReadImpl( p, l, timeout ) ) return rd; + const auto step = len - l; + rd += step; + cbuf += step; + len -= step; + } + return rd; + } + bool ReadRaw( void* buf, int len, int timeout ); bool HasData(); bool IsValid() const; + bool IsConnecting() const; Socket( const Socket& ) = delete; Socket( Socket&& ) = delete; @@ -78,6 +97,8 @@ class ListenSocket bool Listen( uint16_t port, int backlog ); Socket* Accept(); void Close(); + void Adopt( int fd ); + uint16_t LocalPort() const; ListenSocket( const ListenSocket& ) = delete; ListenSocket( ListenSocket&& ) = delete; diff --git a/libs/tracy/common/TracyStackFrames.cpp b/libs/tracy/common/TracyStackFrames.cpp index 7b0abac..ea607c8 100644 --- a/libs/tracy/common/TracyStackFrames.cpp +++ b/libs/tracy/common/TracyStackFrames.cpp @@ -16,6 +16,7 @@ const char* s_tracyStackFrames_[] = { "tracy::Profiler::MemFreeCallstack(void const*, int)", "tracy::ScopedZone::{ctor}", "tracy::ScopedZone::ScopedZone(tracy::SourceLocationData const*, int, bool)", + "tracy::Profiler::LogString", "tracy::Profiler::Message", nullptr }; diff --git a/libs/tracy/common/TracyString.hpp b/libs/tracy/common/TracyString.hpp new file mode 100644 index 0000000..eb963e4 --- /dev/null +++ b/libs/tracy/common/TracyString.hpp @@ -0,0 +1,33 @@ +#ifndef __TRACYSTRING_HPP__ +#define __TRACYSTRING_HPP__ + +#include +#include + +#include "TracyAssert.hpp" + +namespace tracy +{ + +static inline size_t strzcpy( char* __restrict dst, const char* __restrict src, size_t dstSize ) +{ + TRACY_ASSERT( dstSize > 0 ); + + const auto end = (const char*)memchr( src, '\0', dstSize ); + if( end != nullptr ) + { + const auto srcSz = end - src; + memcpy( dst, src, srcSz + 1 ); + return srcSz; + } + else + { + memcpy( dst, src, dstSize - 1 ); + dst[dstSize - 1] = '\0'; + return dstSize - 1; + } +} + +} + +#endif diff --git a/libs/tracy/common/TracySystem.cpp b/libs/tracy/common/TracySystem.cpp index 7696ca3..87b1b0c 100644 --- a/libs/tracy/common/TracySystem.cpp +++ b/libs/tracy/common/TracySystem.cpp @@ -8,11 +8,18 @@ # ifndef NOMINMAX # define NOMINMAX # endif +# define SECURITY_WIN32 # include # include +# include +# include # include "TracyWinFamily.hpp" +# ifdef _MSC_VER +# pragma comment(lib, "secur32.lib") +# endif #else # include +# include # include # include #endif @@ -44,6 +51,10 @@ #include "TracySystem.hpp" +#ifdef TRACY_PLATFORM_HEADER +# include TRACY_PLATFORM_HEADER +#endif + #if defined _WIN32 extern "C" typedef HRESULT (WINAPI *t_SetThreadDescription)( HANDLE, PCWSTR ); extern "C" typedef HRESULT (WINAPI *t_GetThreadDescription)( HANDLE, PWSTR* ); @@ -62,7 +73,9 @@ namespace detail TRACY_API uint32_t GetThreadHandleImpl() { -#if defined _WIN32 +#if defined TRACY_HAS_CUSTOM_THREAD_ID + return PlatformGetThreadId(); +#elif defined _WIN32 static_assert( sizeof( decltype( GetCurrentThreadId() ) ) <= sizeof( uint32_t ), "Thread handle too big to fit in protocol" ); return uint32_t( GetCurrentThreadId() ); #elif defined __APPLE__ @@ -161,26 +174,22 @@ TRACY_API void SetThreadNameWithHint( const char* name, int32_t groupHint ) } #elif defined _GNU_SOURCE && !defined __EMSCRIPTEN__ { +#if defined __APPLE__ + pthread_setname_np( name ); +#else const auto sz = strlen( name ); if( sz <= 15 ) { -#if defined __APPLE__ - pthread_setname_np( name ); -#else pthread_setname_np( pthread_self(), name ); -#endif } else { char buf[16]; memcpy( buf, name, 15 ); buf[15] = '\0'; -#if defined __APPLE__ - pthread_setname_np( buf ); -#else pthread_setname_np( pthread_self(), buf ); -#endif } +#endif } #elif defined __QNX__ { @@ -336,6 +345,57 @@ TRACY_API const char* GetEnvVar( const char* name ) #endif } +TRACY_API const char* GetUserLogin() +{ +#if defined TRACY_HAS_CUSTOM_USER_INFO + return PlatformGetUserLogin(); +#elif defined _WIN32 +# if defined TRACY_WIN32_NO_DESKTOP + return "(?)"; +# else + DWORD userSz = UNLEN+1; + static char user[UNLEN+1]; + GetUserNameA( user, &userSz ); + return user; +# endif +#elif defined __ANDROID__ + const auto user = getlogin(); + if( user ) return user; + return "(?)"; +#else + static char user[1024] = {}; + getlogin_r( user, sizeof( user ) ); + return user; +#endif +} + +TRACY_API const char* GetUserFullName() +{ +#if defined TRACY_HAS_CUSTOM_USER_INFO + return PlatformGetUserFullName(); +#elif defined _WIN32 +# if !defined TRACY_WIN32_NO_DESKTOP + static char buf[1024]; + ULONG size = sizeof( buf ); + if( GetUserNameExA( NameDisplay, buf, &size ) ) return buf; +# endif + return nullptr; +#elif defined __ANDROID__ + const auto passwd = getpwuid( getuid() ); + if( passwd && passwd->pw_gecos && *passwd->pw_gecos ) return passwd->pw_gecos; + return nullptr; +#else + static char buf[4*1024]; + struct passwd pwd; + struct passwd* ptr; + if( getpwuid_r( getuid(), &pwd, buf, sizeof( buf ), &ptr ) == 0 && ptr == &pwd && *pwd.pw_gecos ) + { + return pwd.pw_gecos; + } + return nullptr; +#endif +} + } #ifdef __cplusplus diff --git a/libs/tracy/common/TracySystem.hpp b/libs/tracy/common/TracySystem.hpp index 2f565e9..15fd0ea 100644 --- a/libs/tracy/common/TracySystem.hpp +++ b/libs/tracy/common/TracySystem.hpp @@ -37,6 +37,8 @@ TRACY_API void SetThreadNameWithHint( const char* name, int32_t groupHint ); TRACY_API const char* GetThreadName( uint32_t id ); TRACY_API const char* GetEnvVar( const char* name ); +TRACY_API const char* GetUserLogin(); +TRACY_API const char* GetUserFullName(); } diff --git a/libs/tracy/common/TracyTaggedUserlandAddress.hpp b/libs/tracy/common/TracyTaggedUserlandAddress.hpp new file mode 100644 index 0000000..3daf45e --- /dev/null +++ b/libs/tracy/common/TracyTaggedUserlandAddress.hpp @@ -0,0 +1,34 @@ +#ifndef __TRACYTAGGEDPTR_HPP__ +#define __TRACYTAGGEDPTR_HPP__ + +#include + +#include "TracyAssert.hpp" +#include "TracyForceInline.hpp" + +namespace tracy +{ + +class TaggedUserlandAddress +{ + static constexpr uint64_t ptrShift = 8; + static constexpr uint64_t highBits = 0xFF00000000000000; + +public: + TaggedUserlandAddress() = default; + tracy_force_inline explicit TaggedUserlandAddress( uint64_t address, uint8_t tag = 0 ) + { + TRACY_ASSERT( ( address & highBits ) == 0 ); + m_storage = ( address << ptrShift ) | tag; + } + + tracy_force_inline uint64_t GetAddress() const { return m_storage >> ptrShift; } + tracy_force_inline uint8_t GetTag() const { return uint8_t( m_storage & 0xFF ); } + +private: + uint64_t m_storage; +}; + +} + +#endif diff --git a/libs/tracy/common/TracyUwp.hpp b/libs/tracy/common/TracyUwp.hpp deleted file mode 100644 index 7dce96b..0000000 --- a/libs/tracy/common/TracyUwp.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef __TRACYUWP_HPP__ -#define __TRACYUWP_HPP__ - -#ifdef _WIN32 -# include -# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -# define TRACY_UWP -# endif -#endif - -#endif diff --git a/libs/tracy/common/TracyVersion.hpp b/libs/tracy/common/TracyVersion.hpp index 1b6fc48..e9e0e85 100644 --- a/libs/tracy/common/TracyVersion.hpp +++ b/libs/tracy/common/TracyVersion.hpp @@ -1,14 +1,11 @@ #ifndef __TRACYVERSION_HPP__ #define __TRACYVERSION_HPP__ -namespace tracy +namespace tracy::Version { -namespace Version -{ -enum { Major = 0 }; -enum { Minor = 13 }; -enum { Patch = 0 }; -} +constexpr int Major = 0; +constexpr int Minor = 14; +constexpr int Patch = 1; } #endif diff --git a/libs/tracy/libbacktrace/backtrace.hpp b/libs/tracy/libbacktrace/backtrace.hpp index e4be297..159b68b 100644 --- a/libs/tracy/libbacktrace/backtrace.hpp +++ b/libs/tracy/libbacktrace/backtrace.hpp @@ -87,6 +87,22 @@ extern struct backtrace_state *backtrace_create_state ( const char *filename, int threaded, backtrace_error_callback error_callback, void *data); +/* Like backtrace_create_state, but marks the state as being for an + external file that is not loaded in the current process. This + bypasses the ET_DYN deferral to dl_iterate_phdr that normally + happens for PIE executables and shared libraries, allowing DWARF + data to be loaded directly from the file with base_address=0. + The caller is responsible for converting runtime virtual addresses + to ELF virtual addresses before passing them to backtrace_pcinfo + or backtrace_syminfo. + The filename is copied into state-owned memory (the file is opened + lazily on first use), so the caller's buffer need not outlive this + call. */ + +extern struct backtrace_state *backtrace_create_state_for_file ( + const char *filename, int threaded, + backtrace_error_callback error_callback, void *data); + /* The type of the callback argument to the backtrace_full function. DATA is the argument passed to backtrace_full. PC is the program counter. FILENAME is the name of the file containing PC, or NULL diff --git a/libs/tracy/libbacktrace/config.h b/libs/tracy/libbacktrace/config.h index 87e38a9..c33e273 100644 --- a/libs/tracy/libbacktrace/config.h +++ b/libs/tracy/libbacktrace/config.h @@ -1,9 +1,4 @@ -#include -#if defined(__linux__) && !defined(__GLIBC__) && !defined(__WORDSIZE) -// include __WORDSIZE headers for musl -# include -#endif -#if __WORDSIZE == 64 +#if __SIZEOF_POINTER__ == 8 # define BACKTRACE_ELF_SIZE 64 #else # define BACKTRACE_ELF_SIZE 32 diff --git a/libs/tracy/libbacktrace/dwarf.cpp b/libs/tracy/libbacktrace/dwarf.cpp index 52fa8a8..1561022 100644 --- a/libs/tracy/libbacktrace/dwarf.cpp +++ b/libs/tracy/libbacktrace/dwarf.cpp @@ -1613,6 +1613,194 @@ unit_addrs_search (const void *vkey, const void *ventry) return 0; } +/* Fill in overlapping ranges as needed. This is a subroutine of + resolve_unit_addrs_overlap. */ + +static int +resolve_unit_addrs_overlap_walk (struct backtrace_state *state, + size_t *pfrom, size_t *pto, + struct unit_addrs *enclosing, + struct unit_addrs_vector *old_vec, + backtrace_error_callback error_callback, + void *data, + struct unit_addrs_vector *new_vec) +{ + struct unit_addrs *old_addrs; + size_t old_count; + struct unit_addrs *new_addrs; + size_t from; + size_t to; + + old_addrs = (struct unit_addrs *) old_vec->vec.base; + old_count = old_vec->count; + new_addrs = (struct unit_addrs *) new_vec->vec.base; + + for (from = *pfrom, to = *pto; from < old_count; from++, to++) + { + /* If we are in the scope of a larger range that can no longer + cover any further ranges, return back to the caller. */ + + if (enclosing != NULL + && enclosing->high <= old_addrs[from].low) + { + *pfrom = from; + *pto = to; + return 1; + } + + new_addrs[to] = old_addrs[from]; + + /* If we are in scope of a larger range, fill in any gaps + between this entry and the next one. + + There is an extra entry at the end of the vector, so it's + always OK to refer to from + 1. */ + + if (enclosing != NULL + && enclosing->high > old_addrs[from].high + && old_addrs[from].high < old_addrs[from + 1].low) + { + void *grew; + size_t new_high; + + grew = backtrace_vector_grow (state, sizeof (struct unit_addrs), + error_callback, data, &new_vec->vec); + if (grew == NULL) + return 0; + new_addrs = (struct unit_addrs *) new_vec->vec.base; + to++; + new_addrs[to].low = old_addrs[from].high; + new_high = old_addrs[from + 1].low; + if (enclosing->high < new_high) + new_high = enclosing->high; + new_addrs[to].high = new_high; + new_addrs[to].u = enclosing->u; + } + + /* If this range has a larger scope than the next one, use it to + fill in any gaps. */ + + if (old_addrs[from].high > old_addrs[from + 1].high) + { + *pfrom = from + 1; + *pto = to + 1; + if (!resolve_unit_addrs_overlap_walk (state, pfrom, pto, + &old_addrs[from], old_vec, + error_callback, data, new_vec)) + return 0; + from = *pfrom; + to = *pto; + + /* Undo the increment the loop is about to do. */ + from--; + to--; + } + } + + if (enclosing == NULL) + { + struct unit_addrs *pa; + + /* Add trailing entry. */ + + pa = ((struct unit_addrs *) + backtrace_vector_grow (state, sizeof (struct unit_addrs), + error_callback, data, &new_vec->vec)); + if (pa == NULL) + return 0; + pa->low = 0; + --pa->low; + pa->high = pa->low; + pa->u = NULL; + + new_vec->count = to; + } + + return 1; +} + +/* It is possible for the unit_addrs list to contain overlaps, as in + + 10: low == 10, high == 20, unit 1 + 11: low == 12, high == 15, unit 2 + 12: low == 20, high == 30, unit 1 + + In such a case, for pc == 17, a search using units_addr_search will + return entry 11. However, pc == 17 doesn't fit in that range. We + actually want range 10. + + It seems that in general we might have an arbitrary number of + ranges in between 10 and 12. + + To handle this we look for cases where range R1 is followed by + range R2 such that R2 is a strict subset of R1. In such cases we + insert a new range R3 following R2 that fills in the remainder of + the address space covered by R1. That lets a relatively simple + search find the correct range. + + These overlaps can occur because of the range merging we do in + add_unit_addr. When the linker de-duplicates functions, it can + leave behind an address range that refers to the address range of + the retained duplicate. If the retained duplicate address range is + merged with others, then after sorting we can see overlapping + address ranges. + + See https://github.com/ianlancetaylor/libbacktrace/issues/137. */ + +static int +resolve_unit_addrs_overlap (struct backtrace_state *state, + backtrace_error_callback error_callback, + void *data, struct unit_addrs_vector *addrs_vec) +{ + struct unit_addrs *addrs; + size_t count; + int found; + struct unit_addrs *entry; + size_t i; + struct unit_addrs_vector new_vec; + void *grew; + size_t from; + size_t to; + + addrs = (struct unit_addrs *) addrs_vec->vec.base; + count = addrs_vec->count; + + if (count == 0) + return 1; + + /* Optimistically assume that overlaps are rare. */ + found = 0; + entry = addrs; + for (i = 0; i < count - 1; i++) + { + if (entry->low < (entry + 1)->low + && entry->high > (entry + 1)->high) + { + found = 1; + break; + } + entry++; + } + if (!found) + return 1; + + memset (&new_vec, 0, sizeof new_vec); + grew = backtrace_vector_grow (state, + count * sizeof (struct unit_addrs), + error_callback, data, &new_vec.vec); + if (grew == NULL) + return 0; + + from = 0; + to = 0; + resolve_unit_addrs_overlap_walk (state, &from, &to, NULL, addrs_vec, + error_callback, data, &new_vec); + backtrace_vector_free (state, &addrs_vec->vec, error_callback, data); + *addrs_vec = new_vec; + + return 1; +} + /* Sort the line vector by PC. We want a stable sort here to maintain the order of lines for the same PC values. Since the sequence is being sorted in place, their addresses cannot be relied on to @@ -3317,7 +3505,7 @@ read_line_info (struct backtrace_state *state, struct dwarf_data *ddata, if (vec.count == 0) { - /* This is not a failure in the sense of a generating an error, + /* This is not a failure in the sense of generating an error, but it is a failure in that sense that we have no useful information. */ goto fail; @@ -3965,7 +4153,7 @@ report_inlined_functions (uintptr_t pc, struct function *function, const char* c return ret; /* Report this inlined call. */ - if (*filename[0] != '/' && comp_dir) + if (*filename && *filename[0] != '/' && comp_dir) { char buf[1024]; snprintf (buf, 1024, "%s/%s", comp_dir, *filename); @@ -4246,7 +4434,7 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, if (ret != 0) return ret; - if (filename[0] != '/' && entry->u->comp_dir) + if (filename && filename[0] != '/' && entry->u->comp_dir) { char buf[1024]; snprintf (buf, 1024, "%s/%s", entry->u->comp_dir, filename); @@ -4344,11 +4532,7 @@ build_dwarf_data (struct backtrace_state *state, void *data) { struct unit_addrs_vector addrs_vec; - struct unit_addrs *addrs; - size_t addrs_count; struct unit_vector units_vec; - struct unit **units; - size_t units_count; struct dwarf_data *fdata; if (!build_address_map (state, base_address, dwarf_sections, is_bigendian, @@ -4360,12 +4544,12 @@ build_dwarf_data (struct backtrace_state *state, return NULL; if (!backtrace_vector_release (state, &units_vec.vec, error_callback, data)) return NULL; - addrs = (struct unit_addrs *) addrs_vec.vec.base; - units = (struct unit **) units_vec.vec.base; - addrs_count = addrs_vec.count; - units_count = units_vec.count; - backtrace_qsort (addrs, addrs_count, sizeof (struct unit_addrs), - unit_addrs_compare); + + backtrace_qsort ((struct unit_addrs *) addrs_vec.vec.base, addrs_vec.count, + sizeof (struct unit_addrs), unit_addrs_compare); + if (!resolve_unit_addrs_overlap (state, error_callback, data, &addrs_vec)) + return NULL; + /* No qsort for units required, already sorted. */ fdata = ((struct dwarf_data *) @@ -4377,10 +4561,10 @@ build_dwarf_data (struct backtrace_state *state, fdata->next = NULL; fdata->altlink = altlink; fdata->base_address = base_address; - fdata->addrs = addrs; - fdata->addrs_count = addrs_count; - fdata->units = units; - fdata->units_count = units_count; + fdata->addrs = (struct unit_addrs *) addrs_vec.vec.base; + fdata->addrs_count = addrs_vec.count; + fdata->units = (struct unit **) units_vec.vec.base; + fdata->units_count = units_vec.count; fdata->dwarf_sections = *dwarf_sections; fdata->is_bigendian = is_bigendian; memset (&fdata->fvec, 0, sizeof fdata->fvec); diff --git a/libs/tracy/libbacktrace/elf.cpp b/libs/tracy/libbacktrace/elf.cpp index ffe8d70..9264853 100644 --- a/libs/tracy/libbacktrace/elf.cpp +++ b/libs/tracy/libbacktrace/elf.cpp @@ -75,7 +75,7 @@ namespace tracy { #ifdef TRACY_DEBUGINFOD -int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size ); +int GetDebugInfoDescriptor( const char* buildid_data, size_t buildid_size, const char* filename ); #endif #if !defined(HAVE_DECL_STRNLEN) || !HAVE_DECL_STRNLEN @@ -170,10 +170,10 @@ dl_iterate_phdr (int (*callback) (struct dl_phdr_info *, #undef EI_CLASS #undef EI_DATA #undef EI_VERSION -#undef ELF_MAG0 -#undef ELF_MAG1 -#undef ELF_MAG2 -#undef ELF_MAG3 +#undef ELFMAG0 +#undef ELFMAG1 +#undef ELFMAG2 +#undef ELFMAG3 #undef ELFCLASS32 #undef ELFCLASS64 #undef ELFDATA2LSB @@ -1165,7 +1165,10 @@ elf_fetch_bits (const unsigned char **ppin, const unsigned char *pinend, next = __builtin_bswap32 (next); #endif #else - next = pin[0] | (pin[1] << 8) | (pin[2] << 16) | (pin[3] << 24); + next = ((uint32_t)pin[0] + | ((uint32_t)pin[1] << 8) + | ((uint32_t)pin[2] << 16) + | ((uint32_t)pin[3] << 24)); #endif val |= (uint64_t)next << bits; @@ -1216,7 +1219,10 @@ elf_fetch_bits_backward (const unsigned char **ppin, next = __builtin_bswap32 (next); #endif #else - next = pin[0] | (pin[1] << 8) | (pin[2] << 16) | (pin[3] << 24); + next = ((uint32_t)pin[0] + | ((uint32_t)pin[1] << 8) + | ((uint32_t)pin[2] << 16) + | ((uint32_t)pin[3] << 24)); #endif val <<= 32; @@ -4314,6 +4320,7 @@ elf_zstd_unpack_seq_decode (int mode, decode->table_bits = 0; if (!conv (&entry, 0, table)) return 0; + decode->table = table; } break; @@ -4350,15 +4357,17 @@ elf_zstd_unpack_seq_decode (int mode, return 1; } -/* Decompress a zstd stream from PIN/SIN to POUT/SOUT. Code based on RFC 8878. +/* Decompress a single zstd frame from *PPIN, ending at PINEND, to *PPOUT/SOUT. Return 1 on success, 0 on error. */ static int -elf_zstd_decompress (const unsigned char *pin, size_t sin, - unsigned char *zdebug_table, unsigned char *pout, - size_t sout) +elf_zstd_decompress_frame (const unsigned char **ppin, + const unsigned char *pinend, + unsigned char *zdebug_table, unsigned char **ppout, + size_t sout) { - const unsigned char *pinend; + const unsigned char *pin; + unsigned char *pout; unsigned char *poutstart; unsigned char *poutend; struct elf_zstd_seq_decode literal_decode; @@ -4374,13 +4383,14 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, uint32_t repeated_offset3; uint16_t *scratch; unsigned char hdr; + int single_segment; int has_checksum; uint64_t content_size; int last_block; - pinend = pin + sin; + pin = *ppin; + pout = *ppout; poutstart = pout; - poutend = pout + sout; literal_decode.table = NULL; literal_decode.table_bits = -1; @@ -4406,7 +4416,7 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, repeated_offset2 = 4; repeated_offset3 = 8; - if (unlikely (sin < 4)) + if (unlikely (pinend - pin < 4)) { elf_uncompress_failed (); return 0; @@ -4432,12 +4442,18 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, hdr = *pin++; - /* We expect a single frame. */ - if (unlikely ((hdr & (1 << 5)) == 0)) + single_segment = (hdr & (1 << 5)) != 0; + if (!single_segment) { - elf_uncompress_failed (); - return 0; + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + /* skip Window_Descriptor */ + pin++; } + /* Reserved bit must be zero. */ if (unlikely ((hdr & (1 << 3)) != 0)) { @@ -4454,13 +4470,22 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, switch (hdr >> 6) { case 0: - if (unlikely (pin >= pinend)) - { - elf_uncompress_failed (); - return 0; - } - content_size = (uint64_t) *pin++; - break; + if (single_segment) + { + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = (uint64_t) *pin++; + break; + } + else + { + /* no Frame_Content_Size; use the remaining size as the upper bound */ + content_size = (uint64_t) sout; + break; + } case 1: if (unlikely (pin + 1 >= pinend)) { @@ -4504,12 +4529,14 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, } if (unlikely (content_size != (size_t) content_size - || (size_t) content_size != sout)) + || (size_t) content_size > sout)) { elf_uncompress_failed (); return 0; } + poutend = pout + content_size; + last_block = 0; while (!last_block) { @@ -4628,6 +4655,11 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, pin += 2; } + pback = NULL; + bits = 0; + literal_state = 0; + offset_state = 0; + match_state = 0; if (seq_count > 0) { int (*pfn)(const struct elf_zstd_fse_entry *, @@ -4667,27 +4699,27 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, match_fse_table, 9, pfn, &match_decode)) return 0; - } - pback = pblockend - 1; - if (!elf_fetch_backward_init (&pback, pin, &val, &bits)) - return 0; + pback = pblockend - 1; + if (!elf_fetch_backward_init (&pback, pin, &val, &bits)) + return 0; - bits -= literal_decode.table_bits; - literal_state = ((val >> bits) - & ((1U << literal_decode.table_bits) - 1)); + bits -= literal_decode.table_bits; + literal_state = ((val >> bits) + & ((1U << literal_decode.table_bits) - 1)); - if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) - return 0; - bits -= offset_decode.table_bits; - offset_state = ((val >> bits) - & ((1U << offset_decode.table_bits) - 1)); + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= offset_decode.table_bits; + offset_state = ((val >> bits) + & ((1U << offset_decode.table_bits) - 1)); - if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) - return 0; - bits -= match_decode.table_bits; - match_state = ((val >> bits) - & ((1U << match_decode.table_bits) - 1)); + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= match_decode.table_bits; + match_state = ((val >> bits) + & ((1U << match_decode.table_bits) - 1)); + } seq = 0; while (1) @@ -4709,6 +4741,40 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, uint32_t need; uint32_t add; + if (unlikely (seq >= seq_count)) + { + /* Copy remaining literals. */ + if (literal_count > 0 && plit != pout) + { + if (unlikely ((size_t)(poutend - pout) + < literal_count)) + { + elf_uncompress_failed (); + return 0; + } + + if ((size_t)(plit - pout) < literal_count) + { + uint32_t move; + + move = plit - pout; + while (literal_count > move) + { + memcpy (pout, plit, move); + pout += move; + plit += move; + literal_count -= move; + } + } + + memcpy (pout, plit, literal_count); + } + + pout += literal_count; + + break; + } + pt = &offset_decode.table[offset_state]; offset_basebits = pt->basebits; offset_baseline = pt->baseline; @@ -4946,40 +5012,6 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, } } } - - if (unlikely (seq >= seq_count)) - { - /* Copy remaining literals. */ - if (literal_count > 0 && plit != pout) - { - if (unlikely ((size_t)(poutend - pout) - < literal_count)) - { - elf_uncompress_failed (); - return 0; - } - - if ((size_t)(plit - pout) < literal_count) - { - uint32_t move; - - move = plit - pout; - while (literal_count > move) - { - memcpy (pout, plit, move); - pout += move; - plit += move; - literal_count -= move; - } - } - - memcpy (pout, plit, literal_count); - } - - pout += literal_count; - - break; - } } pin = pblockend; @@ -5008,7 +5040,42 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, pin += 4; } - if (pin != pinend) + *ppin = pin; + *ppout = pout; + + return 1; +} + +/* Decompress a zstd stream from PIN/SIN to POUT/SOUT. Code based on RFC 8878. + Return 1 on success, 0 on error. */ + +static int +elf_zstd_decompress (const unsigned char *pin, size_t sin, + unsigned char *zdebug_table, unsigned char *pout, + size_t sout) +{ + const unsigned char *pinend; + + pinend = pin + sin; + + while (sin > 0) + { + const unsigned char *pin_frame; + unsigned char *pout_frame; + + pin_frame = pin; + pout_frame = pout; + if (!elf_zstd_decompress_frame (&pin_frame, pinend, zdebug_table, + &pout_frame, sout)) + return 0; + + sin -= pin_frame - pin; + pin = pin_frame; + sout -= pout_frame - pout; + pout = pout_frame; + } + + if (sout > 0) { elf_uncompress_failed (); return 0; @@ -5890,10 +5957,10 @@ elf_uncompress_lzma_block (const unsigned char *compressed, /* The byte at compressed[off] is ignored for some reason. */ - code = ((compressed[off + 1] << 24) - + (compressed[off + 2] << 16) - + (compressed[off + 3] << 8) - + compressed[off + 4]); + code = (((uint32_t)compressed[off + 1] << 24) + + ((uint32_t)compressed[off + 2] << 16) + + ((uint32_t)compressed[off + 3] << 8) + + (uint32_t)compressed[off + 4]); off += 5; /* This is the main LZMA decode loop. */ @@ -6354,10 +6421,10 @@ elf_uncompress_lzma (struct backtrace_state *state, /* Before that is the size of the index field, which precedes the footer. */ - index_size = (compressed[offset - 4] - | (compressed[offset - 3] << 8) - | (compressed[offset - 2] << 16) - | (compressed[offset - 1] << 24)); + index_size = ((size_t)compressed[offset - 4] + | ((size_t)compressed[offset - 3] << 8) + | ((size_t)compressed[offset - 2] << 16) + | ((size_t)compressed[offset - 1] << 24)); index_size = (index_size + 1) * 4; offset -= 4; @@ -7404,9 +7471,17 @@ phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, ptr->dlpi_addr = info->dlpi_addr; // calculate the end address as well, so we can quickly determine if a PC is within the range of this image - ptr->dlpi_end_addr = uintptr_t(info->dlpi_addr) + (info->dlpi_phnum ? uintptr_t( - info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + - info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz) : 0); + // headers aren't guaranteed to be in address order; find the max + ptr->dlpi_end_addr = ElfW(Addr)(info->dlpi_addr); + for (uint32_t i = 0; i < info->dlpi_phnum; i++) + { + const auto &phdr = info->dlpi_phdr[i]; + if (phdr.p_type != PT_LOAD) + continue; + + const auto phdr_end = ElfW(Addr)(info->dlpi_addr + phdr.p_vaddr + phdr.p_memsz); + ptr->dlpi_end_addr = std::max(phdr_end, ptr->dlpi_end_addr); + } return 0; } @@ -7550,23 +7625,38 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, struct libbacktrace_base_address zero_base_address; memset (&zero_base_address, 0, sizeof zero_base_address); + + /* For external files (not loaded in the current process), pass + exe=0 so that elf_add does not bail out for ET_DYN files. + This allows DWARF data and symbol tables to be loaded directly + from PIE executables and shared libraries with base_address=0, + letting the caller convert runtime addresses to ELF virtual + addresses before lookup. */ + int exe = state->external_file ? 0 : 1; + ret = elf_add (state, filename, descriptor, NULL, 0, zero_base_address, NULL, error_callback, data, &elf_fileline_fn, &found_sym, - &found_dwarf, NULL, 1, 0, NULL, 0); + &found_dwarf, NULL, exe, 0, NULL, 0); if (!ret) return 0; } - pd.state = state; - pd.error_callback = error_callback; - pd.data = data; - pd.fileline_fn = &elf_fileline_fn; - pd.found_sym = &found_sym; - pd.found_dwarf = &found_dwarf; - pd.exe_filename = filename; - pd.exe_descriptor = ret < 0 ? descriptor : -1; + /* For external files, skip dl_iterate_phdr -- the file is not loaded + in the current process, so enumerating the current process's shared + libraries would only add noise. */ + if (!state->external_file) + { + pd.state = state; + pd.error_callback = error_callback; + pd.data = data; + pd.fileline_fn = &elf_fileline_fn; + pd.found_sym = &found_sym; + pd.found_dwarf = &found_dwarf; + pd.exe_filename = filename; + pd.exe_descriptor = ret < 0 ? descriptor : -1; - elf_iterate_phdr_and_add_new_files(&pd); + elf_iterate_phdr_and_add_new_files(&pd); + } if (!state->threaded) { diff --git a/libs/tracy/libbacktrace/internal.hpp b/libs/tracy/libbacktrace/internal.hpp index 2139597..cf806bb 100644 --- a/libs/tracy/libbacktrace/internal.hpp +++ b/libs/tracy/libbacktrace/internal.hpp @@ -166,6 +166,12 @@ struct backtrace_state struct backtrace_freelist_struct *freelist; /* Trigger an known address range refresh */ request_known_address_ranges_refresh request_known_address_ranges_refresh_fn; + /* Non-zero if this state is for an external file, not the current + process. When set, backtrace_initialize will not treat the file + as the main executable (bypassing the ET_DYN deferral to + dl_iterate_phdr) and will not enumerate the current process's + shared libraries. */ + int external_file; }; /* Open a file for reading. Returns -1 on error. If DOES_NOT_EXIST diff --git a/libs/tracy/libbacktrace/state.cpp b/libs/tracy/libbacktrace/state.cpp index ea3c137..af62057 100644 --- a/libs/tracy/libbacktrace/state.cpp +++ b/libs/tracy/libbacktrace/state.cpp @@ -73,4 +73,30 @@ backtrace_create_state (const char *filename, int threaded, return state; } +/* Like backtrace_create_state, but marks the state as being for an + external file. See backtrace.hpp for details. */ + +struct backtrace_state * +backtrace_create_state_for_file (const char *filename, int threaded, + backtrace_error_callback error_callback, + void *data) +{ + /* The state opens the file lazily on first use (fileline_initialize), + so it must own the filename: the caller's buffer may be freed as + soon as this returns. */ + const size_t len = strlen (filename) + 1; + char *copy = (char*)backtrace_alloc (NULL, len, error_callback, data); + if (copy == NULL) + return NULL; + memcpy (copy, filename, len); + + struct backtrace_state *state = + backtrace_create_state (copy, threaded, error_callback, data); + if (state == NULL) + return NULL; + state->external_file = 1; + + return state; +} + } diff --git a/libs/tracy/tracy/Tracy.hpp b/libs/tracy/tracy/Tracy.hpp index 31289b8..08d13ca 100644 --- a/libs/tracy/tracy/Tracy.hpp +++ b/libs/tracy/tracy/Tracy.hpp @@ -16,6 +16,12 @@ # define TracyLine TracyConcat(__LINE__,U) // MSVC Edit and continue __LINE__ is non-constant. See https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665 #endif + +#define TracyParamTypeInt 0 +#define TracyParamTypeBool 1 +#define TracyParamTypeTrigger 2 + + #ifndef TRACY_ENABLE #define TracyNoop @@ -76,14 +82,9 @@ #define TracyAlloc(x,y) #define TracyFree(x) #define TracyMemoryDiscard(x) -#define TracySecureAlloc(x,y) -#define TracySecureFree(x) -#define TracySecureMemoryDiscard(x) #define TracyAllocN(x,y,z) #define TracyFreeN(x,y) -#define TracySecureAllocN(x,y,z) -#define TracySecureFreeN(x,y) #define ZoneNamedS(x,y,z) #define ZoneNamedNS(x,y,z,w) @@ -101,14 +102,9 @@ #define TracyAllocS(x,y,z) #define TracyFreeS(x,y) #define TracyMemoryDiscardS(x,y) -#define TracySecureAllocS(x,y,z) -#define TracySecureFreeS(x,y) -#define TracySecureMemoryDiscardS(x,y) #define TracyAllocNS(x,y,z,w) #define TracyFreeNS(x,y,z) -#define TracySecureAllocNS(x,y,z,w) -#define TracySecureFreeNS(x,y,z) #define TracyMessageS(x,y,z) #define TracyMessageLS(x,y) @@ -119,9 +115,15 @@ #define TracyParameterRegister(x,y) #define TracyParameterSetup(x,y,z,w) #define TracyIsConnected false +#define TracyPort 0 #define TracyIsStarted false #define TracySetProgramName(x) +#define TracySectionEnter(x, ...) 0 +#define TracySectionEnterCategory(x, y, ...) 0 +#define TracySectionLeave(x) +#define TracySectionSetup(x, y, ...) + #define TracyFiberEnter(x) #define TracyFiberEnterHint(x,y) #define TracyFiberLeave @@ -155,19 +157,19 @@ #define SuppressVarShadowWarning(Expr) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ - Expr \ + Expr; \ _Pragma("clang diagnostic pop") -#elif defined(__GNU__) +#elif defined(__GNUC__) #define SuppressVarShadowWarning(Expr) \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wshadow\"") \ - Expr \ + Expr; \ _Pragma("GCC diagnostic pop") #elif defined(_MSC_VER) #define SuppressVarShadowWarning(Expr) \ _Pragma("warning(push)") \ _Pragma("warning(disable : 4456)") \ - Expr \ + Expr; \ _Pragma("warning(pop)") #else #define SuppressVarShadowWarning(Expr) Expr @@ -214,22 +216,19 @@ #define TracyAppInfo( txt, size ) tracy::Profiler::MessageAppInfo( txt, size ) -#define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size, TRACY_CALLSTACK ) -#define TracyMessageL( txt ) tracy::Profiler::Message( txt, TRACY_CALLSTACK ) -#define TracyMessageC( txt, size, color ) tracy::Profiler::MessageColor( txt, size, color, TRACY_CALLSTACK ) -#define TracyMessageLC( txt, color ) tracy::Profiler::MessageColor( txt, color, TRACY_CALLSTACK ) +#define TracyLogString( severity, color, depth, ... ) tracy::Profiler::LogString( tracy::MessageSourceType::User, severity, color, depth, __VA_ARGS__ ) + +#define TracyMessage( txt, size ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, 0, TRACY_CALLSTACK, size, txt ) +#define TracyMessageL( txt ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, 0, TRACY_CALLSTACK, txt ) +#define TracyMessageC( txt, size, color ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, color, TRACY_CALLSTACK, size, txt ) +#define TracyMessageLC( txt, color ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, color, TRACY_CALLSTACK, txt ) -#define TracyAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, false ) -#define TracyFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, false ) -#define TracySecureAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK, true ) -#define TracySecureFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK, true ) +#define TracyAlloc( ptr, size ) tracy::Profiler::MemAllocCallstack( ptr, size, TRACY_CALLSTACK ) +#define TracyFree( ptr ) tracy::Profiler::MemFreeCallstack( ptr, TRACY_CALLSTACK ) -#define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, false, name ) -#define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, false, name ) -#define TracyMemoryDiscard( name ) tracy::Profiler::MemDiscardCallstack( name, false, TRACY_CALLSTACK ) -#define TracySecureAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, true, name ) -#define TracySecureFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, true, name ) -#define TracySecureMemoryDiscard( name ) tracy::Profiler::MemDiscardCallstack( name, true, TRACY_CALLSTACK ) +#define TracyAllocN( ptr, size, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, TRACY_CALLSTACK, name ) +#define TracyFreeN( ptr, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, TRACY_CALLSTACK, name ) +#define TracyMemoryDiscard( name ) tracy::Profiler::MemDiscardCallstack( name, TRACY_CALLSTACK ) #define ZoneNamedS( varname, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { nullptr, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) #define ZoneNamedNS( varname, name, depth, active ) static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location,TracyLine) { name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; tracy::ScopedZone varname( &TracyConcat(__tracy_source_location,TracyLine), depth, active ) @@ -244,28 +243,29 @@ #define ZoneScopedCS( color, depth ) ZoneNamedCS( ___tracy_scoped_zone, color, depth, true ) #define ZoneScopedNCS( name, color, depth ) ZoneNamedNCS( ___tracy_scoped_zone, name, color, depth, true ) -#define TracyAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, false ) -#define TracyFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, false ) -#define TracySecureAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth, true ) -#define TracySecureFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth, true ) +#define TracyAllocS( ptr, size, depth ) tracy::Profiler::MemAllocCallstack( ptr, size, depth ) +#define TracyFreeS( ptr, depth ) tracy::Profiler::MemFreeCallstack( ptr, depth ) -#define TracyAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, false, name ) -#define TracyFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, false, name ) -#define TracyMemoryDiscardS( name, depth ) tracy::Profiler::MemDiscardCallstack( name, false, depth ) -#define TracySecureAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, true, name ) -#define TracySecureFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, true, name ) -#define TracySecureMemoryDiscardS( name, depth ) tracy::Profiler::MemDiscardCallstack( name, true, depth ) +#define TracyAllocNS( ptr, size, depth, name ) tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, name ) +#define TracyFreeNS( ptr, depth, name ) tracy::Profiler::MemFreeCallstackNamed( ptr, depth, name ) +#define TracyMemoryDiscardS( name, depth ) tracy::Profiler::MemDiscardCallstack( name, depth ) -#define TracyMessageS( txt, size, depth ) tracy::Profiler::Message( txt, size, depth ) -#define TracyMessageLS( txt, depth ) tracy::Profiler::Message( txt, depth ) -#define TracyMessageCS( txt, size, color, depth ) tracy::Profiler::MessageColor( txt, size, color, depth ) -#define TracyMessageLCS( txt, color, depth ) tracy::Profiler::MessageColor( txt, color, depth ) +#define TracyMessageS( txt, size, depth ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, 0, depth, size, txt ) +#define TracyMessageLS( txt, depth ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, 0, depth, txt ) +#define TracyMessageCS( txt, size, color, depth ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, color, depth, size, txt ) +#define TracyMessageLCS( txt, color, depth ) tracy::Profiler::LogString( tracy::MessageSourceType::User, tracy::MessageSeverity::Info, color, depth, txt ) #define TracySourceCallbackRegister( cb, data ) tracy::Profiler::SourceCallbackRegister( cb, data ) #define TracyParameterRegister( cb, data ) tracy::Profiler::ParameterRegister( cb, data ) -#define TracyParameterSetup( idx, name, isBool, val ) tracy::Profiler::ParameterSetup( idx, name, isBool, val ) +#define TracyParameterSetup( idx, name, type, val ) tracy::Profiler::ParameterSetup( idx, name, type, val ) #define TracyIsConnected tracy::GetProfiler().IsConnected() -#define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name ); +#define TracyPort tracy::GetProfiler().GetPort() +#define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name ) + +#define TracySectionEnter( fmt, ... ) tracy::Profiler::SectionEnter( 0, fmt, ##__VA_ARGS__ ) +#define TracySectionEnterCategory( category, fmt, ... ) tracy::Profiler::SectionEnter( category, fmt, ##__VA_ARGS__ ) +#define TracySectionLeave( id ) tracy::Profiler::SectionLeave( id ) +#define TracySectionSetup( category, fmt, ... ) tracy::Profiler::SectionSetup( category, fmt, ##__VA_ARGS__ ) #ifdef TRACY_FIBERS # define TracyFiberEnter( fiber ) tracy::Profiler::EnterFiber( fiber, 0 ) diff --git a/libs/tracy/tracy/TracyC.h b/libs/tracy/tracy/TracyC.h index e77c01f..6642e54 100644 --- a/libs/tracy/tracy/TracyC.h +++ b/libs/tracy/tracy/TracyC.h @@ -5,6 +5,7 @@ #include #include "../common/TracyApi.h" +#include "../common/TracyFormat.h" #ifdef __cplusplus extern "C" { @@ -18,6 +19,16 @@ enum TracyPlotFormatEnum TracyPlotFormatWatt }; +enum TracyMessageSeverity +{ + TracyMessageSeverityTrace, // Broadly track variable states and events in the software program. + TracyMessageSeverityDebug, // Describes variable states and details about specific internal events in the software, that are useful for investigations. + TracyMessageSeverityInfo, // Describes normal events, which inform on the expected progress and state of your software. + TracyMessageSeverityWarning, // Describes potentially dangerous situations caused by unexpected events and states. + TracyMessageSeverityError, // Describes the occurance of unexpected behavior. Does not interrupt the execution of the software. + TracyMessageSeverityFatal, // Describes a critical event that will lead to a software failure/crash. +}; + TRACY_API void ___tracy_set_thread_name( const char* name ); #define TracyCSetThreadName( name ) ___tracy_set_thread_name( name ); @@ -39,6 +50,7 @@ TRACY_API void ___tracy_set_thread_name( const char* name ); typedef const void* TracyCZoneCtx; typedef const void* TracyCLockCtx; +typedef const void* TracyCSharedLockCtx; #define TracyCZone(c,x) #define TracyCZoneN(c,x,y) @@ -46,21 +58,18 @@ typedef const void* TracyCLockCtx; #define TracyCZoneNC(c,x,y,z) #define TracyCZoneEnd(c) #define TracyCZoneText(c,x,y) +#define TracyCZoneTextF(c,x,...) #define TracyCZoneName(c,x,y) +#define TracyCZoneNameF(c,x,...) #define TracyCZoneColor(c,x) #define TracyCZoneValue(c,x) #define TracyCAlloc(x,y) #define TracyCFree(x) #define TracyCMemoryDiscard(x) -#define TracyCSecureAlloc(x,y) -#define TracyCSecureFree(x) -#define TracyCSecureMemoryDiscard(x) #define TracyCAllocN(x,y,z) #define TracyCFreeN(x,y) -#define TracyCSecureAllocN(x,y,z) -#define TracyCSecureFreeN(x,y) #define TracyCFrameMark #define TracyCFrameMarkNamed(x) @@ -87,14 +96,9 @@ typedef const void* TracyCLockCtx; #define TracyCAllocS(x,y,z) #define TracyCFreeS(x,y) #define TracyCMemoryDiscardS(x,y) -#define TracyCSecureAllocS(x,y,z) -#define TracyCSecureFreeS(x,y) -#define TracyCSecureMemoryDiscardS(x,y) #define TracyCAllocNS(x,y,z,w) #define TracyCFreeNS(x,y,z) -#define TracyCSecureAllocNS(x,y,z,w) -#define TracyCSecureFreeNS(x,y,z) #define TracyCMessageS(x,y,z) #define TracyCMessageLS(x,y) @@ -111,6 +115,20 @@ typedef const void* TracyCLockCtx; #define TracyCLockMark(l) #define TracyCLockCustomName(l,x,y) +#define TracyCSharedLockCtx(l) +#define TracyCSharedLockAnnonce(l) +#define TracyCSharedLockTerminate(l) +#define TracyCSharedLockBeforeLock(l) +#define TracyCSharedLockAfterLock(l) +#define TracyCSharedLockAfterUnlock(l) +#define TracyCSharedLockAfterTryLock(l,x) +#define TracyCSharedLockBeforeSharedLock(l) +#define TracyCSharedLockAfterSharedLock(l) +#define TracyCSharedLockAfterSharedUnlock(l) +#define TracyCSharedLockAfterTrySharedLock(l,x) +#define TracyCSharedLockMark(l) +#define TracyCSharedLockCustomName(l,x,y) + #define TracyCIsConnected 0 #define TracyCIsStarted 0 @@ -144,6 +162,9 @@ struct ___tracy_c_zone_context { uint32_t id; int32_t active; +#ifdef TRACY_ON_DEMAND + uint64_t connectionId; +#endif }; struct ___tracy_gpu_time_data @@ -197,12 +218,14 @@ struct ___tracy_gpu_time_sync_data { }; struct __tracy_lockable_context_data; +struct __tracy_shared_lockable_context_data; // Some containers don't support storing const types. // This struct, as visible to user, is immutable, so treat it as if const was declared here. typedef /*const*/ struct ___tracy_c_zone_context TracyCZoneCtx; typedef struct __tracy_lockable_context_data* TracyCLockCtx; +typedef struct __tracy_shared_lockable_context_data* TracyCSharedLockCtx; #ifdef TRACY_MANUAL_LIFETIME TRACY_API void ___tracy_startup_profiler(void); @@ -223,7 +246,9 @@ TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc( uint64_t srcloc, int32_t TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc_callstack( uint64_t srcloc, int32_t depth, int32_t active ); TRACY_API void ___tracy_emit_zone_end( TracyCZoneCtx ctx ); TRACY_API void ___tracy_emit_zone_text( TracyCZoneCtx ctx, const char* txt, size_t size ); +TRACY_API void ___tracy_emit_zone_text_fmt( TracyCZoneCtx ctx, const char* fmt, ... ) TRACY_ATTRIBUTE_FORMAT_PRINTF(2, 3); TRACY_API void ___tracy_emit_zone_name( TracyCZoneCtx ctx, const char* txt, size_t size ); +TRACY_API void ___tracy_emit_zone_name_fmt( TracyCZoneCtx ctx, const char* fmt, ... ) TRACY_ATTRIBUTE_FORMAT_PRINTF(2, 3); TRACY_API void ___tracy_emit_zone_color( TracyCZoneCtx ctx, uint32_t color ); TRACY_API void ___tracy_emit_zone_value( TracyCZoneCtx ctx, uint64_t value ); @@ -263,43 +288,38 @@ TRACY_API int32_t ___tracy_connected(void); #define TracyCZoneEnd( ctx ) ___tracy_emit_zone_end( ctx ); #define TracyCZoneText( ctx, txt, size ) ___tracy_emit_zone_text( ctx, txt, size ); +#define TracyCZoneTextF( ctx, fmt, ... ) ___tracy_emit_zone_text_fmt( ctx, fmt, ##__VA_ARGS__ ); #define TracyCZoneName( ctx, txt, size ) ___tracy_emit_zone_name( ctx, txt, size ); +#define TracyCZoneNameF( ctx, fmt, ... ) ___tracy_emit_zone_name_fmt( ctx, fmt, ##__VA_ARGS__ ); #define TracyCZoneColor( ctx, color ) ___tracy_emit_zone_color( ctx, color ); #define TracyCZoneValue( ctx, value ) ___tracy_emit_zone_value( ctx, value ); -TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size, int32_t secure ); -TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int32_t depth, int32_t secure ); -TRACY_API void ___tracy_emit_memory_free( const void* ptr, int32_t secure ); -TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int32_t depth, int32_t secure ); -TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, int32_t secure, const char* name ); -TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int32_t depth, int32_t secure, const char* name ); -TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, int32_t secure, const char* name ); -TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int32_t depth, int32_t secure, const char* name ); -TRACY_API void ___tracy_emit_memory_discard( const char* name, int32_t secure ); -TRACY_API void ___tracy_emit_memory_discard_callstack( const char* name, int32_t secure, int32_t depth ); - -TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int32_t callstack_depth ); -TRACY_API void ___tracy_emit_messageL( const char* txt, int32_t callstack_depth ); -TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int32_t callstack_depth ); -TRACY_API void ___tracy_emit_messageLC( const char* txt, uint32_t color, int32_t callstack_depth ); - -#define TracyCAlloc( ptr, size ) ___tracy_emit_memory_alloc_callstack( ptr, size, TRACY_CALLSTACK, 0 ) -#define TracyCFree( ptr ) ___tracy_emit_memory_free_callstack( ptr, TRACY_CALLSTACK, 0 ) -#define TracyCMemoryDiscard( name ) ___tracy_emit_memory_discard_callstack( name, 0, TRACY_CALLSTACK ); -#define TracyCSecureAlloc( ptr, size ) ___tracy_emit_memory_alloc_callstack( ptr, size, TRACY_CALLSTACK, 1 ) -#define TracyCSecureFree( ptr ) ___tracy_emit_memory_free_callstack( ptr, TRACY_CALLSTACK, 1 ) -#define TracyCSecureMemoryDiscard( name ) ___tracy_emit_memory_discard_callstack( name, 1, TRACY_CALLSTACK ); - -#define TracyCAllocN( ptr, size, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, TRACY_CALLSTACK, 0, name ) -#define TracyCFreeN( ptr, name ) ___tracy_emit_memory_free_callstack_named( ptr, TRACY_CALLSTACK, 0, name ) -#define TracyCSecureAllocN( ptr, size, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, TRACY_CALLSTACK, 1, name ) -#define TracyCSecureFreeN( ptr, name ) ___tracy_emit_memory_free_callstack_named( ptr, TRACY_CALLSTACK, 1, name ) - -#define TracyCMessage( txt, size ) ___tracy_emit_message( txt, size, TRACY_CALLSTACK ); -#define TracyCMessageL( txt ) ___tracy_emit_messageL( txt, TRACY_CALLSTACK ); -#define TracyCMessageC( txt, size, color ) ___tracy_emit_messageC( txt, size, color, TRACY_CALLSTACK ); -#define TracyCMessageLC( txt, color ) ___tracy_emit_messageLC( txt, color, TRACY_CALLSTACK ); +TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size ); +TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int32_t depth ); +TRACY_API void ___tracy_emit_memory_free( const void* ptr ); +TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int32_t depth ); +TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, const char* name ); +TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int32_t depth, const char* name ); +TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, const char* name ); +TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int32_t depth, const char* name ); +TRACY_API void ___tracy_emit_memory_discard( const char* name ); +TRACY_API void ___tracy_emit_memory_discard_callstack( const char* name, int32_t depth ); + +TRACY_API void ___tracy_emit_logString( int8_t severity, int32_t color, int32_t callstack_depth, size_t size, const char* txt ); +TRACY_API void ___tracy_emit_logStringL( int8_t severity, int32_t color, int32_t callstack_depth, const char* txt ); + +#define TracyCAlloc( ptr, size ) ___tracy_emit_memory_alloc_callstack( ptr, size, TRACY_CALLSTACK ) +#define TracyCFree( ptr ) ___tracy_emit_memory_free_callstack( ptr, TRACY_CALLSTACK ) +#define TracyCMemoryDiscard( name ) ___tracy_emit_memory_discard_callstack( name, TRACY_CALLSTACK ); + +#define TracyCAllocN( ptr, size, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, TRACY_CALLSTACK, name ) +#define TracyCFreeN( ptr, name ) ___tracy_emit_memory_free_callstack_named( ptr, TRACY_CALLSTACK, name ) + +#define TracyCMessage( txt, size ) ___tracy_emit_logString( TracyMessageSeverityInfo, 0, TRACY_CALLSTACK, size, txt ) +#define TracyCMessageL( txt ) ___tracy_emit_logStringL( TracyMessageSeverityInfo, 0, TRACY_CALLSTACK, txt ) +#define TracyCMessageC( txt, size, color ) ___tracy_emit_logString( TracyMessageSeverityInfo, color, TRACY_CALLSTACK, size, txt ) +#define TracyCMessageLC( txt, color ) ___tracy_emit_logStringL( TracyMessageSeverityInfo, color, TRACY_CALLSTACK, txt ) TRACY_API void ___tracy_emit_frame_mark( const char* name ); @@ -332,22 +352,17 @@ TRACY_API void ___tracy_emit_message_appinfo( const char* txt, size_t size ); #define TracyCZoneCS( ctx, color, depth, active ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, color }; TracyCZoneCtx ctx = ___tracy_emit_zone_begin_callstack( &TracyConcat(__tracy_source_location,TracyLine), depth, active ); #define TracyCZoneNCS( ctx, name, color, depth, active ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { name, __func__, TracyFile, (uint32_t)TracyLine, color }; TracyCZoneCtx ctx = ___tracy_emit_zone_begin_callstack( &TracyConcat(__tracy_source_location,TracyLine), depth, active ); -#define TracyCAllocS( ptr, size, depth ) ___tracy_emit_memory_alloc_callstack( ptr, size, depth, 0 ) -#define TracyCFreeS( ptr, depth ) ___tracy_emit_memory_free_callstack( ptr, depth, 0 ) -#define TracyCMemoryDiscardS( name, depth ) ___tracy_emit_memory_discard_callstack( name, 0, depth ) -#define TracyCSecureAllocS( ptr, size, depth ) ___tracy_emit_memory_alloc_callstack( ptr, size, depth, 1 ) -#define TracyCSecureFreeS( ptr, depth ) ___tracy_emit_memory_free_callstack( ptr, depth, 1 ) -#define TracyCSecureMemoryDiscardS( name, depth ) ___tracy_emit_memory_discard_callstack( name, 1, depth ) +#define TracyCAllocS( ptr, size, depth ) ___tracy_emit_memory_alloc_callstack( ptr, size, depth ) +#define TracyCFreeS( ptr, depth ) ___tracy_emit_memory_free_callstack( ptr, depth ) +#define TracyCMemoryDiscardS( name, depth ) ___tracy_emit_memory_discard_callstack( name, depth ) -#define TracyCAllocNS( ptr, size, depth, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, depth, 0, name ) -#define TracyCFreeNS( ptr, depth, name ) ___tracy_emit_memory_free_callstack_named( ptr, depth, 0, name ) -#define TracyCSecureAllocNS( ptr, size, depth, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, depth, 1, name ) -#define TracyCSecureFreeNS( ptr, depth, name ) ___tracy_emit_memory_free_callstack_named( ptr, depth, 1, name ) +#define TracyCAllocNS( ptr, size, depth, name ) ___tracy_emit_memory_alloc_callstack_named( ptr, size, depth, name ) +#define TracyCFreeNS( ptr, depth, name ) ___tracy_emit_memory_free_callstack_named( ptr, depth, name ) -#define TracyCMessageS( txt, size, depth ) ___tracy_emit_message( txt, size, depth ); -#define TracyCMessageLS( txt, depth ) ___tracy_emit_messageL( txt, depth ); -#define TracyCMessageCS( txt, size, color, depth ) ___tracy_emit_messageC( txt, size, color, depth ); -#define TracyCMessageLCS( txt, color, depth ) ___tracy_emit_messageLC( txt, color, depth ); +#define TracyCMessageS( txt, size, depth ) ___tracy_emit_logString( TracyMessageSeverityInfo, 0, depth, size, txt ) +#define TracyCMessageLS( txt, depth ) ___tracy_emit_logStringL( TracyMessageSeverityInfo, 0, depth, txt ) +#define TracyCMessageCS( txt, size, color, depth ) ___tracy_emit_logString( TracyMessageSeverityInfo, color, depth, size, txt ) +#define TracyCMessageLCS( txt, color, depth ) ___tracy_emit_logStringL( TracyMessageSeverityInfo, color, depth, txt ) TRACY_API struct __tracy_lockable_context_data* ___tracy_announce_lockable_ctx( const struct ___tracy_source_location_data* srcloc ); @@ -359,6 +374,20 @@ TRACY_API void ___tracy_after_try_lock_lockable_ctx( struct __tracy_lockable_con TRACY_API void ___tracy_mark_lockable_ctx( struct __tracy_lockable_context_data* lockdata, const struct ___tracy_source_location_data* srcloc ); TRACY_API void ___tracy_custom_name_lockable_ctx( struct __tracy_lockable_context_data* lockdata, const char* name, size_t nameSz ); +TRACY_API struct __tracy_shared_lockable_context_data* ___tracy_announce_shared_lockable_ctx( const struct ___tracy_source_location_data* srcloc ); +TRACY_API void ___tracy_terminate_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API int32_t ___tracy_before_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_unlock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_try_lock_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired ); +TRACY_API int32_t ___tracy_before_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_unlock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata ); +TRACY_API void ___tracy_after_try_lock_shared_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, int32_t acquired ); +TRACY_API void ___tracy_mark_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, const struct ___tracy_source_location_data* srcloc ); +TRACY_API void ___tracy_custom_name_shared_lockable_ctx( struct __tracy_shared_lockable_context_data* lockdata, const char* name, size_t nameSz ); + + #define TracyCLockAnnounce( lock ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, 0 }; lock = ___tracy_announce_lockable_ctx( &TracyConcat(__tracy_source_location,TracyLine) ); #define TracyCLockTerminate( lock ) ___tracy_terminate_lockable_ctx( lock ); #define TracyCLockBeforeLock( lock ) ___tracy_before_lock_lockable_ctx( lock ); @@ -368,10 +397,23 @@ TRACY_API void ___tracy_custom_name_lockable_ctx( struct __tracy_lockable_contex #define TracyCLockMark( lock ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, 0 }; ___tracy_mark_lockable_ctx( lock, &TracyConcat(__tracy_source_location,TracyLine) ); #define TracyCLockCustomName( lock, name, nameSz ) ___tracy_custom_name_lockable_ctx( lock, name, nameSz ); +#define TracyCSharedLockAnnounce( lock ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, 0 }; lock = ___tracy_announce_shared_lockable_ctx( &TracyConcat(__tracy_source_location,TracyLine) ); +#define TracyCSharedLockTerminate( lock ) ___tracy_terminate_shared_lockable_ctx( lock ); +#define TracyCSharedLockBeforeLock( lock ) ___tracy_before_lock_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterLock( lock ) ___tracy_after_lock_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterUnlock( lock ) ___tracy_after_unlock_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterTryLock( lock, acquired ) ___tracy_after_try_lock_shared_lockable_ctx( lock, acquired ); +#define TracyCSharedLockBeforeSharedLock( lock ) ___tracy_before_lock_shared_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterSharedLock( lock ) ___tracy_after_lock_shared_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterSharedUnlock( lock ) ___tracy_after_unlock_shared_shared_lockable_ctx( lock ); +#define TracyCSharedLockAfterTrySharedLock( lock, acquired ) ___tracy_after_try_lock_shared_shared_lockable_ctx( lock, acquired ); +#define TracyCSharedLockMark( lock ) static const struct ___tracy_source_location_data TracyConcat(__tracy_source_location,TracyLine) = { NULL, __func__, TracyFile, (uint32_t)TracyLine, 0 }; ___tracy_mark_shared_lockable_ctx( lock, &TracyConcat(__tracy_source_location,TracyLine) ); +#define TracyCSharedLockCustomName( lock, name, nameSz ) ___tracy_custom_name_shared_lockable_ctx( lock, name, nameSz ); + #define TracyCIsConnected ___tracy_connected() -TRACY_API int ___tracy_begin_sampling_profiler( void ); -TRACY_API void ___tracy_end_sampling_profiler( void ); +TRACY_API int ___tracy_begin_sampling_profiling( void ); +TRACY_API void ___tracy_end_sampling_profiling( void ); #define TracyCBeginSamplingProfiling() ___tracy_begin_sampling_profiling() #define TracyCEndSamplingProfiling() ___tracy_end_sampling_profiling() @@ -384,6 +426,8 @@ TRACY_API void ___tracy_fiber_leave( void ); # define TracyCFiberLeave ___tracy_fiber_leave(); #endif +TRACY_API int64_t ___tracy_get_time( void ); + #endif #ifdef __cplusplus diff --git a/libs/tracy/tracy/TracyCUDA.hpp b/libs/tracy/tracy/TracyCUDA.hpp index 40ff55d..4de959b 100644 --- a/libs/tracy/tracy/TracyCUDA.hpp +++ b/libs/tracy/tracy/TracyCUDA.hpp @@ -12,12 +12,20 @@ #define TracyCUDACollect(ctx) +namespace tracy{ +using CUDACtx = std::nullptr_t; +} // namespace tracy + #else #include -#include +#if CUDA_VERSION < 12040 +#error "CUDA v12.4 (or later) is required by TracyCUDA.hpp" +#endif + #include #include +#include #include #include #include @@ -31,7 +39,8 @@ #include #endif -#include +#include "../common/TracyAssert.hpp" +#include "Tracy.hpp" #ifndef UNREFERENCED #define UNREFERENCED(x) (void)x @@ -112,7 +121,7 @@ TracyTimestamp tracyFromCUpti(CUptiTimestamp cuptiTime) { auto [slope, intercept] = getCachedRegressionParameters(); double y_hat = slope * cuptiTime + intercept; TracyTimestamp tracyTime = TracyTimestamp(y_hat); - assert(tracyTime >= 0); + TRACY_ASSERT(tracyTime >= 0); return tracyTime; } @@ -201,7 +210,7 @@ void tracyEmitMemAlloc(const char* name, const void* ptr, size_t size, TracyTime } else { - assert(sizeof(size) == 8); + TRACY_ASSERT(sizeof(size) == 8); memcpy(&item->memAlloc.size, &size, 4); memcpy(((char *)&item->memAlloc.size) + 4, ((char *)&size) + 4, 2); } @@ -285,7 +294,7 @@ CUptiResult CUptiCallChecked(CUptiResult result, const char* call, const char* f const char* resultMsg = ""; CUPTI_API_CALL(cuptiGetResultString(result, &resultMsg)); // maybe not a good idea to recurse here... fprintf(stderr, "ERROR:\t%s:%d:\n\tfunction '%s' failed with error '%s'.\n", file, line, call, resultMsg); - //assert(result == CUPTI_SUCCESS); + //TRACY_ASSERT(result == CUPTI_SUCCESS); return result; } @@ -295,7 +304,7 @@ CUresult cudaDriverCallChecked(CUresult result, const char* call, const char* fi const char* resultMsg = ""; DRIVER_API_CALL(cuGetErrorString(result, &resultMsg)); // maybe not a good idea to recurse here... fprintf(stderr, "ERROR:\t%s:%d:\n\tfunction '%s' failed with error '%s'.\n", file, line, call, resultMsg); - //assert(result == CUDA_SUCCESS); + //TRACY_ASSERT(result == CUDA_SUCCESS); return result; } @@ -333,6 +342,7 @@ struct ConcurrentHashMap { } auto fetch(TKey key, TValue& value) { ZoneNamed(fetch, instrument); + auto lock = acquire_read_lock(); auto it = mapping.find(key); if (it != mapping.end()) { value = it->second; @@ -356,6 +366,11 @@ struct ConcurrentHashMap { auto lock = acquire_write_lock(); return mapping.erase(key); } + auto insert_or_assign(TKey key, TValue value) { + ZoneNamed(insert_or_assign, instrument); + auto lock = acquire_write_lock(); + return mapping.insert_or_assign(std::move(key), std::move(value)); + } }; #if TRACY_CUDA_ENABLE_CUDA_CALL_STATS @@ -391,7 +406,7 @@ struct StringTable { if (!table.fetch(str, memoized)) { ZoneNamedN(lookup, "StringTable::insert", instrument); char* copy = (char*)tracyMalloc(str.size() + 1); - strncpy(copy, str.data(), str.size()); + memcpy(copy, str.data(), str.size()); copy[str.size()] = '\0'; std::string_view value (copy, str.size()); auto [it, inserted] = table.emplace(value, value); @@ -401,7 +416,7 @@ struct StringTable { } memoized = it->second; } - assert(str == memoized); + TRACY_ASSERT(str == memoized); return memoized; } }; @@ -424,8 +439,8 @@ struct SourceLocationMap { tracy::SourceLocationData* add(std::string_view function, std::string_view file, int line, uint32_t color=0) { ZoneNamed(emplace, instrument); - assert(*function.end() == '\0'); - assert(*file.end() == '\0'); + TRACY_ASSERT(*function.end() == '\0'); + TRACY_ASSERT(*file.end() == '\0'); void* bytes = tracyMalloc(sizeof(tracy::SourceLocationData)); auto pSrcLoc = new(bytes)tracy::SourceLocationData{ function.data(), TracyFunction, file.data(), (uint32_t)line, color }; auto [it, inserted] = locations.emplace(function, pSrcLoc); @@ -433,7 +448,7 @@ struct SourceLocationMap { // another thread inserted it while we were trying to: cleanup tracyFree(pSrcLoc); // POD: no destructor to call } - assert(it->second != nullptr); + TRACY_ASSERT(it->second != nullptr); return it->second; } }; @@ -476,8 +491,8 @@ struct SourceLocationLUT { uint32_t tracyTimelineId(uint32_t contextId, uint32_t streamId) { // 0xA7C5 = 42,949 => 42,949 * 100,000 = 4,294,900,000 // 4,294,900,000 + 65,535 = 4,294,965,535 < 4,294,967,295 (max uint32) - assert(contextId <= 0xA7C5); - assert((streamId == CUPTI_INVALID_STREAM_ID) || (streamId < 0xFFFF)); + TRACY_ASSERT(contextId <= 0xA7C5); + TRACY_ASSERT((streamId == CUPTI_INVALID_STREAM_ID) || (streamId < 0xFFFF)); uint32_t packed = (contextId * 100'000) + (streamId & 0x0000'FFFF); return packed; } @@ -493,7 +508,7 @@ namespace tracy auto& s = Singleton::Get(); std::unique_lock lock (s.m); if (s.ref_count == 0) { - assert(s.ctx == nullptr); + TRACY_ASSERT(s.ctx == nullptr); s.ctx = new CUDACtx(s.ctx_id); s.ref_count += 1; s.ctx_id = s.ctx->m_tracyGpuContext; @@ -504,7 +519,7 @@ namespace tracy static void Destroy(CUDACtx* ctx) { auto& s = Singleton::Get(); std::unique_lock lock(s.m); - assert(ctx == s.ctx); + TRACY_ASSERT(ctx == s.ctx); s.ref_count -= 1; if (s.ref_count == 0) { delete s.ctx; @@ -561,7 +576,7 @@ namespace tracy auto item = Profiler::QueueSerial(); tracyMemWrite(item->hdr.type, QueueType::GpuContextName); - tracyMemWrite(item->gpuContextNameFat.context, m_tracyGpuContext); + tracyMemWrite(item->gpuContextNameFat.context, (uint8_t)m_tracyGpuContext); tracyMemWrite(item->gpuContextNameFat.ptr, (uint64_t)ptr); tracyMemWrite(item->gpuContextNameFat.size, len); SubmitQueueItem(item); @@ -608,7 +623,7 @@ namespace tracy tracyMemWrite(item->gpuCalibration.gpuTime, (int64_t)tCUpti); tracyMemWrite(item->gpuCalibration.cpuTime, tTracy); tracyMemWrite(item->gpuCalibration.cpuDelta, deltaTicksCUpti); - tracyMemWrite(item->gpuCalibration.context, m_tracyGpuContext); + tracyMemWrite(item->gpuCalibration.context, (uint8_t)m_tracyGpuContext); Profiler::QueueSerialFinish(); } #endif @@ -629,8 +644,8 @@ namespace tracy //uint32_t timelineId = tracy::GetThreadHandle(); uint32_t timelineId = tracyTimelineId(cudaContextId, cudaStreamId); uint16_t queryId = m_queryIdGen.fetch_add(2); - tracyAnnounceGpuTimestamp(apiStart, apiEnd, queryId, m_tracyGpuContext, pSrcLoc, timelineId); - tracySubmitGpuTimestamp(gpuStart, gpuEnd, queryId, m_tracyGpuContext); + tracyAnnounceGpuTimestamp(apiStart, apiEnd, queryId, (uint8_t)m_tracyGpuContext, pSrcLoc, timelineId); + tracySubmitGpuTimestamp(gpuStart, gpuEnd, queryId, (uint8_t)m_tracyGpuContext); } void OnEventsProcessed() { @@ -638,6 +653,8 @@ namespace tracy } struct CUPTI { + using GraphID = uint32_t; + static void CUPTIAPI OnBufferRequested(uint8_t **buffer, size_t *size, size_t *maxNumRecords) { ZoneScoped; @@ -646,19 +663,44 @@ namespace tracy // should return as quickly as possible from these callbacks." *size = 1 * 1024*1024; // 1MB *buffer = (uint8_t*)tracyMalloc(*size); - assert(*buffer != nullptr); + TRACY_ASSERT(*buffer != nullptr); FlushActivityAsync(); } + // Returns the graphId field from activity record kinds that carry one, + // or 0 for records that are not graph-launched or don't have the field. + static GraphID getGraphIdFromRecord(const CUpti_Activity* record) { + switch (record->kind) { + case CUPTI_ACTIVITY_KIND_CONCURRENT_KERNEL: + return reinterpret_cast(record)->graphId; + case CUPTI_ACTIVITY_KIND_MEMCPY: + return reinterpret_cast(record)->graphId; + case CUPTI_ACTIVITY_KIND_MEMSET: + return reinterpret_cast(record)->graphId; + default: + return 0; + } + } + static void CUPTIAPI OnBufferCompleted(CUcontext ctx, uint32_t streamId, uint8_t* buffer, size_t size, size_t validSize) { // CUDA 6.0 onwards: all buffers from this callback are "global" buffers // (i.e. there is no context/stream specific buffer; ctx is always NULL) ZoneScoped; tracy::SetThreadName("NVIDIA CUPTI Worker"); + // Check if any graph execs are pending retirement before entering + // the record loop — avoids the graphIdsSeenInBuffer heap allocation + // on the hot path when no execs have been destroyed. + // Uses an atomic flag so we skip the mutex entirely in the common case. + bool trackRetirement = PersistentState::Get().graphRetirePending.load(std::memory_order_acquire); CUptiResult status; CUpti_Activity* record = nullptr; + std::unordered_set graphIdsSeenInBuffer; while ((status = cuptiActivityGetNextRecord(buffer, validSize, &record)) == CUPTI_SUCCESS) { + if (trackRetirement) { + GraphID gId = getGraphIdFromRecord(record); + if (gId != 0) graphIdsSeenInBuffer.insert(gId); + } DoProcessDeviceEvent(record); } if (status != CUPTI_ERROR_MAX_LIMIT_REACHED) { @@ -666,8 +708,33 @@ namespace tracy } size_t dropped = 0; CUPTI_API_CALL(cuptiActivityGetNumDroppedRecords(ctx, streamId, &dropped)); - assert(dropped == 0); + TRACY_ASSERT(dropped == 0); tracyFree(buffer); + + // Retire cudaGraphCurrentLaunch entries for destroyed exec handles. + // We defer erasure until a buffer arrives that contains no records + // from the exec: if the graphId still appears here, more records may + // be in flight. Once a full buffer passes with no records for that + // graphId, all in-flight activity for that exec has been delivered. + // Note: holds graphRetireMutex then acquires cudaGraphCurrentLaunch's + // internal shared_mutex — lock order is always outer→inner, no deadlock. + if (trackRetirement) { + auto& state = PersistentState::Get(); + std::lock_guard lock(state.graphRetireMutex); + for (auto it = state.graphExecPendingRetire.begin(); + it != state.graphExecPendingRetire.end(); ) { + if (graphIdsSeenInBuffer.count(*it) == 0) { + state.cudaGraphCurrentLaunch.erase(*it); + it = state.graphExecPendingRetire.erase(it); + } else { + ++it; + } + } + if (state.graphExecPendingRetire.empty()) { + state.graphRetirePending.store(false, std::memory_order_release); + } + } + PersistentState::Get().profilerHost->OnEventsProcessed(); } @@ -758,9 +825,10 @@ namespace tracy { CUPTI_RUNTIME_TRACE_CBID_cudaLaunchKernel_ptsz_v7000, GET_STREAM_FUNC(cudaLaunchKernel_ptsz_v7000_params, stream) }, { CUPTI_RUNTIME_TRACE_CBID_cudaLaunchKernelExC_v11060, GET_STREAM_FUNC(cudaLaunchKernelExC_v11060_params, config->stream) }, { CUPTI_RUNTIME_TRACE_CBID_cudaLaunchKernelExC_ptsz_v11060, GET_STREAM_FUNC(cudaLaunchKernelExC_ptsz_v11060_params, config->stream) }, - // Runtime: Memory - { CUPTI_RUNTIME_TRACE_CBID_cudaMalloc_v3020, NON_STREAM_FUNC() }, - { CUPTI_RUNTIME_TRACE_CBID_cudaFree_v3020, NON_STREAM_FUNC() }, + // Runtime: Memory — NOT tracked here. The MEMORY2 handler + // only needs address/size/timestamp from the activity record + // and never calls EmitGpuZone, so there is no consumer for + // the cudaCallSiteInfo entry. Tracking them would leak entries. // Runtime: Memcpy { CUPTI_RUNTIME_TRACE_CBID_cudaMemcpy_v3020, NON_STREAM_FUNC() }, { CUPTI_RUNTIME_TRACE_CBID_cudaMemcpyAsync_v3020, GET_STREAM_FUNC(cudaMemcpyAsync_v3020_params, stream) }, @@ -773,6 +841,10 @@ namespace tracy { CUPTI_RUNTIME_TRACE_CBID_cudaEventQuery_v3020, NON_STREAM_FUNC() }, { CUPTI_RUNTIME_TRACE_CBID_cudaStreamWaitEvent_v3020, NON_STREAM_FUNC() }, { CUPTI_RUNTIME_TRACE_CBID_cudaDeviceSynchronize_v3020, NON_STREAM_FUNC() }, + // Graph launch: tracked so all CONCURRENT_KERNEL/MEMCPY/MEMSET activities + // sharing the launch's correlationId can be correlated back to this call site. + { CUPTI_RUNTIME_TRACE_CBID_cudaGraphLaunch_v10000, GET_STREAM_FUNC(cudaGraphLaunch_v10000_params, stream) }, + { CUPTI_RUNTIME_TRACE_CBID_cudaGraphLaunch_ptsz_v10000, GET_STREAM_FUNC(cudaGraphLaunch_v10000_params, stream) }, }; #undef NON_STREAM_FUNC #undef GET_STREAM_FUNC @@ -791,6 +863,44 @@ namespace tracy { CUPTI_DRIVER_TRACE_CBID_cuLaunchKernel_ptsz, GET_STREAM_FUNC(cuLaunchKernel_ptsz_params, hStream)} , { CUPTI_DRIVER_TRACE_CBID_cuLaunchKernelEx, GET_STREAM_FUNC(cuLaunchKernelEx_params, config->hStream) }, { CUPTI_DRIVER_TRACE_CBID_cuLaunchKernelEx_ptsz, GET_STREAM_FUNC(cuLaunchKernelEx_params, config->hStream) }, + // Driver: Memory — NOT tracked (see Runtime: Memory comment above). + // Driver: Memcpy - Synchronous + { CUPTI_DRIVER_TRACE_CBID_cuMemcpy, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyHtoD_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyDtoH_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyDtoD_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpy2D_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpy3D_v2, NON_STREAM_FUNC() }, + // Driver: Memcpy - Asynchronous + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyAsync, GET_STREAM_FUNC(cuMemcpyAsync_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyHtoDAsync_v2, GET_STREAM_FUNC(cuMemcpyHtoDAsync_v2_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyDtoHAsync_v2, GET_STREAM_FUNC(cuMemcpyDtoHAsync_v2_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpyDtoDAsync_v2, GET_STREAM_FUNC(cuMemcpyDtoDAsync_v2_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpy2DAsync_v2, GET_STREAM_FUNC(cuMemcpy2DAsync_v2_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemcpy3DAsync_v2, GET_STREAM_FUNC(cuMemcpy3DAsync_v2_params, hStream) }, + // Driver: Memset - Synchronous + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD8_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD16_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD32_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D8_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D16_v2, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D32_v2, NON_STREAM_FUNC() }, + // Driver: Memset - Asynchronous + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD8Async, GET_STREAM_FUNC(cuMemsetD8Async_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD16Async, GET_STREAM_FUNC(cuMemsetD16Async_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD32Async, GET_STREAM_FUNC(cuMemsetD32Async_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D8Async, GET_STREAM_FUNC(cuMemsetD2D8Async_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D16Async, GET_STREAM_FUNC(cuMemsetD2D16Async_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuMemsetD2D32Async, GET_STREAM_FUNC(cuMemsetD2D32Async_params, hStream) }, + // Driver: Synchronization + { CUPTI_DRIVER_TRACE_CBID_cuStreamSynchronize, GET_STREAM_FUNC(cuStreamSynchronize_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuEventSynchronize, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuCtxSynchronize, NON_STREAM_FUNC() }, + { CUPTI_DRIVER_TRACE_CBID_cuStreamWaitEvent, GET_STREAM_FUNC(cuStreamWaitEvent_params, hStream) }, + // Graph launch: tracked so all CONCURRENT_KERNEL/MEMCPY/MEMSET activities + // sharing the launch's correlationId can be correlated back to this call site. + { CUPTI_DRIVER_TRACE_CBID_cuGraphLaunch, GET_STREAM_FUNC(cuGraphLaunch_params, hStream) }, + { CUPTI_DRIVER_TRACE_CBID_cuGraphLaunch_ptsz, GET_STREAM_FUNC(cuGraphLaunch_params, hStream) }, }; #undef NON_STREAM_FUNC #undef GET_STREAM_FUNC @@ -814,8 +924,33 @@ namespace tracy cudaCallSiteInfo.emplace(apiInfo->correlationId, APICallInfo{ apiCallStartTime, apiCallStartTime, tgpu, profilerHost }); } auto& entryFlags = *apiInfo->correlationData; - assert(entryFlags == 0); + TRACY_ASSERT(entryFlags == 0); entryFlags |= trackDeviceActivity ? 0x8000 : 0; + + // On graph exec destruction, record the graphId for deferred + // retirement of its cudaGraphCurrentLaunch cache entry. + // cuptiGetGraphExecId must be called here (ENTER), while the + // handle is still valid — at EXIT it has already been freed. + // Actual erasure is deferred to OnBufferCompleted so we don't + // race with CUPTI activity records that are still in-flight. + { + GraphID retireGraphId = 0; + if (domain == CUPTI_CB_DOMAIN_RUNTIME_API && + cbid == CUPTI_RUNTIME_TRACE_CBID_cudaGraphExecDestroy_v10000) { + auto* p = (cudaGraphExecDestroy_v10000_params*)apiInfo->functionParams; + CUPTI_API_CALL(cuptiGetGraphExecId((CUgraphExec)p->graphExec, &retireGraphId)); + } else if (domain == CUPTI_CB_DOMAIN_DRIVER_API && + cbid == CUPTI_DRIVER_TRACE_CBID_cuGraphExecDestroy) { + auto* p = (cuGraphExecDestroy_params*)apiInfo->functionParams; + CUPTI_API_CALL(cuptiGetGraphExecId(p->hGraphExec, &retireGraphId)); + } + if (retireGraphId != 0) { + auto& state = PersistentState::Get(); + std::lock_guard lock(state.graphRetireMutex); + state.graphExecPendingRetire.insert(retireGraphId); + state.graphRetirePending.store(true, std::memory_order_release); + } + } } if (apiInfo->callbackSite == CUPTI_API_EXIT) { @@ -849,7 +984,24 @@ namespace tracy return false; } cudaCallSiteInfo.erase(correlationId); - assert(apiCallInfo.host != nullptr); + TRACY_ASSERT(apiCallInfo.host != nullptr); + return true; + } + + // Like matchActivityToAPICall, but also handles graph-launched activities. + // All activities in one cuGraphLaunch share the launch's correlationId, so + // the first activity consumes the cudaCallSiteInfo entry and caches it by + // graphId; subsequent activities from the same launch find it via graphId. + static bool matchGraphActivityToAPICall(uint32_t correlationId, GraphID graphId, + APICallInfo& apiCallInfo) { + auto& graphLaunchCache = PersistentState::Get().cudaGraphCurrentLaunch; + if (!matchActivityToAPICall(correlationId, apiCallInfo)) { + if (graphId == 0 || !graphLaunchCache.fetch(graphId, apiCallInfo)) { + return false; + } + } else if (graphId != 0) { + graphLaunchCache.insert_or_assign(graphId, apiCallInfo); + } return true; } @@ -955,7 +1107,7 @@ namespace tracy ZoneNamedN(kernel, "tracy::CUDACtx::DoProcessDeviceEvent[kernel]", instrument); CUpti_ActivityKernel9* kernel9 = (CUpti_ActivityKernel9*) record; APICallInfo apiCall; - if (!matchActivityToAPICall(kernel9->correlationId, apiCall)) { + if (!matchGraphActivityToAPICall(kernel9->correlationId, kernel9->graphId, apiCall)) { return matchError(kernel9->correlationId, "KERNEL"); } apiCall.host->EmitGpuZone(apiCall.start, apiCall.end, kernel9->start, kernel9->end, getKernelSourceLocation(kernel9->name), kernel9->contextId, kernel9->streamId); @@ -969,7 +1121,7 @@ namespace tracy ZoneNamedN(kernel, "tracy::CUDACtx::DoProcessDeviceEvent[memcpy]", instrument); CUpti_ActivityMemcpy5* memcpy5 = (CUpti_ActivityMemcpy5*) record; APICallInfo apiCall; - if (!matchActivityToAPICall(memcpy5->correlationId, apiCall)) { + if (!matchGraphActivityToAPICall(memcpy5->correlationId, memcpy5->graphId, apiCall)) { return matchError(memcpy5->correlationId, "MEMCPY"); } static constexpr tracy::SourceLocationData TracyCUPTISrcLocDeviceMemcpy { "CUDA::memcpy", TracyFunction, TracyFile, (uint32_t)TracyLine, tracy::Color::Blue }; @@ -985,7 +1137,7 @@ namespace tracy ZoneNamedN(kernel, "tracy::CUDACtx::DoProcessDeviceEvent[memset]", instrument); CUpti_ActivityMemset4* memset4 = (CUpti_ActivityMemset4*) record; APICallInfo apiCall; - if (!matchActivityToAPICall(memset4->correlationId, apiCall)) { + if (!matchGraphActivityToAPICall(memset4->correlationId, memset4->graphId, apiCall)) { return matchError(memset4->correlationId, "MEMSET"); } static constexpr tracy::SourceLocationData TracyCUPTISrcLocDeviceMemset { "CUDA::memset", TracyFunction, TracyFile, (uint32_t)TracyLine, tracy::Color::Blue }; @@ -1034,10 +1186,10 @@ namespace tracy { ZoneNamedN(kernel, "tracy::CUDACtx::DoProcessDeviceEvent[malloc/free]", instrument); CUpti_ActivityMemory3* memory3 = (CUpti_ActivityMemory3*)record; - APICallInfo apiCall; - if (!matchActivityToAPICall(memory3->correlationId, apiCall)) { - return matchError(memory3->correlationId, "MEMORY"); - } + // No API call correlation needed — this handler only uses address, + // size, and timestamp directly from the activity record. Memory + // API CBIDs are intentionally excluded from cbidRuntimeTrackers / + // cbidDriverTrackers so no cudaCallSiteInfo entry is created. static constexpr const char* graph_name = "CUDA Memory Allocation"; if (memory3->memoryOperationType == CUPTI_ACTIVITY_MEMORY_OPERATION_TYPE_ALLOCATION){ auto& memAllocAddress = PersistentState::Get().memAllocAddress; @@ -1064,8 +1216,15 @@ namespace tracy { // NOTE(marcos): a byproduct of CUPTI_ACTIVITY_KIND_SYNCHRONIZATION // (I think this is related to cudaEvent*() API calls) +#if CUDA_VERSION < 12080 + // prior to CUDA v12.8 + CUpti_ActivityCudaEvent* event = (CUpti_ActivityCudaEvent*)record; + UNREFERENCED(event); +#else + // starting from CUDA v12.8 CUpti_ActivityCudaEvent2* event = (CUpti_ActivityCudaEvent2*)record; UNREFERENCED(event); +#endif break; } default: @@ -1094,6 +1253,10 @@ namespace tracy CUPTI_ACTIVITY_KIND_MEMSET, CUPTI_ACTIVITY_KIND_SYNCHRONIZATION, CUPTI_ACTIVITY_KIND_MEMORY2, + // NOTE: CUPTI_ACTIVITY_KIND_GRAPH_TRACE must NOT be enabled alongside + // CONCURRENT_KERNEL — enabling it suppresses per-kernel activity records + // for graph-launched kernels, replacing them with graph-level summaries. + //CUPTI_ACTIVITY_KIND_GRAPH_TRACE, //CUPTI_ACTIVITY_KIND_MEMCPY2, //CUPTI_ACTIVITY_KIND_OVERHEAD, //CUPTI_ACTIVITY_KIND_INTERNAL_LAUNCH_API, @@ -1222,7 +1385,18 @@ namespace tracy // NOTE(marcos): these objects do not need to persist, but their relative // footprint is trivial enough that we don't care if we let them leak ConcurrentHashMap cudaCallSiteInfo; + // Graph launch cache: entries are retired via graphExecPendingRetire + // when the corresponding cudaGraphExec is destroyed. + ConcurrentHashMap cudaGraphCurrentLaunch; ConcurrentHashMap memAllocAddress; + // Pending retirement: graphIds whose exec handles have been destroyed. + // Entries are erased from cudaGraphCurrentLaunch in OnBufferCompleted + // once no further activity records for the exec arrive in a buffer. + // graphRetirePending is an atomic dirty-flag so OnBufferCompleted can + // skip the mutex on the hot path when no execs have been destroyed. + std::atomic graphRetirePending{false}; + std::mutex graphRetireMutex; + std::unordered_set graphExecPendingRetire; CUpti_SubscriberHandle subscriber = {}; CUDACtx* profilerHost = nullptr; @@ -1236,17 +1410,17 @@ namespace tracy }; - CUDACtx(uint8_t gpuContextID = 255) + CUDACtx(int32_t gpuContextID = InvalidGpuContextId) { ZoneScoped; - if (gpuContextID != 255) { + if (gpuContextID != InvalidGpuContextId) { m_tracyGpuContext = gpuContextID; return; } - m_tracyGpuContext = GetGpuCtxCounter().fetch_add(1, std::memory_order_relaxed); - assert(m_tracyGpuContext != 255); + m_tracyGpuContext = NextGpuContextId(); + TRACY_ASSERT(m_tracyGpuContext != InvalidGpuContextId); TracyTimestamp tTracy; CUptiTimestamp tCUpti; @@ -1260,12 +1434,15 @@ namespace tracy tracyMemWrite(item->gpuNewContext.thread, (uint32_t)0); tracyMemWrite(item->gpuNewContext.period, 1.0f); tracyMemWrite(item->gpuNewContext.type, GpuContextType::CUDA); - tracyMemWrite(item->gpuNewContext.context, m_tracyGpuContext); + tracyMemWrite(item->gpuNewContext.context, (uint8_t)m_tracyGpuContext); #if TRACY_CUDA_CALIBRATED_CONTEXT tracyMemWrite(item->gpuNewContext.flags, GpuContextCalibration); #else tracyMemWrite(item->gpuNewContext.flags, tracy::GpuContextFlags(0)); #endif + #ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem(*item); + #endif Profiler::QueueSerialFinish(); constexpr const char* tracyCtxName = "CUDA GPU/Device Activity"; @@ -1293,7 +1470,7 @@ namespace tracy CUDACtx* ctx = nullptr; std::mutex m; int ref_count = 0; - uint8_t ctx_id = 255; + int32_t ctx_id = InvalidGpuContextId; static Singleton& Get() { static Singleton singleton; return singleton; @@ -1304,7 +1481,7 @@ namespace tracy ProfilerStats stats = {}; #endif - uint8_t m_tracyGpuContext = 255; + int32_t m_tracyGpuContext = InvalidGpuContextId; static constexpr size_t cacheline = 64; alignas(cacheline) std::atomic m_queryIdGen = 0; }; @@ -1322,4 +1499,4 @@ namespace tracy #endif -#endif \ No newline at end of file +#endif diff --git a/libs/tracy/tracy/TracyD3D11.hpp b/libs/tracy/tracy/TracyD3D11.hpp index acab383..af64fea 100644 --- a/libs/tracy/tracy/TracyD3D11.hpp +++ b/libs/tracy/tracy/TracyD3D11.hpp @@ -33,17 +33,17 @@ using TracyD3D11Ctx = void*; #else #include -#include #include #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" #include "../client/TracyCallstack.hpp" +#include "../common/TracyAssert.hpp" #include "../common/TracyYield.hpp" #include -#define TracyD3D11Panic(msg, ...) do { assert(false && "TracyD3D11: " msg); TracyMessageLC("TracyD3D11: " msg, tracy::Color::Red4); __VA_ARGS__; } while(false); +#define TracyD3D11Panic(msg, ...) do { TRACY_ASSERT(false && "TracyD3D11: " msg); TracyMessageLC("TracyD3D11: " msg, tracy::Color::Red4); __VA_ARGS__; } while(false); namespace tracy { @@ -108,7 +108,7 @@ class D3D11Ctx continue; } - if (disjoint.Disjoint) + if (disjoint.Disjoint || disjoint.Frequency == 0) continue; UINT64 timestamp = 0; @@ -121,7 +121,7 @@ class D3D11Ctx } // ready to roll - m_contextId = GetGpuCtxCounter().fetch_add(1); + m_contextId = NextGpuContextId(); m_immediateDevCtx->Begin(m_disjointQuery); m_previousCheckpoint = m_nextCheckpoint = 0; @@ -131,8 +131,8 @@ class D3D11Ctx MemWrite( &item->gpuNewContext.gpuTime, tgpu ); MemWrite( &item->gpuNewContext.thread, uint32_t(0) ); // #TODO: why not GetThreadHandle()? MemWrite( &item->gpuNewContext.period, 1.0f ); - MemWrite( &item->gpuNewContext.context, m_contextId); - MemWrite( &item->gpuNewContext.flags, uint8_t(0) ); + MemWrite( &item->gpuNewContext.context, uint8_t(m_contextId)); + MemWrite( &item->gpuNewContext.flags, GpuContextFlags(0) ); MemWrite( &item->gpuNewContext.type, GpuContextType::Direct3D11 ); #ifdef TRACY_ON_DEMAND @@ -167,7 +167,7 @@ class D3D11Ctx auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuContextName ); - MemWrite( &item->gpuContextNameFat.context, m_contextId ); + MemWrite( &item->gpuContextNameFat.context, uint8_t(m_contextId) ); MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); MemWrite( &item->gpuContextNameFat.size, len ); #ifdef TRACY_ON_DEMAND @@ -217,6 +217,13 @@ class D3D11Ctx return; } + if (disjoint.Frequency == 0) + { + m_previousCheckpoint = m_nextCheckpoint; + TracyD3D11Panic("zero GPU timestamp frequency; dropping."); + return; + } + auto begin = m_previousCheckpoint; auto end = m_nextCheckpoint; for (auto i = begin; i != end; ++i) @@ -233,7 +240,7 @@ class D3D11Ctx MemWrite(&item->hdr.type, QueueType::GpuTime); MemWrite(&item->gpuTime.gpuTime, static_cast(timestamp)); MemWrite(&item->gpuTime.queryId, static_cast(k)); - MemWrite(&item->gpuTime.context, m_contextId); + MemWrite(&item->gpuTime.context, uint8_t(m_contextId)); Profiler::QueueSerialFinish(); } @@ -280,7 +287,7 @@ class D3D11Ctx YieldThread(); // busy-wait :-( attempt to reduce power usage with _mm_pause() & friends... } - tracy_force_inline uint8_t GetContextId() const + tracy_force_inline int32_t GetContextId() const { return m_contextId; } @@ -291,7 +298,7 @@ class D3D11Ctx ID3D11Query* m_queries[MaxQueries]; ID3D11Query* m_disjointQuery = nullptr; - uint8_t m_contextId = 255; // NOTE: apparently, 255 means invalid id; is this documented anywhere? + int32_t m_contextId = InvalidGpuContextId; uintptr_t m_queryCounter = 0; @@ -365,12 +372,15 @@ class D3D11ZoneScope const auto queryId = m_ctx->NextQueryId(); m_ctx->m_immediateDevCtx->End(m_ctx->GetQueryObjectFromId(queryId)); +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif auto* item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuZoneEndSerial ); MemWrite( &item->gpuZoneEnd.cpuTime, Profiler::GetTime() ); MemWrite( &item->gpuZoneEnd.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneEnd.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneEnd.context, m_ctx->GetContextId() ); + MemWrite( &item->gpuZoneEnd.context, uint8_t(m_ctx->GetContextId()) ); Profiler::QueueSerialFinish(); } @@ -378,6 +388,7 @@ class D3D11ZoneScope tracy_force_inline D3D11ZoneScope( D3D11Ctx* ctx, bool active ) #ifdef TRACY_ON_DEMAND : m_active( active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( active ) #endif @@ -396,12 +407,16 @@ class D3D11ZoneScope MemWrite( &item->gpuZoneBegin.srcloc, sourceLocation ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, m_ctx->GetContextId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t(m_ctx->GetContextId()) ); Profiler::QueueSerialFinish(); } const bool m_active; +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif + D3D11Ctx* m_ctx; }; @@ -418,7 +433,6 @@ static inline void DestroyD3D11Context( D3D11Ctx* ctx ) tracy_free( ctx ); } } - #undef TracyD3D11Panic using TracyD3D11Ctx = tracy::D3D11Ctx*; diff --git a/libs/tracy/tracy/TracyD3D12.hpp b/libs/tracy/tracy/TracyD3D12.hpp index d36253d..9c8121c 100644 --- a/libs/tracy/tracy/TracyD3D12.hpp +++ b/libs/tracy/tracy/TracyD3D12.hpp @@ -33,45 +33,70 @@ using TracyD3D12Ctx = void*; #else #include "Tracy.hpp" -#include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" +#include "../client/TracyFastVector.hpp" +#include "../common/TracyAssert.hpp" +#include +#include #include -#include +#include #include #include -#include -#define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); TracyMessageLC("TracyD3D12: " msg, tracy::Color::Red4); __VA_ARGS__; } while(false); +#ifndef TRACY_D3D12_DEBUG_LEVEL +#define TRACY_D3D12_DEBUG_LEVEL (0) +#endif//TRACY_D3D12_DEBUG_LEVEL + +#ifndef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER +#define TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER (1) +#endif//TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + +#if TRACY_D3D12_DEBUG_LEVEL +# define TracyD3D12Debug(...) __VA_ARGS__; +# ifdef _MSC_VER +# define TracyD3D12Break() if (IsDebuggerPresent()) __debugbreak() +# else +# define TracyD3D12Break() /* TODO */ +# endif +# define TracyD3D12Assert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyD3D12Break(); } +#else +# define TracyD3D12Debug(...) +# define TracyD3D12Break() +# define TracyD3D12Assert(predicate, ...) TRACY_ASSERT(predicate); +#endif + +#define TracyD3D12Log(severity, msg) tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, msg ); +#define TracyD3D12Panic(msg, ...) do { TracyD3D12Log(Error, msg); TracyD3D12Assert(false && "TracyD3D12: " msg); __VA_ARGS__; } while(false); namespace tracy { - struct D3D12QueryPayload - { - uint32_t m_queryIdStart = 0; - uint32_t m_queryCount = 0; - }; - // Command queue context. class D3D12QueueCtx { friend class D3D12ZoneScope; + int32_t m_contextId = InvalidGpuContextId; + + std::mutex m_collectionMutex; + ID3D12Device* m_device = nullptr; ID3D12CommandQueue* m_queue = nullptr; - uint8_t m_contextId = 255; // TODO: apparently, 255 means "invalid id"; is this documented somewhere? ID3D12QueryHeap* m_queryHeap = nullptr; ID3D12Resource* m_readbackBuffer = nullptr; - // In-progress payload. - uint32_t m_queryLimit = 0; - std::atomic m_queryCounter = 0; - uint32_t m_previousQueryCounter = 0; +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + UINT64* m_persistentTimestampBuffer = nullptr; +#endif + + using atomic_counter = std::atomic; + atomic_counter m_queryCounter = 0; + atomic_counter m_previousCheckpoint = 0; - uint32_t m_activePayload = 0; - ID3D12Fence* m_payloadFence = nullptr; - std::queue m_payloadQueue; + uint32_t m_queryLimit = 64 * 1024; // Must be even: each scope is a (begin, end) pair of queries + + FastVector m_shadowBuffer; + UINT64 m_latestKnownGpuTimestamp = 0; UINT64 m_prevCalibrationTicksCPU = 0; @@ -87,6 +112,10 @@ namespace tracy int64_t cpuDeltaTicks = cpuTimestamp - m_prevCalibrationTicksCPU; if (cpuDeltaTicks > 0) { + // WARNING: technically, we should not emit a GpuCalibration event if the GPU counter + // did not move, to prevent division by a gpuDelta of zero in later on (in the server). + // In practice, GetClockCalibration() should be advancing CPU and GPU together. + static const int64_t nanosecodsPerTick = int64_t(1000000000) / GetFrequencyQpc(); int64_t cpuDeltaNS = cpuDeltaTicks * nanosecodsPerTick; // Save the device cpu timestamp, not the Tracy profiler timestamp: @@ -96,10 +125,10 @@ namespace tracy auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuCalibration); - MemWrite(&item->gpuCalibration.gpuTime, gpuTimestamp); - MemWrite(&item->gpuCalibration.cpuTime, cpuTimestamp); + MemWrite(&item->gpuCalibration.gpuTime, int64_t(gpuTimestamp)); + MemWrite(&item->gpuCalibration.cpuTime, int64_t(cpuTimestamp)); MemWrite(&item->gpuCalibration.cpuDelta, cpuDeltaNS); - MemWrite(&item->gpuCalibration.context, GetId()); + MemWrite(&item->gpuCalibration.context, static_cast(GetId())); SubmitQueueItem(item); } } @@ -116,13 +145,16 @@ namespace tracy D3D12QueueCtx(ID3D12Device* device, ID3D12CommandQueue* queue) : m_device(device) , m_queue(queue) + , m_shadowBuffer(m_queryLimit) { - // Verify we support timestamp queries on this queue. + ZoneScopedC(Color::Red4); + + m_queue->AddRef(); + // Verify we support timestamp queries on this queue. if (queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_COPY) { D3D12_FEATURE_DATA_D3D12_OPTIONS3 featureData{}; - HRESULT hr = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &featureData, sizeof(featureData)); if (FAILED(hr) || (featureData.CopyQueueTimestampQueriesSupported == FALSE)) { @@ -130,9 +162,6 @@ namespace tracy } } - static constexpr uint32_t MaxQueries = 64 * 1024; // Must be even, because queries are (begin, end) pairs - m_queryLimit = MaxQueries; - D3D12_QUERY_HEAP_DESC heapDesc{}; heapDesc.Type = queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_COPY ? D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP : D3D12_QUERY_HEAP_TYPE_TIMESTAMP; heapDesc.Count = m_queryLimit; @@ -171,10 +200,11 @@ namespace tracy TracyD3D12Panic("Failed to create query readback buffer.", return); } - if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_payloadFence)))) - { - TracyD3D12Panic("Failed to create payload fence.", return); - } + UINT64* timestampBuffer = MapTimestampBuffer(); + if (timestampBuffer == nullptr) return; + for (uint64_t i = 0; i < m_queryLimit; ++i) + timestampBuffer[i] = 0; + UnmapTimestampBuffer(timestampBuffer); float period = [queue]() { @@ -198,21 +228,27 @@ namespace tracy TracyD3D12Panic("Failed to get queue clock calibration.", return); } + // FastVector: clean/resize/init + for (size_t i = 0; i < m_queryLimit; ++i) *m_shadowBuffer.push_next() = gpuTimestamp; + + m_latestKnownGpuTimestamp = gpuTimestamp; + // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; cpuTimestamp = Profiler::GetTime(); - // all checked: ready to roll - m_contextId = GetGpuCtxCounter().fetch_add(1); + // All setup/init checks completed: ready to create the context. + m_contextId = NextGpuContextId(); + ZoneValue(m_contextId); auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuNewContext); - MemWrite(&item->gpuNewContext.cpuTime, cpuTimestamp); - MemWrite(&item->gpuNewContext.gpuTime, gpuTimestamp); - MemWrite(&item->gpuNewContext.thread, decltype(item->gpuNewContext.thread)(0)); // #TODO: why 0 instead of GetThreadHandle()? - MemWrite(&item->gpuNewContext.period, period); - MemWrite(&item->gpuNewContext.context, GetId()); + MemWrite(&item->gpuNewContext.cpuTime, static_cast(cpuTimestamp)); + MemWrite(&item->gpuNewContext.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuNewContext.thread, static_cast(0)); // zero means the context is not associated with a specific thread + MemWrite(&item->gpuNewContext.period, static_cast(period)); + MemWrite(&item->gpuNewContext.context, static_cast(GetId())); MemWrite(&item->gpuNewContext.flags, GpuContextCalibration); MemWrite(&item->gpuNewContext.type, GpuContextType::Direct3D12); SubmitQueueItem(item); @@ -221,28 +257,40 @@ namespace tracy ~D3D12QueueCtx() { ZoneScopedC(Color::Red4); - // collect all pending timestamps - while (m_payloadFence->GetCompletedValue() != m_activePayload) - /* busy-wait ... */; - Collect(); - m_payloadFence->Release(); - m_readbackBuffer->Release(); - m_queryHeap->Release(); - } - - - void NewFrame() - { - uint32_t queryCounter = m_queryCounter.exchange(0); - m_payloadQueue.emplace(D3D12QueryPayload{ m_previousQueryCounter, queryCounter }); - m_previousQueryCounter += queryCounter; - - if (m_previousQueryCounter >= m_queryLimit) + ZoneValue(m_contextId); + + // TODO: could use queue->Signal() to inject a progress point in the queue + // and the immediately wait for the signal, in order to avoid busy-waiting + // (need to create an ID3D12Fence and associate an Event object to it) + // NOTE: even with Signal(), there are no guarantees the queries were sent + // to the GPU for execution, so the Signal() does not give us much "signal" + + // collect all pending queries up to the latest known query + uint64_t endTicket = m_queryCounter; + uint64_t lastIssuedTicket = endTicket - 2; + Drain(lastIssuedTicket, 200); + + // if the client is still pushing queries past the latest checkpoint above, + // assume there's a bug in the client, and ignore them (don't collect) + if (Distance(endTicket, m_queryCounter) > 0) + TracyD3D12Panic("client is still pushing queries."); + +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + if (m_readbackBuffer) { - m_previousQueryCounter -= m_queryLimit; + D3D12_RANGE emptyWrittenRange { 0, 0 }; + m_readbackBuffer->Unmap(0, &emptyWrittenRange); + m_persistentTimestampBuffer = nullptr; } +#endif + if (m_readbackBuffer) m_readbackBuffer->Release(); + if (m_queryHeap) m_queryHeap->Release(); + m_queue->Release(); + } - m_queue->Signal(m_payloadFence, ++m_activePayload); + tracy_force_inline int32_t GetId() const + { + return m_contextId; } void Name( const char* name, uint16_t len ) @@ -252,7 +300,7 @@ namespace tracy auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuContextName ); - MemWrite( &item->gpuContextNameFat.context, GetId()); + MemWrite( &item->gpuContextNameFat.context, static_cast(GetId())); MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); MemWrite( &item->gpuContextNameFat.size, len ); SubmitQueueItem(item); @@ -260,108 +308,314 @@ namespace tracy void Collect() { - ZoneScopedC(Color::Red4); - #ifdef TRACY_ON_DEMAND - if (!GetProfiler().IsConnected()) - { - m_queryCounter = 0; + if (!GetProfiler().IsConnected()) return; +#endif + // Only one thread is allowed to collect timestamps at any given time + // but there's no need to block contending threads + if (!m_collectionMutex.try_lock()) return; + std::unique_lock lock (m_collectionMutex, std::adopt_lock); + uint64_t targetTicket = m_queryCounter; + Collect(lock, m_queryCounter, false); + } + private: + void Collect(std::unique_lock& lock, uint64_t targetTicket, bool urgent) + { + ZoneScopedC(Color::Red4); + TracyD3D12Assert( lock.owns_lock() ); + TracyD3D12Debug( ZoneValue(m_contextId) ); + + uint64_t earliestTicket = m_previousCheckpoint.load(std::memory_order_relaxed); + uint64_t endTicket = m_queryCounter; + TracyD3D12Debug( ZoneValue(earliestTicket) ); + TracyD3D12Debug( ZoneValue(endTicket) ); + if (Distance(earliestTicket, endTicket) <= 0) return; + + UINT64* timestampBuffer = MapTimestampBuffer(); + if (timestampBuffer == nullptr) return; + + // Attempt to collect as many resolved queries as possible + uint64_t ticket = earliestTicket; + for (ticket = earliestTicket; ticket != endTicket; ticket += 2) + { + if (!ResolveTimestamp(ticket, timestampBuffer)) + // TODO: implement preemptive timeout policy + break; } -#endif - // Find out what payloads are available. - const auto newestReadyPayload = m_payloadFence->GetCompletedValue(); - const auto payloadCount = m_payloadQueue.size() - (m_activePayload - newestReadyPayload); + // Urgent request: ensure 'targetTicket' is collected before returning + if (urgent) + { + TracyD3D12Assert( Distance(targetTicket, endTicket) > 0 ); + while (Distance(ticket, targetTicket) >= 0) + { + DropTimestamp(ticket, timestampBuffer); + ticket += 2; + } + } - if (!payloadCount) + // Panic: overflow, start dropping queries to normalize the situation + while (Distance(ticket, endTicket) > RingCapacity()) { - return; // No payloads are available yet, exit out. + DropTimestamp(ticket, timestampBuffer); + ticket += 2; } - D3D12_RANGE mapRange{ 0, m_queryLimit * sizeof(uint64_t) }; + UnmapTimestampBuffer(timestampBuffer); - // Map the readback buffer so we can fetch the query data from the GPU. - void* readbackBufferMapping = nullptr; + // TODO: check for device status (device lost). + // Technically speaking, values read from the timestamp buffer (readback heap) + // should only be trusted while the device is "fine". Ideally, queries should + // be "peeked" first, and those deemed "resolved" should only be "emitted" if + // the device is fine, or dropped otherwise. All that said, the collect code, + // as is, should not cause catastrophic issues. - if (FAILED(m_readbackBuffer->Map(0, &mapRange, &readbackBufferMapping))) + RecalibrateClocks(); + } + + tracy_force_inline bool IsTicketPending(uint64_t queryTicket) + { + auto checkpoint = m_previousCheckpoint.load(std::memory_order_acquire); + return (Distance(checkpoint, queryTicket) >= 0); + } + + bool Wait(uint64_t queryTicket, uint64_t timeout_ms) + { + ZoneScopedC(Color::Red4); + TracyD3D12Debug( ZoneValue(m_contextId) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + auto ini = GetTickCount64(); + auto elapsed = 0; + // TODO: could use condition variable to avoid spurious lock + collect iterations + while (IsTicketPending(queryTicket)) { - TracyD3D12Panic("Failed to map readback buffer.", return); + if (elapsed >= timeout_ms) + return false; + std::unique_lock lock (m_collectionMutex); + Collect(lock, queryTicket, false); + elapsed = GetTickCount64() - ini; } + return true; + } - auto* timestampData = static_cast(readbackBufferMapping); + void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) + { + ZoneScopedC(Color::Red4); + TracyD3D12Debug( ZoneValue(m_contextId) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + if (Wait(queryTicket, gracePeriod_ms)) + return; + // can't wait anymore: urgent collect request + std::unique_lock lock (m_collectionMutex); + Collect(lock, queryTicket, true); + } - for (uint32_t i = 0; i < payloadCount; ++i) - { - const auto& payload = m_payloadQueue.front(); + bool ResolveTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) + { + uint32_t queryId = RingIndex(queryTicket); + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; + UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; + UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; + int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); + if (baseline_diff <= 0) + return false; + EmitGpuTime(gpuZoneBeginTimestamp, queryId); + EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + RetireTicket(queryTicket); + if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) + m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; + return true; + } - for (uint32_t j = 0; j < payload.m_queryCount; ++j) - { - const auto counter = (payload.m_queryIdStart + j) % m_queryLimit; - const auto timestamp = timestampData[counter]; - const auto queryId = counter; + void DropTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) + { + // might as well attempt to resolve it before dropping it + if (ResolveTimestamp(queryTicket, timestampBuffer)) + return; + // emit a "bogus" GpuTime to avoid problems with the internal + // zone tracking and matching logic in the server/profiler + uint32_t queryId = RingIndex(queryTicket); + TracyD3D12Debug( ZoneScopedC(Color::Red4) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(queryId) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(1)) ); + uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; + EmitGpuTime(latestGpuTimestamp, queryId); + EmitGpuTime(latestGpuTimestamp, queryId+1); + RetireTicket(queryTicket); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(1)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(0)) ); + } - auto* item = Profiler::QueueSerial(); - MemWrite(&item->hdr.type, QueueType::GpuTime); - MemWrite(&item->gpuTime.gpuTime, timestamp); - MemWrite(&item->gpuTime.queryId, static_cast(queryId)); - MemWrite(&item->gpuTime.context, GetId()); + void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) + { + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuTime); + MemWrite(&item->gpuTime.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuTime.queryId, static_cast(queryId)); + MemWrite(&item->gpuTime.context, static_cast(GetId())); + Profiler::QueueSerialFinish(); + m_shadowBuffer[queryId] = gpuTimestamp; + TracyD3D12Debug( + TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12|Query"); + ); + } - Profiler::QueueSerialFinish(); - } + tracy_force_inline uint32_t RingCapacity() const + { + return m_queryLimit; + } - m_payloadQueue.pop(); - } + tracy_force_inline uint32_t RingIndex(uint64_t logicalSlot) const + { + return static_cast(logicalSlot % RingCapacity()); + } - m_readbackBuffer->Unmap(0, nullptr); + tracy_force_inline static int64_t Distance(uint64_t begin, uint64_t end) + { + // difference accounting for unsigned wrap-around + return static_cast(end - begin); + } - // Recalibrate to account for drift. - RecalibrateClocks(); + void RetireTicket(uint64_t ticket) + { + TracyD3D12Assert( m_previousCheckpoint == ticket ); + uint64_t nextTicket = ticket + 2; + m_previousCheckpoint.store(nextTicket, std::memory_order_release); } - private: tracy_force_inline uint32_t NextQueryId() { - uint32_t queryCounter = m_queryCounter.fetch_add(2); - if (queryCounter >= m_queryLimit) + // WARN: the moment m_queryCounter is incremented, Collect() will have instant + // visibility of the change and may therefore start attempting to collect them. + // Under most circumstances, this is fine. However, suppose Collect() ends up + // dropping a query due to timeout, but said query is indeed submitted to the + // GPU queue for execution later on. This "late" query will eventually become + // resolved by the GPU asynchronously writting to the corresponding query slot. + // The newly produced query below could have matching ids/slot with said query. + // From there, there's no bullet-proof way for Collect() to distinguish between + // the timestamp written to the query slot belonging to the old/late query, or + // to the query that has just been generated. The value of the "late" query may + // may end up being collected as if it belonged to the the new query. + const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); + const uint64_t checkpoint = m_previousCheckpoint.load(std::memory_order_relaxed); + if (Distance(checkpoint, ticket) >= RingCapacity()) { - TracyD3D12Panic("Submitted too many GPU queries! Consider increasing MaxQueries."); - // #TODO: consider returning an invalid id or sentinel value here + ZoneScopedC(Color::Red4); + TracyD3D12Log(Warning, "Too many pending GPU queries: stalling!"); + uint64_t oldTicket = ticket - RingCapacity(); + Drain(oldTicket, 0); } - const uint32_t id = (m_previousQueryCounter + queryCounter) % m_queryLimit; + const uint32_t queryId = RingIndex(ticket); + TracyD3D12Debug( + TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12|Query"); + TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12|Query"); + ); + return queryId; + } - return id; + UINT64* MapTimestampBuffer() + { +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + if (m_persistentTimestampBuffer != nullptr) + return m_persistentTimestampBuffer; +#endif + D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; + void* readbackBufferMapping = nullptr; + if (FAILED(m_readbackBuffer->Map(0, &fullRange, &readbackBufferMapping))) + { + TracyD3D12Panic("failed to map timestamp buffer.", return nullptr); + } + UINT64* timestampBuffer = static_cast(readbackBufferMapping); +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + TracyD3D12Assert( m_persistentTimestampBuffer == nullptr ); + m_persistentTimestampBuffer = timestampBuffer; +#endif + return timestampBuffer; } - tracy_force_inline uint8_t GetId() const + void UnmapTimestampBuffer(UINT64*) { - return m_contextId; +#if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + D3D12_RANGE emptyWrittenRange { 0, 0 }; + m_readbackBuffer->Unmap(0, &emptyWrittenRange); +#endif + } + + bool DeviceLost() + { + HRESULT status = m_device->GetDeviceRemovedReason(); + return (status != S_OK); } }; class D3D12ZoneScope { const bool m_active; +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif D3D12QueueCtx* m_ctx = nullptr; ID3D12GraphicsCommandList* m_cmdList = nullptr; - uint32_t m_queryId = 0; // Used for tracking in nested zones. + uint32_t m_queryId = 0; - tracy_force_inline void WriteQueueItem(QueueItem* item, QueueType type, uint64_t srcLocation) + tracy_force_inline void WriteQueueItem(const SourceLocationData* srcLocation, int32_t callstackDepth, uint32_t sourceLine, const char* sourceFile, size_t sourceFileLen, const char* functionName, size_t functionNameLen, const char* zoneName, size_t zoneNameLen) { - MemWrite(&item->hdr.type, type); - MemWrite(&item->gpuZoneBegin.cpuTime, Profiler::GetTime()); - MemWrite(&item->gpuZoneBegin.srcloc, srcLocation); - MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); - MemWrite(&item->gpuZoneBegin.queryId, static_cast(m_queryId)); - MemWrite(&item->gpuZoneBegin.context, m_ctx->GetId()); + if (!m_active) return; + + const bool captureCallstack = callstackDepth > 0 && has_callstack(); + const bool transientZone = srcLocation == nullptr; + uint64_t srcLocationAddr = reinterpret_cast( srcLocation ); + + QueueItem* item = nullptr; + QueueType itemType; + if( transientZone ) + { + srcLocationAddr = Profiler::AllocSourceLocation( sourceLine, sourceFile, sourceFileLen, functionName, functionNameLen, zoneName, zoneNameLen); + if( captureCallstack ) + { + item = Profiler::QueueSerialCallstack( Callstack( callstackDepth ) ); + itemType = QueueType::GpuZoneBeginAllocSrcLocCallstackSerial; + } + else + { + item = Profiler::QueueSerial(); + itemType = QueueType::GpuZoneBeginAllocSrcLocSerial; + } + } + else + { + if( captureCallstack ) + { + item = Profiler::QueueSerialCallstack( Callstack( callstackDepth ) ); + itemType = QueueType::GpuZoneBeginCallstackSerial; + } + else + { + item = Profiler::QueueSerial(); + itemType = QueueType::GpuZoneBeginSerial; + } + } + + MemWrite( &item->hdr.type, itemType ); + MemWrite( &item->gpuZoneBegin.cpuTime, Profiler::GetTime() ); + MemWrite( &item->gpuZoneBegin.srcloc, srcLocationAddr ); + MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); + MemWrite( &item->gpuZoneBegin.queryId, static_cast( m_queryId ) ); + MemWrite( &item->gpuZoneBegin.context, static_cast(m_ctx->GetId()) ); Profiler::QueueSerialFinish(); } tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, ID3D12GraphicsCommandList* cmdList, bool active) #ifdef TRACY_ON_DEMAND : m_active(active&& GetProfiler().IsConnected()) + , m_connectionId(GetProfiler().ConnectionId()) #else : m_active(active) #endif @@ -379,41 +633,25 @@ namespace tracy tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, ID3D12GraphicsCommandList* cmdList, const SourceLocationData* srcLocation, bool active) : D3D12ZoneScope(ctx, cmdList, active) { - if (!m_active) return; - - auto* item = Profiler::QueueSerial(); - WriteQueueItem(item, QueueType::GpuZoneBeginSerial, reinterpret_cast(srcLocation)); + WriteQueueItem(srcLocation, 0, 0, nullptr, 0, nullptr, 0, nullptr, 0 ); } tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, ID3D12GraphicsCommandList* cmdList, const SourceLocationData* srcLocation, int32_t depth, bool active) : D3D12ZoneScope(ctx, cmdList, active) { - if (!m_active) return; - - auto* item = Profiler::QueueSerialCallstack(Callstack(depth)); - WriteQueueItem(item, QueueType::GpuZoneBeginCallstackSerial, reinterpret_cast(srcLocation)); + WriteQueueItem(srcLocation, depth, 0, nullptr, 0, nullptr, 0, nullptr, 0 ); } tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, ID3D12GraphicsCommandList* cmdList, bool active) : D3D12ZoneScope(ctx, cmdList, active) { - if (!m_active) return; - - const auto sourceLocation = Profiler::AllocSourceLocation(line, source, sourceSz, function, functionSz, name, nameSz); - - auto* item = Profiler::QueueSerial(); - WriteQueueItem(item, QueueType::GpuZoneBeginAllocSrcLocSerial, sourceLocation); + WriteQueueItem(nullptr, 0, line, source, sourceSz, function, functionSz, name, nameSz); } tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, ID3D12GraphicsCommandList* cmdList, int32_t depth, bool active) : D3D12ZoneScope(ctx, cmdList, active) { - if (!m_active) return; - - const auto sourceLocation = Profiler::AllocSourceLocation(line, source, sourceSz, function, functionSz, name, nameSz); - - auto* item = Profiler::QueueSerialCallstack(Callstack(depth)); - WriteQueueItem(item, QueueType::GpuZoneBeginAllocSrcLocCallstackSerial, sourceLocation); + WriteQueueItem(nullptr, depth, line, source, sourceSz, function, functionSz, name, nameSz); } tracy_force_inline ~D3D12ZoneScope() @@ -421,37 +659,54 @@ namespace tracy if (!m_active) return; const auto queryId = m_queryId + 1; // Our end query slot is immediately after the begin slot. - m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); - auto* item = Profiler::QueueSerial(); - MemWrite(&item->hdr.type, QueueType::GpuZoneEndSerial); - MemWrite(&item->gpuZoneEnd.cpuTime, Profiler::GetTime()); - MemWrite(&item->gpuZoneEnd.thread, GetThreadHandle()); - MemWrite(&item->gpuZoneEnd.queryId, static_cast(queryId)); - MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); - Profiler::QueueSerialFinish(); +#ifdef TRACY_ON_DEMAND + if (GetProfiler().ConnectionId() == m_connectionId) + { +#endif + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuZoneEndSerial); + MemWrite(&item->gpuZoneEnd.cpuTime, Profiler::GetTime()); + MemWrite(&item->gpuZoneEnd.thread, GetThreadHandle()); + MemWrite(&item->gpuZoneEnd.queryId, static_cast(queryId)); + MemWrite( &item->gpuZoneEnd.context, static_cast(m_ctx->GetId()) ); + Profiler::QueueSerialFinish(); +#ifdef TRACY_ON_DEMAND + } +#endif + m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); + // NOTE: can't quite move this ResolveQueryData() call to Collect()... + // If a command is instrumented, but the command list is never submitted + // for execution, we should not ask the GPU to resolve the corresponding + // queries (we'll get stale/garbage data if we do so) m_cmdList->ResolveQueryData(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId, 2, m_ctx->m_readbackBuffer, m_queryId * sizeof(uint64_t)); } }; + static inline void DestroyD3D12Context(D3D12QueueCtx* ctx) + { + TracyD3D12Assert(ctx); + ctx->~D3D12QueueCtx(); + tracy_free(ctx); + } + static inline D3D12QueueCtx* CreateD3D12Context(ID3D12Device* device, ID3D12CommandQueue* queue) { auto* ctx = static_cast(tracy_malloc(sizeof(D3D12QueueCtx))); new (ctx) D3D12QueueCtx{ device, queue }; - return ctx; } - static inline void DestroyD3D12Context(D3D12QueueCtx* ctx) - { - ctx->~D3D12QueueCtx(); - tracy_free(ctx); - } - } #undef TracyD3D12Panic +#undef TracyD3D12Log +#undef TracyD3D12Assert +#undef TracyD3D12Break +#undef TracyD3D12Debug +#undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER +#undef TRACY_D3D12_DEBUG_LEVEL using TracyD3D12Ctx = tracy::D3D12QueueCtx*; @@ -459,7 +714,7 @@ using TracyD3D12Ctx = tracy::D3D12QueueCtx*; #define TracyD3D12Destroy(ctx) tracy::DestroyD3D12Context(ctx); #define TracyD3D12ContextName(ctx, name, size) ctx->Name(name, size); -#define TracyD3D12NewFrame(ctx) ctx->NewFrame(); +#define TracyD3D12NewFrame(ctx) ((void)(ctx)) #define TracyD3D12UnnamedZone ___tracy_gpu_d3d12_zone #define TracyD3D12SrcLocSymbol TracyConcat(__tracy_d3d12_source_location,TracyLine) diff --git a/libs/tracy/tracy/TracyLua.hpp b/libs/tracy/tracy/TracyLua.hpp index f0c5c40..717bdf2 100644 --- a/libs/tracy/tracy/TracyLua.hpp +++ b/libs/tracy/tracy/TracyLua.hpp @@ -13,6 +13,7 @@ namespace tracy namespace detail { static inline int noop( lua_State* L ) { return 0; } +static inline int zero( lua_State* L ) { lua_pushinteger( L, 0 ); return 1; } } static inline void LuaRegister( lua_State* L ) @@ -34,6 +35,10 @@ static inline void LuaRegister( lua_State* L ) lua_setfield( L, -2, "ZoneName" ); lua_pushcfunction( L, detail::noop ); lua_setfield( L, -2, "Message" ); + lua_pushcfunction( L, detail::zero ); + lua_setfield( L, -2, "SectionEnter" ); + lua_pushcfunction( L, detail::noop ); + lua_setfield( L, -2, "SectionLeave" ); lua_setglobal( L, "tracy" ); } @@ -108,6 +113,19 @@ static inline void LuaRemove( char* script ) memset( script, ' ', end - script ); script = end; } + else if( strncmp( script + 6, "SectionEnter(", 13 ) == 0 ) + { + auto end = FindEnd( script + 19 ); + *script = '0'; + memset( script + 1, ' ', end - script - 1 ); + script = end; + } + else if( strncmp( script + 6, "SectionLeave(", 13 ) == 0 ) + { + auto end = FindEnd( script + 19 ); + memset( script, ' ', end - script ); + script = end; + } else { script += 6; @@ -126,9 +144,9 @@ static inline void LuaHook( lua_State* L, lua_Debug* ar ) {} #else -#include #include +#include "../common/TracyAssert.hpp" #include "../common/TracyColor.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyForceInline.hpp" @@ -148,14 +166,18 @@ namespace detail static inline void LuaShortenSrc( char* dst, const char* src ) { size_t l = std::min( (size_t)255, strlen( src ) ); - memcpy( dst, src, l ); + for( size_t i=0; i::max)() ); + TRACY_ASSERT( fsz[i] <= (std::numeric_limits::max)() ); memcpy( dst, fsz+i, 2 ); dst += 2; memcpy( dst, func[i], fsz[i] ); dst += fsz[i]; - assert( ssz[i] <= (std::numeric_limits::max)() ); + TRACY_ASSERT( ssz[i] <= (std::numeric_limits::max)() ); memcpy( dst, ssz+i, 2 ); dst += 2; memcpy( dst, dbg[i].source, ssz[i] ), dst += ssz[i]; } - assert( dst - ptr == spaceNeeded + 2 ); + TRACY_ASSERT( dst - ptr == spaceNeeded + 2 ); TracyQueuePrepare( QueueType::CallstackAlloc ); MemWrite( &item->callstackAllocFat.ptr, (uint64_t)ptr ); @@ -200,17 +222,17 @@ static tracy_force_inline void SendLuaCallstack( lua_State* L, uint32_t depth ) static inline int LuaZoneBeginS( lua_State* L ) { #ifdef TRACY_ON_DEMAND - const auto zoneCnt = GetLuaZoneState().counter++; - if( zoneCnt != 0 && !GetLuaZoneState().active ) return 0; - GetLuaZoneState().active = GetProfiler().IsConnected(); - if( !GetLuaZoneState().active ) return 0; + const auto connectionId = GetProfiler().IsConnected() ? GetProfiler().ConnectionId() : 0; + GetLuaZoneState().Push( connectionId ); + if( !connectionId ) return 0; #endif -#ifdef TRACY_CALLSTACK +#if defined TRACY_CALLSTACK && TRACY_CALLSTACK > 0 const uint32_t depth = TRACY_CALLSTACK; #else const auto depth = uint32_t( lua_tointeger( L, 1 ) ); #endif + TRACY_ASSERT( depth > 0 ); // Would crash later anyway, this is not allowed SendLuaCallstack( L, depth ); lua_Debug dbg; @@ -231,17 +253,17 @@ static inline int LuaZoneBeginS( lua_State* L ) static inline int LuaZoneBeginNS( lua_State* L ) { #ifdef TRACY_ON_DEMAND - const auto zoneCnt = GetLuaZoneState().counter++; - if( zoneCnt != 0 && !GetLuaZoneState().active ) return 0; - GetLuaZoneState().active = GetProfiler().IsConnected(); - if( !GetLuaZoneState().active ) return 0; + const auto connectionId = GetProfiler().IsConnected() ? GetProfiler().ConnectionId() : 0; + GetLuaZoneState().Push( connectionId ); + if( !connectionId ) return 0; #endif -#ifdef TRACY_CALLSTACK +#if defined TRACY_CALLSTACK && TRACY_CALLSTACK > 0 const uint32_t depth = TRACY_CALLSTACK; #else const auto depth = uint32_t( lua_tointeger( L, 2 ) ); #endif + TRACY_ASSERT( depth > 0 ); // Would crash later anyway, this is not allowed SendLuaCallstack( L, depth ); lua_Debug dbg; @@ -264,14 +286,13 @@ static inline int LuaZoneBeginNS( lua_State* L ) static inline int LuaZoneBegin( lua_State* L ) { -#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK && TRACY_CALLSTACK > 0 return LuaZoneBeginS( L ); #else #ifdef TRACY_ON_DEMAND - const auto zoneCnt = GetLuaZoneState().counter++; - if( zoneCnt != 0 && !GetLuaZoneState().active ) return 0; - GetLuaZoneState().active = GetProfiler().IsConnected(); - if( !GetLuaZoneState().active ) return 0; + const auto connectionId = GetProfiler().IsConnected() ? GetProfiler().ConnectionId() : 0; + GetLuaZoneState().Push( connectionId ); + if( !connectionId ) return 0; #endif lua_Debug dbg; @@ -291,14 +312,13 @@ static inline int LuaZoneBegin( lua_State* L ) static inline int LuaZoneBeginN( lua_State* L ) { -#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK && TRACY_CALLSTACK > 0 return LuaZoneBeginNS( L ); #else #ifdef TRACY_ON_DEMAND - const auto zoneCnt = GetLuaZoneState().counter++; - if( zoneCnt != 0 && !GetLuaZoneState().active ) return 0; - GetLuaZoneState().active = GetProfiler().IsConnected(); - if( !GetLuaZoneState().active ) return 0; + const auto connectionId = GetProfiler().IsConnected() ? GetProfiler().ConnectionId() : 0; + GetLuaZoneState().Push( connectionId ); + if( !connectionId ) return 0; #endif lua_Debug dbg; @@ -321,14 +341,8 @@ static inline int LuaZoneBeginN( lua_State* L ) static inline int LuaZoneEnd( lua_State* L ) { #ifdef TRACY_ON_DEMAND - assert( GetLuaZoneState().counter != 0 ); - GetLuaZoneState().counter--; - if( !GetLuaZoneState().active ) return 0; - if( !GetProfiler().IsConnected() ) - { - GetLuaZoneState().active = false; - return 0; - } + const auto connectionId = GetLuaZoneState().Pop(); + if( !connectionId || GetProfiler().ConnectionId() != connectionId ) return 0; #endif TracyQueuePrepare( QueueType::ZoneEnd ); @@ -340,17 +354,13 @@ static inline int LuaZoneEnd( lua_State* L ) static inline int LuaZoneText( lua_State* L ) { #ifdef TRACY_ON_DEMAND - if( !GetLuaZoneState().active ) return 0; - if( !GetProfiler().IsConnected() ) - { - GetLuaZoneState().active = false; - return 0; - } + const auto connectionId = GetLuaZoneState().Top(); + if( !connectionId || GetProfiler().ConnectionId() != connectionId ) return 0; #endif auto txt = lua_tostring( L, 1 ); const auto size = strlen( txt ); - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); @@ -365,17 +375,13 @@ static inline int LuaZoneText( lua_State* L ) static inline int LuaZoneName( lua_State* L ) { #ifdef TRACY_ON_DEMAND - if( !GetLuaZoneState().active ) return 0; - if( !GetProfiler().IsConnected() ) - { - GetLuaZoneState().active = false; - return 0; - } + const auto connectionId = GetLuaZoneState().Top(); + if( !connectionId || GetProfiler().ConnectionId() != connectionId ) return 0; #endif auto txt = lua_tostring( L, 1 ); const auto size = strlen( txt ); - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); @@ -395,19 +401,70 @@ static inline int LuaMessage( lua_State* L ) auto txt = lua_tostring( L, 1 ); const auto size = strlen( txt ); - assert( size < (std::numeric_limits::max)() ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); auto ptr = (char*)tracy_malloc( size ); memcpy( ptr, txt, size ); + TaggedUserlandAddress taggedPtr{ (uint64_t)ptr, MakeMessageMetadata( MessageSourceType::User, MessageSeverity::Info ) }; + TracyQueuePrepare( QueueType::Message ); MemWrite( &item->messageFat.time, Profiler::GetTime() ); - MemWrite( &item->messageFat.text, (uint64_t)ptr ); + MemWrite( &item->messageFat.textAndMetadata, taggedPtr ); MemWrite( &item->messageFat.size, (uint16_t)size ); TracyQueueCommit( messageFatThread ); return 0; } +static inline int LuaSectionEnter( lua_State* L ) +{ + auto& profiler = GetProfiler(); +#ifdef TRACY_ON_DEMAND + if( !profiler.IsConnected() ) + { + lua_pushinteger( L, 0 ); + return 1; + } +#endif + + auto txt = lua_tostring( L, 1 ); + const auto size = strlen( txt ); + TRACY_ASSERT( size < (std::numeric_limits::max)() ); + + uint16_t category = lua_isnumber( L, 2 ) ? lua_tointeger( L, 2 ) : 0; + + auto ptr = (char*)tracy_malloc( size ); + memcpy( ptr, txt, size ); + + const auto id = profiler.GetNextSectionId(); + TracyLfqPrepare( QueueType::SectionEnter ); + MemWrite( &item->sectionEnterFat.time, Profiler::GetTime() ); + MemWrite( &item->sectionEnterFat.id, id ); + MemWrite( &item->sectionEnterFat.category, category ); + MemWrite( &item->sectionEnterFat.text, (uint64_t)ptr ); + MemWrite( &item->sectionEnterFat.size, (uint16_t)size ); + TracyLfqCommit; + + lua_pushinteger( L, id ); + return 1; +} + +static inline int LuaSectionLeave( lua_State* L ) +{ +#ifdef TRACY_ON_DEMAND + if( !GetProfiler().IsConnected() ) return 0; +#endif + + const auto id = uint32_t( lua_tointeger( L, 1 ) ); + if( id == 0 ) return 0; + + TracyLfqPrepare( QueueType::SectionLeave ); + MemWrite( &item->sectionLeave.time, Profiler::GetTime() ); + MemWrite( &item->sectionLeave.id, id ); + TracyLfqCommit; + return 0; +} + } static inline void LuaRegister( lua_State* L ) @@ -436,6 +493,10 @@ static inline void LuaRegister( lua_State* L ) lua_setfield( L, -2, "ZoneName" ); lua_pushcfunction( L, detail::LuaMessage ); lua_setfield( L, -2, "Message" ); + lua_pushcfunction( L, detail::LuaSectionEnter ); + lua_setfield( L, -2, "SectionEnter" ); + lua_pushcfunction( L, detail::LuaSectionLeave ); + lua_setfield( L, -2, "SectionLeave" ); lua_setglobal( L, "tracy" ); } @@ -446,10 +507,9 @@ static inline void LuaHook( lua_State* L, lua_Debug* ar ) if ( ar->event == LUA_HOOKCALL ) { #ifdef TRACY_ON_DEMAND - const auto zoneCnt = GetLuaZoneState().counter++; - if ( zoneCnt != 0 && !GetLuaZoneState().active ) return; - GetLuaZoneState().active = GetProfiler().IsConnected(); - if ( !GetLuaZoneState().active ) return; + const auto connectionId = GetProfiler().IsConnected() ? GetProfiler().ConnectionId() : 0; + GetLuaZoneState().Push( connectionId ); + if ( !connectionId ) return; #endif lua_getinfo( L, "Snl", ar ); @@ -464,14 +524,8 @@ static inline void LuaHook( lua_State* L, lua_Debug* ar ) } else if (ar->event == LUA_HOOKRET) { #ifdef TRACY_ON_DEMAND - assert( GetLuaZoneState().counter != 0 ); - GetLuaZoneState().counter--; - if ( !GetLuaZoneState().active ) return; - if ( !GetProfiler().IsConnected() ) - { - GetLuaZoneState().active = false; - return; - } + const auto connectionId = GetLuaZoneState().Pop(); + if ( !connectionId || GetProfiler().ConnectionId() != connectionId ) return; #endif TracyQueuePrepare( QueueType::ZoneEnd ); MemWrite( &item->zoneEnd.time, Profiler::GetTime() ); diff --git a/libs/tracy/tracy/TracyMetal.hmm b/libs/tracy/tracy/TracyMetal.hmm index a4b4cb5..214c5aa 100644 --- a/libs/tracy/tracy/TracyMetal.hmm +++ b/libs/tracy/tracy/TracyMetal.hmm @@ -58,7 +58,6 @@ using TracyMetalCtx = void; #endif #include -#include #include #include "Tracy.hpp" @@ -66,6 +65,7 @@ using TracyMetalCtx = void; #include "../client/TracyCallstack.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" // ok to import if in obj-c code #import @@ -125,11 +125,6 @@ public: ZoneScopedNC("tracy::MetalCtx::Create", Color::Red4); auto ctx = static_cast(tracy_malloc(sizeof(MetalCtx))); new (ctx) MetalCtx(device); - if (ctx->m_contextId == 255) - { - TracyMetalPanic({assert(false);} return nullptr, "ERROR: unable to create context."); - Destroy(ctx); - } return ctx; } @@ -147,7 +142,7 @@ public: auto* item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuContextName ); - MemWrite( &item->gpuContextNameFat.context, m_contextId ); + MemWrite( &item->gpuContextNameFat.context, static_cast(m_contextId) ); MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); MemWrite( &item->gpuContextNameFat.size, len ); SubmitQueueItem(item); @@ -259,7 +254,7 @@ public: MemWrite(&item->hdr.type, QueueType::GpuTime); MemWrite(&item->gpuTime.gpuTime, static_cast(t_start)); MemWrite(&item->gpuTime.queryId, static_cast(k)); - MemWrite(&item->gpuTime.context, m_contextId); + MemWrite(&item->gpuTime.context, static_cast(m_contextId)); Profiler::QueueSerialFinish(); } { @@ -267,7 +262,7 @@ public: MemWrite(&item->hdr.type, QueueType::GpuTime); MemWrite(&item->gpuTime.gpuTime, static_cast(t_end)); MemWrite(&item->gpuTime.queryId, static_cast(k+1)); - MemWrite(&item->gpuTime.context, m_contextId); + MemWrite(&item->gpuTime.context, static_cast(m_contextId)); Profiler::QueueSerialFinish(); } m_mostRecentTimestamp = (t_end > m_mostRecentTimestamp) ? t_end : m_mostRecentTimestamp; @@ -302,11 +297,11 @@ private: if (m_device == nil) { - TracyMetalPanic({assert(false);} return, "device is nil."); + TracyMetalPanic({TRACY_ASSERT(false);} return, "device is nil."); } if (![m_device supportsCounterSampling:MTLCounterSamplingPointAtStageBoundary]) { - TracyMetalPanic({assert(false);} return, "ERROR: timestamp sampling at pipeline stage boundary is not supported."); + TracyMetalPanic({TRACY_ASSERT(false);} return, "ERROR: timestamp sampling at pipeline stage boundary is not supported."); } if (![m_device supportsCounterSampling:MTLCounterSamplingPointAtDrawBoundary]) { @@ -342,7 +337,7 @@ private: float period = 1.0f; - m_contextId = GetGpuCtxCounter().fetch_add(1); + m_contextId = NextGpuContextId(); auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuNewContext); @@ -350,7 +345,7 @@ private: MemWrite(&item->gpuNewContext.gpuTime, int64_t(gpuTimestamp)); MemWrite(&item->gpuNewContext.thread, uint32_t(0)); // TODO: why not GetThreadHandle()? MemWrite(&item->gpuNewContext.period, period); - MemWrite(&item->gpuNewContext.context, m_contextId); + MemWrite(&item->gpuNewContext.context, static_cast(m_contextId)); //MemWrite(&item->gpuNewContext.flags, GpuContextCalibration); MemWrite(&item->gpuNewContext.flags, GpuContextFlags(0)); MemWrite(&item->gpuNewContext.type, GpuContextType::Metal); @@ -421,7 +416,7 @@ private: return Query{ buffer, idx }; } - tracy_force_inline uint8_t GetContextId() const + tracy_force_inline int32_t GetContextId() const { return m_contextId; } @@ -441,7 +436,7 @@ private: } if (timestampCounterSet == nil) { - TracyMetalPanic({assert(false);} return nil, "ERROR: timestamp counters are not supported on the platform."); + TracyMetalPanic({TRACY_ASSERT(false);} return nil, "ERROR: timestamp counters are not supported on the platform."); } MTLCounterSampleBufferDescriptor* sampleDescriptor = [[MTLCounterSampleBufferDescriptor alloc] init]; @@ -455,7 +450,7 @@ private: if (error != nil) { //NSLog(@"%@ | %@", error.localizedDescription, error.localizedFailureReason); - TracyMetalPanic({assert(false);} return nil, + TracyMetalPanic({TRACY_ASSERT(false);} return nil, "ERROR: unable to create sample buffer for timestamp counters : %s | %s", [error.localizedDescription cString], [error.localizedFailureReason cString]); } @@ -463,7 +458,7 @@ private: return counterSampleBuffer; } - uint8_t m_contextId = 255; + int32_t m_contextId = InvalidGpuContextId; id m_device = nil; id m_counterSampleBuffers [2] = {}; @@ -486,12 +481,13 @@ public: tracy_force_inline MetalZoneScope( MetalCtx* ctx, MTLComputePassDescriptor* desc, const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif { if ( !m_active ) return; - if (desc == nil) TracyMetalPanic({assert(false);} return, "compute pass descriptor is nil."); + if (desc == nil) TracyMetalPanic({TRACY_ASSERT(false);} return, "compute pass descriptor is nil."); m_ctx = ctx; auto& query = m_query = ctx->NextQuery(); @@ -506,12 +502,13 @@ public: tracy_force_inline MetalZoneScope( MetalCtx* ctx, MTLBlitPassDescriptor* desc, const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif { if ( !m_active ) return; - if (desc == nil) TracyMetalPanic({assert(false); }return, "blit pass descriptor is nil."); + if (desc == nil) TracyMetalPanic({TRACY_ASSERT(false); }return, "blit pass descriptor is nil."); m_ctx = ctx; auto& query = m_query = ctx->NextQuery(); @@ -526,12 +523,13 @@ public: tracy_force_inline MetalZoneScope( MetalCtx* ctx, MTLRenderPassDescriptor* desc, const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif { if ( !m_active ) return; - if (desc == nil) TracyMetalPanic({assert(false);} return, "render pass descriptor is nil."); + if (desc == nil) TracyMetalPanic({TRACY_ASSERT(false);} return, "render pass descriptor is nil."); m_ctx = ctx; auto& query = m_query = ctx->NextQuery(); @@ -549,6 +547,7 @@ public: tracy_force_inline MetalZoneScope( MetalCtx* ctx, id cmdEncoder, const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -569,6 +568,9 @@ public: { if( !m_active ) return; +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif SubmitZoneEndGpu(m_ctx, m_query.idx + 1); } @@ -577,6 +579,10 @@ public: private: const bool m_active; +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif + MetalCtx* m_ctx; /* TODO: declare it for "command-level" profiling @@ -591,7 +597,7 @@ private: MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, ctx->GetContextId() ); + MemWrite( &item->gpuZoneBegin.context, static_cast(ctx->GetContextId()) ); Profiler::QueueSerialFinish(); TracyMetalDebugMasked(1<<2, TracyAllocN((void*)(uintptr_t)queryId, 1, "TracyMetalGpuZone")); @@ -604,7 +610,7 @@ private: MemWrite( &item->gpuZoneEnd.cpuTime, Profiler::GetTime() ); MemWrite( &item->gpuZoneEnd.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneEnd.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneEnd.context, ctx->GetContextId() ); + MemWrite( &item->gpuZoneEnd.context, static_cast(ctx->GetContextId()) ); Profiler::QueueSerialFinish(); TracyMetalDebugMasked(1<<2, TracyAllocN((void*)(uintptr_t)queryId, 1, "TracyMetalGpuZone")); diff --git a/libs/tracy/tracy/TracyOpenCL.hpp b/libs/tracy/tracy/TracyOpenCL.hpp index ede5c46..a99fb44 100644 --- a/libs/tracy/tracy/TracyOpenCL.hpp +++ b/libs/tracy/tracy/TracyOpenCL.hpp @@ -36,19 +36,19 @@ using TracyCLCtx = void*; #include #include -#include #include #include "Tracy.hpp" #include "../client/TracyCallstack.hpp" #include "../client/TracyProfiler.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" #define TRACY_CL_TO_STRING_INDIRECT(T) #T #define TRACY_CL_TO_STRING(T) TRACY_CL_TO_STRING_INDIRECT(T) #define TRACY_CL_ASSERT(p) if(!(p)) { \ TracyMessageL( "TRACY_CL_ASSERT failed on " TracyFile ":" TRACY_CL_TO_STRING(TracyLine) ); \ - assert(false && "TRACY_CL_ASSERT failed"); \ + TRACY_ASSERT(false && "TRACY_CL_ASSERT failed"); \ } #define TRACY_CL_CHECK_ERROR(err) if(err != CL_SUCCESS) { \ std::ostringstream oss; \ @@ -56,7 +56,7 @@ using TracyCLCtx = void*; << ": error code " << err; \ auto msg = oss.str(); \ TracyMessage(msg.data(), msg.size()); \ - assert(false && "TRACY_CL_CHECK_ERROR failed"); \ + TRACY_ASSERT(false && "TRACY_CL_CHECK_ERROR failed"); \ } namespace tracy { @@ -76,15 +76,15 @@ namespace tracy { class OpenCLCtx { public: - enum { QueryCount = 64 * 1024 }; + static constexpr size_t QueryCount = 64 * 1024; OpenCLCtx(cl_context context, cl_device_id device) - : m_contextId(GetGpuCtxCounter().fetch_add(1, std::memory_order_relaxed)) + : m_contextId(NextGpuContextId()) , m_head(0) , m_tail(0) { int64_t tcpu, tgpu; - TRACY_CL_ASSERT(m_contextId != 255); + TRACY_CL_ASSERT(m_contextId != InvalidGpuContextId); cl_int err = CL_SUCCESS; cl_command_queue queue = clCreateCommandQueue(context, device, CL_QUEUE_PROFILING_ENABLE, &err); @@ -113,8 +113,8 @@ namespace tracy { memset(&item->gpuNewContext.thread, 0, sizeof(item->gpuNewContext.thread)); MemWrite(&item->gpuNewContext.period, 1.0f); MemWrite(&item->gpuNewContext.type, GpuContextType::OpenCL); - MemWrite(&item->gpuNewContext.context, (uint8_t) m_contextId); - MemWrite(&item->gpuNewContext.flags, (uint8_t)0); + MemWrite(&item->gpuNewContext.context, uint8_t(m_contextId)); + MemWrite(&item->gpuNewContext.flags, GpuContextFlags(0)); #ifdef TRACY_ON_DEMAND GetProfiler().DeferItem(*item); #endif @@ -164,7 +164,7 @@ namespace tracy { if (eventInfo.event == nullptr) { TracyMessageL("A TracyCLZone must be paird with a TracyCLZoneSetEvent, check your code!"); } - assert(false && "clGetEventInfo failed, maybe a TracyCLZone is not paired with TracyCLZoneSetEvent"); + TRACY_ASSERT(false && "clGetEventInfo failed, maybe a TracyCLZone is not paired with TracyCLZoneSetEvent"); continue; } if (eventStatus != CL_COMPLETE) return; @@ -178,7 +178,7 @@ namespace tracy { if (err == CL_PROFILING_INFO_NOT_AVAILABLE) { TracyMessageL("command queue is not created with CL_QUEUE_PROFILING_ENABLE flag, check your code!"); - assert(false && "command queue is not created with CL_QUEUE_PROFILING_ENABLE flag"); + TRACY_ASSERT(false && "command queue is not created with CL_QUEUE_PROFILING_ENABLE flag"); } else TRACY_CL_CHECK_ERROR(err); @@ -187,9 +187,9 @@ namespace tracy { auto item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuTime); - MemWrite(&item->gpuTime.gpuTime, (int64_t)eventTimeStamp); - MemWrite(&item->gpuTime.queryId, (uint16_t)m_tail); - MemWrite(&item->gpuTime.context, m_contextId); + MemWrite(&item->gpuTime.gpuTime, int64_t(eventTimeStamp)); + MemWrite(&item->gpuTime.queryId, uint16_t(m_tail)); + MemWrite(&item->gpuTime.context, uint8_t(m_contextId)); Profiler::QueueSerialFinish(); if (eventInfo.phase == EventPhase::End) @@ -200,7 +200,7 @@ namespace tracy { } } - tracy_force_inline uint8_t GetId() const + tracy_force_inline int32_t GetId() const { return m_contextId; } @@ -222,7 +222,7 @@ namespace tracy { private: - unsigned int m_contextId; + int32_t m_contextId; EventInfo m_query[QueryCount]; unsigned int m_head; // index at which a new event should be inserted @@ -251,7 +251,7 @@ namespace tracy { MemWrite(&item->gpuZoneBegin.srcloc, (uint64_t)srcLoc); MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); MemWrite(&item->gpuZoneBegin.queryId, (uint16_t)m_beginQueryId); - MemWrite(&item->gpuZoneBegin.context, ctx->GetId()); + MemWrite(&item->gpuZoneBegin.context, uint8_t(ctx->GetId())); Profiler::QueueSerialFinish(); } @@ -276,7 +276,7 @@ namespace tracy { MemWrite(&item->gpuZoneBegin.srcloc, (uint64_t)srcLoc); MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); MemWrite(&item->gpuZoneBegin.queryId, (uint16_t)m_beginQueryId); - MemWrite(&item->gpuZoneBegin.context, ctx->GetId()); + MemWrite(&item->gpuZoneBegin.context, uint8_t(ctx->GetId())); Profiler::QueueSerialFinish(); } @@ -300,7 +300,7 @@ namespace tracy { MemWrite(&item->gpuZoneBegin.srcloc, srcloc); MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); MemWrite(&item->gpuZoneBegin.queryId, (uint16_t)m_beginQueryId); - MemWrite(&item->gpuZoneBegin.context, ctx->GetId()); + MemWrite(&item->gpuZoneBegin.context, uint8_t(ctx->GetId())); Profiler::QueueSerialFinish(); } @@ -324,7 +324,7 @@ namespace tracy { MemWrite(&item->gpuZoneBegin.srcloc, srcloc); MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); MemWrite(&item->gpuZoneBegin.queryId, (uint16_t)m_beginQueryId); - MemWrite(&item->gpuZoneBegin.context, ctx->GetId()); + MemWrite(&item->gpuZoneBegin.context, uint8_t(ctx->GetId())); Profiler::QueueSerialFinish(); } @@ -346,7 +346,7 @@ namespace tracy { MemWrite(&item->gpuZoneEnd.cpuTime, Profiler::GetTime()); MemWrite(&item->gpuZoneEnd.thread, GetThreadHandle()); MemWrite(&item->gpuZoneEnd.queryId, (uint16_t)queryId); - MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); + MemWrite(&item->gpuZoneEnd.context, uint8_t(m_ctx->GetId())); Profiler::QueueSerialFinish(); } diff --git a/libs/tracy/tracy/TracyOpenGL.hpp b/libs/tracy/tracy/TracyOpenGL.hpp index 30abd4f..d36851a 100644 --- a/libs/tracy/tracy/TracyOpenGL.hpp +++ b/libs/tracy/tracy/TracyOpenGL.hpp @@ -1,7 +1,12 @@ #ifndef __TRACYOPENGL_HPP__ #define __TRACYOPENGL_HPP__ -#if !defined TRACY_ENABLE || defined __APPLE__ +#ifdef __APPLE__ +#define TRACY_OPENGL_DISABLE +#warning "OpenGL timestamps are unreliable on Apple devices that still run OpenGL." +#endif + +#if !defined TRACY_ENABLE || defined TRACY_OPENGL_DISABLE #define TracyGpuContext #define TracyGpuContextName(x,y) @@ -32,23 +37,41 @@ class GpuCtxScope #else #include -#include #include +#ifdef TRACY_OPENGL_AUTO_CALIBRATION +# include +#endif #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" #include "../client/TracyCallstack.hpp" #include "../common/TracyAlign.hpp" #include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" #if !defined GL_TIMESTAMP && defined GL_TIMESTAMP_EXT # define GL_TIMESTAMP GL_TIMESTAMP_EXT # define GL_QUERY_COUNTER_BITS GL_QUERY_COUNTER_BITS_EXT +# define GL_QUERY_RESULT GL_QUERY_RESULT_EXT +# define GL_QUERY_RESULT_AVAILABLE GL_QUERY_RESULT_AVAILABLE_EXT +# define glGenQueries glGenQueriesEXT +# define glGetQueryiv glGetQueryivEXT # define glGetQueryObjectiv glGetQueryObjectivEXT # define glGetQueryObjectui64v glGetQueryObjectui64vEXT +# define glGetInteger64v glGetInteger64vEXT # define glQueryCounter glQueryCounterEXT #endif +#ifndef GL_MAJOR_VERSION +# define GL_MAJOR_VERSION 0x821B +#endif +#ifndef GL_NUM_EXTENSIONS +# define GL_NUM_EXTENSIONS 0x821D +#endif +#ifndef GL_QUERY_RESULT_NO_WAIT +# define GL_QUERY_RESULT_NO_WAIT 0x9194 +#endif + #define TracyGpuContext tracy::GetGpuCtx().ptr = (tracy::GpuCtx*)tracy::tracy_malloc( sizeof( tracy::GpuCtx ) ); new(tracy::GetGpuCtx().ptr) tracy::GpuCtx; #define TracyGpuContextName( name, size ) tracy::GetGpuCtx().ptr->Name( name, size ); #if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK @@ -87,24 +110,57 @@ class GpuCtx { friend class GpuCtxScope; - enum { QueryCount = 64 * 1024 }; + static constexpr size_t QueryCount = 64 * 1024; public: GpuCtx() - : m_context( GetGpuCtxCounter().fetch_add( 1, std::memory_order_relaxed ) ) + : m_context( NextGpuContextId() ) , m_head( 0 ) , m_tail( 0 ) + , m_supportsQueryBufferObject( false ) { - assert( m_context != 255 ); + ZoneScopedC( Color::Red4 ); - glGenQueries( QueryCount, m_query ); + TRACY_ASSERT( m_context != InvalidGpuContextId ); + + if( !CheckFeature( "GL_ARB_timer_query" ) && !CheckFeature( "GL_EXT_disjoint_timer_query" ) ) + { + Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Warning, Color::Tomato, 0, + "OpenGL context does not support timer queries." ); + } + + // check for GL_QUERY_RESULT_NO_WAIT support + m_supportsQueryBufferObject = CheckFeature( "GL_ARB_query_buffer_object" ); + if( !m_supportsQueryBufferObject ) + { + Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Info, 0, 0, + "OpenGL context does not support GL_ARB_query_buffer_object." ); + } + + GLint bits; + glGetQueryiv( GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &bits ); + if( bits == 0 ) + { + // all timestamp queries would resolve to 0 (and produce 0ns GPU zones). + // (this is the case for many TBDR GPUs, including Apple Silicon) + Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Warning, Color::Tomato, 0, + "OpenGL driver does not implement GL_TIMESTAMP precision." ); + } + TRACY_ASSERT( bits > 0 ); int64_t tgpu; glGetInteger64v( GL_TIMESTAMP, &tgpu ); int64_t tcpu = Profiler::GetTime(); - GLint bits; - glGetQueryiv( GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &bits ); +#ifdef TRACY_OPENGL_AUTO_CALIBRATION + // The anchor above is never refreshed; advertise calibration and emit periodic + // GpuCalibration events to correct CPU/GPU drift (see Recalibrate). Opt-in, + // because Recalibrate() calls glGetInteger64v( GL_TIMESTAMP ), which forces a + // CPU/GPU sync. + m_prevCalibration = GetHostTimeNs(); +#endif + + glGenQueries( QueryCount, m_query ); const float period = 1.f; const auto thread = GetThreadHandle(); @@ -113,8 +169,12 @@ class GpuCtx MemWrite( &item->gpuNewContext.gpuTime, tgpu ); MemWrite( &item->gpuNewContext.thread, thread ); MemWrite( &item->gpuNewContext.period, period ); - MemWrite( &item->gpuNewContext.context, m_context ); - MemWrite( &item->gpuNewContext.flags, uint8_t( 0 ) ); + MemWrite( &item->gpuNewContext.context, uint8_t( m_context ) ); +#ifdef TRACY_OPENGL_AUTO_CALIBRATION + MemWrite( &item->gpuNewContext.flags, GpuContextFlags( GpuContextCalibration ) ); +#else + MemWrite( &item->gpuNewContext.flags, GpuContextFlags( 0 ) ); +#endif MemWrite( &item->gpuNewContext.type, GpuContextType::OpenGl ); #ifdef TRACY_ON_DEMAND @@ -130,7 +190,7 @@ class GpuCtx memcpy( ptr, name, len ); TracyLfqPrepare( QueueType::GpuContextName ); - MemWrite( &item->gpuContextNameFat.context, m_context ); + MemWrite( &item->gpuContextNameFat.context, uint8_t( m_context ) ); MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); MemWrite( &item->gpuContextNameFat.size, len ); #ifdef TRACY_ON_DEMAND @@ -143,8 +203,6 @@ class GpuCtx { ZoneScopedC( Color::Red4 ); - if( m_tail == m_head ) return; - #ifdef TRACY_ON_DEMAND if( !GetProfiler().IsConnected() ) { @@ -153,19 +211,23 @@ class GpuCtx } #endif +#ifdef TRACY_OPENGL_AUTO_CALIBRATION + // Before the drain's early-returns, so it runs even on frames with no + // completed queries. + Recalibrate(); +#endif + + if( m_tail == m_head ) return; + while( m_tail != m_head ) { - GLint available; - glGetQueryObjectiv( m_query[m_tail], GL_QUERY_RESULT_AVAILABLE, &available ); - if( !available ) return; - uint64_t time; - glGetQueryObjectui64v( m_query[m_tail], GL_QUERY_RESULT, &time ); + if( !GetTimestamp(time, m_tail) ) return; TracyLfqPrepare( QueueType::GpuTime ); MemWrite( &item->gpuTime.gpuTime, (int64_t)time ); MemWrite( &item->gpuTime.queryId, (uint16_t)m_tail ); - MemWrite( &item->gpuTime.context, m_context ); + MemWrite( &item->gpuTime.context, uint8_t( m_context ) ); TracyLfqCommit; m_tail = ( m_tail + 1 ) % QueryCount; @@ -173,11 +235,92 @@ class GpuCtx } private: + // Returns whether the driver advertises a single extension (full GL_-prefixed token). + static bool CheckFeature( const char* feature ) + { + GLint major = 0; + glGetIntegerv( GL_MAJOR_VERSION, &major ); + if( glGetError() != GL_NO_ERROR ) major = 0; // pre-3.0: enum not supported + +#if defined(GL_VERSION_3_0) || defined(GL_ES_VERSION_3_0) + // GL 3 onwards: glGetStringi + if( major >= 3 ) + { + GLint numExt = 0; + glGetIntegerv( GL_NUM_EXTENSIONS, &numExt ); + for( GLint i = 0; i < numExt; i++ ) + { + auto ext = (const char*)glGetStringi( GL_EXTENSIONS, i ); + if( ext && strcmp( ext, feature ) == 0 ) return true; + } + return false; + } +#endif + + // pre GL3 fallback: + auto exts = (const char*)glGetString( GL_EXTENSIONS ); + return exts && strstr( exts, feature ) != nullptr; + } + + tracy_force_inline bool GetTimestamp( uint64_t& timestamp, unsigned int queryId ) + { + if( m_supportsQueryBufferObject ) + { + constexpr uint64_t sentinel = ~uint64_t(0); + uint64_t time = sentinel; + glGetQueryObjectui64v( m_query[queryId], GL_QUERY_RESULT_NO_WAIT, &time ); + if ( time == sentinel ) return false; + timestamp = time; + } + else + { + GLint available; + glGetQueryObjectiv( m_query[queryId], GL_QUERY_RESULT_AVAILABLE, &available ); + if( available == GL_FALSE ) return false; + uint64_t time; + glGetQueryObjectui64v( m_query[queryId], GL_QUERY_RESULT, &time ); + timestamp = time; + } + return true; + } + +#ifdef TRACY_OPENGL_AUTO_CALIBRATION + // Monotonic host ns for the inter-calibration interval (cpuDelta), kept + // separate from Profiler::GetTime() as in the D3D12/Vulkan backends. + static tracy_force_inline int64_t GetHostTimeNs() + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() ).count(); + } + + // OpenGL has no atomic CPU+GPU timestamp query, so sample back-to-back; the + // gap is negligible against the recalibration interval below. Note this forces + // a CPU/GPU sync, which is why the whole path is opt-in (TRACY_OPENGL_AUTO_CALIBRATION). + tracy_force_inline void Recalibrate() + { + const int64_t hostNow = GetHostTimeNs(); + const int64_t delta = hostNow - m_prevCalibration; + if( delta < 1000ll * 1000 * 1000 ) return; // throttle: ~once per second + + int64_t tgpu; + glGetInteger64v( GL_TIMESTAMP, &tgpu ); + const int64_t refCpu = Profiler::GetTime(); + m_prevCalibration = hostNow; + + TracyLfqPrepare( QueueType::GpuCalibration ); + MemWrite( &item->gpuCalibration.gpuTime, tgpu ); + MemWrite( &item->gpuCalibration.cpuTime, refCpu ); + MemWrite( &item->gpuCalibration.cpuDelta, delta ); + MemWrite( &item->gpuCalibration.context, uint8_t( m_context ) ); + TracyLfqCommit; + } +#endif + tracy_force_inline unsigned int NextQueryId() { const auto id = m_head; m_head = ( m_head + 1 ) % QueryCount; - assert( m_head != m_tail ); + TRACY_ASSERT( m_head != m_tail ); return id; } @@ -186,16 +329,22 @@ class GpuCtx return m_query[id]; } - tracy_force_inline uint8_t GetId() const + tracy_force_inline int32_t GetId() const { return m_context; } unsigned int m_query[QueryCount]; - uint8_t m_context; + int32_t m_context; unsigned int m_head; unsigned int m_tail; + +#ifdef TRACY_OPENGL_AUTO_CALIBRATION + int64_t m_prevCalibration; // host-ns timestamp of the last emitted calibration +#endif + + bool m_supportsQueryBufferObject; }; class GpuCtxScope @@ -204,6 +353,7 @@ class GpuCtxScope tracy_force_inline GpuCtxScope( const SourceLocationData* srcloc, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -217,7 +367,7 @@ class GpuCtxScope MemWrite( &item->gpuZoneBegin.cpuTime, Profiler::GetTime() ); memset( &item->gpuZoneBegin.thread, 0, sizeof( item->gpuZoneBegin.thread ) ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, GetGpuCtx().ptr->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( GetGpuCtx().ptr->GetId() ) ); MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); TracyLfqCommit; } @@ -225,6 +375,7 @@ class GpuCtxScope tracy_force_inline GpuCtxScope( const SourceLocationData* srcloc, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -244,7 +395,7 @@ class GpuCtxScope #endif MemWrite( &item->gpuZoneBegin.cpuTime, Profiler::GetTime() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, GetGpuCtx().ptr->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( GetGpuCtx().ptr->GetId() ) ); MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); TracyLfqCommit; } @@ -252,6 +403,7 @@ class GpuCtxScope tracy_force_inline GpuCtxScope( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -266,7 +418,7 @@ class GpuCtxScope MemWrite( &item->gpuZoneBegin.cpuTime, Profiler::GetTime() ); memset( &item->gpuZoneBegin.thread, 0, sizeof( item->gpuZoneBegin.thread ) ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, GetGpuCtx().ptr->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( GetGpuCtx().ptr->GetId() ) ); MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); TracyLfqCommit; } @@ -274,6 +426,7 @@ class GpuCtxScope tracy_force_inline GpuCtxScope( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -294,7 +447,7 @@ class GpuCtxScope const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); MemWrite( &item->gpuZoneBegin.cpuTime, Profiler::GetTime() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, GetGpuCtx().ptr->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( GetGpuCtx().ptr->GetId() ) ); MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); TracyLfqCommit; } @@ -306,16 +459,23 @@ class GpuCtxScope const auto queryId = GetGpuCtx().ptr->NextQueryId(); glQueryCounter( GetGpuCtx().ptr->TranslateOpenGlQueryId( queryId ), GL_TIMESTAMP ); +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif TracyLfqPrepare( QueueType::GpuZoneEnd ); MemWrite( &item->gpuZoneEnd.cpuTime, Profiler::GetTime() ); memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) ); MemWrite( &item->gpuZoneEnd.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneEnd.context, GetGpuCtx().ptr->GetId() ); + MemWrite( &item->gpuZoneEnd.context, uint8_t( GetGpuCtx().ptr->GetId() ) ); TracyLfqCommit; } private: const bool m_active; + +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif }; } diff --git a/libs/tracy/tracy/TracyVulkan.hpp b/libs/tracy/tracy/TracyVulkan.hpp index 429f299..9a3ac47 100644 --- a/libs/tracy/tracy/TracyVulkan.hpp +++ b/libs/tracy/tracy/TracyVulkan.hpp @@ -37,11 +37,11 @@ using TracyVkCtx = void*; # error "You must include Vulkan headers before including TracyVulkan.hpp" #endif -#include #include #include "Tracy.hpp" #include "../client/TracyProfiler.hpp" #include "../client/TracyCallstack.hpp" +#include "../common/TracyAssert.hpp" #include @@ -91,7 +91,11 @@ class VkCtx { friend class VkCtxScope; - enum { QueryCount = 64 * 1024 }; +#if !defined __APPLE__ + static constexpr size_t QueryCount = 64 * 1024; +#else + static constexpr size_t QueryCount = 4 * 1024; +#endif public: #if defined TRACY_VK_USE_SYMBOL_TABLE @@ -101,7 +105,7 @@ class VkCtx #endif : m_device( device ) , m_timeDomain( VK_TIME_DOMAIN_DEVICE_EXT ) - , m_context( GetGpuCtxCounter().fetch_add( 1, std::memory_order_relaxed ) ) + , m_context( NextGpuContextId() ) , m_head( 0 ) , m_tail( 0 ) , m_oldCnt( 0 ) @@ -110,7 +114,7 @@ class VkCtx , m_vkGetCalibratedTimestampsEXT( vkGetCalibratedTimestampsEXT ) #endif { - assert( m_context != 255 ); + TRACY_ASSERT( m_context != InvalidGpuContextId ); #if defined TRACY_VK_USE_SYMBOL_TABLE PopulateSymbolTable(instance, instanceProcAddr, deviceProcAddr); @@ -164,13 +168,13 @@ class VkCtx else { FindCalibratedTimestampDeviation(); - Calibrate( device, m_prevCalibration, tgpu ); + Calibrate( m_prevCalibration, tgpu ); tcpu = Profiler::GetTime(); } WriteInitialItem( physdev, tcpu, tgpu ); - m_res = (int64_t*)tracy_malloc( sizeof( int64_t ) * m_queryCount ); + AllocateQueryResultBuffer(); } #if defined VK_EXT_host_query_reset @@ -186,7 +190,7 @@ class VkCtx #endif : m_device( device ) , m_timeDomain( VK_TIME_DOMAIN_DEVICE_EXT ) - , m_context( GetGpuCtxCounter().fetch_add(1, std::memory_order_relaxed) ) + , m_context( NextGpuContextId() ) , m_head( 0 ) , m_tail( 0 ) , m_oldCnt( 0 ) @@ -195,23 +199,23 @@ class VkCtx , m_vkGetCalibratedTimestampsEXT( vkGetCalibratedTimestampsEXT ) #endif { - assert( m_context != 255); + TRACY_ASSERT( m_context != InvalidGpuContextId); #if defined TRACY_VK_USE_SYMBOL_TABLE PopulateSymbolTable(instance, instanceProcAddr, deviceProcAddr); m_vkGetCalibratedTimestampsEXT = m_symbols.vkGetCalibratedTimestampsEXT; #endif - assert( VK_FUNCTION_WRAPPER( vkResetQueryPool ) != nullptr ); - assert( VK_FUNCTION_WRAPPER( vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ) != nullptr ); - assert( VK_FUNCTION_WRAPPER( vkGetCalibratedTimestampsEXT ) != nullptr ); + TRACY_ASSERT( VK_FUNCTION_WRAPPER( vkResetQueryPool ) != nullptr ); + TRACY_ASSERT( VK_FUNCTION_WRAPPER( vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ) != nullptr ); + TRACY_ASSERT( VK_FUNCTION_WRAPPER( vkGetCalibratedTimestampsEXT ) != nullptr ); FindAvailableTimeDomains( physdev, VK_FUNCTION_WRAPPER( vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ) ); // We require a host time domain to be available to properly calibrate. FindCalibratedTimestampDeviation(); int64_t tgpu; - Calibrate( device, m_prevCalibration, tgpu ); + Calibrate( m_prevCalibration, tgpu ); int64_t tcpu = Profiler::GetTime(); CreateQueryPool(); @@ -219,9 +223,7 @@ class VkCtx WriteInitialItem( physdev, tcpu, tgpu ); - // We need the buffer to be twice as large for availability values - size_t resSize = sizeof( int64_t ) * m_queryCount * 2; - m_res = (int64_t*)tracy_malloc( resSize ); + AllocateQueryResultBuffer(); } #endif @@ -238,7 +240,7 @@ class VkCtx auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuContextName ); - MemWrite( &item->gpuContextNameFat.context, m_context ); + MemWrite( &item->gpuContextNameFat.context, uint8_t( m_context ) ); MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr ); MemWrite( &item->gpuContextNameFat.size, len ); #ifdef TRACY_ON_DEMAND @@ -263,11 +265,11 @@ class VkCtx m_tail = head; m_oldCnt = 0; int64_t tgpu; - if( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ) Calibrate( m_device, m_prevCalibration, tgpu ); + if( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ) Calibrate( m_prevCalibration, tgpu, 10 ); return; } #endif - assert( head > m_tail ); + TRACY_ASSERT( head > m_tail ); const unsigned int wrappedTail = (unsigned int)( m_tail % m_queryCount ); @@ -280,7 +282,7 @@ class VkCtx else { cnt = (unsigned int)( head - m_tail ); - assert( cnt <= m_queryCount ); + TRACY_ASSERT( cnt <= m_queryCount ); if( wrappedTail + cnt > m_queryCount ) { cnt = m_queryCount - wrappedTail; @@ -305,14 +307,14 @@ class VkCtx MemWrite( &item->hdr.type, QueueType::GpuTime ); MemWrite( &item->gpuTime.gpuTime, m_res[idx * 2] ); MemWrite( &item->gpuTime.queryId, uint16_t( wrappedTail + idx ) ); - MemWrite( &item->gpuTime.context, m_context ); + MemWrite( &item->gpuTime.context, uint8_t( m_context ) ); Profiler::QueueSerialFinish(); } if( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ) { - int64_t tgpu, tcpu; - Calibrate( m_device, tcpu, tgpu ); + int64_t tgpu, tcpu = m_prevCalibration; + Calibrate( tcpu, tgpu, 10 ); const auto refCpu = Profiler::GetTime(); const auto delta = tcpu - m_prevCalibration; if( delta > 0 ) @@ -323,7 +325,7 @@ class VkCtx MemWrite( &item->gpuCalibration.gpuTime, tgpu ); MemWrite( &item->gpuCalibration.cpuTime, refCpu ); MemWrite( &item->gpuCalibration.cpuDelta, delta ); - MemWrite( &item->gpuCalibration.context, m_context ); + MemWrite( &item->gpuCalibration.context, uint8_t( m_context ) ); Profiler::QueueSerialFinish(); } } @@ -341,7 +343,7 @@ class VkCtx return id % m_queryCount; } - tracy_force_inline uint8_t GetId() const + tracy_force_inline int32_t GetId() const { return m_context; } @@ -352,30 +354,49 @@ class VkCtx } private: - tracy_force_inline void Calibrate( VkDevice device, int64_t& tCpu, int64_t& tGpu ) + tracy_force_inline int64_t VulkanTimeToPlatformTime(uint64_t tCpu) { - assert( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ); +#if defined _WIN32 + return tCpu * m_qpcToNs; +#elif defined __linux__ && defined CLOCK_MONOTONIC_RAW + return tCpu; +#else + TRACY_ASSERT( false ); + return 0; +#endif + } + + tracy_force_inline bool GetCalibratedTimestamps( int64_t& tCpu, int64_t& tGpu, uint64_t& tDeviation ) + { + TRACY_ASSERT( m_device ); + TRACY_ASSERT( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ); VkCalibratedTimestampInfoEXT spec[2] = { { VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, VK_TIME_DOMAIN_DEVICE_EXT }, { VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, m_timeDomain }, }; uint64_t ts[2]; uint64_t deviation; - do + VkResult result = m_vkGetCalibratedTimestampsEXT( m_device, 2, spec, ts, &deviation ); + if ( result != VK_SUCCESS ) return false; + tGpu = ts[0]; + tCpu = VulkanTimeToPlatformTime(ts[1]); + tDeviation = deviation; + return true; + } + + tracy_force_inline bool Calibrate( int64_t& tCpu, int64_t& tGpu, uint64_t maxSamples = ~uint64_t(0) ) + { + for ( uint64_t i = 0; i < maxSamples; ++i ) { - m_vkGetCalibratedTimestampsEXT( device, 2, spec, ts, &deviation ); + int64_t cpu, gpu; + uint64_t deviation; + if( !GetCalibratedTimestamps( cpu, gpu, deviation ) ) continue; + if( deviation > m_deviation ) continue; + tCpu = cpu; + tGpu = gpu; + return true; } - while( deviation > m_deviation ); - -#if defined _WIN32 - tGpu = ts[0]; - tCpu = ts[1] * m_qpcToNs; -#elif defined __linux__ && defined CLOCK_MONOTONIC_RAW - tGpu = ts[0]; - tCpu = ts[1]; -#else - assert( false ); -#endif + return false; } tracy_force_inline void CreateQueryPool() @@ -391,6 +412,12 @@ class VkCtx } } + tracy_force_inline void AllocateQueryResultBuffer() + { + // We need the buffer to be twice as large for availability values + m_res = (int64_t*)tracy_malloc( sizeof( int64_t ) * m_queryCount * 2 ); + } + tracy_force_inline void FindAvailableTimeDomains( VkPhysicalDevice physicalDevice, PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT _vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ) { uint32_t num; @@ -414,28 +441,25 @@ class VkCtx tracy_force_inline void FindCalibratedTimestampDeviation() { - assert( m_timeDomain != VK_TIME_DOMAIN_DEVICE_EXT ); +#if defined _WIN32 + m_qpcToNs = int64_t( 1000000000. / GetFrequencyQpc() ); +#endif + constexpr size_t NumProbes = 32; - VkCalibratedTimestampInfoEXT spec[2] = { - { VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, VK_TIME_DOMAIN_DEVICE_EXT }, - { VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, m_timeDomain }, - }; - uint64_t ts[2]; uint64_t deviation[NumProbes]; for( size_t i=0; i deviation[i] ) { minDeviation = deviation[i]; } } - m_deviation = minDeviation * 3 / 2; - -#if defined _WIN32 - m_qpcToNs = int64_t( 1000000000. / GetFrequencyQpc() ); -#endif + m_deviation = minDeviation * 3 / 2; // i.e., 1.5x minDeviation } tracy_force_inline void WriteInitialItem( VkPhysicalDevice physdev, int64_t tcpu, int64_t tgpu ) @@ -453,8 +477,8 @@ class VkCtx MemWrite( &item->gpuNewContext.gpuTime, tgpu ); memset( &item->gpuNewContext.thread, 0, sizeof( item->gpuNewContext.thread ) ); MemWrite( &item->gpuNewContext.period, period ); - MemWrite( &item->gpuNewContext.context, m_context ); - MemWrite( &item->gpuNewContext.flags, flags ); + MemWrite( &item->gpuNewContext.context, uint8_t( m_context ) ); + MemWrite( &item->gpuNewContext.flags, GpuContextFlags( flags ) ); MemWrite( &item->gpuNewContext.type, GpuContextType::Vulkan ); #ifdef TRACY_ON_DEMAND @@ -497,7 +521,7 @@ class VkCtx int64_t m_qpcToNs; #endif int64_t m_prevCalibration; - uint8_t m_context; + int32_t m_context; std::atomic m_head; uint64_t m_tail; @@ -515,6 +539,7 @@ class VkCtxScope tracy_force_inline VkCtxScope( VkCtx* ctx, const SourceLocationData* srcloc, VkCommandBuffer cmdbuf, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -532,13 +557,14 @@ class VkCtxScope MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, ctx->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( ctx->GetId() ) ); Profiler::QueueSerialFinish(); } tracy_force_inline VkCtxScope( VkCtx* ctx, const SourceLocationData* srcloc, VkCommandBuffer cmdbuf, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -565,13 +591,14 @@ class VkCtxScope MemWrite( &item->gpuZoneBegin.srcloc, (uint64_t)srcloc ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, ctx->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( ctx->GetId() ) ); Profiler::QueueSerialFinish(); } tracy_force_inline VkCtxScope( VkCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, VkCommandBuffer cmdbuf, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -590,13 +617,14 @@ class VkCtxScope MemWrite( &item->gpuZoneBegin.srcloc, srcloc ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, ctx->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( ctx->GetId() ) ); Profiler::QueueSerialFinish(); } tracy_force_inline VkCtxScope( VkCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, VkCommandBuffer cmdbuf, int32_t depth, bool is_active ) #ifdef TRACY_ON_DEMAND : m_active( is_active && GetProfiler().IsConnected() ) + , m_connectionId( GetProfiler().ConnectionId() ) #else : m_active( is_active ) #endif @@ -624,7 +652,7 @@ class VkCtxScope MemWrite( &item->gpuZoneBegin.srcloc, srcloc ); MemWrite( &item->gpuZoneBegin.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneBegin.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneBegin.context, ctx->GetId() ); + MemWrite( &item->gpuZoneBegin.context, uint8_t( ctx->GetId() ) ); Profiler::QueueSerialFinish(); } @@ -635,18 +663,25 @@ class VkCtxScope const auto queryId = m_ctx->NextQueryId(); CONTEXT_VK_FUNCTION_WRAPPER( vkCmdWriteTimestamp( m_cmdbuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, m_ctx->m_query, queryId ) ); +#ifdef TRACY_ON_DEMAND + if( GetProfiler().ConnectionId() != m_connectionId ) return; +#endif auto item = Profiler::QueueSerial(); MemWrite( &item->hdr.type, QueueType::GpuZoneEndSerial ); MemWrite( &item->gpuZoneEnd.cpuTime, Profiler::GetTime() ); MemWrite( &item->gpuZoneEnd.thread, GetThreadHandle() ); MemWrite( &item->gpuZoneEnd.queryId, uint16_t( queryId ) ); - MemWrite( &item->gpuZoneEnd.context, m_ctx->GetId() ); + MemWrite( &item->gpuZoneEnd.context, uint8_t( m_ctx->GetId() ) ); Profiler::QueueSerialFinish(); } private: const bool m_active; +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif + VkCommandBuffer m_cmdbuf; VkCtx* m_ctx; }; diff --git a/libs/tracy/tracy/TracyWebGPU.hpp b/libs/tracy/tracy/TracyWebGPU.hpp new file mode 100644 index 0000000..10c5a93 --- /dev/null +++ b/libs/tracy/tracy/TracyWebGPU.hpp @@ -0,0 +1,982 @@ +#ifndef __TRACYWEBGPU_HPP__ +#define __TRACYWEBGPU_HPP__ + +// WebGPU, unlike other graphics APIs, has many annoying restrictions that complicate +// the design of the Tracy WebGPU back-end: +// - there's no CPU/GPU clock calibration API +// - submitting GPU commands that touch a buffer that the host is mapping is not permitted +// - resolving timestamps require destination offsets aligned to 256 bytes +// - timestamps are only available at pass granularity (implementations may need to emulate this) +// - spec mandates timestamps to be in nanoseconds (implementationw may need to emulate this) + +#ifndef TRACY_ENABLE + +#define TracyWebGPUSetupDeviceDescriptor(deviceDescriptor) + +#define TracyWebGPUContext(instance, device, queue) nullptr +#define TracyWebGPUDestroy(ctx) +#define TracyWebGPUContextName(ctx, name, size) + +#define TracyWebGPUZone(ctx, encoder, passDesc, name) +#define TracyWebGPUZoneC(ctx, encoder, passDesc, name, color) +#define TracyWebGPUNamedZone(ctx, varname, encoder, passDesc, name, active) +#define TracyWebGPUNamedZoneC(ctx, varname, encoder, passDesc, name, color, active) +#define TracyWebGPUZoneTransient(ctx, varname, encoder, passDesc, name, active) + +#define TracyWebGPUZoneS(ctx, encoder, passDesc, name, depth) +#define TracyWebGPUZoneCS(ctx, encoder, passDesc, name, color, depth) +#define TracyWebGPUNamedZoneS(ctx, varname, encoder, passDesc, name, depth, active) +#define TracyWebGPUNamedZoneCS(ctx, varname, encoder, passDesc, name, color, depth, active) +#define TracyWebGPUZoneTransientS(ctx, varname, encoder, passDesc, name, depth, active) + +#define TracyWebGPUCollect(ctx) + +namespace tracy +{ + class WebGPUZoneScope {}; +} + +using TracyWebGPUCtx = void*; + +#else + +#include "Tracy.hpp" +#include "../client/TracyProfiler.hpp" +#include "../client/TracyCallstack.hpp" +#include "../common/TracyAlign.hpp" +#include "../common/TracyAlloc.hpp" +#include "../common/TracyAssert.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// piggy-back on WGPU_DAWN_TOGGLES_DESCRIPTOR_INIT to detect Dawn header +#ifdef WGPU_DAWN_TOGGLES_DESCRIPTOR_INIT +#define TRACY_WEBGPU_DAWN_NATIVE (1) +#include +#else +#define TRACY_WEBGPU_WGPU_NATIVE (1) +#include +#endif + +#ifndef TRACY_WEBGPU_DEBUG_LEVEL +#define TRACY_WEBGPU_DEBUG_LEVEL (0) +#endif//TRACY_WEBGPU_DEBUG_LEVEL + +#if TRACY_WEBGPU_DEBUG_LEVEL +#define TracyWebGPUDebug(...) __VA_ARGS__; +#if defined(_MSC_VER) +extern "C" int32_t IsDebuggerPresent(void); +#define TracyWebGPUBreak() if (IsDebuggerPresent()) __debugbreak() +#else +#define TracyWebGPUBreak() ((void)0) +#endif +#define TracyWebGPUAssert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyWebGPUBreak(); } +#else +#define TracyWebGPUDebug(...) +#define TracyWebGPUBreak() +#define TracyWebGPUAssert(predicate, ...) TRACY_ASSERT(predicate); +#endif + +#define TracyWebGPULog(severity, msg) do { char buffer [1024]; int len = snprintf(buffer, sizeof(buffer), "TracyWebGPU: %s", msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, len, buffer ); } while(false) +#define TracyWebGPUPanic(msg, ...) do { TracyWebGPULog(Error, msg); TracyWebGPUAssert(false && "TracyWebGPU: " msg); __VA_ARGS__; } while(false) + +namespace tracy +{ + + class WebGPUQueueCtx + { + friend class WebGPUZoneScope; + + int32_t m_contextId = InvalidGpuContextId; + + std::mutex m_collectionMutex; + + WGPUInstance m_instance = nullptr; + WGPUDevice m_device = nullptr; + WGPUQueue m_queue = nullptr; + + struct ReadbackStage + { + WGPUBuffer buffer = nullptr; + std::atomic copiedUpto {0}; + std::atomic mapStatus = {}; + WGPUFuture pendingFuture = {}; + }; + static_assert(std::atomic::is_always_lock_free, "WGPUMapAsyncStatus must be lock-free atomic"); + + WGPUQuerySet m_querySet = nullptr; + WGPUBuffer m_resolveBuffer = nullptr; + ReadbackStage m_readbackReel [3]; + std::atomic m_writeIdx {0}; + + using atomic_counter = std::atomic; + atomic_counter m_queryCounter = 0; + atomic_counter m_previousCheckpoint = 0; + + uint32_t m_queryLimit = 0; + + std::vector m_shadowBuffer; + + using WallTime = std::chrono::steady_clock::time_point; + static tracy_force_inline auto GetWallTime() { return WallTime::clock::now(); } + static tracy_force_inline auto Milliseconds(int value) { return std::chrono::milliseconds(value); } + + static bool WaitQueueIdle(WGPUQueue queue, WGPUInstance instance) + { + bool gpuDone = false; + WGPUQueueWorkDoneCallbackInfo doneCB = {}; + doneCB.mode = WGPUCallbackMode_AllowProcessEvents; + doneCB.callback = [](WGPUQueueWorkDoneStatus, WGPUStringView, void* userData, void*) { + *static_cast(userData) = true; + }; + doneCB.userdata1 = &gpuDone; + wgpuQueueOnSubmittedWorkDone(queue, doneCB); + + const auto deadline = GetWallTime() + Milliseconds(2000); + while (!gpuDone && GetWallTime() < deadline) + wgpuInstanceProcessEvents(instance); + return gpuDone; + } + + static const uint64_t* MapBufferSync(WGPUBuffer buffer, WGPUInstance instance) + { + struct MapCtx { WGPUMapAsyncStatus status = {}; } ctx; + WGPUBufferMapCallbackInfo cbInfo = {}; + cbInfo.mode = WGPUCallbackMode_AllowProcessEvents; + cbInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView, void* userData, void*) { + auto* ctx = static_cast(userData); + ctx->status = status; + }; + cbInfo.userdata1 = &ctx; + size_t offset = 0; + size_t size = 2 * sizeof(uint64_t); + wgpuBufferMapAsync(buffer, WGPUMapMode_Read, offset, size, cbInfo); + + const auto deadline = GetWallTime() + Milliseconds(2000); + while (ctx.status == 0 && GetWallTime() < deadline) + wgpuInstanceProcessEvents(instance); + + if (ctx.status != WGPUMapAsyncStatus_Success) return nullptr; + auto data = wgpuBufferGetConstMappedRange(buffer, offset, size); + return static_cast(data); + } + + struct Calibration { + int64_t minCpuRange = ~uint64_t(0) >> 1; + struct Regression + { + int64_t n = 0; + int64_t mean_x = 0; + int64_t mean_y = 0; + int64_t S_xx = 0; + int64_t S_xy = 0; + void Update(int64_t x, int64_t y) + { + n += 1; + int64_t dx = x - mean_x; + int64_t dy = y - mean_y; + mean_x += dx / n; + mean_y += dy / n; + S_xx += dx * (x - mean_x); + S_xy += dx * (y - mean_y); + } + double Slope() const { return double(S_xy) / S_xx; } + double Intercept() const { return mean_y - Slope() * mean_x; } + }; + Regression cpuToGpuModel; // cpu-ticks to gpu-ticks + Regression cpuRangeModel; // cpu-tick interval uncertainty + Regression wallToGpuModel; // nanoseconds to gpu-ticks + void GetReferenceTime(uint64_t& cpuTime, uint64_t& gpuTime) const + { + // the mean belongs to the regression line + cpuTime = cpuToGpuModel.mean_x; + gpuTime = cpuToGpuModel.mean_y; + } + double Period() const { return 1.0 / wallToGpuModel.Slope(); } // ns/tick + bool AcceptX(const Regression& r, int64_t x, double threshold = 3.0) const { + if (r.n < 2) return true; + auto dx = x - r.mean_x; + if (dx <= 0) return true; // always accept "tighter" outliers + double variance = double(r.S_xx) / (r.n - 1); + if (variance == 0.0) return true; + // WARN: dx*dx "could" overflow, but very unlikely in practice + double zz = (double)(dx*dx) / variance; + return zz <= (threshold*threshold); + } + bool Update(WallTime twall0, WallTime twall1, uint64_t tcpu0, uint64_t tcpu1, uint64_t tgpu) + { + using namespace std::chrono; + int64_t cpuRange = tcpu1 - tcpu0; + cpuRangeModel.Update(cpuRange, 0); + if (!AcceptX(cpuRangeModel, cpuRange, 1.0)) return false; + // Process sample: + int64_t tcpu = tcpu0 + (tcpu1 - tcpu0) / 2; // mid-point + int64_t twall = duration_cast( + (twall0 + (twall1 - twall0) / 2) // mid-point + .time_since_epoch() + ).count(); + // incremental regression: + cpuToGpuModel.Update(tcpu, tgpu); + wallToGpuModel.Update(twall, tgpu); + TracyWebGPUDebug( fprintf(stderr, "----- (sample accepted! wall = %lld | cpu = %lld | gpu = %lld | period = %f)\n", twall, tcpu, tgpu, Period()) ); + return true; + } + } m_calibration; + + tracy_force_inline void SubmitQueueItem(tracy::QueueItem* item) + { +#ifdef TRACY_ON_DEMAND + GetProfiler().DeferItem(*item); +#endif + Profiler::QueueSerialFinish(); + } + + bool CalibrateClocks(uint64_t& outCpuTime, uint64_t& outGpuTime, double& period) + { + // WebGPU does not have any clock calibration API. + // This routine attempts to estimates a reasonable (cpuTime, gpuTime) correlation + // by sampling CPU and GPU timestamps around a "synchronous" draw call. + // Several samples are taken to tighten the estimation. + + ZoneScoped; + + WGPUShaderSourceWGSL wgslSrc = {}; + wgslSrc.chain.sType = WGPUSType_ShaderSourceWGSL; + wgslSrc.code = + { + R"( + @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { + var p = array(vec4f(-1,-1,.5,1), vec4f(3,-1,.5,1), vec4f(-1,3,.5,1)); + return p[i]; + } + @fragment fn fs() -> @location(0) vec4f { return vec4f(0.0); } + )", + WGPU_STRLEN + }; + WGPUShaderModuleDescriptor smDesc = {}; + smDesc.nextInChain = reinterpret_cast(&wgslSrc); + WGPUShaderModule calibShader = wgpuDeviceCreateShaderModule(m_device, &smDesc); + if (!calibShader) { TracyWebGPUPanic("Failed to create calibration shader.", return false); } + + WGPUTextureDescriptor texDesc = {}; + texDesc.usage = WGPUTextureUsage_RenderAttachment; + texDesc.dimension = WGPUTextureDimension_2D; + texDesc.size = { 1, 1, 1 }; + texDesc.format = WGPUTextureFormat_BGRA8Unorm; + texDesc.mipLevelCount = 1; + texDesc.sampleCount = 1; + WGPUTexture tex = wgpuDeviceCreateTexture(m_device, &texDesc); + if (!tex) { wgpuShaderModuleRelease(calibShader); TracyWebGPUPanic("Failed to create calibration scratch texture.", return false); } + WGPUTextureView texView = wgpuTextureCreateView(tex, nullptr); + if (!texView) { wgpuTextureRelease(tex); wgpuShaderModuleRelease(calibShader); TracyWebGPUPanic("Failed to create calibration scratch texture view.", return false); } + + WGPUColorTargetState colorTarget = {}; + colorTarget.format = WGPUTextureFormat_BGRA8Unorm; + colorTarget.writeMask = WGPUColorWriteMask_All; + WGPUFragmentState fragState = {}; + fragState.module = calibShader; + fragState.entryPoint = { "fs", WGPU_STRLEN }; + fragState.targetCount = 1; + fragState.targets = &colorTarget; + WGPURenderPipelineDescriptor pipeDesc = {}; + pipeDesc.vertex.module = calibShader; + pipeDesc.vertex.entryPoint = { "vs", WGPU_STRLEN }; + pipeDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeDesc.multisample.count = 1; + pipeDesc.fragment = &fragState; + WGPURenderPipeline calibPipeline = wgpuDeviceCreateRenderPipeline(m_device, &pipeDesc); + if (!calibPipeline) { wgpuTextureViewRelease(texView); wgpuTextureRelease(tex); wgpuShaderModuleRelease(calibShader); TracyWebGPUPanic("Failed to create calibration pipeline.", return false); } + + uint32_t queryId = 0; + WGPUPassTimestampWrites anchorTs = {}; + anchorTs.querySet = m_querySet; + anchorTs.beginningOfPassWriteIndex = queryId; + anchorTs.endOfPassWriteIndex = queryId+1; + + WGPURenderPassColorAttachment att = {}; + att.view = texView; + att.loadOp = WGPULoadOp_Clear; + att.storeOp = WGPUStoreOp_Store; + att.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + + WGPURenderPassDescriptor passDesc = {}; + passDesc.colorAttachmentCount = 1; + passDesc.colorAttachments = &att; + passDesc.timestampWrites = &anchorTs; + + // calibration loop + const auto deadline = GetWallTime() + Milliseconds(100); + for (int i = 0; i < 1000; ++i) + { + // loop until time budget (100ms) allows, but ensure at least 5 iterations + if ((GetWallTime() >= deadline) && (i > 5)) + break; + + WGPUCommandEncoder enc = wgpuDeviceCreateCommandEncoder(m_device, nullptr); + if (!enc) { TracyWebGPUPanic("Failed to create command encoder for time calibration.", return false); } + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(enc, &passDesc); + wgpuRenderPassEncoderSetPipeline(pass, calibPipeline); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); + + WGPUBuffer readBackBuffer = m_readbackReel[0].buffer; + uint32_t byteOffset = queryId * sizeof(uint64_t); + uint32_t sizeInBytes = 2 * sizeof(uint64_t); + wgpuCommandEncoderResolveQuerySet(enc, m_querySet, queryId, 2, m_resolveBuffer, byteOffset); + wgpuCommandEncoderCopyBufferToBuffer(enc, m_resolveBuffer, byteOffset, readBackBuffer, byteOffset, sizeInBytes); + + WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(enc, nullptr); + wgpuCommandEncoderRelease(enc); + if (!cmd) { TracyWebGPUPanic("Failed to finish calibration command encoder.", return false); } + + WaitQueueIdle(m_queue, m_instance); + int64_t cpu [2] = {}; + int64_t gpu [2] = {}; + WallTime wall [2] = {}; + cpu[0] = Profiler::GetTime(); + wall[0] = GetWallTime(); + wgpuQueueSubmit(m_queue, 1, &cmd); + wgpuCommandBufferRelease(cmd); + WaitQueueIdle(m_queue, m_instance); + wall[1] = GetWallTime(); + cpu[1] = Profiler::GetTime(); + auto gpuTimestamps = MapBufferSync(readBackBuffer, m_instance); + TracyWebGPUAssert(gpuTimestamps != nullptr); + gpu[0] = gpuTimestamps[0]; + gpu[1] = gpuTimestamps[1]; + wgpuBufferUnmap(readBackBuffer); + TracyWebGPUDebug( + fprintf(stdout, "[%03d] CalibrateClocks() [CPU] %16lld | %16lld | /// %lld\n", i, cpu[0], cpu[1], cpu[1]-cpu[0]); + fprintf(stdout, "----------------------- [GPU] %16llu | %16llu | /// %lld\n", gpu[0], gpu[1], gpu[1]-gpu[0]); + uint64_t cpuTimeRef, gpuTimeRef; + m_calibration.GetReferenceTime(cpuTimeRef, gpuTimeRef); + if (gpu[0] < gpuTimeRef) + fprintf(stdout, "!!!!! CalibrateClocks() -> WARNING!!! going backwards!\n%llu\n%llu\n%lld\n", gpuTimeRef, gpu[0], gpu[0] - gpuTimeRef); + ); + + // skip first sample since it is quite jittery (lazy intialization of WebGPU objects) + if (i == 0) + continue; + + m_calibration.Update(wall[0], wall[1], cpu[0], cpu[1], gpu[0]); + }; + + TracyWebGPUDebug( + fprintf(stdout, "##### CalibrateClocks() WALL = %lld | CPU = %lld | GPU = %lld | period = %f\n", + m_calibration.wallToGpuModel.mean_x, + m_calibration.cpuToGpuModel.mean_x, + m_calibration.cpuToGpuModel.mean_y, + m_calibration.Period()); + ); + + wgpuRenderPipelineRelease(calibPipeline); + wgpuShaderModuleRelease(calibShader); + wgpuTextureViewRelease(texView); + wgpuTextureRelease(tex); + + m_calibration.GetReferenceTime(outCpuTime, outGpuTime); + period = m_calibration.Period(); + // assume 1 ns/tick if the period estimation is close enough to 1 + if (std::abs(period - 1.0) < 0.001) + period = 1.0; + + return true; + } + + public: + class Requirements + { + private: +# if (TRACY_WEBGPU_DAWN_NATIVE) + WGPUDawnTogglesDescriptor dawnTogglesDesc = {}; + static constexpr int NumExtras = 0; +# elif (TRACY_WEBGPU_WGPU_NATIVE) + static constexpr int NumExtras = 1; +# endif + + public: + static constexpr int NumFeatures = 1 + NumExtras; + WGPUFeatureName features [NumFeatures] = {}; + WGPUChainedStruct* togglesDesc = nullptr; + + Requirements() + { + this->features[0] = WGPUFeatureName_TimestampQuery; +# if (TRACY_WEBGPU_WGPU_NATIVE) + this->features[1] = (WGPUFeatureName)WGPUNativeFeature_TimestampQueryInsideEncoders; +# endif +# if (TRACY_WEBGPU_DAWN_NATIVE) + static const char* dawnDisabledToggles[] = { "timestamp_quantization" }; + static const char* dawnEnabledToggles[] = { "disable_timestamp_query_conversion" }; + this->dawnTogglesDesc.chain.sType = WGPUSType_DawnTogglesDescriptor; + this->dawnTogglesDesc.disabledToggles = dawnDisabledToggles; + this->dawnTogglesDesc.disabledToggleCount = 1; + this->dawnTogglesDesc.enabledToggles = dawnEnabledToggles; + this->dawnTogglesDesc.enabledToggleCount = 1; + this->togglesDesc = reinterpret_cast(&this->dawnTogglesDesc); +# endif + } + + static bool VerifyDevice(WGPUDevice device) + { + if (device == nullptr) + TracyWebGPUPanic("Invalid WGPUDevice.", return false); + if (wgpuDeviceHasFeature(device, WGPUFeatureName_TimestampQuery) == WGPU_FALSE) + TracyWebGPUPanic("Device is missing feature WGPUFeatureName_TimestampQuery.", return false); +# if (TRACY_WEBGPU_DAWN_NATIVE) + bool hasDisableConversion = false, hasQuantization = false; + for (const char* t : ::dawn::native::GetTogglesUsed(device)) + { + if (strcmp(t, "disable_timestamp_query_conversion") == 0) + hasDisableConversion = true; + if (strcmp(t, "timestamp_quantization") == 0) + hasQuantization = true; + } + if (!hasDisableConversion) + TracyWebGPUPanic("Device must toggle disable_timestamp_query_conversion (Dawn).", return false); + if (hasQuantization) + TracyWebGPUPanic("Device must disable timestamp_quantization (Dawn).", return false); +# elif (TRACY_WEBGPU_WGPU_NATIVE) + if (wgpuDeviceHasFeature(device, (WGPUFeatureName)WGPUNativeFeature_TimestampQueryInsideEncoders) == WGPU_FALSE) + TracyWebGPUPanic("Device is missing feature WGPUNativeFeature_TimestampQueryInsideEncoders (wgpu-native).", return false); +# endif + return true; + } + + void ApplyToDeviceDescriptor(WGPUDeviceDescriptor& deviceDescriptor) + { + size_t userCount = deviceDescriptor.requiredFeatureCount; + size_t totalCount = userCount + NumFeatures; + // NOTE: this allocation will leak... + auto* mergedFeatures = static_cast(tracy_malloc(totalCount * sizeof(WGPUFeatureName))); + if (userCount > 0 && deviceDescriptor.requiredFeatures) + memcpy(mergedFeatures, deviceDescriptor.requiredFeatures, userCount * sizeof(WGPUFeatureName)); + memcpy(mergedFeatures + userCount, features, NumFeatures * sizeof(WGPUFeatureName)); + deviceDescriptor.requiredFeatures = mergedFeatures; + deviceDescriptor.requiredFeatureCount = totalCount; + + if (togglesDesc) + { + togglesDesc->next = deviceDescriptor.nextInChain; + deviceDescriptor.nextInChain = togglesDesc; + } + } + }; + + WebGPUQueueCtx(WGPUInstance instance, WGPUDevice device, WGPUQueue queue) + { + ZoneScopedC(Color::Red4); + + if (!Requirements::VerifyDevice(device)) + TracyWebGPUPanic("GPU profiling disabled: the device did not set the required features.", return); + + TracyWebGPUAssert(instance); wgpuInstanceAddRef(instance); m_instance = instance; + TracyWebGPUAssert(device); wgpuDeviceAddRef(device); m_device = device; + TracyWebGPUAssert(queue); wgpuQueueAddRef(queue); m_queue = queue; + + // Setup Query Set: must have even size since queries are issued in pairs. + // (The WebGPU spec mandates 4096, with no way to query the device limit.) + WGPUQuerySetDescriptor qsDesc = {}; + qsDesc.type = WGPUQueryType_Timestamp; + qsDesc.count = 4096; + for (;;) + { + m_querySet = wgpuDeviceCreateQuerySet(m_device, &qsDesc); + if (m_querySet) break; + qsDesc.count /= 2; + if (qsDesc.count < 128) break; + } + if (m_querySet == nullptr) + TracyWebGPUPanic("Failed to create timestamp query set.", return); + m_queryLimit = qsDesc.count; + + WGPUBufferDescriptor resolveDesc = {}; + resolveDesc.usage = WGPUBufferUsage_QueryResolve | WGPUBufferUsage_CopySrc; + resolveDesc.size = static_cast(m_queryLimit) * sizeof(uint64_t); + m_resolveBuffer = wgpuDeviceCreateBuffer(m_device, &resolveDesc); + if (!m_resolveBuffer) + TracyWebGPUPanic("Failed to create timestamp resolve buffer.", return); + + WGPUBufferDescriptor readbackDesc = {}; + readbackDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead; + readbackDesc.size = static_cast(m_queryLimit) * sizeof(uint64_t); + for (auto& stage : m_readbackReel) + { + stage.buffer = wgpuDeviceCreateBuffer(m_device, &readbackDesc); + stage.copiedUpto = 0; + if (!stage.buffer) { TracyWebGPUPanic("Failed to create timestamp readback buffer.", return); } + } + + uint64_t cpuTimestamp = 0; + uint64_t gpuTimestamp = 0; + double period = 0.0; // in nanoseconds per gpu-tick + if (!CalibrateClocks(cpuTimestamp, gpuTimestamp, period)) + TracyWebGPUPanic("Failed to calibrate CPU/GPU clocks.", return); + + TracyWebGPUDebug( fprintf(stdout, "[WebGPUQueueCtx] cpuTimestamp: %llu | gpuTimestamp: %llu | period: %f\n", cpuTimestamp, gpuTimestamp, period) ); + m_shadowBuffer.resize(m_queryLimit, gpuTimestamp); + + // All setup completed: register the context. + m_contextId = NextGpuContextId(); + ZoneValue(m_contextId); + + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuNewContext); + MemWrite(&item->gpuNewContext.cpuTime, static_cast(cpuTimestamp)); + MemWrite(&item->gpuNewContext.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuNewContext.thread, static_cast(0)); + MemWrite(&item->gpuNewContext.period, static_cast(period)); + MemWrite(&item->gpuNewContext.context, static_cast(GetId())); + MemWrite(&item->gpuNewContext.flags, GpuContextFlags(0)); // no calibration available + MemWrite(&item->gpuNewContext.type, GpuContextType::WebGPU); + SubmitQueueItem(item); + } + + ~WebGPUQueueCtx() + { + // TODO: a few problems to address later during this final Collect(): + // 1. ensure "partial" query batches are collected + // 2. ensure all readback stages are collected and empty + // 3. ensure readback buffers are not mapped before deleting them + Collect(); + + for (auto& stage : m_readbackReel) + if (stage.buffer) { wgpuBufferRelease(stage.buffer); stage.buffer = nullptr; } + if (m_resolveBuffer) { wgpuBufferRelease(m_resolveBuffer); m_resolveBuffer = nullptr; } + if (m_querySet) { wgpuQuerySetRelease(m_querySet); m_querySet = nullptr; } + if (m_queue) { wgpuQueueRelease(m_queue); m_queue = nullptr; } + if (m_device) { wgpuDeviceRelease(m_device); m_device = nullptr; } + if (m_instance) { wgpuInstanceRelease(m_instance); m_instance = nullptr; } + } + + tracy_force_inline int32_t GetId() const + { + return m_contextId; + } + + void Name(const char* name, uint16_t len) + { + auto ptr = (char*)tracy_malloc(len); + memcpy(ptr, name, len); + + auto item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuContextName); + MemWrite(&item->gpuContextNameFat.context, static_cast(GetId())); + MemWrite(&item->gpuContextNameFat.ptr, (uint64_t)ptr); + MemWrite(&item->gpuContextNameFat.size, len); + SubmitQueueItem(item); + } + + void Collect(bool webgpuProcessEvents=false) + { +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif + if (!m_collectionMutex.try_lock()) return; + std::unique_lock lock(m_collectionMutex, std::adopt_lock); + + ZoneScopedC(Color::Red4); + + if (Distance(m_previousCheckpoint, m_queryCounter) <= 0) + return; + + // Current Readback "Reel" Stages: + const int state = m_writeIdx; + const int fillingIdx = (state + 0) % 3; // this is where instrumentation is pushing new queries + const int pendingIdx = (state + 1) % 3; // instrumentation is done here; ready to be collected + const int collectIdx = (state + 2) % 3; // this is where queries are being collected right now + + // Ensure readback buffer has been mapped to the host + auto& collectStage = m_readbackReel[collectIdx]; + if (collectStage.pendingFuture.id != 0) + { + if (webgpuProcessEvents) + wgpuInstanceProcessEvents(m_instance); + if (collectStage.mapStatus == WGPUMapAsyncStatus{}) + return; // callback hasn't fired yet + collectStage.pendingFuture = {}; + if (collectStage.mapStatus != WGPUMapAsyncStatus_Success) + TracyWebGPUPanic("Colect(): unable to map readback buffer.", return); + } + + if (collectStage.mapStatus == WGPUMapAsyncStatus_Success) + { + const uint64_t* ts = static_cast( + wgpuBufferGetConstMappedRange(collectStage.buffer, 0, + static_cast(m_queryLimit) * sizeof(uint64_t))); + if (ts) + { + uint64_t ticket = m_previousCheckpoint; + const uint64_t end = collectStage.copiedUpto; + TracyWebGPUDebug( fprintf(stdout, "[TWG] Collect [%d] (%llu, %llu)\n", collectIdx, ticket, end) ); + for (; Distance(ticket, end) > 0; ticket += 2) + { + const uint32_t slotB = RingIndex(ticket); + const uint32_t slotE = slotB + 1; + TracyWebGPUDebug( + fprintf(stderr, + "[TWG] slot B=%4u E=%4u ts[B]=%llu ts[E]=%llu shadow[E]=%llu ts-diff=%lld shadow-diff=%lld\n", + slotB, slotE, + ts[slotB], ts[slotE], m_shadowBuffer[slotE], + Distance(ts[slotB], ts[slotE]), + Distance(m_shadowBuffer[slotE], ts[slotE])); + ); + if (Distance(m_shadowBuffer[slotE], ts[slotE]) <= 0) + break; // GPU hasn't written this timestamp yet; retry next Collect() + EmitGpuTime(ts[slotB], slotB); + EmitGpuTime(ts[slotE], slotE); + } + m_previousCheckpoint = ticket; + + if (Distance(ticket, end) > 0) + return; // still unresolved queries in this buffer; come back next Collect() + } + + // All queries resolved (or getMappedRange failed): unmap and fall through to rotate. + wgpuBufferUnmap(collectStage.buffer); + collectStage.mapStatus = {}; + } + + // At this point, all queries in the collect buffer have been processed. + // (it's now tie to "rotate" the buffers around...) + + // Has any ResolveQueryBatch call landed in this reel stage since it was last recycled? + // (Are there any queries to resolve and collect at all?) + if (m_readbackReel[fillingIdx].copiedUpto <= m_previousCheckpoint) + return; + + // Rotate/Cycle the Readback Pipeline State: + // the buffer that was just collected shall now be used for instrumentation + collectStage.copiedUpto = m_previousCheckpoint.load(); + m_writeIdx = collectIdx; // atomically commit the pipeline rotation + + auto& nextToCollect = m_readbackReel[pendingIdx]; + WGPUBufferMapCallbackInfo cbInfo = {}; + cbInfo.mode = WGPUCallbackMode_AllowProcessEvents; + cbInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView, void* userData, void*) + { + auto* stage = static_cast(userData); + stage->mapStatus = status; + }; + cbInfo.userdata1 = &nextToCollect; + nextToCollect.pendingFuture = wgpuBufferMapAsync( + nextToCollect.buffer, WGPUMapMode_Read, 0, + static_cast(m_queryLimit) * sizeof(uint64_t), cbInfo); + } + + private: + void EmitGpuTime(uint64_t gpuTimestamp, uint32_t queryId) + { + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuTime); + MemWrite(&item->gpuTime.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuTime.queryId, static_cast(queryId)); + MemWrite(&item->gpuTime.context, static_cast(GetId())); + Profiler::QueueSerialFinish(); + m_shadowBuffer[queryId] = gpuTimestamp; + } + + tracy_force_inline uint32_t RingCapacity() const { return m_queryLimit; } + + tracy_force_inline uint32_t RingIndex(uint64_t t) const + { + return static_cast(t % RingCapacity()); + } + + tracy_force_inline static int64_t Distance(uint64_t begin, uint64_t end) + { + return static_cast(end - begin); + } + + tracy_force_inline uint64_t NextQueryId() + { + const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); + if (Distance(m_previousCheckpoint, ticket) + >= static_cast(RingCapacity())) + { + TracyWebGPULog(Warning, "Too many pending GPU queries: stalling!"); + Collect(); + } + return ticket; + } + }; + + class WebGPUZoneScope + { + const bool m_active; +#ifdef TRACY_ON_DEMAND + uint64_t m_connectionId = 0; +#endif + WebGPUQueueCtx* m_ctx = nullptr; + WGPUCommandEncoder m_encoder = nullptr; + uint64_t m_rawTicket = 0; + uint32_t m_queryId = 0; + + WGPUPassTimestampWrites m_timestampWrites = {}; + + void ResolveQueryBatch(uint32_t queryBatchStartId) + { + // Ensure there are pending queries to resolve in the batch + auto& stage = m_ctx->m_readbackReel[m_ctx->m_writeIdx]; + if (WebGPUQueueCtx::Distance(stage.copiedUpto, m_rawTicket) <= 0) return; + + // 32 queries = 32 * 8 bytes = 256 bytes + TracyWebGPUAssert(queryBatchStartId % 32 == 0, return); + queryBatchStartId = m_ctx->RingIndex(queryBatchStartId); + + const uint64_t blockOffset = static_cast(queryBatchStartId) * sizeof(uint64_t); + wgpuCommandEncoderResolveQuerySet( + m_encoder, + m_ctx->m_querySet, + queryBatchStartId, 32, + m_ctx->m_resolveBuffer, + blockOffset // MUST be a multiple of (aligned to) 256... + ); + + auto readbackBuffer = stage.buffer; + wgpuCommandEncoderCopyBufferToBuffer( + m_encoder, + m_ctx->m_resolveBuffer, + blockOffset, + readbackBuffer, + blockOffset, + 32 * sizeof(uint64_t) + ); + + // Advance this stage's high-water mark to cover the block just encoded. + // TODO: maybe we can use fetch_add to increment the atomic and not need + // to keep track of the raw ticket; Collect would need to derive the raw + // end ticket number. + const uint64_t blockEnd = m_rawTicket; + uint64_t prev = stage.copiedUpto; + while ((WebGPUQueueCtx::Distance(prev, blockEnd) > 0) && + !stage.copiedUpto.compare_exchange_weak(prev, blockEnd)) {} + TracyWebGPUDebug( fprintf(stdout, "[TWG] WebGPUZoneScope [%d] (%d,%d)\n", (int)m_ctx->m_writeIdx, queryBatchStartId, queryBatchStartId+32) ); + } + + tracy_force_inline void WriteQueueItem(const SourceLocationData* srcLocation, int32_t callstackDepth, uint32_t sourceLine, const char* sourceFile, size_t sourceFileLen, const char* functionName, size_t functionNameLen, const char* zoneName, size_t zoneNameLen) + { + if (!m_active) return; + + const bool captureCallstack = callstackDepth > 0 && has_callstack(); + const bool transientZone = srcLocation == nullptr; + uint64_t srcLocationAddr = reinterpret_cast(srcLocation); + + QueueItem* item = nullptr; + QueueType itemType; + if (transientZone) + { + srcLocationAddr = Profiler::AllocSourceLocation(sourceLine, sourceFile, sourceFileLen, functionName, functionNameLen, zoneName, zoneNameLen); + if (captureCallstack) + { + item = Profiler::QueueSerialCallstack(Callstack(callstackDepth)); + itemType = QueueType::GpuZoneBeginAllocSrcLocCallstackSerial; + } + else + { + item = Profiler::QueueSerial(); + itemType = QueueType::GpuZoneBeginAllocSrcLocSerial; + } + } + else + { + if (captureCallstack) + { + item = Profiler::QueueSerialCallstack(Callstack(callstackDepth)); + itemType = QueueType::GpuZoneBeginCallstackSerial; + } + else + { + item = Profiler::QueueSerial(); + itemType = QueueType::GpuZoneBeginSerial; + } + } + + MemWrite(&item->hdr.type, itemType); + MemWrite(&item->gpuZoneBegin.cpuTime, Profiler::GetTime()); + MemWrite(&item->gpuZoneBegin.srcloc, srcLocationAddr); + MemWrite(&item->gpuZoneBegin.thread, GetThreadHandle()); + MemWrite(&item->gpuZoneBegin.queryId, static_cast(m_queryId)); + MemWrite(&item->gpuZoneBegin.context, static_cast(m_ctx->GetId())); + Profiler::QueueSerialFinish(); + } + + // Fills in m_timestampWrites and assigns its address to passDesc.timestampWrites. + // Works with both WGPURenderPassDescriptor and WGPUComputePassDescriptor. + template + tracy_force_inline void InitBase(WebGPUQueueCtx* ctx, WGPUCommandEncoder encoder, PassDescriptor& passDesc) + { + m_ctx = ctx; + m_encoder = encoder; + + m_rawTicket = m_ctx->NextQueryId(); + m_queryId = m_ctx->RingIndex(m_rawTicket); + + m_timestampWrites.querySet = m_ctx->m_querySet; + m_timestampWrites.beginningOfPassWriteIndex = m_queryId; + m_timestampWrites.endOfPassWriteIndex = m_queryId + 1; + passDesc.timestampWrites = &m_timestampWrites; + } + + public: + template + tracy_force_inline WebGPUZoneScope(WebGPUQueueCtx* ctx, WGPUCommandEncoder encoder, PassDescriptor& passDesc, const SourceLocationData* srcLocation, bool active) +#ifdef TRACY_ON_DEMAND + : m_active(active && GetProfiler().IsConnected()) + , m_connectionId(GetProfiler().ConnectionId()) +#else + : m_active(active) +#endif + { + if (!m_active || !ctx) return; + InitBase(ctx, encoder, passDesc); + WriteQueueItem(srcLocation, 0, 0, nullptr, 0, nullptr, 0, nullptr, 0); + } + + template + tracy_force_inline WebGPUZoneScope(WebGPUQueueCtx* ctx, WGPUCommandEncoder encoder, PassDescriptor& passDesc, const SourceLocationData* srcLocation, int32_t depth, bool active) +#ifdef TRACY_ON_DEMAND + : m_active(active && GetProfiler().IsConnected()) + , m_connectionId(GetProfiler().ConnectionId()) +#else + : m_active(active) +#endif + { + if (!m_active || !ctx) return; + InitBase(ctx, encoder, passDesc); + WriteQueueItem(srcLocation, depth, 0, nullptr, 0, nullptr, 0, nullptr, 0); + } + + template + tracy_force_inline WebGPUZoneScope(WebGPUQueueCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, WGPUCommandEncoder encoder, PassDescriptor& passDesc, bool active) +#ifdef TRACY_ON_DEMAND + : m_active(active && GetProfiler().IsConnected()) + , m_connectionId(GetProfiler().ConnectionId()) +#else + : m_active(active) +#endif + { + if (!m_active || !ctx) return; + InitBase(ctx, encoder, passDesc); + WriteQueueItem(nullptr, 0, line, source, sourceSz, function, functionSz, name, nameSz); + } + + template + tracy_force_inline WebGPUZoneScope(WebGPUQueueCtx* ctx, uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, WGPUCommandEncoder encoder, PassDescriptor& passDesc, int32_t depth, bool active) +#ifdef TRACY_ON_DEMAND + : m_active(active && GetProfiler().IsConnected()) + , m_connectionId(GetProfiler().ConnectionId()) +#else + : m_active(active) +#endif + { + if (!m_active || !ctx) return; + InitBase(ctx, encoder, passDesc); + WriteQueueItem(nullptr, depth, line, source, sourceSz, function, functionSz, name, nameSz); + } + + tracy_force_inline ~WebGPUZoneScope() + { + if (!m_active || !m_ctx) return; + + const auto queryId = m_queryId + 1; + +#ifdef TRACY_ON_DEMAND + if (GetProfiler().ConnectionId() == m_connectionId) + { +#endif + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuZoneEndSerial); + MemWrite(&item->gpuZoneEnd.cpuTime, Profiler::GetTime()); + MemWrite(&item->gpuZoneEnd.thread, GetThreadHandle()); + MemWrite(&item->gpuZoneEnd.queryId, static_cast(queryId)); + MemWrite(&item->gpuZoneEnd.context, static_cast(m_ctx->GetId())); + Profiler::QueueSerialFinish(); +#ifdef TRACY_ON_DEMAND + } +#endif + + if (m_queryId % 32 == 0) + ResolveQueryBatch(m_queryId-32); + } + }; + + static inline void DestroyWebGPUContext(WebGPUQueueCtx* ctx) + { + if (!ctx) return; + ctx->~WebGPUQueueCtx(); + tracy_free(ctx); + } + + static inline WebGPUQueueCtx* CreateWebGPUContext(WGPUInstance instance, WGPUDevice device, WGPUQueue queue) + { + auto* ctx = static_cast(tracy_malloc(sizeof(WebGPUQueueCtx))); + new (ctx) WebGPUQueueCtx{ instance, device, queue }; + return ctx; + } + +} + +#undef TracyWebGPUPanic +#undef TracyWebGPULog +#undef TracyWebGPUAssert +#undef TracyWebGPUBreak +#undef TracyWebGPUDebug +#undef TRACY_WEBGPU_DEBUG_LEVEL + +using TracyWebGPUCtx = tracy::WebGPUQueueCtx*; + +#define TracyWebGPUSetupDeviceDescriptor(deviceDescriptor) tracy::WebGPUQueueCtx::Requirements TracyConcat(__tracy_wgpu_setup_, TracyLine); TracyConcat(__tracy_wgpu_setup_, TracyLine).ApplyToDeviceDescriptor(deviceDescriptor) + +#define TracyWebGPUContext(instance, device, queue) tracy::CreateWebGPUContext(instance, device, queue); +#define TracyWebGPUDestroy(ctx) tracy::DestroyWebGPUContext(ctx); +#define TracyWebGPUContextName(ctx, name, size) if (ctx) ctx->Name(name, size); + +#define TracyWebGPUUnnamedZone ___tracy_gpu_webgpu_zone +#define TracyWebGPUSrcLocSymbol TracyConcat(__tracy_webgpu_source_location,TracyLine) +#define TracyWebGPUSrcLocObject(name, color) static constexpr tracy::SourceLocationData TracyWebGPUSrcLocSymbol { name, TracyFunction, TracyFile, (uint32_t)TracyLine, color }; + +#if defined TRACY_HAS_CALLSTACK && defined TRACY_CALLSTACK +# define TracyWebGPUZone(ctx, encoder, passDesc, name) TracyWebGPUNamedZoneS(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, TRACY_CALLSTACK, true) +# define TracyWebGPUZoneC(ctx, encoder, passDesc, name, color) TracyWebGPUNamedZoneCS(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, color, TRACY_CALLSTACK, true) +# define TracyWebGPUNamedZone(ctx, varname, encoder, passDesc, name, active) TracyWebGPUSrcLocObject(name, 0); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, TRACY_CALLSTACK, active }; +# define TracyWebGPUNamedZoneC(ctx, varname, encoder, passDesc, name, color, active) TracyWebGPUSrcLocObject(name, color); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, TRACY_CALLSTACK, active }; +# define TracyWebGPUZoneTransient(ctx, varname, encoder, passDesc, name, active) TracyWebGPUZoneTransientS(ctx, varname, encoder, passDesc, name, TRACY_CALLSTACK, active) +#else +# define TracyWebGPUZone(ctx, encoder, passDesc, name) TracyWebGPUNamedZone(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, true) +# define TracyWebGPUZoneC(ctx, encoder, passDesc, name, color) TracyWebGPUNamedZoneC(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, color, true) +# define TracyWebGPUNamedZone(ctx, varname, encoder, passDesc, name, active) TracyWebGPUSrcLocObject(name, 0); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, active }; +# define TracyWebGPUNamedZoneC(ctx, varname, encoder, passDesc, name, color, active) TracyWebGPUSrcLocObject(name, color); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, active }; +# define TracyWebGPUZoneTransient(ctx, varname, encoder, passDesc, name, active) tracy::WebGPUZoneScope varname{ ctx, TracyLine, TracyFile, strlen(TracyFile), TracyFunction, strlen(TracyFunction), name, strlen(name), encoder, passDesc, active }; +#endif + +#ifdef TRACY_HAS_CALLSTACK +# define TracyWebGPUZoneS(ctx, encoder, passDesc, name, depth) TracyWebGPUNamedZoneS(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, depth, true) +# define TracyWebGPUZoneCS(ctx, encoder, passDesc, name, color, depth) TracyWebGPUNamedZoneCS(ctx, TracyWebGPUUnnamedZone, encoder, passDesc, name, color, depth, true) +# define TracyWebGPUNamedZoneS(ctx, varname, encoder, passDesc, name, depth, active) TracyWebGPUSrcLocObject(name, 0); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, depth, active }; +# define TracyWebGPUNamedZoneCS(ctx, varname, encoder, passDesc, name, color, depth, active) TracyWebGPUSrcLocObject(name, color); tracy::WebGPUZoneScope varname{ ctx, encoder, passDesc, &TracyWebGPUSrcLocSymbol, depth, active }; +# define TracyWebGPUZoneTransientS(ctx, varname, encoder, passDesc, name, depth, active) tracy::WebGPUZoneScope varname{ ctx, TracyLine, TracyFile, strlen(TracyFile), TracyFunction, strlen(TracyFunction), name, strlen(name), encoder, passDesc, depth, active }; +#else +# define TracyWebGPUZoneS(ctx, encoder, passDesc, name, depth) TracyWebGPUZone(ctx, encoder, passDesc, name) +# define TracyWebGPUZoneCS(ctx, encoder, passDesc, name, color, depth) TracyWebGPUZoneC(ctx, encoder, passDesc, name, color) +# define TracyWebGPUNamedZoneS(ctx, varname, encoder, passDesc, name, depth, active) TracyWebGPUNamedZone(ctx, varname, encoder, passDesc, name, active) +# define TracyWebGPUNamedZoneCS(ctx, varname, encoder, passDesc, name, color, depth, active) TracyWebGPUNamedZoneC(ctx, varname, encoder, passDesc, name, color, active) +# define TracyWebGPUZoneTransientS(ctx, varname, encoder, passDesc, name, depth, active) TracyWebGPUZoneTransient(ctx, varname, encoder, passDesc, name, active) +#endif + +#define TracyWebGPUCollect(ctx) if (ctx) ctx->Collect(); + +#endif + +#endif diff --git a/src/ztracy.zig b/src/ztracy.zig index 27c9f94..1f9ecf1 100644 --- a/src/ztracy.zig +++ b/src/ztracy.zig @@ -80,5 +80,5 @@ pub const AppInfo = if (enabled) impl.AppInfo else stub.AppInfo; pub const TracyAllocator = if (enabled) impl.TracyAllocator else stub.TracyAllocator; test { - std.testing.refAllDeclsRecursive(@This()); + std.testing.refAllDecls(@This()); }