From 801f9f6ef4418e7413ef84f5615cd71956a98c6c Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 1 Dec 2024 17:02:09 +0200 Subject: [PATCH] add caching to graph drawing --- src/app.zig | 12 +++-- src/graph.zig | 145 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/app.zig b/src/app.zig index 66d29a8..5c4f1f1 100644 --- a/src/app.zig +++ b/src/app.zig @@ -19,7 +19,9 @@ const Vec2 = rl.Vector2; const App = @This(); const Channel = struct { - view_rect: Graph.ViewRectangle, + view_cache: Graph.Cache = .{}, + view_rect: Graph.ViewOptions, + min_value: f64, max_value: f64, samples: union(enum) { @@ -58,10 +60,11 @@ pub fn init( } pub fn deinit(self: *App) void { - for (self.channels.items) |channel| { + for (self.channels.items) |*channel| { if (channel.samples == .owned) { self.allocator.free(channel.samples.owned); } + channel.view_cache.deinit(); } self.channels.deinit(); self.task_pool.deinit(self.allocator); @@ -163,14 +166,17 @@ pub fn tick(self: *App) !void { { var channels_stack = UI.Stack.init(RectUtils.shrink(graphs_rect, 10), .top_to_bottom); - for (self.channels.items) |channel| { + for (self.channels.items) |*channel| { const channel_rect = channels_stack.next(128); Graph.draw( + &channel.view_cache, channel_rect, channel.view_rect, channel.samples.owned ); + + rl.drawRectangleLinesEx(channel_rect, 1, rl.Color.black); } } diff --git a/src/graph.zig b/src/graph.zig index 27eea14..cceda15 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -6,7 +6,7 @@ const assert = std.debug.assert; const Vec2 = rl.Vector2; const clamp = std.math.clamp; -pub const ViewRectangle = struct { +pub const ViewOptions = struct { from: f32, // inclusive to: f32, // exclusive min_value: f64, @@ -16,12 +16,29 @@ pub const ViewRectangle = struct { dot_size: f32 = 2 }; +pub const Cache = struct { + texture: ?rl.RenderTexture2D = null, + options: ?ViewOptions = null, + + pub fn deinit(self: *Cache) void { + if (self.texture) |texture| { + texture.unload(); + self.texture = null; + } + self.options = null; + } + + pub fn invalidate(self: *Cache) void { + self.options = null; + } +}; + fn remap(comptime T: type, from_min: T, from_max: T, to_min: T, to_max: T, value: T) T { const t = (value - from_min) / (from_max - from_min); return std.math.lerp(to_min, to_max, t); } -fn mapSampleX(draw_rect: rl.Rectangle, view_rect: ViewRectangle, index: f64) f64 { +fn mapSampleX(draw_rect: rl.Rectangle, view_rect: ViewOptions, index: f64) f64 { return remap( f64, view_rect.from, view_rect.to, @@ -30,16 +47,16 @@ fn mapSampleX(draw_rect: rl.Rectangle, view_rect: ViewRectangle, index: f64) f64 ); } -fn mapSampleY(draw_rect: rl.Rectangle, view_rect: ViewRectangle, sample: f64) f64 { +fn mapSampleY(draw_rect: rl.Rectangle, view_rect: ViewOptions, sample: f64) f64 { return remap( f64, view_rect.min_value, view_rect.max_value, - @floatCast(draw_rect.y + draw_rect.height), @floatCast(draw_rect.y), + draw_rect.y + draw_rect.height, draw_rect.y, sample ); } -fn mapSamplePointToGraph(draw_rect: rl.Rectangle, view_rect: ViewRectangle, index: f64, sample: f64) Vec2 { +fn mapSamplePointToGraph(draw_rect: rl.Rectangle, view_rect: ViewOptions, index: f64, sample: f64) Vec2 { return .{ .x = @floatCast(mapSampleX(draw_rect, view_rect, index)), .y = @floatCast(mapSampleY(draw_rect, view_rect, sample)) @@ -56,22 +73,20 @@ fn clampIndexUsize(value: f32, size: usize) usize { return @intFromFloat(clamp(value, 0, size_f32)); } -pub fn draw(draw_rect: rl.Rectangle, view_rect: ViewRectangle, samples: []const f64) void { - assert(view_rect.left_aligned); // TODO: - assert(view_rect.to > view_rect.from); +fn drawSamples(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void { + assert(options.left_aligned); // TODO: + assert(options.to > options.from); - rl.drawRectangleLinesEx(draw_rect, 1, rl.Color.black); + if (options.from > @as(f32, @floatFromInt(samples.len))) return; + if (options.to < 0) return; - if (view_rect.from > @as(f32, @floatFromInt(samples.len))) return; - if (view_rect.to < 0) return; - - const sample_count = view_rect.to - view_rect.from; + const sample_count = options.to - options.from; const samples_per_column = sample_count / draw_rect.width; const samples_threshold = 2; if (samples_per_column >= samples_threshold) { - var i = clampIndex(view_rect.from, samples.len); - while (i < clampIndex(view_rect.to, samples.len)) : (i += samples_per_column) { + var i = clampIndex(options.from, samples.len); + while (i < clampIndex(options.to, samples.len)) : (i += samples_per_column) { const from_index = clampIndexUsize(i, samples.len); const to_index = clampIndexUsize(i+samples_per_column, samples.len); const column_samples = samples[from_index..to_index]; @@ -85,32 +100,32 @@ pub fn draw(draw_rect: rl.Rectangle, view_rect: ViewRectangle, samples: []const column_max = @max(column_max, sample); } - const x = mapSampleX(draw_rect, view_rect, @floatFromInt(from_index)); - const y_min = mapSampleY(draw_rect, view_rect, column_min); - const y_max = mapSampleY(draw_rect, view_rect, column_max); + const x = mapSampleX(draw_rect, options, @floatFromInt(from_index)); + const y_min = mapSampleY(draw_rect, options, column_min); + const y_max = mapSampleY(draw_rect, options, column_max); if (column_samples.len == 1) { rl.drawLineV( - mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]), - mapSamplePointToGraph(draw_rect, view_rect, i-1, samples[clampIndexUsize(i-1, samples.len-1)]), - view_rect.color + mapSamplePointToGraph(draw_rect, options, i, samples[from_index]), + mapSamplePointToGraph(draw_rect, options, i-1, samples[clampIndexUsize(i-1, samples.len-1)]), + options.color ); rl.drawLineV( - mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]), - mapSamplePointToGraph(draw_rect, view_rect, i+1, samples[clampIndexUsize(i+1, samples.len-1)]), - view_rect.color + mapSamplePointToGraph(draw_rect, options, i, samples[from_index]), + mapSamplePointToGraph(draw_rect, options, i+1, samples[clampIndexUsize(i+1, samples.len-1)]), + options.color ); } else if (@abs(y_max - y_min) < 1) { rl.drawPixelV( .{ .x = @floatCast(x), .y = @floatCast(y_min) }, - view_rect.color + options.color ); } else { rl.drawLineV( .{ .x = @floatCast(x), .y = @floatCast(y_min) }, .{ .x = @floatCast(x), .y = @floatCast(y_max) }, - view_rect.color + options.color ); } } @@ -124,29 +139,87 @@ pub fn draw(draw_rect: rl.Rectangle, view_rect: ViewRectangle, samples: []const defer rl.endScissorMode(); { - const from_index = clampIndexUsize(@floor(view_rect.from), samples.len); - const to_index = clampIndexUsize(@ceil(view_rect.to) + 1, samples.len); + const from_index = clampIndexUsize(@floor(options.from), samples.len); + const to_index = clampIndexUsize(@ceil(options.to) + 1, samples.len); for (from_index..(to_index-1)) |i| { - const from_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]); - const to_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i + 1), samples[i + 1]); - rl.drawLineV(from_point, to_point, view_rect.color); + const from_point = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i), samples[i]); + const to_point = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i + 1), samples[i + 1]); + rl.drawLineV(from_point, to_point, options.color); } } { - const from_index = clampIndexUsize(@ceil(view_rect.from), samples.len); - const to_index = clampIndexUsize(@ceil(view_rect.to), samples.len); + const from_index = clampIndexUsize(@ceil(options.from), samples.len); + const to_index = clampIndexUsize(@ceil(options.to), samples.len); const min_circle_size = 0.5; - const max_circle_size = view_rect.dot_size; + const max_circle_size = options.dot_size; var circle_size = remap(f32, samples_threshold, 0.2, min_circle_size, max_circle_size, samples_per_column); circle_size = @min(circle_size, max_circle_size); for (from_index..to_index) |i| { - const center = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]); - rl.drawCircleV(center, circle_size, view_rect.color); + const center = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i), samples[i]); + rl.drawCircleV(center, circle_size, options.color); } } } +} + +pub fn draw(cache: ?*Cache, draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void { + if (cache) |c| { + const render_width: i32 = @intFromFloat(@ceil(draw_rect.width)); + const render_height: i32 = @intFromFloat(@ceil(draw_rect.height)); + + // Unload render texture if rendering width or height changed + if (c.texture) |render_texture| { + const texure = render_texture.texture; + if (texure.width != render_width or texure.height != render_height) { + render_texture.unload(); + c.texture = null; + c.options = null; + } + } + + if (c.texture == null) { + const texture = rl.loadRenderTexture(render_width, render_height); + // TODO: Maybe fallback to just drawing without caching, if GPU doesn't have enough memory? + assert(rl.isRenderTextureReady(texture)); + c.texture = texture; + } + + const render_texture = c.texture.?; + + if (c.options != null and std.meta.eql(c.options.?, options)) { + const source = rl.Rectangle{ + .x = 0, + .y = 0, + .width = @floatFromInt(render_texture.texture.width), + .height = @floatFromInt(-render_texture.texture.height) + }; + rl.drawTexturePro( + render_texture.texture, + source, + draw_rect, + rl.Vector2.zero(), + 0, rl.Color.white + ); + return; + } + + c.options = options; + render_texture.begin(); + + rl.gl.rlPushMatrix(); + rl.gl.rlTranslatef(-draw_rect.x, -draw_rect.y, 0); + } + + drawSamples(draw_rect, options, samples); + + if (cache) |c| { + rl.gl.rlPopMatrix(); + + const render_texture = c.texture.?; + render_texture.end(); + } } \ No newline at end of file