diff --git a/draw/draw.odin b/draw/draw.odin index 9d162e0..a2684ca 100644 --- a/draw/draw.odin +++ b/draw/draw.odin @@ -2,11 +2,9 @@ package edit2draw import "base:runtime" import "core:fmt" -import "core:math" import "core:math/linalg" import gl "vendor:OpenGL" import b2 "vendor:box2d" -import "vendor:glfw" Camera :: struct { @@ -236,10 +234,6 @@ background_destroy :: proc(back : ^Background) { background_draw :: proc(back : ^Background, cam : ^Camera) { gl.UseProgram(back.program) - time := f32(glfw.GetTime()) - time = math.mod_f32(time, f32(10.0)) - - gl.Uniform1f(back.uniforms["time"].location, time) gl.Uniform2f( back.uniforms["resolution"].location, f32(cam.width), diff --git a/glyph/glyph.odin b/glyph/glyph.odin new file mode 100644 index 0000000..ec6b575 --- /dev/null +++ b/glyph/glyph.odin @@ -0,0 +1,308 @@ +package e2glyph + +/* + Provides text rendering using glfw, stb_truetype for the engine + + +*/ + + + +import "core:math" +import os "core:os" +import "core:mem" +import gl "vendor:OpenGL" +import stbtt "vendor:stb/truetype" + +rect4 :: struct { + l, t, r, b: f32, +} + +GlyphAtlasItem :: struct #packed{ + filled: bool, + tex_coords: rect4, + index: i32, + + //Baseline to top + distance_b2t: i32, +} + +GlyphRectInstance :: struct #packed{ + pos: rect4, + tex_coords: rect4, + color: [4]f32, + index: u32, + subpixel_shift: f32, +} + +GlyphState :: struct { + font_info: stbtt.fontinfo, + font_size_pt: f32, + vao: u32, + program_id: u32, + curr, pos : [2]f32, + atlas_width, atlas_height: i32, + atlas_items: [127]GlyphAtlasItem, + atlas_texture: u32, + rect_buffer: [dynamic]GlyphRectInstance, + rect_instances_vbo: u32, + framebuf: u32, + + width, height : i32, +} + + +glyph_init :: proc(glyph: ^GlyphState) { + + ok : bool + glyph.program_id, ok = gl.load_shaders_source( + #load("./shaders/font_vert.glsl"), + #load("./shaders/font_frag.glsl"), + ) + + //gl.UseProgram(glyph.program_id) + + + gl.CreateBuffers(1, &glyph.rect_instances_vbo) + gl.CreateVertexArrays(1, &glyph.vao) + + gl.VertexArrayVertexBuffer(glyph.vao, 0, glyph.rect_instances_vbo, 0, size_of(GlyphRectInstance)) + + gl.VertexArrayBindingDivisor(glyph.vao, 0, 1) + + gl.EnableVertexArrayAttrib(glyph.vao, 0) // read it from a data source + gl.VertexArrayAttribBinding(glyph.vao, 0, 0) // read from data source 1 + gl.VertexArrayAttribFormat(glyph.vao, 0, 4, gl.FLOAT, false, 0) + + gl.EnableVertexArrayAttrib(glyph.vao, 1) // read it from a data source + gl.VertexArrayAttribBinding(glyph.vao, 1, 0) // read from data source 1 + gl.VertexArrayAttribFormat(glyph.vao, 1, 4, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, tex_coords))) + gl.EnableVertexArrayAttrib(glyph.vao, 2) // read it from a data source + gl.VertexArrayAttribBinding(glyph.vao, 2, 0) // read from data source 1 + gl.VertexArrayAttribFormat(glyph.vao, 2, 4, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, color))) + gl.EnableVertexArrayAttrib(glyph.vao, 3) // read it from a data source + gl.VertexArrayAttribBinding(glyph.vao, 3, 0) // read from data source 1 + gl.VertexArrayAttribFormat(glyph.vao, 3, 1, gl.FLOAT, false, u32(offset_of(GlyphRectInstance, subpixel_shift))) + + + gl.CreateTextures(gl.TEXTURE_RECTANGLE, 1, &glyph.atlas_texture) + gl.TextureStorage2D(glyph.atlas_texture, 1, gl.RGB8, glyph.atlas_width, glyph.atlas_height) + + + /* + gl.BindVertexArray(0) + gl.BindBuffer(gl.ARRAY_BUFFER, 0) + gl.BindTexture(gl.TEXTURE_RECTANGLE, 0) + gl.BindTexture(gl.TEXTURE_2D, 0) + gl.DisableVertexArrayAttrib(glyph.vao, 0) + gl.DisableVertexArrayAttrib(glyph.vao, 1) + gl.DisableVertexArrayAttrib(glyph.vao, 2) + gl.DisableVertexArrayAttrib(glyph.vao, 3) + */ + + font_data, _ := os.read_entire_file_from_path("./Ubuntu-Regular.ttf", context.allocator) + + stbtt.InitFont(&glyph.font_info, &font_data[0], 0) + + gl.UseProgram(0) +} + + +glyph_draw_font :: proc(glyph_state: ^GlyphState, text: string, pos : [2]f32, color : [4]u8) { + + //using app + //text := get_string(&gap_buf) + + coverage_adjustment: f32 = 0.0 + text_color: [4]f32 = {f32( color.r)/ 255.0 ,f32(color.g)/255.0, f32(color.b)/255.0, f32(color.a)/255.0} + + glyph_state.rect_buffer = make([dynamic]GlyphRectInstance, 0, 1000) + { + //put every glyph in text into rect_buffer + + //Get the font metrics, the stbtt_ScaleForMappingEmToPixels() and stbtt_GetFontVMetrics() documentation for details + + //From "Font size in pixels or points" in stb_truetype + + font_size_px := glyph_state.font_size_pt * 1.3333 + font_scale := stbtt.ScaleForMappingEmToPixels(&glyph_state.font_info, font_size_px) + + font_ascent, font_descent, font_line_gap: i32 = 0, 0, 0 + stbtt.GetFontVMetrics(&glyph_state.font_info, &font_ascent, &font_descent, &font_line_gap) + line_height := f32(font_ascent - font_descent + font_line_gap) * font_scale + baseline := f32(font_ascent) * font_scale + + + //glyph_state.curr.x, glyph_state.curr.y = glyph_state.pos.x, f32(glyph_state.pos.y) + math.round(baseline) + + prev_codepoint: rune = 0 + + delete(glyph_state.rect_buffer) + glyph_state.rect_buffer = make([dynamic]GlyphRectInstance, 0, 1000) + glyph_state.curr = pos + glyph_state.curr.y += math.round(baseline) + + for c, i in text { + codepoint := u32(c) + + if prev_codepoint != 0 { + glyph_state.curr.x += f32(stbtt.GetCodepointKernAdvance(&glyph_state.font_info, prev_codepoint, c)) * font_scale + } + + prev_codepoint = c + + if c == '\n' { + glyph_state.curr.x = glyph_state.pos.x + glyph_state.curr.y += math.round(line_height) + + } else if c == '\t' { + + glyph_state.curr.x += 2 * glyph_state.font_size_pt + } else { + + horizontal_filter_padding, subpixel_positioning_left_padding: i32 = 1, 1 + assert(codepoint <= 127) + glyph_atlas := glyph_state.atlas_items[codepoint] + + glyph_index := stbtt.FindGlyphIndex(&glyph_state.font_info, c) + + x0, y0, x1, y1: i32 = 0, 0, 0, 0 + + stbtt.GetGlyphBitmapBox(&glyph_state.font_info, glyph_index, font_scale, font_scale, &x0, &y0, &x1, &y1) + + glyph_width_px : i32 = x1 - x0 + glyph_height_px: i32 = y1 - y0 + + distance_from_baseline_to_top_px: i32 = -y0 + + if glyph_width_px > 0 && glyph_height_px > 0 + { + padded_glyph_width_px : i32 = subpixel_positioning_left_padding + horizontal_filter_padding + glyph_width_px + horizontal_filter_padding + padded_glyph_height_px: i32 = glyph_height_px + + atlas_item_width, atlas_item_height: i32 = 32, 32 + + atlas_item_x: i32 = i32( codepoint % u32(glyph_state.atlas_width / atlas_item_width), ) * atlas_item_width + atlas_item_y: i32 = i32( codepoint / u32(glyph_state.atlas_height / atlas_item_height), ) * atlas_item_height + + assert(padded_glyph_width_px <= atlas_item_width && padded_glyph_height_px <= atlas_item_height) + + horizontal_resolution: i32 = 3 + bitmap_stride : i32 = atlas_item_width * horizontal_resolution + bitmap_size : uint = uint(bitmap_stride * atlas_item_height) + glyph_bitmap, _ := mem.alloc_bytes(int(bitmap_size)) + glyph_offset_x := (subpixel_positioning_left_padding + horizontal_filter_padding) * horizontal_resolution + + stbtt.MakeGlyphBitmap(&glyph_state.font_info, &glyph_bitmap[glyph_offset_x], atlas_item_width * horizontal_resolution, atlas_item_height, bitmap_stride, font_scale * f32(horizontal_resolution), font_scale, glyph_index) + + atlas_item_bitmap, _ := mem.alloc_bytes(int(bitmap_size)) + + filter_weights: [5]u8 = {0x08, 0x4D, 0x56, 0x4D, 0x08} + + for y in 0 ..< padded_glyph_height_px + { + + x_end: i32 = padded_glyph_width_px * horizontal_resolution - 1 + + for x in 4 ..< x_end { + filter_weight_index: i32 = 0 + sum: i32 + kernel_x_end: i32 = (x == x_end - 1) ? x + 1 : x + 2 + + for kernel_x in x - 2 ..= kernel_x_end + { + assert(kernel_x >= 0 && kernel_x < x_end + 1) + assert(y >= 0 && y < padded_glyph_height_px) + + offset: i32 = kernel_x + y * bitmap_stride + assert(offset >= 0 && uint(offset) < bitmap_size) + + sum += i32(i32(glyph_bitmap[offset]) * i32(filter_weights[filter_weight_index])) + + filter_weight_index += 1 + } + sum = sum / 255 + atlas_item_bitmap[x + y * bitmap_stride] = (sum > 255) ? 255 : u8(sum) + } + } + + mem.free_bytes(glyph_bitmap) + gl.TextureSubImage2D(glyph_state.atlas_texture, 0, atlas_item_x, atlas_item_y, atlas_item_width, atlas_item_height, gl.RGB, gl.UNSIGNED_BYTE, rawptr(&atlas_item_bitmap[0])) + + mem.free_bytes(atlas_item_bitmap) + glyph_atlas.tex_coords.l = f32(atlas_item_x) + glyph_atlas.tex_coords.t = f32(atlas_item_y) + glyph_atlas.tex_coords.r = f32(atlas_item_x + padded_glyph_width_px) + glyph_atlas.tex_coords.b = f32(atlas_item_y + padded_glyph_height_px) + } else { + glyph_atlas.tex_coords.l = -1 + glyph_atlas.tex_coords.t = -1 + glyph_atlas.tex_coords.r = -1 + glyph_atlas.tex_coords.b = -1 + } + + glyph_atlas.index = glyph_index + glyph_atlas.distance_b2t = distance_from_baseline_to_top_px + glyph_atlas.filled = true + glyph_state.atlas_items[codepoint] = glyph_atlas + + glyph_advance_width, glyph_left_side_bearing: i32 = 0, 0 + + stbtt.GetGlyphHMetrics(&glyph_state.font_info, glyph_atlas.index, &glyph_advance_width, &glyph_left_side_bearing) + + if glyph_atlas.tex_coords.l != -1 + { + glyph_pos_x: f32 = glyph_state.curr.x + (f32(glyph_left_side_bearing) * font_scale) + + glyph_pos_x_px, glyph_pos_x_subpixel_shift: f32 = math.modf_f32(glyph_pos_x) + + + glyph_pos_y_px: f32 = glyph_state.curr.y - f32(glyph_atlas.distance_b2t) + + glyph_width_with_horiz_filter_padding: i32 = i32(glyph_atlas.tex_coords.r - glyph_atlas.tex_coords.l) + glyph_height: i32 = i32(glyph_atlas.tex_coords.b - glyph_atlas.tex_coords.t) + + r: GlyphRectInstance + + r.pos.l = f32(glyph_pos_x_px - f32(subpixel_positioning_left_padding + horizontal_filter_padding)) + r.pos.t = f32(glyph_pos_y_px) + r.pos.r = f32(glyph_pos_x_px - f32(subpixel_positioning_left_padding + horizontal_filter_padding) + f32(glyph_width_with_horiz_filter_padding)) + r.pos.b = f32(glyph_pos_y_px + f32(glyph_height)) + + r.subpixel_shift = glyph_pos_x_subpixel_shift + r.tex_coords = glyph_atlas.tex_coords + r.color = text_color + r.index = u32(i + 1) + + + append(&glyph_state.rect_buffer, r) + } + + glyph_state.curr.x += f32(glyph_advance_width) * font_scale + } + } + } + + { + if len(text) > 0 { + gl.NamedBufferData(glyph_state.rect_instances_vbo, int(len(glyph_state.rect_buffer) * size_of(GlyphRectInstance)), &glyph_state.rect_buffer[0], gl.DYNAMIC_DRAW) + } + + gl.Enable(gl.BLEND) + gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC1_COLOR) + + gl.BindVertexArray(glyph_state.vao) + gl.UseProgram(glyph_state.program_id) + gl.ProgramUniform2f(glyph_state.program_id, 0, f32(glyph_state.width) / 2.0, f32(glyph_state.height) / 2.0) + gl.ProgramUniform1ui(glyph_state.program_id, 1, u32(coverage_adjustment)) + gl.ProgramUniform1ui(glyph_state.program_id, 2, 0) + gl.BindTextureUnit(0, glyph_state.atlas_texture) + gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, i32(len(glyph_state.rect_buffer))) + + + gl.UseProgram(0) + gl.BindVertexArray(0) + + gl.InvalidateBufferData(glyph_state.rect_instances_vbo) + } +} diff --git a/glyph/shaders/font_frag.glsl b/glyph/shaders/font_frag.glsl new file mode 100644 index 0000000..70b2962 --- /dev/null +++ b/glyph/shaders/font_frag.glsl @@ -0,0 +1,52 @@ +#version 450 core +#extension GL_ARB_texture_rectangle: enable + +layout(location=1) uniform uint coverage_adjustment; +layout(location=2) uniform uint front_index; //gap buffer front + +layout(binding = 0) uniform sampler2DRect glyph_atlas; + +in vec2 tex_coords; +in flat vec4 color; +in flat float subpixel_shift; +in flat uint index_out; + + +layout(location = 0, index = 0) out vec4 fragment_color; +layout(location = 0, index = 1) out vec4 blend_weights; + +void main (){ + + + + vec3 current = texelFetch(glyph_atlas, ivec2(tex_coords) + ivec2( 0, 0)).rgb; + vec3 previous = texelFetch(glyph_atlas, ivec2(tex_coords) + ivec2(-1, 0)).rgb; + float r = current.r, g = current.g, b = current.b; + if (subpixel_shift <= 1.0/3.0) { + float z = 3.0 * subpixel_shift; + r = mix(current.r, previous.b, z); + g = mix(current.g, current.r, z); + b = mix(current.b, current.g, z); + } else if (subpixel_shift <= 2.0/3.0) { + float z = 3.0 * subpixel_shift - 1.0; + r = mix(previous.b, previous.g, z); + g = mix(current.r, previous.b, z); + b = mix(current.g, current.r, z); + } else if (subpixel_shift < 1.0) { + float z = 3.0 * subpixel_shift - 2.0; + r = mix(previous.g, previous.r, z); + g = mix(previous.b, previous.g, z); + b = mix(current.r, previous.b, z); + } + + vec3 pixel_coverages = vec3(r, g, b); + + if(coverage_adjustment >= 0){ + pixel_coverages = min(pixel_coverages * (1 + coverage_adjustment), 1); + }else{ + pixel_coverages = max((1 - (1 - pixel_coverages) * (1 + -coverage_adjustment)), 0); + } + + fragment_color = color * vec4(pixel_coverages, 1); + blend_weights = vec4(color.a * pixel_coverages, color.a); +} diff --git a/glyph/shaders/font_vert.glsl b/glyph/shaders/font_vert.glsl new file mode 100644 index 0000000..c18969a --- /dev/null +++ b/glyph/shaders/font_vert.glsl @@ -0,0 +1,45 @@ +#version 450 core +#extension GL_ARB_texture_rectangle: enable + + +layout(location=0) uniform vec2 half_viewport_size; + +//layout(location=0) in uvec2 ltrb_index; +layout(location=0) in vec4 rect_ltrb; +layout(location=1) in vec4 rect_tex_ltrb; +layout(location=2) in vec4 rect_color; +//layout(location=4) in uint index; +layout(location=3) in float rect_subpixel_shift; + + +flat out vec2 tex_coords; +flat out vec4 color; +flat out float subpixel_shift; +flat out uint index_out; + + +void main(){ + uvec2[6] rect_vertices = { + {0, 1}, //left top + {0, 3}, //left buttom + {2, 1}, //right top + {0, 3}, //Left buttom + {2, 3}, //right bottom + {2, 1}, //right top + }; + //Convert color to pre-multipled alpha + + uvec2 indexx = rect_vertices[gl_VertexID]; + + color = vec4(rect_color.rgb * rect_color.a, rect_color.a); + + vec2 pos = vec2( rect_ltrb[indexx.x], rect_ltrb[indexx.y]); + tex_coords = vec2(rect_tex_ltrb[indexx.x], rect_tex_ltrb[indexx.y]); + subpixel_shift = rect_subpixel_shift; + //index_out = index; + + + vec2 axes_flip = vec2(1, -1); // to flip y axis from bottom up + vec2 pos_in_ndc = (pos / half_viewport_size - 1.0) * axes_flip; + gl_Position = vec4(pos_in_ndc, 0, 1); +}