add profiler

This commit is contained in:
Rokas Puzonas 2025-02-03 01:02:41 +02:00
parent dc3e66cfdc
commit a87a9c104e
3 changed files with 348 additions and 185 deletions

View File

@ -34,6 +34,11 @@ ui: UI,
channels: std.BoundedArray(Channel, 64) = .{}, channels: std.BoundedArray(Channel, 64) = .{},
ni_daq: NIDaq, ni_daq: NIDaq,
shown_window: enum {
channels,
add_from_device
} = .channels,
pub fn init(allocator: std.mem.Allocator) !App { pub fn init(allocator: std.mem.Allocator) !App {
return App{ return App{
.allocator = allocator, .allocator = allocator,
@ -143,10 +148,201 @@ pub fn appendChannelFromFile(self: *App, file: std.fs.File) !void {
}); });
} }
pub fn tick(self: *App) !void { fn showChannelsWindow(self: *App) void {
rl.beginDrawing(); const scroll_area = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels"));
defer rl.endDrawing(); scroll_area.layout_axis = .Y;
defer self.ui.popScrollbar();
for (self.channels.slice()) |*_channel| {
const channel: *Channel = _channel;
const channel_box = self.ui.newBoxFromPtr(channel);
channel_box.background = rl.Color.blue;
channel_box.layout_axis = .Y;
channel_box.size.x = UI.Size.percent(1, 0);
channel_box.size.y = UI.Size.childrenSum(1);
self.ui.pushParent(channel_box);
defer self.ui.popParent();
const graph_box = self.ui.newBoxFromString("Graph");
graph_box.background = rl.Color.blue;
graph_box.layout_axis = .Y;
graph_box.size.x = UI.Size.percent(1, 0);
graph_box.size.y = UI.Size.pixels(256, 1);
Graph.drawCached(&channel.view_cache, graph_box.persistent.size, channel.view_rect, channel.samples.owned);
if (channel.view_cache.texture) |texture| {
graph_box.texture = texture.texture;
}
{
const sample_count: f32 = @floatFromInt(channel.samples.owned.len);
const min_visible_samples = 1; // sample_count*0.02;
const minimap_box = self.ui.newBoxFromString("Minimap");
minimap_box.background = rl.Color.dark_purple;
minimap_box.layout_axis = .X;
minimap_box.size.x = UI.Size.percent(1, 0);
minimap_box.size.y = UI.Size.pixels(32, 1);
self.ui.pushParent(minimap_box);
defer self.ui.popParent();
const minimap_rect = minimap_box.computedRect();
const middle_box = self.ui.newBoxFromString("Middle knob");
{
middle_box.flags.insert(.clickable);
middle_box.flags.insert(.draggable_x);
middle_box.background = rl.Color.black.alpha(0.5);
middle_box.size.y = UI.Size.pixels(32, 1);
}
const left_knob_box = self.ui.newBoxFromString("Left knob");
{
left_knob_box.flags.insert(.clickable);
left_knob_box.flags.insert(.draggable_x);
left_knob_box.background = rl.Color.black.alpha(0.5);
left_knob_box.size.x = UI.Size.pixels(8, 1);
left_knob_box.size.y = UI.Size.pixels(32, 1);
}
const right_knob_box = self.ui.newBoxFromString("Right knob");
{
right_knob_box.flags.insert(.clickable);
right_knob_box.flags.insert(.draggable_x);
right_knob_box.background = rl.Color.black.alpha(0.5);
right_knob_box.size.x = UI.Size.pixels(8, 1);
right_knob_box.size.y = UI.Size.pixels(32, 1);
}
const left_knob_size = left_knob_box.persistent.size.x;
const right_knob_size = right_knob_box.persistent.size.x;
const left_signal = self.ui.signalFromBox(left_knob_box);
if (left_signal.dragged()) {
channel.view_rect.from += remap(
f32,
0, minimap_rect.width,
0, sample_count,
left_signal.drag.x
);
channel.view_rect.from = clamp(channel.view_rect.from, 0, channel.view_rect.to-min_visible_samples);
}
const right_signal = self.ui.signalFromBox(right_knob_box);
if (right_signal.dragged()) {
channel.view_rect.to += remap(
f32,
0, minimap_rect.width,
0, sample_count,
right_signal.drag.x
);
channel.view_rect.to = clamp(channel.view_rect.to, channel.view_rect.from+min_visible_samples, sample_count);
}
const middle_signal = self.ui.signalFromBox(middle_box);
if (middle_signal.dragged()) {
var samples_moved = middle_signal.drag.x / minimap_rect.width * sample_count;
samples_moved = clamp(samples_moved, -channel.view_rect.from, sample_count - channel.view_rect.to);
channel.view_rect.from += samples_moved;
channel.view_rect.to += samples_moved;
}
left_knob_box.setFixedX(remap(f32,
0, sample_count,
0, minimap_rect.width - left_knob_size - right_knob_size,
channel.view_rect.from
));
right_knob_box.setFixedX(remap(f32,
0, sample_count,
left_knob_size, minimap_rect.width - right_knob_size,
channel.view_rect.to
));
middle_box.setFixedX(remap(f32,
0, sample_count,
left_knob_size, minimap_rect.width - right_knob_size,
channel.view_rect.from
));
middle_box.setFixedWidth(remap(f32,
0, sample_count,
0, minimap_rect.width - right_knob_size - left_knob_size,
channel.view_rect.to - channel.view_rect.from
));
}
}
}
fn showAddFromDeviceWindow(self: *App) !void {
const names = try self.ni_daq.listDeviceNames();
_ = names;
}
fn showToolbar(self: *App) void {
const toolbar = self.ui.newBoxFromString("Toolbar");
toolbar.flags.insert(.clickable);
toolbar.background = rl.Color.green;
toolbar.layout_axis = .X;
toolbar.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.pixels(32, 1),
};
self.ui.pushParent(toolbar);
defer self.ui.popParent();
{
const box = self.ui.newBoxFromString("Add from file");
box.flags.insert(.clickable);
box.background = rl.Color.red;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from file", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
if (Platform.openFilePicker()) |file| {
defer file.close();
// TODO: Handle error
self.appendChannelFromFile(file) catch @panic("Failed to append channel from file");
} else |err| {
// TODO: Show error message to user;
log.err("Failed to pick file: {}", .{ err });
}
}
}
{
const box = self.ui.newBoxFromString("Add from device");
box.flags.insert(.clickable);
box.background = rl.Color.lime;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from device", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
if (self.shown_window == .add_from_device) {
self.shown_window = .channels;
} else {
self.shown_window = .add_from_device;
}
}
}
}
pub fn tick(self: *App) !void {
rl.clearBackground(srcery.black); rl.clearBackground(srcery.black);
if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) { if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) {
@ -156,191 +352,16 @@ pub fn tick(self: *App) !void {
{ {
self.ui.begin(); self.ui.begin();
defer self.ui.end(); defer self.ui.end();
self.ui.getParent().?.layout_axis = .Y;
{ const root_box = self.ui.getParent().?;
const toolbar = self.ui.newBoxFromString("Toolbar"); root_box.layout_axis = .Y;
toolbar.flags.insert(.clickable);
toolbar.background = rl.Color.green;
toolbar.layout_axis = .X;
toolbar.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.pixels(32, 1),
};
self.ui.pushParent(toolbar);
defer self.ui.popParent();
{ self.showToolbar();
const box = self.ui.newBoxFromString("Add from file");
box.flags.insert(.clickable);
box.background = rl.Color.red;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from file", .text);
const signal = self.ui.signalFromBox(box); if (self.shown_window == .channels) {
if (signal.clicked()) { self.showChannelsWindow();
if (Platform.openFilePicker()) |file| { } else if (self.shown_window == .add_from_device) {
defer file.close(); try self.showAddFromDeviceWindow();
// TODO: Handle error
self.appendChannelFromFile(file) catch @panic("Failed to append channel from file");
} else |err| {
// TODO: Show error message to user;
log.err("Failed to pick file: {}", .{ err });
}
}
}
{
const box = self.ui.newBoxFromString("Add from device");
box.flags.insert(.clickable);
box.background = rl.Color.lime;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from device", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
std.debug.print("click two!\n", .{});
}
}
}
{
const scroll_area = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels"));
scroll_area.layout_axis = .Y;
defer self.ui.popScrollbar();
for (self.channels.slice()) |*_channel| {
const channel: *Channel = _channel;
const channel_box = self.ui.newBoxFromPtr(channel);
channel_box.background = rl.Color.blue;
channel_box.layout_axis = .Y;
channel_box.size.x = UI.Size.percent(1, 0);
channel_box.size.y = UI.Size.childrenSum(1);
self.ui.pushParent(channel_box);
defer self.ui.popParent();
const graph_box = self.ui.newBoxFromString("Graph");
graph_box.background = rl.Color.blue;
graph_box.layout_axis = .Y;
graph_box.size.x = UI.Size.percent(1, 0);
graph_box.size.y = UI.Size.pixels(256, 1);
Graph.drawCached(&channel.view_cache, graph_box.persistent.size, channel.view_rect, channel.samples.owned);
if (channel.view_cache.texture) |texture| {
graph_box.texture = texture.texture;
}
{
const sample_count: f32 = @floatFromInt(channel.samples.owned.len);
const min_visible_samples = 1; // sample_count*0.02;
const minimap_box = self.ui.newBoxFromString("Minimap");
minimap_box.background = rl.Color.dark_purple;
minimap_box.layout_axis = .X;
minimap_box.size.x = UI.Size.percent(1, 0);
minimap_box.size.y = UI.Size.pixels(32, 1);
self.ui.pushParent(minimap_box);
defer self.ui.popParent();
const minimap_rect = minimap_box.computedRect();
const middle_box = self.ui.newBoxFromString("Middle knob");
{
middle_box.flags.insert(.clickable);
middle_box.flags.insert(.draggable_x);
middle_box.background = rl.Color.black.alpha(0.5);
middle_box.size.y = UI.Size.pixels(32, 1);
}
const left_knob_box = self.ui.newBoxFromString("Left knob");
{
left_knob_box.flags.insert(.clickable);
left_knob_box.flags.insert(.draggable_x);
left_knob_box.background = rl.Color.black.alpha(0.5);
left_knob_box.size.x = UI.Size.pixels(8, 1);
left_knob_box.size.y = UI.Size.pixels(32, 1);
}
const right_knob_box = self.ui.newBoxFromString("Right knob");
{
right_knob_box.flags.insert(.clickable);
right_knob_box.flags.insert(.draggable_x);
right_knob_box.background = rl.Color.black.alpha(0.5);
right_knob_box.size.x = UI.Size.pixels(8, 1);
right_knob_box.size.y = UI.Size.pixels(32, 1);
}
const left_knob_size = left_knob_box.persistent.size.x;
const right_knob_size = right_knob_box.persistent.size.x;
const left_signal = self.ui.signalFromBox(left_knob_box);
if (left_signal.dragged()) {
channel.view_rect.from += remap(
f32,
0, minimap_rect.width,
0, sample_count,
left_signal.drag.x
);
channel.view_rect.from = clamp(channel.view_rect.from, 0, channel.view_rect.to-min_visible_samples);
}
const right_signal = self.ui.signalFromBox(right_knob_box);
if (right_signal.dragged()) {
channel.view_rect.to += remap(
f32,
0, minimap_rect.width,
0, sample_count,
right_signal.drag.x
);
channel.view_rect.to = clamp(channel.view_rect.to, channel.view_rect.from+min_visible_samples, sample_count);
}
const middle_signal = self.ui.signalFromBox(middle_box);
if (middle_signal.dragged()) {
var samples_moved = middle_signal.drag.x / minimap_rect.width * sample_count;
samples_moved = clamp(samples_moved, -channel.view_rect.from, sample_count - channel.view_rect.to);
channel.view_rect.from += samples_moved;
channel.view_rect.to += samples_moved;
}
left_knob_box.setFixedX(remap(f32,
0, sample_count,
0, minimap_rect.width - left_knob_size - right_knob_size,
channel.view_rect.from
));
right_knob_box.setFixedX(remap(f32,
0, sample_count,
left_knob_size, minimap_rect.width - right_knob_size,
channel.view_rect.to
));
middle_box.setFixedX(remap(f32,
0, sample_count,
left_knob_size, minimap_rect.width - right_knob_size,
channel.view_rect.from
));
middle_box.setFixedWidth(remap(f32,
0, sample_count,
0, minimap_rect.width - right_knob_size - left_knob_size,
channel.view_rect.to - channel.view_rect.from
));
}
}
} }
} }

