add caching to graph drawing
This commit is contained in:
parent
0d9033c926
commit
801f9f6ef4
12
src/app.zig
12
src/app.zig
@ -19,7 +19,9 @@ const Vec2 = rl.Vector2;
|
|||||||
const App = @This();
|
const App = @This();
|
||||||
|
|
||||||
const Channel = struct {
|
const Channel = struct {
|
||||||
view_rect: Graph.ViewRectangle,
|
view_cache: Graph.Cache = .{},
|
||||||
|
view_rect: Graph.ViewOptions,
|
||||||
|
|
||||||
min_value: f64,
|
min_value: f64,
|
||||||
max_value: f64,
|
max_value: f64,
|
||||||
samples: union(enum) {
|
samples: union(enum) {
|
||||||
@ -58,10 +60,11 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
for (self.channels.items) |channel| {
|
for (self.channels.items) |*channel| {
|
||||||
if (channel.samples == .owned) {
|
if (channel.samples == .owned) {
|
||||||
self.allocator.free(channel.samples.owned);
|
self.allocator.free(channel.samples.owned);
|
||||||
}
|
}
|
||||||
|
channel.view_cache.deinit();
|
||||||
}
|
}
|
||||||
self.channels.deinit();
|
self.channels.deinit();
|
||||||
self.task_pool.deinit(self.allocator);
|
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);
|
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);
|
const channel_rect = channels_stack.next(128);
|
||||||
|
|
||||||
Graph.draw(
|
Graph.draw(
|
||||||
|
&channel.view_cache,
|
||||||
channel_rect,
|
channel_rect,
|
||||||
channel.view_rect,
|
channel.view_rect,
|
||||||
channel.samples.owned
|
channel.samples.owned
|
||||||
);
|
);
|
||||||
|
|
||||||
|
rl.drawRectangleLinesEx(channel_rect, 1, rl.Color.black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
145
src/graph.zig
145
src/graph.zig
@ -6,7 +6,7 @@ const assert = std.debug.assert;
|
|||||||
const Vec2 = rl.Vector2;
|
const Vec2 = rl.Vector2;
|
||||||
const clamp = std.math.clamp;
|
const clamp = std.math.clamp;
|
||||||
|
|
||||||
pub const ViewRectangle = struct {
|
pub const ViewOptions = struct {
|
||||||
from: f32, // inclusive
|
from: f32, // inclusive
|
||||||
to: f32, // exclusive
|
to: f32, // exclusive
|
||||||
min_value: f64,
|
min_value: f64,
|
||||||
@ -16,12 +16,29 @@ pub const ViewRectangle = struct {
|
|||||||
dot_size: f32 = 2
|
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 {
|
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);
|
const t = (value - from_min) / (from_max - from_min);
|
||||||
return std.math.lerp(to_min, to_max, t);
|
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(
|
return remap(
|
||||||
f64,
|
f64,
|
||||||
view_rect.from, view_rect.to,
|
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(
|
return remap(
|
||||||
f64,
|
f64,
|
||||||
view_rect.min_value, view_rect.max_value,
|
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
|
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 .{
|
return .{
|
||||||
.x = @floatCast(mapSampleX(draw_rect, view_rect, index)),
|
.x = @floatCast(mapSampleX(draw_rect, view_rect, index)),
|
||||||
.y = @floatCast(mapSampleY(draw_rect, view_rect, sample))
|
.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));
|
return @intFromFloat(clamp(value, 0, size_f32));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(draw_rect: rl.Rectangle, view_rect: ViewRectangle, samples: []const f64) void {
|
fn drawSamples(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
|
||||||
assert(view_rect.left_aligned); // TODO:
|
assert(options.left_aligned); // TODO:
|
||||||
assert(view_rect.to > view_rect.from);
|
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;
|
const sample_count = options.to - options.from;
|
||||||
if (view_rect.to < 0) return;
|
|
||||||
|
|
||||||
const sample_count = view_rect.to - view_rect.from;
|
|
||||||
const samples_per_column = sample_count / draw_rect.width;
|
const samples_per_column = sample_count / draw_rect.width;
|
||||||
|
|
||||||
const samples_threshold = 2;
|
const samples_threshold = 2;
|
||||||
if (samples_per_column >= samples_threshold) {
|
if (samples_per_column >= samples_threshold) {
|
||||||
var i = clampIndex(view_rect.from, samples.len);
|
var i = clampIndex(options.from, samples.len);
|
||||||
while (i < clampIndex(view_rect.to, samples.len)) : (i += samples_per_column) {
|
while (i < clampIndex(options.to, samples.len)) : (i += samples_per_column) {
|
||||||
const from_index = clampIndexUsize(i, samples.len);
|
const from_index = clampIndexUsize(i, samples.len);
|
||||||
const to_index = clampIndexUsize(i+samples_per_column, samples.len);
|
const to_index = clampIndexUsize(i+samples_per_column, samples.len);
|
||||||
const column_samples = samples[from_index..to_index];
|
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);
|
column_max = @max(column_max, sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = mapSampleX(draw_rect, view_rect, @floatFromInt(from_index));
|
const x = mapSampleX(draw_rect, options, @floatFromInt(from_index));
|
||||||
const y_min = mapSampleY(draw_rect, view_rect, column_min);
|
const y_min = mapSampleY(draw_rect, options, column_min);
|
||||||
const y_max = mapSampleY(draw_rect, view_rect, column_max);
|
const y_max = mapSampleY(draw_rect, options, column_max);
|
||||||
|
|
||||||
if (column_samples.len == 1) {
|
if (column_samples.len == 1) {
|
||||||
rl.drawLineV(
|
rl.drawLineV(
|
||||||
mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]),
|
mapSamplePointToGraph(draw_rect, options, i, samples[from_index]),
|
||||||
mapSamplePointToGraph(draw_rect, view_rect, i-1, samples[clampIndexUsize(i-1, samples.len-1)]),
|
mapSamplePointToGraph(draw_rect, options, i-1, samples[clampIndexUsize(i-1, samples.len-1)]),
|
||||||
view_rect.color
|
options.color
|
||||||
);
|
);
|
||||||
|
|
||||||
rl.drawLineV(
|
rl.drawLineV(
|
||||||
mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]),
|
mapSamplePointToGraph(draw_rect, options, i, samples[from_index]),
|
||||||
mapSamplePointToGraph(draw_rect, view_rect, i+1, samples[clampIndexUsize(i+1, samples.len-1)]),
|
mapSamplePointToGraph(draw_rect, options, i+1, samples[clampIndexUsize(i+1, samples.len-1)]),
|
||||||
view_rect.color
|
options.color
|
||||||
);
|
);
|
||||||
} else if (@abs(y_max - y_min) < 1) {
|
} else if (@abs(y_max - y_min) < 1) {
|
||||||
rl.drawPixelV(
|
rl.drawPixelV(
|
||||||
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
||||||
view_rect.color
|
options.color
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rl.drawLineV(
|
rl.drawLineV(
|
||||||
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
||||||
.{ .x = @floatCast(x), .y = @floatCast(y_max) },
|
.{ .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();
|
defer rl.endScissorMode();
|
||||||
|
|
||||||
{
|
{
|
||||||
const from_index = clampIndexUsize(@floor(view_rect.from), samples.len);
|
const from_index = clampIndexUsize(@floor(options.from), samples.len);
|
||||||
const to_index = clampIndexUsize(@ceil(view_rect.to) + 1, samples.len);
|
const to_index = clampIndexUsize(@ceil(options.to) + 1, samples.len);
|
||||||
|
|
||||||
for (from_index..(to_index-1)) |i| {
|
for (from_index..(to_index-1)) |i| {
|
||||||
const from_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]);
|
const from_point = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i), samples[i]);
|
||||||
const to_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i + 1), samples[i + 1]);
|
const to_point = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i + 1), samples[i + 1]);
|
||||||
rl.drawLineV(from_point, to_point, view_rect.color);
|
rl.drawLineV(from_point, to_point, options.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const from_index = clampIndexUsize(@ceil(view_rect.from), samples.len);
|
const from_index = clampIndexUsize(@ceil(options.from), samples.len);
|
||||||
const to_index = clampIndexUsize(@ceil(view_rect.to), samples.len);
|
const to_index = clampIndexUsize(@ceil(options.to), samples.len);
|
||||||
|
|
||||||
const min_circle_size = 0.5;
|
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);
|
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);
|
circle_size = @min(circle_size, max_circle_size);
|
||||||
|
|
||||||
for (from_index..to_index) |i| {
|
for (from_index..to_index) |i| {
|
||||||
const center = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]);
|
const center = mapSamplePointToGraph(draw_rect, options, @floatFromInt(i), samples[i]);
|
||||||
rl.drawCircleV(center, circle_size, view_rect.color);
|
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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user