242 lines
7.3 KiB
Zig
242 lines
7.3 KiB
Zig
const builtin = @import("builtin");
|
|
const std = @import("std");
|
|
const rl = @import("raylib");
|
|
const srcery = @import("./srcery.zig");
|
|
const RangeF64 = @import("./range.zig").RangeF64;
|
|
|
|
const remap = @import("./utils.zig").remap;
|
|
const assert = std.debug.assert;
|
|
const Vec2 = rl.Vector2;
|
|
const Rect = rl.Rectangle;
|
|
const clamp = std.math.clamp;
|
|
|
|
const disable_caching = false;
|
|
|
|
comptime {
|
|
// Just making sure that release build has caching enabled
|
|
if (builtin.mode != .Debug) {
|
|
assert(disable_caching == false);
|
|
}
|
|
}
|
|
|
|
pub const ViewOptions = struct {
|
|
x_range: RangeF64,
|
|
y_range: RangeF64,
|
|
|
|
color: rl.Color = srcery.red,
|
|
};
|
|
|
|
pub const Cache = struct {
|
|
const Key = struct {
|
|
options: ViewOptions,
|
|
drawn_x_range: RangeF64
|
|
};
|
|
|
|
texture: ?rl.RenderTexture2D = null,
|
|
key: ?Key = null,
|
|
|
|
pub fn deinit(self: *Cache) void {
|
|
if (self.texture) |texture| {
|
|
texture.unload();
|
|
self.texture = null;
|
|
}
|
|
self.key = null;
|
|
}
|
|
|
|
pub fn invalidate(self: *Cache) void {
|
|
self.key = null;
|
|
}
|
|
|
|
pub fn draw(self: Cache, rect: rl.Rectangle) void {
|
|
if (self.texture) |texture| {
|
|
const source = rl.Rectangle{
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = @floatFromInt(texture.texture.width),
|
|
.height = @floatFromInt(texture.texture.height)
|
|
};
|
|
rl.drawTexturePro(
|
|
texture.texture,
|
|
source,
|
|
rect,
|
|
rl.Vector2.zero(),
|
|
0, rl.Color.white
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
fn drawSamplesExact(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
|
|
rl.beginScissorMode(
|
|
@intFromFloat(draw_rect.x),
|
|
@intFromFloat(draw_rect.y),
|
|
@intFromFloat(draw_rect.width),
|
|
@intFromFloat(draw_rect.height),
|
|
);
|
|
defer rl.endScissorMode();
|
|
|
|
const draw_x_range, const draw_y_range = RangeF64.initRect(draw_rect);
|
|
|
|
const i_range = options.x_range.intersectPositive(
|
|
RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0))
|
|
);
|
|
if (i_range.lower > i_range.upper) {
|
|
return;
|
|
}
|
|
|
|
const from_i: usize = @intFromFloat(i_range.lower);
|
|
const to_i: usize = @intFromFloat(i_range.upper);
|
|
if (to_i == 0 or from_i == to_i) {
|
|
return;
|
|
}
|
|
|
|
for (from_i..(to_i-1)) |i| {
|
|
const i_f64: f64 = @floatFromInt(i);
|
|
rl.drawLineV(
|
|
.{
|
|
.x = @floatCast(options.x_range.remapTo(draw_x_range, i_f64)),
|
|
.y = @floatCast(options.y_range.remapTo(draw_y_range, samples[i])),
|
|
},
|
|
.{
|
|
.x = @floatCast(options.x_range.remapTo(draw_x_range, i_f64 + 1)),
|
|
.y = @floatCast(options.y_range.remapTo(draw_y_range, samples[i + 1])),
|
|
},
|
|
options.color
|
|
);
|
|
}
|
|
}
|
|
|
|
fn drawSamplesApproximate(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
|
|
const draw_x_range, const draw_y_range = RangeF64.initRect(draw_rect);
|
|
|
|
const i_range = options.x_range.intersectPositive(
|
|
RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0))
|
|
);
|
|
if (i_range.lower > i_range.upper) {
|
|
return;
|
|
}
|
|
|
|
const samples_per_column = options.x_range.size() / draw_x_range.size();
|
|
assert(samples_per_column >= 1);
|
|
|
|
var i = i_range.lower;
|
|
while (i < i_range.upper - samples_per_column) : (i += samples_per_column) {
|
|
const column_start: usize = @intFromFloat(i);
|
|
const column_end: usize = @intFromFloat(i + samples_per_column);
|
|
const column_samples = samples[column_start..column_end];
|
|
if (column_samples.len == 0) continue;
|
|
|
|
var column_min = column_samples[0];
|
|
var column_max = column_samples[0];
|
|
|
|
for (column_samples) |sample| {
|
|
column_min = @min(column_min, sample);
|
|
column_max = @max(column_max, sample);
|
|
}
|
|
|
|
const x = options.x_range.remapTo(draw_x_range, i);
|
|
const y_min = options.y_range.remapTo(draw_y_range, column_min);
|
|
const y_max = options.y_range.remapTo(draw_y_range, column_max);
|
|
|
|
if (@abs(y_max - y_min) < 1) {
|
|
const avg = (y_min + y_max) / 2;
|
|
rl.drawLineV(
|
|
.{ .x = @floatCast(x), .y = @floatCast(avg) },
|
|
.{ .x = @floatCast(x), .y = @floatCast(avg+1) },
|
|
options.color
|
|
);
|
|
} else {
|
|
rl.drawLineV(
|
|
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
|
.{ .x = @floatCast(x), .y = @floatCast(y_max) },
|
|
options.color
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn drawSamples(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
|
|
const x_range = options.x_range;
|
|
|
|
if (x_range.lower >= @as(f64, @floatFromInt(samples.len))) return;
|
|
if (x_range.upper < 0) return;
|
|
|
|
const samples_per_column = x_range.size() / draw_rect.width;
|
|
if (samples_per_column >= 2) {
|
|
drawSamplesApproximate(draw_rect, options, samples);
|
|
} else {
|
|
drawSamplesExact(draw_rect, options, samples);
|
|
}
|
|
}
|
|
|
|
pub fn drawCached(cache: *Cache, render_size: Vec2, options: ViewOptions, samples: []const f64) void {
|
|
const render_width: i32 = @intFromFloat(@ceil(render_size.x));
|
|
const render_height: i32 = @intFromFloat(@ceil(render_size.y));
|
|
|
|
if (render_width <= 0 or render_height <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Unload render texture if rendering width or height changed
|
|
if (cache.texture) |render_texture| {
|
|
const texure = render_texture.texture;
|
|
if (texure.width != render_width or texure.height != render_height) {
|
|
render_texture.unload();
|
|
cache.texture = null;
|
|
cache.key = null;
|
|
}
|
|
}
|
|
|
|
if (cache.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));
|
|
cache.texture = texture;
|
|
}
|
|
|
|
const render_texture = cache.texture.?;
|
|
|
|
const cache_key = Cache.Key{
|
|
.options = options,
|
|
.drawn_x_range = RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0)).intersectPositive(options.x_range)
|
|
};
|
|
|
|
if (cache.key != null and std.meta.eql(cache.key.?, cache_key)) {
|
|
// Cached graph hasn't changed, no need to redraw.
|
|
return;
|
|
}
|
|
|
|
cache.key = cache_key;
|
|
|
|
render_texture.begin();
|
|
defer render_texture.end();
|
|
|
|
rl.gl.rlPushMatrix();
|
|
defer rl.gl.rlPopMatrix();
|
|
|
|
rl.clearBackground(rl.Color.black.alpha(0));
|
|
rl.gl.rlTranslatef(0, render_size.y, 0);
|
|
rl.gl.rlScalef(1, -1, 1);
|
|
|
|
const draw_rect = rl.Rectangle{
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = render_size.x,
|
|
.height = render_size.y
|
|
};
|
|
drawSamples(draw_rect, options, samples);
|
|
}
|
|
|
|
pub fn draw(cache: ?*Cache, draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
|
|
if (draw_rect.width < 0 or draw_rect.height < 0) {
|
|
return;
|
|
}
|
|
|
|
if (cache != null and !disable_caching) {
|
|
const c = cache.?;
|
|
drawCached(c, .{ .x = draw_rect.width, .y = draw_rect.height }, options, samples);
|
|
c.draw(draw_rect);
|
|
} else {
|
|
drawSamples(draw_rect, options, samples);
|
|
}
|
|
} |