View File

@ -3,12 +3,14 @@ const rl = @import("raylib");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Application = @import("./app.zig"); const Application = @import("./app.zig");
const Assets = @import("./assets.zig"); const Assets = @import("./assets.zig");
const Profiler = @import("./profiler.zig");
const raylib_h = @cImport({ const raylib_h = @cImport({
@cInclude("stdio.h"); @cInclude("stdio.h");
@cInclude("raylib.h"); @cInclude("raylib.h");
}); });
const log = std.log; const log = std.log;
const profiler_enabled = builtin.mode == .Debug;
// TODO: Maybe move this to a config.zig or options.zig file. // TODO: Maybe move this to a config.zig or options.zig file.
// Have all of the contstants in a single file. // Have all of the contstants in a single file.
@ -134,6 +136,9 @@ pub fn main() !void {
rl.setWindowMinSize(256, 256); rl.setWindowMinSize(256, 256);
rl.setWindowIcon(icon_image); rl.setWindowIcon(icon_image);
const target_fps = 60;
rl.setTargetFPS(target_fps);
if (builtin.mode != .Debug) { if (builtin.mode != .Debug) {
rl.setExitKey(.key_null); rl.setExitKey(.key_null);
} }
@ -158,8 +163,35 @@ pub fn main() !void {
} }
} }
var profiler: ?Profiler = null;
defer if (profiler) |p| p.deinit();
var profiler_shown = false;
if (profiler_enabled) {
const font_face = Assets.font(.text);
profiler = try Profiler.init(allocator, 10 * target_fps, @divFloor(std.time.ns_per_s, target_fps), font_face);
}
while (!rl.windowShouldClose()) { while (!rl.windowShouldClose()) {
rl.beginDrawing();
defer rl.endDrawing();
if (profiler) |*p| {
p.start();
}
try app.tick(); try app.tick();
if (profiler) |*p| {
p.stop();
if (rl.isKeyPressed(.key_p) and rl.isKeyDown(.key_left_control)) {
profiler_shown = !profiler_shown;
}
if (profiler_shown) {
try p.showResults();
}
}
} }
} }

