diff --git a/module.jai b/module.jai new file mode 100644 index 0000000..16b2547 --- /dev/null +++ b/module.jai @@ -0,0 +1,1117 @@ +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); +} + +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