opengl_plot/module.jai

1125 lines
35 KiB
Plaintext

PlotSettings :: struct {
colors : PlotColors;
fixed_bounds : bool;
top_left : Vector2;
bottom_right : Vector2;
}
PLOT_STYLE :: enum {
TICK_MARKS;
SCALE_MARKER;
}
draw_plot :: (key: string, xdata: []float64, yarrays: [][]float64, style: PLOT_STYLE, using settings: PlotSettings) {
plot_size := ImGui.GetContentRegionAvail();
plot, success := table_find(*plots, key);
if !success {
plot = New(Plot);
init_plot(plot, key, xx plot_size.x, xx plot_size.y);
table_set(*plots, key, plot);
plot.xfloat = NewArray(xdata.count, float);
for xdata {
plot.xfloat[it_index] = cast(float) it;
}
plot.xmin = 0.0;
plot.xmax = 0.0;
x_acc : float;
for xdata {
if it > plot.xmax plot.xmax = xx it;
if it < plot.xmin plot.xmin = xx it;
is_nan, is_inf := is_nan_is_inf(cast(float) it);
if !is_nan && !is_inf {
x_acc += cast(float) it;
}
}
plot.x_avg = x_acc / xdata.count;
plot.ymin = 0.0;
plot.ymax = 0.0;
y_acc : float;
for ydata: yarrays {
yfloat := NewArray(ydata.count, float);
array_add(*plot.yfloats, yfloat);
for ydata {
yfloat[it_index] = cast(float) it;
if it > plot.ymax plot.ymax = xx it;
if it < plot.ymin plot.ymin = xx it;
is_nan, is_inf := is_nan_is_inf(cast(float) it);
if !is_nan && !is_inf {
y_acc += cast(float) it;
}
}
}
plot.y_avg = y_acc / (yarrays[0].count * yarrays.count);
if fixed_bounds {
plot.xmin = top_left.x;
plot.xmax = bottom_right.x;
plot.ymin = bottom_right.y;
plot.ymax = top_left.y;
plot.x_avg = plot.xmax - plot.xmin;
plot.y_avg = plot.ymax - plot.ymin;
}
//plot.pos.x = plot.x_avg;
//plot.pos.y = plot.y_avg;
plot.zoom = 1.0;
}
if style == {
case .TICK_MARKS;
plot.left_bearing = 40;
plot.bottom_bearing = 20;
case .SCALE_MARKER;
plot.left_bearing = 0;
plot.bottom_bearing = 20;
}
child_pos := ImGui.GetCursorScreenPos();
plot.x = xx child_pos.x;
plot.y = xx child_pos.y;
//plot_size = .{plot_size.x, min(260.0, plot_size.y / workspace.f64_arrays.count)};
if plot_size.x < 0 || plot_size.y < 0 {
log("[Warning] The plot has dimensions lower than 0.");
} else {
if plot.width != cast(s32) plot_size.x || plot.height != cast(s32) plot_size.y {
plot_resize(plot, xx plot_size.x, xx plot_size.y);
}
ar : float = cast(float) plot.width / cast(float) plot.height;
pixel_size_x : float = (plot.xmax - plot.xmin) / plot.width;
pixel_size_y : float = (plot.ymax - plot.ymin) / plot.height;
if is_in_rect(xy(xx plot.x, xx plot.y), xy(xx plot.width, xx plot.height), mouse) {
if mouse_left {
plot.pos.x += mouse_delta.x * pixel_size_x / plot.zoom;
plot.pos.y += -mouse_delta.y * pixel_size_y / plot.zoom;
}
if !fixed_bounds {
plot.zoom += plot.zoom * (cast(float) mouse_wheel_delta * 0.1);
if plot.zoom < 0.01 {
plot.zoom = 0.01;
}
}
}
glBindFramebuffer(GL_FRAMEBUFFER, plot.fbo);
glViewport(xx plot.left_bearing, xx plot.bottom_bearing,
xx (plot.width - plot.left_bearing), xx (plot.height - plot.bottom_bearing));
glClearColor(colors.background.x, colors.background.y, colors.background.z, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
draw_axis(plot, style, colors);
if style == .SCALE_MARKER
draw_scale(plot, colors);
glViewport(xx plot.left_bearing, xx plot.bottom_bearing,
xx (plot.width - plot.left_bearing), xx (plot.height - plot.bottom_bearing));
glBindVertexArray(plot.vao);
glBindBuffer(GL_ARRAY_BUFFER, plot.xvbo);
glEnableVertexAttribArray(0);
glBufferData(GL_ARRAY_BUFFER, plot.xfloat.count * size_of(float), plot.xfloat.data, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, size_of(float), null);
for yfloat: plot.yfloats {
glBindFramebuffer(GL_FRAMEBUFFER, plot.fbo);
glBindBuffer(GL_ARRAY_BUFFER, plot.yvbo);
glEnableVertexAttribArray(1);
glBufferData(GL_ARRAY_BUFFER, yfloat.count * size_of(float), yfloat.data, GL_DYNAMIC_DRAW);
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, size_of(float), null);
model := identity_of(Matrix4);
model = translate(model, .{plot.pos.x, plot.pos.y, 0.0});
model = translate(model, .{plot.x_avg, plot.y_avg, 0.0});
model = translate(model, .{-plot.pos.x, -plot.pos.y, 0.0});
model = scale(model, .{plot.zoom, plot.zoom, 1.0});
model = translate(model, .{plot.pos.x, plot.pos.y, 0.0});
model = translate(model, .{-plot.x_avg, -plot.y_avg, 0.0});
proj := orthographic_projection_matrix((plot.xmin),
(plot.xmax),
(plot.ymin),
(plot.ymax), -1.0, 1.0);
glUseProgram(data_shader);
glUniformMatrix4fv(glGetUniformLocation(data_shader, "model"), 1, GL_TRUE, xx *model);
glUniformMatrix4fv(glGetUniformLocation(data_shader, "proj"), 1, GL_TRUE, xx *proj);
color := data_colors[it_index % data_colors.count];
glUniform4f(glGetUniformLocation(data_shader, "data_color"), color.x, color.y, color.z, color.w);
glDrawArrays(GL_LINE_STRIP, 0, xx (plot.xfloat.count));
//glBindFramebuffer(GL_READ_FRAMEBUFFER, plot.msfbo);
//glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plot.fbo);
//glBlitFramebuffer(0, 0, plot.width, plot.height, 0, 0, plot.width, plot.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
ImGui.Image(cast(ImGui.ImTextureID) plot.texture, plot_size, .{0.0, 1.0}, .{1.0, 0.0});
plot.last_frame = frame;
}
}
free_old_plots :: () {
for plot: plots {
if plot.last_frame < frame {
free_plot(plot);
table_remove(*plots, plot.key);
}
}
}
init :: (_win_width: s32, _win_height: s32) {
win_width = _win_width;
win_height = _win_height;
error := FT_Init_FreeType(*ftlib);
if error {
log("[Error] FreeType could not initialise. That is very bad!");
log(" with error code %.", error);
}
data_shader = make_shader(data_vertex_shader_text, data_fragment_shader_text);
line_shader = make_shader(line_vertex_shader_text, line_fragment_shader_text);
text_shader = make_shader(text_vertex_shader_text, text_fragment_shader_text);
window_projection_matrix = orthographic_projection_matrix(0.0, xx win_width, xx win_height, 0.0, -1.0, 1.0);
init_font(*JetBrainsMonoRegular, "fonts/JetBrainsMono-Regular.ttf", 12);
}
window_resize :: (_win_width: s32, _win_height: s32) {
win_width = _win_width;
win_height = _win_height;
window_projection_matrix = orthographic_projection_matrix(0.0, xx win_width, xx win_height, 0.0, -1.0, 1.0);
}
new_frame :: (_mouse: Vector2, _mouse_delta: Vector2, _mouse_wheel_delta: float, _mouse_left: bool) {
frame += 1;
mouse = _mouse;
mouse_delta = _mouse_delta;
mouse_wheel_delta = _mouse_wheel_delta;
mouse_left = _mouse_left;
}
#scope_file
#import "Basic";
#import "GL";
#import "Hash_Table";
#import "Math";
ImGui :: #import,dir "../imgui-lib";
frame : s64;
mouse : Vector2;
mouse_delta : Vector2;
mouse_wheel_delta : float;
mouse_left : bool;
win_width : s32;
win_height : s32;
PlotColors :: struct {
background : Vector3;
lines : Vector3;
text : Vector3;
}
Plot :: struct {
key : string;
msfbo : GLuint;
mstexture : GLuint;
fbo : GLuint;
texture : GLuint;
vao : GLuint;
xvbo : GLuint;
yvbo : GLuint;
xfloat : []float;
yfloats : [..][]float;
x : s32;
y : s32;
width : s32;
height : s32;
xmin : float;
xmax : float;
ymax : float;
ymin : float;
x_avg : float;
y_avg : float;
left_bearing : float;
bottom_bearing : float;
pos : Vector2;
zoom : float;
last_frame : s64;
}
plots : Table(string, *Plot);
data_colors := Vector4.[
.{1.0, 0.0, 0.0, 1.0},
.{0.0, 1.0, 0.0, 1.0},
.{0.0, 0.0, 1.0, 1.0},
.{0.76, 0.56, 0.1, 1.0},
.{0.8, 0.2, 0.2, 1.0},
.{0.1, 0.56, 0.48, 1.0},
];
free_plot :: (plot: *Plot) {
glDeleteTextures(1, *plot.texture);
glDeleteTextures(1, *plot.mstexture);
glDeleteFramebuffers(1, *plot.fbo);
glDeleteFramebuffers(1, *plot.msfbo);
glDeleteVertexArrays(1, *plot.vao);
glDeleteBuffers(1, *plot.xvbo);
glDeleteBuffers(1, *plot.yvbo);
array_free(plot.xfloat);
for plot.yfloats {
array_free(it);
}
}
init_plot :: (plot: *Plot, key: string, width: s32, height: s32) {
plot.key = key;
plot.width = 1024;
plot.height = 1024;
glGenFramebuffers(1, *plot.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, plot.fbo);
glGenTextures(1, *plot.texture);
glBindTexture(GL_TEXTURE_2D, plot.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, xx plot.width, xx plot.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, plot.texture, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE {
log("[ERROR] FRAMEBUFFER: Framebuffer is not complete!");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
glGenFramebuffers(1, *plot.msfbo);
glBindFramebuffer(GL_FRAMEBUFFER, plot.msfbo);
glGenTextures(1, *plot.mstexture);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, plot.mstexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_SRGB8_ALPHA8, xx plot.width, xx plot.height, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, plot.mstexture, 0);
if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE {
log("[ERROR] FRAMEBUFFER: Framebuffer is not complete!");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
glGenVertexArrays(1, *plot.vao);
glBindVertexArray(plot.vao);
glGenBuffers(1, *plot.xvbo);
glGenBuffers(1, *plot.yvbo);
}
plot_resize :: (plot: *Plot, width: s32, height: s32) {
plot.width = width;
plot.height = height;
glBindTexture(GL_TEXTURE_2D, plot.texture);
if width > 0 && height > 0 {
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, xx plot.width, xx plot.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
}
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, plot.mstexture);
if width > 0 && height > 0 {
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_SRGB8_ALPHA8, xx plot.width, xx plot.height, GL_TRUE);
}
}
truncate :: (x: float, n: s32) -> float {
ret : float = x;
ret *= pow(10.0, xx n);
ret = floor(ret);
ret /= pow(10.0, xx n);
return ret;
}
draw_axis :: (using plot: Plot, style: PLOT_STYLE, colors: PlotColors) {
ar : float = cast(float) width / cast(float) height;
content_x : s32 = x + xx left_bearing;
content_y : s32;
content_width : s32 = width - xx left_bearing;
content_height : s32 = height - xx bottom_bearing;
extent_x := xmax - xmin;
extent_y := ymax - ymin;
positions : [..]Vector2;
defer array_reset(*positions);
array_add(*positions, .{0.0, 0.0});
array_add(*positions, .{xx (content_width ), 0.0});
array_add(*positions, .{xx (content_width ), 0.0});
array_add(*positions, .{xx (content_width ), xx (content_height )});
array_add(*positions, .{xx (content_width ), xx (content_height )});
array_add(*positions, .{0.0, xx (content_height )});
array_add(*positions, .{0.0, xx (content_height )});
array_add(*positions, .{0.0, 0.0});
x_ticks := 10;
for 0..x_ticks {
xpos : s64 = cast(s64) (cast(float) it / cast(float) x_ticks * cast(float) (content_width ));
xxx := (-0.5 + cast(float) it / cast(float) x_ticks) / zoom + 0.5;
xxx = xxx * (xmax - pos.x / zoom) + (1 - xxx) * (xmin - pos.x / zoom);
array_add(*positions, .{cast(float) xpos, 0.0});
array_add(*positions, .{cast(float) xpos, cast(float) (content_height )});
to_render := tprint("%", formatFloat(cast(float) xxx, trailing_width = 1, zero_removal = .NO));
label_size := calculate_string_draw_size(*JetBrainsMonoRegular, to_render);
if style == .TICK_MARKS {
render_string(*JetBrainsMonoRegular, to_render,
.{xpos + left_bearing - (label_size.x / 2.0), xx (plot.height - bottom_bearing + 4.0)},
colors.text, plot.width, plot.height);
}
}
y_ticks := 10;
for 0..y_ticks {
ypos : s64 = cast(s64) (cast(float) it / cast(float) y_ticks * (content_height ));
yyy := ((0.5) - cast(float) it / cast(float) y_ticks) / zoom + 0.5;
yyy = yyy * (ymax - pos.y / zoom) + (1 - yyy) * (ymin - pos.y / zoom);
array_add(*positions, .{0.0, cast(float) ypos});
array_add(*positions, .{cast(float) (content_width ), cast(float) ypos});
to_render := tprint("%", formatFloat(cast(float) yyy, trailing_width = 1, zero_removal = .NO));
label_size := calculate_string_draw_size(*JetBrainsMonoRegular, to_render);
if style == .TICK_MARKS {
render_string(*JetBrainsMonoRegular, to_render,
.{xx cast(s64) (left_bearing - label_size.x - 4.0), xx cast(s64) (ypos - label_size.y / 2.0)},
colors.text, plot.width, plot.height);
}
}
glViewport(xx left_bearing, xx bottom_bearing, xx (width - left_bearing), xx (height - bottom_bearing));
axis_vao : GLuint;
glGenVertexArrays(1, *axis_vao);
glBindVertexArray(axis_vao);
axis_vbo : GLuint;
glGenBuffers(1, *axis_vbo);
glBindBuffer(GL_ARRAY_BUFFER, axis_vbo);
glBufferData(GL_ARRAY_BUFFER, positions.count * size_of(Vector2), positions.data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null);
proj := orthographic_projection_matrix(0.0, cast(float) (content_width ), 0.0, cast(float) (content_height ), -1.0, 1.0);
glUseProgram(line_shader);
glUniformMatrix4fv(glGetUniformLocation(line_shader, "proj"), 1, GL_TRUE, xx *proj);
glUniform4f(glGetUniformLocation(line_shader, "data_color"), colors.lines.x, colors.lines.y, colors.lines.z, 1.0);
glDrawArrays(GL_LINES, 0, xx positions.count);
glDeleteBuffers(1, *axis_vbo);
glDeleteVertexArrays(1, *axis_vao);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
draw_scale :: (using plot: Plot, colors: PlotColors) {
ar : float = cast(float) width / cast(float) height;
content_x : s32 = x + xx left_bearing;
content_y : s32;
content_width : s32 = width - xx left_bearing;
content_height : s32 = height - xx bottom_bearing;
extent_x := xmax - xmin;
extent_y := ymax - ymin;
positions : [..]Vector2;
defer array_reset(*positions);
// array_add(*positions, .{0.0, 0.0});
// array_add(*positions, .{xx width, 0.0});
// array_add(*positions, .{xx width, 0.0});
// array_add(*positions, .{xx width, xx height});
// array_add(*positions, .{xx width, xx height});
// array_add(*positions, .{0.0, xx height});
// array_add(*positions, .{0.0, xx height});
// array_add(*positions, .{0.0, 0.0});
array_add(*positions, .{5.0, 5.0});
array_add(*positions, .{5.0 + content_width / 10.0, 5.0});
to_render := tprint("%", formatFloat(cast(float) extent_x / zoom / 10.0, trailing_width = 1, zero_removal = .NO));
label_size := calculate_string_draw_size(*JetBrainsMonoRegular, to_render);
render_string(*JetBrainsMonoRegular, to_render,
.{xx cast(s64) (5.0 + content_width / 10.0), xx height - label_size.y - 5.0},
colors.text, plot.width, plot.height);
array_add(*positions, .{5.0, 5.0});
array_add(*positions, .{5.0, 5.0 + content_height / 10.0});
to_render = tprint("%", formatFloat(cast(float) extent_y / zoom / 10.0, trailing_width = 1, zero_removal = .NO));
label_size = calculate_string_draw_size(*JetBrainsMonoRegular, to_render);
render_string(*JetBrainsMonoRegular, to_render,
.{xx cast(s64) (5.0), height - 5.0 - content_height / 10.0 - label_size.y},
colors.text, plot.width, plot.height);
glViewport(0, 0, xx width, xx height);
scale_vao : GLuint;
glGenVertexArrays(1, *scale_vao);
glBindVertexArray(scale_vao);
scale_vbo : GLuint;
glGenBuffers(1, *scale_vbo);
glBindBuffer(GL_ARRAY_BUFFER, scale_vbo);
glBufferData(GL_ARRAY_BUFFER, positions.count * size_of(Vector2), positions.data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null);
proj := orthographic_projection_matrix(0.0, cast(float) (width), 0.0, cast(float) (height), -1.0, 1.0);
//proj := orthographic_projection_matrix(xmin * zoom, xmax * zoom, ymin * zoom, ymax * zoom, -1.0, 1.0);
glUseProgram(line_shader);
glUniformMatrix4fv(glGetUniformLocation(line_shader, "proj"), 1, GL_TRUE, xx *proj);
glUniform4f(glGetUniformLocation(line_shader, "data_color"), 1.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_LINES, 0, xx positions.count);
glDeleteBuffers(1, *scale_vbo);
glDeleteVertexArrays(1, *scale_vao);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
is_in_rect :: (pos: Vector2, size: Vector2, test: Vector2) -> bool {
top_left : Vector2 = pos;
bottom_right : Vector2 = pos + size;
if (test.x < top_left.x) return false;
if (test.y < top_left.y) return false;
if (test.x > bottom_right.x) return false;
if (test.y > bottom_right.y) return false;
return true;
}
// FONTS
#import "freetype-2.12.1";
#import "harfbuzz";
#import "stb_rect_pack";
ftlib : FT_Library;
ATLAS_SIZE :: 2048;
JetBrainsMonoRegular: Font;
Glyph :: struct {
utf32 : u32;
index : u32;
x, y : s16;
width, height : u32;
bearing_x, bearing_y : s16;
y_max, y_min : s16;
ascent : s16;
advance : s16;
uv0, uv1 : Vector2;
bitmap : *u8;
}
Font :: struct {
face : FT_Face;
glyphs : Table(u32, Glyph);
graphics_font : GraphicsFont;
hb : *hb_font_t;
}
init_font :: (using font: *Font, filename: string, size: s32) {
pixel_size := size * 96.0 / 72.0;
//pixel_size := size;
FT_New_Face(ftlib, filename.data, 0, *face);
//error := FT_Set_Pixel_Sizes(face, 0, xx size);
error := FT_Set_Char_Size(face, 0, size * 64, 0, 96);
if error
log("%", to_string(FT_Error_String(error)));
rects : [..] stbrp_rect;
defer array_free(rects);
nodes : [ATLAS_SIZE] stbrp_node;
stbrpcontext : stbrp_context;
stbrp_init_target(*stbrpcontext, ATLAS_SIZE, ATLAS_SIZE, nodes.data, nodes.count);
atlas : *u8 = alloc(ATLAS_SIZE * ATLAS_SIZE);
defer free(atlas);
for 0..face.num_glyphs {
error = FT_Load_Glyph(face, cast(u32) it, FT_LOAD_RENDER);
if error
log("%", to_string(FT_Error_String(error)));
array_add(*rects, stbrp_rect.{cast(s32) face.glyph.glyph_index, cast(s32) face.glyph.bitmap.width,
cast(s32) face.glyph.bitmap.rows, 0, 0, 0});
glyph : Glyph;
glyph.utf32 = cast(u32) it;
glyph.index = face.glyph.glyph_index;
glyph.bearing_x = cast,trunc(s16) face.glyph.bitmap_left;
glyph.bearing_y = cast,trunc(s16) face.glyph.bitmap_top;
glyph.width = face.glyph.bitmap.width;
glyph.height = face.glyph.bitmap.rows;
glyph.advance = cast(s16) face.glyph.advance.x >> 6;
glyph.bitmap = alloc(glyph.height * glyph.width);
memcpy(glyph.bitmap, face.glyph.bitmap.buffer, glyph.height * glyph.width);
table_set(*font.glyphs, glyph.index, glyph);
}
stbrp_pack_rects(*stbrpcontext, rects.data, cast(s32) rects.count);
copybitmaptoatlas :: (bitmap : *u8, width: s32, height: s32, atlas: *u8, destx: s32, desty: s32) {
if (destx >= ATLAS_SIZE)
destx = ATLAS_SIZE - 1;
if (desty >= ATLAS_SIZE)
desty = ATLAS_SIZE - 1;
for j: 0..height - 1 {
for i: 0..width - 1 {
atlas[(desty + j) * ATLAS_SIZE + (destx + i)] = bitmap[j * width + i];
}
}
}
for rect : rects {
glyph : *Glyph = table_find_pointer(*font.glyphs, cast(u32) rect.id);
if (glyph.bitmap) {
glyph.x = cast,trunc(s16) rect.x;
glyph.y = cast,trunc(s16) rect.y;
copybitmaptoatlas(glyph.bitmap, cast(s32) glyph.width, cast(s32) glyph.height, atlas, cast(s32) rect.x, cast(s32) rect.y);
}
}
graphics_init_font(*font.graphics_font, atlas);
blob : *hb_blob_t = hb_blob_create_from_file(filename.data);
hb_face : *hb_face_t = hb_face_create(blob, 0);
hb = hb_font_create(hb_face);
hb_font_set_ppem(hb, xx pixel_size, xx pixel_size);
hb_font_set_scale(hb, cast(s32) pixel_size * 64, cast(s32) pixel_size * 64);
}
render_string :: (font: *Font, text: string, pos: Vector2, colour: Vector3 = .{1.0, 1.0, 1.0}, fb_width: s32, fb_height: s32,
nicebackground := false) {
if !text
return;
if nicebackground {
render_string(font, text, pos + .{3.0, 3.0}, xyz(0.0), fb_width, fb_height, false);
}
buf : *hb_buffer_t = hb_buffer_create();
hb_buffer_add_utf8(buf, text.data, xx text.count, 0, -1);
hb_buffer_set_direction(buf, hb_direction_t.LTR);
hb_buffer_set_script(buf, hb_script_t.HB_SCRIPT_LATIN);
hb_buffer_set_language(buf, hb_language_from_string("en", -1));
features : [1]hb_feature_t;
//features[0].tag = HB_TAG(#char "c", #char "a", #char "l", #char "t");
//features[0].value = 1;
//features[0].start = HB_FEATURE_GLOBAL_START;
//features[0].end = HB_FEATURE_GLOBAL_END;
hb_shape(font.hb, buf, features.data, features.count);
glyph_count : u32;
glyph_info : *hb_glyph_info_t = hb_buffer_get_glyph_infos(buf, *glyph_count);
glyph_pos : *hb_glyph_position_t = hb_buffer_get_glyph_positions(buf, *glyph_count);
vertices : [..] Vector2;
tex : [..] Vector2;
defer array_reset(*vertices);
defer array_reset(*tex);
render_pos := pos;
draw_size := calculate_string_draw_size(font, text);
for i : 0..glyph_count - 1 {
glyphid : hb_codepoint_t = glyph_info[i].codepoint;
x_offset : hb_position_t = glyph_pos[i].x_offset;
y_offset : hb_position_t = glyph_pos[i].y_offset;
x_advance : hb_position_t = glyph_pos[i].x_advance;
y_advance : hb_position_t = glyph_pos[i].y_advance;
glyph : *Glyph = table_find_pointer(*font.glyphs, glyphid);
//v0 := render_pos + xy(xx glyph.bearing_x, xx -glyph.bearing_y + draw_size.y);
v0 := render_pos + xy(cast(float) x_offset + cast(float) glyph.bearing_x,
cast(float) y_offset - cast(float) glyph.bearing_y + draw_size.y);
#if Y_IS_UP {
t0 := xy(cast(float) (glyph.x) / ATLAS_SIZE, (ATLAS_SIZE - (cast(float) (glyph.y))) / ATLAS_SIZE);
} else {
t0 := xy(cast(float) glyph.x / ATLAS_SIZE, cast(float) glyph.y / ATLAS_SIZE);
}
#if Y_IS_UP {
array_add(*vertices, v0);
array_add(*vertices, v0 + xy(0.0, cast(float) glyph.height));
array_add(*vertices, v0 + xy(cast(float) glyph.width, 0.0));
array_add(*vertices, v0 + xy(cast(float) glyph.width, 0.0));
array_add(*vertices, v0 + xy(0.0, cast(float) glyph.height));
array_add(*vertices, v0 + xy(cast(float) glyph.width, cast(float) glyph.height));
array_add(*tex, t0);
array_add(*tex, t0 + xy(0.0, -cast(float) (glyph.height) / ATLAS_SIZE));
array_add(*tex, t0 + xy(cast(float) (glyph.width) / ATLAS_SIZE, 0.0));
array_add(*tex, t0 + xy(cast(float) (glyph.width) / ATLAS_SIZE, 0.0));
array_add(*tex, t0 + xy(0.0, -cast(float) (glyph.height) / ATLAS_SIZE));
array_add(*tex, t0 + xy(cast(float) (glyph.width) / ATLAS_SIZE, -cast(float) (glyph.height) / ATLAS_SIZE));
} else {
array_add(*vertices, v0);
array_add(*vertices, v0 + xy(cast(float) glyph.width, 0.0));
array_add(*vertices, v0 + xy(0.0, cast(float) glyph.height));
array_add(*vertices, v0 + xy(cast(float) glyph.width, 0.0));
array_add(*vertices, v0 + xy(cast(float) glyph.width, cast(float) glyph.height));
array_add(*vertices, v0 + xy(0.0, cast(float) glyph.height));
array_add(*tex, t0);
array_add(*tex, t0 + xy(0.0, cast(float) glyph.height / ATLAS_SIZE));
array_add(*tex, t0 + xy(cast(float) glyph.width / ATLAS_SIZE, cast(float) glyph.height / ATLAS_SIZE));
array_add(*tex, t0 + xy(cast(float) glyph.width / ATLAS_SIZE, cast(float) glyph.height / ATLAS_SIZE));
array_add(*tex, t0 + xy(cast(float) glyph.width / ATLAS_SIZE, 0.0));
array_add(*tex, t0);
}
render_pos += xy(xx x_advance / 64.0, xx y_advance / 64.0);
}
graphics_render_font(*font.graphics_font, vertices, tex, colour, fb_width, fb_height);
hb_buffer_destroy(buf);
}
HB_TAG :: (c1: u8, c2: u8, c3: u8, c4: u8) -> hb_tag_t #expand {
return cast(hb_tag_t)(((cast(u32)(c1)&0xFF)<<24)|((cast(u32)(c2)&0xFF)<<16)|((cast(u32)(c3)&0xFF)<<8)|(cast(u32)(c4)&0xFF));
}
HB_FEATURE_GLOBAL_END :: cast,no_check(u32) -1;
calculate_string_draw_size :: (font: *Font, text: string) -> Vector2 {
if !text
return .{};
buf : *hb_buffer_t = hb_buffer_create();
hb_buffer_add_utf8(buf, text.data, xx text.count, 0, -1);
hb_buffer_set_direction(buf, hb_direction_t.LTR);
hb_buffer_set_script(buf, hb_script_t.HB_SCRIPT_LATIN);
hb_buffer_set_language(buf, hb_language_from_string("en", -1));
features : [1]hb_feature_t;
features[0].tag = HB_TAG(#char "c", #char "a", #char "l", #char "t");
features[0].value = 1;
features[0].start = HB_FEATURE_GLOBAL_START;
features[0].end = HB_FEATURE_GLOBAL_END;
hb_shape(font.hb, buf, features.data, features.count);
glyph_count_u : u32;
glyph_info : *hb_glyph_info_t = hb_buffer_get_glyph_infos(buf, *glyph_count_u);
glyph_pos : *hb_glyph_position_t = hb_buffer_get_glyph_positions(buf, *glyph_count_u);
glyph_count : s32 = xx glyph_count_u;
size : Vector2;
for i : 0..glyph_count - 1 {
glyphid : hb_codepoint_t = glyph_info[i].codepoint;
x_offset : hb_position_t = glyph_pos[i].x_offset;
y_offset : hb_position_t = glyph_pos[i].y_offset;
x_advance : hb_position_t = glyph_pos[i].x_advance;
y_advance : hb_position_t = glyph_pos[i].y_advance;
glyph : *Glyph = table_find_pointer(*font.glyphs, glyphid);
size.y = max(size.y, xx glyph.height);
size.x += x_advance / 64.0;
}
hb_buffer_destroy(buf);
return size;
}
// RENDERING
data_shader : GLuint;
line_shader : GLuint;
text_shader : GLuint;
Y_IS_UP :: true;
window_projection_matrix : Matrix4;
GraphicsFont :: struct {
atlasid : GLuint;
vao: GLuint;
vbo : GLuint;
}
graphics_init_font :: (graphics_font: *GraphicsFont, atlas: *u8) {
glGenTextures(1, *graphics_font.atlasid);
glBindTexture(GL_TEXTURE_2D, graphics_font.atlasid);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, ATLAS_SIZE, ATLAS_SIZE, 0, GL_RED, GL_UNSIGNED_BYTE, atlas);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
graphics_render_font :: (graphics_font: *GraphicsFont, vertices: []Vector2, tex: []Vector2, color: Vector3,
fb_width: s32, fb_height: s32) {
vao : GLuint;
glGenVertexArrays(1, *vao);
glViewport(0, 0, xx fb_width, xx fb_height);
glBindVertexArray(vao);
posvbo : GLuint;
glGenBuffers(1, *posvbo);
glBindBuffer(GL_ARRAY_BUFFER, posvbo);
glBufferData(GL_ARRAY_BUFFER, size_of(Vector2) * vertices.count, vertices.data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null);
texvbo : GLuint;
glGenBuffers(1, *texvbo);
glBindBuffer(GL_ARRAY_BUFFER, texvbo);
glBufferData(GL_ARRAY_BUFFER, size_of(Vector2) * tex.count, tex.data, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null);
proj: Matrix4 = orthographic_projection_matrix(0.0, xx fb_width, xx fb_height, 0.0, -1.0, 1.0);
view : Matrix4 = identity_of(Matrix4);
model : Matrix4 = identity_of(Matrix4);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, graphics_font.atlasid);
glUseProgram(text_shader);
glUniformMatrix4fv(glGetUniformLocation(text_shader, "proj"), 1, GL_TRUE, cast(*float) *proj);
glUniformMatrix4fv(glGetUniformLocation(text_shader, "view"), 1, GL_TRUE, cast(*float) *view);
glUniformMatrix4fv(glGetUniformLocation(text_shader, "model"), 1, GL_TRUE, cast(*float) *model);
glUniform3fv(glGetUniformLocation(text_shader, "data_color"), 1, *color.x);
glDrawArrays(GL_TRIANGLES, 0, cast(u32) vertices.count);
glDeleteBuffers(1, *posvbo);
glDeleteBuffers(1, *texvbo);
glDeleteVertexArrays(1, *vao);
glBindVertexArray(0);
}
make_shader :: (vertex_shader_text: string, fragment_shader_text: string) -> GLuint {
success : s32;
info : [512]u8;
vertex_shader := glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, *vertex_shader_text.data, null);
glCompileShader(vertex_shader);
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, *success);
if !success {
glGetShaderInfoLog(vertex_shader, 512, null, info.data);
log("[ERROR] SHADER VERTEX COMPILATION_FAILED %", to_string(info.data));
}
fragment_shader := glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, *fragment_shader_text.data, null);
glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, *success);
if !success {
glGetShaderInfoLog(fragment_shader, 512, null, info.data);
log("[ERROR] SHADER VERTEX COMPILATION_FAILED %", to_string(info.data));
}
shader := glCreateProgram();
glAttachShader(shader, vertex_shader);
glAttachShader(shader, fragment_shader);
glLinkProgram(shader);
glGetProgramiv(shader, GL_LINK_STATUS, *success);
if !success {
glGetProgramInfoLog(shader, 512, null, info.data);
log("[ERROR] SHADER PROGRAM LINKING_FAILED %", to_string(info.data));
}
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return shader;
}
///////////////////////////////////////////////////
// shader to render the lines representing the data
///////////////////////////////////////////////////
data_vertex_shader_text : string = #string DONE
#version 330 core
layout (location = 0) in float data_x;
layout (location = 1) in float data_y;
uniform mat4 model;
uniform mat4 proj;
void main() {
gl_Position = proj * model * vec4(data_x, data_y, 0.0, 1.0);
}
DONE
data_fragment_shader_text : string = #string DONE
#version 330 core
uniform vec4 data_color;
out vec4 color;
float float_to_srgb(float l) {
if (l < 0.0031308) {
return l * 12.92;
} else {
return 1.055 * pow(l, 0.41666) - 0.055;
}
}
void main() {
color = vec4(data_color.rgb, float_to_srgb(data_color.a));
}
DONE
//////////////////////////////////////////////////////////
// general line shader for axis, squares, tick marks, etc.
//////////////////////////////////////////////////////////
line_vertex_shader_text : string = #string DONE
#version 330 core
layout (location = 0) in vec2 data_pos;
uniform mat4 proj;
void main() {
gl_Position = proj * vec4(data_pos.x, data_pos.y, 0.0, 1.0);
}
DONE
line_fragment_shader_text : string = #string DONE
#version 330 core
uniform vec4 data_color;
out vec4 color;
float float_to_srgb(float l) {
if (l < 0.0031308) {
return l * 12.92;
} else {
return 1.055 * pow(l, 0.41666) - 0.055;
}
}
void main() {
color = vec4(data_color.rgb, float_to_srgb(data_color.a));
}
DONE
////////////////////////
// shader to render text
////////////////////////
text_vertex_shader_text : string = #string DONE
#version 330 core
in vec2 data_pos;
in vec2 data_tex;
out vec2 tex;
uniform mat4 proj;
uniform mat4 view;
uniform mat4 model;
void main() {
gl_Position = proj * view * model * vec4(data_pos, 0.0, 1.0);
tex = vec2(data_tex.x, 1.0 - data_tex.y);
}
DONE
text_fragment_shader_text : string = #string DONE
#version 330 core
in vec2 tex;
out vec4 color;
uniform sampler2D sampler;
uniform vec3 data_color;
float float_to_srgb(float l) {
if (l < 0.0031308) {
return l * 12.92;
} else {
return 1.055 * pow(l, 0.41666) - 0.055;
}
}
vec4 linear_to_srgb(vec4 linear) {
vec4 srgb;
if (linear.x < 0.0031308) {
srgb.x = linear.x * 12.92;
} else {
srgb.x = 1.055 * pow(linear.x, 0.41666) - 0.055;
}
if (linear.y < 0.0031308) {
srgb.y = linear.y * 12.92;
} else {
srgb.y = 1.055 * pow(linear.y, 0.41666) - 0.055;
}
if (linear.z < 0.0031308) {
srgb.z = linear.z * 12.92;
} else {
srgb.z = 1.055 * pow(linear.z, 0.41666) - 0.055;
}
srgb.w = linear.w;
return srgb;
}
float float_to_linear(float s) {
if (s <= 0.04045) {
return s / 12.92;
} else {
return pow((s + 0.055) / 1.055, 2.4);
}
}
vec4 srgb_to_linear(vec4 srgb) {
return vec4(float_to_linear(srgb.r), float_to_linear(srgb.g), float_to_linear(srgb.b), srgb.a);
}
void main() {
float sampled = texture(sampler, tex).r;
color = vec4(data_color, float_to_srgb(sampled));
}
DONE