110
src/profiler.zig Normal file
View File

@ -0,0 +1,110 @@
const std = @import("std");
const rl = @import("raylib");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const FontFace = @import("./font-face.zig");
const srcery = @import("./srcery.zig");
const rect_utils = @import("./rect-utils.zig");
allocator: Allocator,
history: []u128,
history_size: usize,
history_head: usize,
started_at: i128,
ns_budget: u128,
font_face: FontFace,
pub fn init(allocator: Allocator, data_points: usize, ns_budget: u128, font_face: FontFace) !@This() {
return @This(){
.allocator = allocator,
.history = try allocator.alloc(u128, data_points),
.history_size = 0,
.history_head = 0,
.started_at = 0,
.ns_budget = ns_budget,
.font_face = font_face,
};
}
pub fn deinit(self: @This()) void {
self.allocator.free(self.history);
}
pub fn start(self: *@This()) void {
self.started_at = std.time.nanoTimestamp();
}
pub fn stop(self: *@This()) void {
const stopped_at = std.time.nanoTimestamp();
assert(stopped_at >= self.started_at);
const duration: u128 = @intCast(stopped_at - self.started_at);
if (self.history_size < self.history.len) {
self.history[self.history_size] = duration;
self.history_size += 1;
} else {
self.history_head = @mod(self.history_head + 1, self.history.len);
self.history[self.history_head] = duration;
}
}
pub fn showResults(self: *const @This()) !void {
const screen_width: f32 = @floatFromInt(rl.getScreenWidth());
const screen_height: f32 = @floatFromInt(rl.getScreenHeight());
const profile_height = 200;
const profile_box = rl.Rectangle.init(0, screen_height - profile_height, screen_width, profile_height);
const color = srcery.bright_white;
rl.drawRectangleLinesEx(profile_box, 1, color);
const ns_budget: f32 = @floatFromInt(self.ns_budget);
const measurement_width = profile_box.width / @as(f32, @floatFromInt(self.history.len));
for (0..self.history_size) |i| {
const measurement = self.history[@mod(self.history_head + i, self.history.len)];
const measurement_height = @as(f32, @floatFromInt(measurement)) / ns_budget * profile_box.height;
rl.drawRectangleV(
.{
.x = profile_box.x + measurement_width * @as(f32, @floatFromInt(i + self.history.len - self.history_size)),
.y = profile_box.y + profile_box.height - measurement_height
},
.{ .x = measurement_width, .y = measurement_height },
color
);
}
var min_time_taken = self.history[0];
var max_time_taken = self.history[0];
var sum_time_taken: u128 = 0;
for (self.history[0..self.history_size]) |measurement| {
min_time_taken = @min(min_time_taken, measurement);
max_time_taken = @max(max_time_taken, measurement);
sum_time_taken += measurement;
}
const avg_time_taken = @as(f32, @floatFromInt(sum_time_taken)) / @as(f32, @floatFromInt(self.history_size));
const content_rect = rect_utils.shrink(profile_box, 10);
var layout_offset: f32 = 0;
const allocator = self.allocator;
const font_size = self.font_face.getSize();
const labels = .{
.{ "Min", @as(f32, @floatFromInt(min_time_taken)) },
.{ "Max", @as(f32, @floatFromInt(max_time_taken)) },
.{ "Avg", avg_time_taken }
};
inline for (labels) |label| {
const label_name = label[0];
const time_taken = label[1];
const min_time_str = try std.fmt.allocPrintZ(allocator, "{s}: {d:10.0}us ({d:.3}%)", .{ label_name, time_taken / std.time.ns_per_us, time_taken / ns_budget * 100 });
defer allocator.free(min_time_str);
self.font_face.drawText(min_time_str, .{ .x = content_rect.x, .y = content_rect.y + layout_offset }, color);
layout_offset += font_size;
}
}