WIP (Broken) docs and huge changes
This commit is contained in:
		| @@ -1,9 +1,8 @@ | ||||
| VEFontCache Odin Port | ||||
| VE Text Rendering Library | ||||
| Copyright 2024 Edward R. Gonzalez | ||||
|  | ||||
| This project is based on Vertex Engine GPU Font Cache | ||||
| by Xi Chen (https://github.com/hypernewbie/VEFontCache). It has been substantially | ||||
| rewritten and redesigned for the Odin programming language. | ||||
| by Xi Chen (https://github.com/hypernewbie/VEFontCache). It has been substantially overhauled from its original implementation. | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||||
|   | ||||
| @@ -1,7 +1,15 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| /* | ||||
| The choice was made to keep the LRU cache implementation as close to the original as possible. | ||||
| /* Note(Ed): | ||||
| 	Original implementation has been changed moderately. | ||||
| 	Notably the LRU is now type generic for its key value. | ||||
| 	This was done to profile between using u64, u32, and u16. | ||||
|  | ||||
| 	What ended up happening was using u32 for both the atlas and the shape cache  | ||||
| 	yielded a several ms save for processing thousands of draw text calls. | ||||
|  | ||||
| 	There was an attempt at an optimization pass but the directives done here (other than force_inline) | ||||
| 	are marginal changes at best. | ||||
| */ | ||||
|  | ||||
| import "base:runtime" | ||||
| @@ -177,7 +185,7 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| LRU_Link :: struct { | ||||
| LRU_Link :: struct #packed { | ||||
| 	value : i32, | ||||
| 	ptr   : Pool_ListIter, | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,45 @@ | ||||
| # VE Font Cache : Odin Port | ||||
| # VE Text Rendering Library | ||||
|  | ||||
| This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library. | ||||
| > Vertex Engine GPU Text Rendering Library | ||||
|  | ||||
| https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff | ||||
|  | ||||
| This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. | ||||
| Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. | ||||
|  | ||||
| Since then the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements. | ||||
|  | ||||
| Features: | ||||
|  | ||||
| * Simple and well documented. | ||||
| * Load and unload fonts at anytime | ||||
| * Almost entirely configurabe and tunable at runtime! | ||||
| * Full support for hot-reload | ||||
|   * Clear the caches at any-time! | ||||
| * Robust quality of life features: | ||||
|   * Tracks text layers! | ||||
|   * Push and pop stack for font, font_size, view, position, scale and zoom! | ||||
|   * Enforce even only font-sizing | ||||
|   * Snap-positining to view for better hinting | ||||
| * Basic or advanced text shaping via Harfbuzz | ||||
| * All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu. | ||||
|   * Can hand thousands of draw text calls with very large or small shapes. | ||||
| * 4-Level Regioned Texture Atlas for caching rendered glyphs | ||||
| * Text shape caching | ||||
| * Glyph texture buffer for rendering the text with super-sampling to downsample to the atlas or direct to target screen. | ||||
| * Super-sample by a font size scalar for sharper glyphs | ||||
| * All caching backed by an optimized 32-bit LRU indexing cache | ||||
| * Provides a draw list that is backend agnostic (see [backend](./backend) for usage example). | ||||
|  | ||||
| Upcoming: | ||||
|  | ||||
| * Support for ear-clipping triangulation | ||||
|   * Support for which triangulation method used on a by font basis? | ||||
| * Multi-threading supported job queue. | ||||
|   * Lift heavy-lifting portion of the library's context into a thread context. | ||||
|   * Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread. | ||||
|   * User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.) | ||||
|  | ||||
| See: [docs/Readme.md](docs/Readme.md) for the library's interface. | ||||
|  | ||||
| ## Building | ||||
| @@ -12,40 +48,7 @@ See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing th | ||||
|  | ||||
| Currently the scripts provided & the library itself were developed & tested on Windows. There are bash scripts for building on linux (they build on WSL but need additional testing). | ||||
|  | ||||
| The library depends on freetype, harfbuzz, & stb_truetype to build.   | ||||
| Note: freetype and harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). | ||||
| The library depends on harfbuzz, & stb_truetype to build.   | ||||
| Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). | ||||
|  | ||||
| ## Changes from orignal | ||||
|  | ||||
| * Font Parser & Glyph shaper are abstracted to their own warpper interface | ||||
| * ve_fontcache_loadfile not ported (ust use core:os or os2, then call load_font) | ||||
| * Macro defines have been coverted (mostly) to runtime parameters | ||||
| * Support for hot_reloading | ||||
| * Curve quality step interpolation for glyph rendering can be set on a per font basis. | ||||
| * All codepaths heavily changed (its faster) | ||||
|  | ||||
| ## TODOs | ||||
|  | ||||
| ### Additional Features: | ||||
|  | ||||
| * Support for freetype (WIP, Currently a mess... and slow) | ||||
| * Add ability to conditionally compile dependencies (so that the user may not need to resolve those packages). | ||||
| * Ability to set a draw transform, viewport and projection | ||||
|   * By default the library's position is in unsigned normalized render space | ||||
|   * Could implement a similar design to sokol_gp's interface | ||||
|  | ||||
| ### Optimization: | ||||
|  | ||||
| * Check if its better to store the glyph vertices if they need to be re-cached to atlas or directly drawn. | ||||
| * Look into setting up multi-threading by giving each thread a context | ||||
|   * There is a heavy performance bottleneck in iterating the text/shape/glyphs on the cpu (single-thread) vs the actual rendering *(if doing thousands of drawing commands)* | ||||
|   * draw_text can provide in the context a job list per thread for the user to thenk hookup to their own threading solution to handle. | ||||
|   * Context would need to be segregated into staged data structures for each thread to utilize | ||||
|     * This would need to converge to the singlar draw_list on a per layer basis. The interface expects the user to issue commands single-threaded unless, its assumed the user is going to feed the gpu the commands & data through separate threads as well (not ideal ux). | ||||
|   * How the contexts are given jobs should be left up to the user (can recommend a screen quadrant based approach in demo examples) | ||||
|  | ||||
| Failed Attempts: | ||||
|  | ||||
| * Attempted to chunk the text to more granular 'shapes' from `draw_list` before doing the actual call to `draw_text_shape`. This lead to a larger performance cost due to the additional iteration across the text string. | ||||
| * Attempted to cache the shape draw_list for future calls. Led to larger performance cost due to additional iteration in the `merge_draw_list`. | ||||
|   * The shapes glyphs must still be traversed to identify if the glyph is cached. This arguably could be handled in `shape_text_uncached`, however that would require a significan't amount of refactoring to identify... (and would be more unergonomic when shapers libs are processing the text) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| // There are only 4 actual regions of the atlas. E represents the atlas_decide_region detecting an oversized glyph. | ||||
| // Note(Ed): None should never really occur anymore. So its safe to most likely add an assert when its detected. | ||||
| Atlas_Region_Kind :: enum u8 { | ||||
| 	None   = 0x00, | ||||
| 	A      = 0x01, | ||||
| @@ -10,8 +12,13 @@ Atlas_Region_Kind :: enum u8 { | ||||
| 	Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call | ||||
| } | ||||
|  | ||||
| // Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers) | ||||
| Atlas_Region_Key :: u32 | ||||
|  | ||||
| // TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas. | ||||
| /* Essentially a sub-atlas of the atlas. There is a state cache per region that tracks the glyph inventory (what slot they occupy). | ||||
| 	Unlike the shape cache this one's fixed capacity (natrually) and the next avail slot is tracked. | ||||
| */ | ||||
| Atlas_Region :: struct { | ||||
| 	state : LRU_Cache(Atlas_Region_Key), | ||||
|  | ||||
| @@ -24,6 +31,14 @@ Atlas_Region :: struct { | ||||
| 	next_idx : i32, | ||||
| } | ||||
|  | ||||
| /* There are four regions each succeeding region holds larger sized slots. | ||||
| 	The generator pipeline for draw lists utilizes the regions array for info lookup. | ||||
|  | ||||
| 	Note(Ed): | ||||
| 	Padding can techncially be larger than 1, however recently I haven't had any artififact issues... | ||||
| 	size_multiplier usage isn't fully resolved. Intent was to further setup over_sampling or just having  | ||||
| 	a more massive cache for content that used more than the usual common glyphs. | ||||
| */ | ||||
| Atlas :: struct { | ||||
| 	region_a : Atlas_Region, | ||||
| 	region_b : Atlas_Region, | ||||
| @@ -38,7 +53,7 @@ Atlas :: struct { | ||||
| 	size : Vec2i, | ||||
| } | ||||
|  | ||||
|  | ||||
| // Hahser for the atlas. | ||||
| atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Region_Key) { | ||||
| 	// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 ) | ||||
| 	font        := font | ||||
|   | ||||
| @@ -1,10 +1,21 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| /* | ||||
| 	Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation. | ||||
|  | ||||
|  | ||||
| */ | ||||
|  | ||||
| import "base:runtime" | ||||
| import "base:intrinsics" | ||||
| import "core:slice" | ||||
| import "thirdparty:freetype" | ||||
|  | ||||
| Glyph_Trianglation_Method :: enum(i32) { | ||||
| 	Ear_Clipping, | ||||
| 	Triangle_Fanning, | ||||
| } | ||||
|  | ||||
| Vertex :: struct { | ||||
| 	pos  : Vec2, | ||||
| 	u, v : f32, | ||||
| @@ -65,7 +76,7 @@ Draw_Call :: struct { | ||||
| 	end_index         : u32, | ||||
| 	clear_before_draw : b32, | ||||
| 	region            : Atlas_Region_Kind, | ||||
| 	colour            : Colour, | ||||
| 	colour            : RGBAN, | ||||
| } | ||||
|  | ||||
| Draw_Call_Default :: Draw_Call { | ||||
| @@ -97,10 +108,12 @@ Glyph_Batch_Cache :: struct { | ||||
| 	cap   : i32, | ||||
| } | ||||
|  | ||||
| // The general tracker for a generator pipeline  | ||||
| Glyph_Draw_Buffer :: struct{ | ||||
| 	over_sample   : Vec2, | ||||
| 	size          : Vec2i, | ||||
| 	draw_padding  : f32, | ||||
| 	over_sample       : Vec2, | ||||
| 	size              : Vec2i, | ||||
| 	draw_padding      : f32, | ||||
| 	snap_glyph_height : f32, | ||||
|  | ||||
| 	allocated_x     : i32, // Space used (horizontally) within the glyph buffer | ||||
| 	clear_draw_list : Draw_List, | ||||
| @@ -115,6 +128,7 @@ Glyph_Draw_Buffer :: struct{ | ||||
| 	cached     : [dynamic]i32, | ||||
| } | ||||
|  | ||||
| // Contructs a  | ||||
| @(optimization_mode="favor_size") | ||||
| blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} ) | ||||
| { | ||||
| @@ -149,9 +163,9 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Constructs a triangle fan to fill a shape using the provided path outside_point represents the center point of the fan. | ||||
| // Constructs a triangle fan mesh to fill a shape using the provided path outside_point represents the center point of the fan. | ||||
| @(optimization_mode="favor_size") | ||||
| construct_filled_path :: proc( draw_list : ^Draw_List,  | ||||
| fill_path_via_fan_triangulation :: proc( draw_list : ^Draw_List,  | ||||
| 	outside_point : Vec2,  | ||||
| 	path          : []Vertex, | ||||
| 	scale         := Vec2 { 1, 1 }, | ||||
| @@ -187,6 +201,7 @@ construct_filled_path :: proc( draw_list : ^Draw_List, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Glyph triangulation generator | ||||
| @(optimization_mode="favor_size") | ||||
| generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]Vertex, | ||||
| 	glyph_shape      : Parser_Glyph_Shape,  | ||||
| @@ -209,7 +224,7 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V | ||||
| 	{ | ||||
| 		case .Move: | ||||
| 			if len(path) > 0 { | ||||
| 				construct_filled_path( draw_list, outside, path[:], scale, translate) | ||||
| 				fill_path_via_fan_triangulation( draw_list, outside, path[:], scale, translate) | ||||
| 				clear(path) | ||||
| 			} | ||||
| 			fallthrough | ||||
| @@ -242,7 +257,7 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V | ||||
| 	} | ||||
|  | ||||
| 	if len(path) > 0 { | ||||
| 		construct_filled_path(draw_list, outside, path[:], scale, translate) | ||||
| 		fill_path_via_fan_triangulation(draw_list, outside, path[:], scale, translate) | ||||
| 	} | ||||
|  | ||||
| 	draw.end_index = u32(len(draw_list.indices)) | ||||
| @@ -251,39 +266,63 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V | ||||
| 	} | ||||
| } | ||||
|  | ||||
| generate_shapes_draw_list :: proc ( ctx : ^Context, font : Font_ID, colour : Colour, entry : Entry, px_size, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text ) | ||||
| // Just a warpper of generate_shape_draw_list for handling an array of shapes | ||||
| generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context, font : Font_ID, colour : RGBAN, entry : Entry, px_size, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text ) | ||||
| { | ||||
| 	assert(len(shapes) > 0) | ||||
| 	for shape in shapes { | ||||
| 		ctx.cursor_pos = {} | ||||
| 		ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, | ||||
| 			ctx.enable_draw_type_visualization, | ||||
| 			colour,  | ||||
| 			entry, | ||||
| 			px_size, | ||||
| 			font_scale,  | ||||
| 			position, | ||||
| 			scale,  | ||||
| 			ctx.snap_width,  | ||||
| 			ctx.snap_height | ||||
| 		) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @(optimization_mode="favor_size") | ||||
| /* Core generator pipeline for shapes | ||||
|  | ||||
| 	If you'd like to make a custom draw procedure, this may either be directly used, or  | ||||
| 	should be straight forward to make an augmented derivative for a specific codepath. | ||||
|  | ||||
| 	This procedure has no awareness of layers. That should be handled by a higher-order codepath.  | ||||
| 	For this level of codepaths what matters is maximizing memory locality for: | ||||
| 	  * Dealing with shaping (essentially minimizing having to ever deal with it in a hot path if possible) | ||||
| 	  * Metric resolution of glyphs within a shape  | ||||
| 		  * Note: It may be better to have the shape to track/store the glyph bounds and lru codes | ||||
| 			* The shaper has access to the atlas's dependencies and to parser's metrics (including the unscaled bounds). | ||||
|  | ||||
| 	Pipleine order: | ||||
| 	* Resolve atlas lru codes and track shape indexes | ||||
| 	* Resolve the glyph's position offset from the target position | ||||
| 	* Resolve glyph bounds and scale | ||||
| 	* Resolve atlas region the glyph is associated with | ||||
| 	* Segregate the glyphs into three slices: oversized, to_cache, cached.  | ||||
| 	  * If oversized is not necessary for your use case and your hitting a bottle neck, remove it in a derivative procedure. | ||||
| 		  * You have to to be drawing a px font size > ~140 px for it to trigger. | ||||
| 			* The atlas can be scaled with the size_multiplier parameter of startup so that it becomes more irrelevant if processing a larger atlas is a non-issue. | ||||
| 		* The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params) | ||||
| 		  * When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation. | ||||
| 	* This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly so keep that in mind. | ||||
| */ | ||||
| generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, | ||||
| 	atlas        : ^Atlas, | ||||
| 	glyph_buffer : ^Glyph_Draw_Buffer, | ||||
| 	px_scalar    : f32, | ||||
|  | ||||
| 	colour       : Colour, | ||||
| 	entry        : Entry, | ||||
| 	px_size      : f32, | ||||
| 	font_scale   : f32, | ||||
| 	enable_debug_vis_type : f32, | ||||
|  | ||||
| 	colour           : RGBAN, | ||||
| 	entry            : Entry, | ||||
| 	px_size          : f32, | ||||
| 	font_scale       : f32, | ||||
|  | ||||
| 	target_position : Vec2, | ||||
| 	target_scale    : Vec2, | ||||
| 	snap_width      : f32,  | ||||
| 	snap_height     : f32 | ||||
| ) -> (cursor_pos : Vec2) #no_bounds_check | ||||
| { | ||||
| 	profile(#procedure) | ||||
| @@ -425,7 +464,8 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, | ||||
| 			entry,  | ||||
| 			colour,  | ||||
| 			font_scale,  | ||||
| 			target_scale | ||||
| 			target_scale, | ||||
| 			enable_debug_vis_type | ||||
| 		) | ||||
|  | ||||
| 		reset_batch( & glyph_buffer.batch_cache) | ||||
| @@ -446,7 +486,8 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, | ||||
| 			entry,  | ||||
| 			colour,  | ||||
| 			font_scale,  | ||||
| 			target_scale | ||||
| 			target_scale, | ||||
| 			enable_debug_vis_type | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| @@ -454,6 +495,19 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, | ||||
| 	return | ||||
| } | ||||
|  | ||||
| /* | ||||
| 	The glyphs types have been segregated by this point into a batch slice of indices to the glyph_pack | ||||
| 	The transform and draw quads are computed first (getting the math done in one spot as possible...) | ||||
| 	Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it...) | ||||
|  | ||||
| 	Order: Oversized first, then to_cache, then cached. | ||||
|  | ||||
| 	Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target. | ||||
| 	The compute section will have operations reguarding how many glyphs they made alloate before a flush must occur. | ||||
| 	A flush will force one of the following: | ||||
| 	  * Oversized will have a draw call setup to blit directly from the glyph buffer to the target. | ||||
| 		* to_cache will blit the glyphs rendered to the buffer to the atlas. | ||||
| */ | ||||
| batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 	glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry, | ||||
| 	cached     : []i32,  | ||||
| @@ -465,29 +519,53 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 	atlas_size        : Vec2, | ||||
| 	glyph_buffer_size : Vec2, | ||||
|  | ||||
| 	entry             : Entry, | ||||
| 	colour            : Colour, | ||||
| 	font_scale        : Vec2, | ||||
| 	target_scale      : Vec2, | ||||
| 	entry                 : Entry, | ||||
| 	colour                : RGBAN, | ||||
| 	font_scale            : Vec2, | ||||
| 	target_scale          : Vec2, | ||||
| 	enable_debug_vis_type : f32, | ||||
| ) #no_bounds_check | ||||
| { | ||||
| 	profile(#procedure) | ||||
|  | ||||
| 	when ENABLE_DRAW_TYPE_VIS { | ||||
| 		colour := colour | ||||
| 	} | ||||
| 	colour := colour | ||||
|  | ||||
| 	profile_begin("glyph buffer transform & draw quads compute") | ||||
| 	for id, index in cached | ||||
| 	for id, index in oversized | ||||
| 	{ | ||||
| 		// Quad to for drawing atlas slot to target | ||||
| 		glyph := & glyph_pack[id] | ||||
| 		quad  := & glyph.draw_quad | ||||
| 		quad.dst_pos   = glyph.position + (glyph.bounds_scaled.p0) * target_scale | ||||
| 		quad.dst_scale =                  (glyph.scale)            * target_scale | ||||
| 		quad.src_scale =                  (glyph.scale) | ||||
| 		quad.src_pos   = (glyph.region_pos)  | ||||
| 		to_target_space( & quad.src_pos, & quad.src_scale, atlas_size ) | ||||
|  | ||||
| 		f32_allocated_x := cast(f32) glyph_buffer.allocated_x | ||||
| 		// Resolve how much space this glyph will allocate in the buffer | ||||
| 		buffer_size   := (glyph.bounds_size_scaled + glyph_buffer.draw_padding) * glyph.over_sample | ||||
|  | ||||
| 		// Allocate a glyph glyph render target region (FBO) | ||||
| 		to_allocate_x            := buffer_size.x + 2.0 | ||||
| 		glyph_buffer.allocated_x += i32(to_allocate_x) | ||||
|  | ||||
| 		// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered. | ||||
| 		glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x) | ||||
| 		glyph.buffer_x           = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) ) | ||||
|  | ||||
| 		// Quad to for drawing atlas slot to target | ||||
| 		draw_quad := & glyph.draw_quad | ||||
|  | ||||
| 		glyph_padding := vec2(glyph_buffer.draw_padding) | ||||
|  | ||||
| 		// Target position (draw_list's target image) | ||||
| 		draw_quad.dst_pos   = glyph.position + (glyph.bounds_scaled.p0   - glyph_padding) * target_scale | ||||
| 		draw_quad.dst_scale =                  (glyph.bounds_size_scaled + glyph_padding) * target_scale | ||||
| 		 | ||||
| 		// The glyph buffer space transform for generate_glyph_pass_draw_list | ||||
| 		draw_transform       := & glyph.draw_transform | ||||
| 		draw_transform.scale  = font_scale * glyph.over_sample  | ||||
| 		draw_transform.pos    = -1 * glyph.bounds.p0 * draw_transform.scale + vec2(atlas.glyph_padding) | ||||
| 		draw_transform.pos.x += glyph.buffer_x | ||||
| 		to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size ) | ||||
|  | ||||
|  | ||||
| 		draw_quad.src_pos   = Vec2 { glyph.buffer_x, 0 } | ||||
| 		draw_quad.src_scale = glyph.bounds_size_scaled * glyph.over_sample + glyph_padding | ||||
| 		to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size ) | ||||
| 	} | ||||
| 	for id, index in to_cache | ||||
| 	{ | ||||
| @@ -526,52 +604,25 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 		draw_quad.src_pos   = (glyph.region_pos) | ||||
| 		to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, atlas_size ) | ||||
| 	} | ||||
| 	for id, index in oversized | ||||
| 	for id, index in cached | ||||
| 	{ | ||||
| 		glyph := & glyph_pack[id] | ||||
|  | ||||
| 		f32_allocated_x := cast(f32) glyph_buffer.allocated_x | ||||
| 		// Resolve how much space this glyph will allocate in the buffer | ||||
| 		buffer_size   := (glyph.bounds_size_scaled + glyph_buffer.draw_padding) * glyph.over_sample | ||||
|  | ||||
| 		// Allocate a glyph glyph render target region (FBO) | ||||
| 		to_allocate_x            := buffer_size.x + 2.0 | ||||
| 		glyph_buffer.allocated_x += i32(to_allocate_x) | ||||
|  | ||||
| 		// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered. | ||||
| 		glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x) | ||||
| 		glyph.buffer_x           = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) ) | ||||
|  | ||||
| 		// Quad to for drawing atlas slot to target | ||||
| 		draw_quad := & glyph.draw_quad | ||||
|  | ||||
| 		glyph_padding := vec2(glyph_buffer.draw_padding) | ||||
|  | ||||
| 		// Target position (draw_list's target image) | ||||
| 		draw_quad.dst_pos   = glyph.position + (glyph.bounds_scaled.p0   - glyph_padding) * target_scale | ||||
| 		draw_quad.dst_scale =                  (glyph.bounds_size_scaled + glyph_padding) * target_scale | ||||
| 		 | ||||
| 		// The glyph buffer space transform for generate_glyph_pass_draw_list | ||||
| 		draw_transform       := & glyph.draw_transform | ||||
| 		draw_transform.scale  = font_scale * glyph.over_sample  | ||||
| 		draw_transform.pos    = -1 * glyph.bounds.p0 * draw_transform.scale + vec2(atlas.glyph_padding) | ||||
| 		draw_transform.pos.x += glyph.buffer_x | ||||
| 		to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size ) | ||||
|  | ||||
|  | ||||
| 		draw_quad.src_pos   = Vec2 { glyph.buffer_x, 0 } | ||||
| 		draw_quad.src_scale = glyph.bounds_size_scaled * glyph.over_sample + glyph_padding | ||||
| 		to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size ) | ||||
| 		glyph := & glyph_pack[id] | ||||
| 		quad  := & glyph.draw_quad | ||||
| 		quad.dst_pos   = glyph.position + (glyph.bounds_scaled.p0) * target_scale | ||||
| 		quad.dst_scale =                  (glyph.scale)            * target_scale | ||||
| 		quad.src_scale =                  (glyph.scale) | ||||
| 		quad.src_pos   = (glyph.region_pos)  | ||||
| 		to_target_space( & quad.src_pos, & quad.src_scale, atlas_size ) | ||||
| 	} | ||||
| 	profile_end() | ||||
|  | ||||
| 	profile_begin("generate oversized glyphs draw_list") | ||||
| 	if len(oversized) > 0 | ||||
| 	{ | ||||
| 		when ENABLE_DRAW_TYPE_VIS { | ||||
| 			colour.r = 1.0 | ||||
| 			colour.g = 1.0 | ||||
| 			colour.b = 0.0 | ||||
| 		} | ||||
| 		colour.r = max(colour.a, enable_debug_vis_type) | ||||
| 		colour.g = max(colour.g, enable_debug_vis_type) | ||||
| 		colour.b = colour.b * f32(cast(i32) ! b32(cast(i32) enable_debug_vis_type)) | ||||
| 		for id, index in oversized { | ||||
| 			error : Allocator_Error | ||||
| 			glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index) | ||||
| @@ -611,12 +662,34 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 			append( & draw_list.calls, draw_to_target ) | ||||
| 		} | ||||
|  | ||||
| 		if len(oversized) > 0 do flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) | ||||
| 		flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) | ||||
| 		for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape) | ||||
| 	} | ||||
| 	profile_end() | ||||
|  | ||||
| 	generate_cached_draw_list :: #force_inline proc  (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : RGBAN ) | ||||
| 	{ | ||||
| 		profile(#procedure) | ||||
| 		call             := Draw_Call_Default | ||||
| 		call.pass         = .Target | ||||
| 		call.colour       = colour | ||||
| 		for id, index in sub_pack | ||||
| 		{ | ||||
| 			profile("glyph") | ||||
| 			call.start_index = u32(len(draw_list.indices)) | ||||
|  | ||||
| 			quad := glyph_pack[id].draw_quad | ||||
| 			blit_quad(draw_list, | ||||
| 				quad.dst_pos, quad.dst_pos + quad.dst_scale, | ||||
| 				quad.src_pos, quad.src_pos + quad.src_scale | ||||
| 			) | ||||
| 			call.end_index = u32(len(draw_list.indices)) | ||||
| 			append(& draw_list.calls, call) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	profile_begin("to_cache: caching to atlas") | ||||
| 	if len(to_cache) > 0 | ||||
| 	{ | ||||
| 		for id, index in to_cache { | ||||
| 			error : Allocator_Error | ||||
| @@ -651,12 +724,12 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 			 | ||||
| 			dst_glyph_pos    := glyph.region_pos | ||||
| 			dst_glyph_size   := glyph.bounds_size_scaled + atlas.glyph_padding | ||||
| 			// dst_glyph_size.y  = ceil(dst_glyph_size.y) // Note(Ed): Seems to improve hinting | ||||
| 			dst_glyph_size.y  = ceil(dst_glyph_size.y) * glyph_buffer.snap_glyph_height  // Note(Ed): Seems to improve hinting | ||||
| 			to_glyph_buffer_space( & dst_glyph_pos, & dst_glyph_size, atlas_size ) | ||||
| 	 | ||||
| 			src_position  := Vec2 { glyph.buffer_x, 0 } | ||||
| 			src_size      := (glyph.bounds_size_scaled + atlas.glyph_padding) * glyph_buffer.over_sample | ||||
| 			// src_size.y     = ceil(src_size.y) // Note(Ed): Seems to improve hinting | ||||
| 			src_size.y     = ceil(src_size.y) * glyph_buffer.snap_glyph_height // Note(Ed): Seems to improve hinting | ||||
| 			to_target_space( & src_position, & src_size, glyph_buffer_size ) | ||||
| 			 | ||||
| 			blit_to_atlas : Draw_Call | ||||
| @@ -681,49 +754,28 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, | ||||
| 			) | ||||
| 		} | ||||
|  | ||||
| 		if len(to_cache) > 0 do flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) | ||||
| 		profile_begin("generate_cached_draw_list: to_cache") | ||||
| 		if enable_debug_vis_tyeps { | ||||
| 			colour.r = 1.0 | ||||
| 			colour.g = 1.0 | ||||
| 			colour.b = 1.0 | ||||
| 		} | ||||
| 		generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour ) | ||||
| 		profile_end() | ||||
|  | ||||
| 		flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) | ||||
| 		for id, index in to_cache do parser_free_shape(entry.parser_info, glyph_pack[id].shape) | ||||
| 	} | ||||
| 	profile_end() | ||||
|  | ||||
| 	generate_cached_draw_list :: #force_inline proc  (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : Colour ) | ||||
| 	{ | ||||
| 		profile(#procedure) | ||||
| 		call             := Draw_Call_Default | ||||
| 		call.pass         = .Target | ||||
| 		call.colour       = colour | ||||
| 		for id, index in sub_pack | ||||
| 		{ | ||||
| 			profile("glyph") | ||||
| 			call.start_index = u32(len(draw_list.indices)) | ||||
|  | ||||
| 			quad := glyph_pack[id].draw_quad | ||||
| 			blit_quad(draw_list, | ||||
| 				quad.dst_pos, quad.dst_pos + quad.dst_scale, | ||||
| 				quad.src_pos, quad.src_pos + quad.src_scale | ||||
| 			) | ||||
| 			call.end_index = u32(len(draw_list.indices)) | ||||
| 			append(& draw_list.calls, call) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	profile_begin("generate_cached_draw_list: to_cache") | ||||
| 	when ENABLE_DRAW_TYPE_VIS { | ||||
| 	if enable_debug_vis_tyeps { | ||||
| 		colour.r = 0.80 | ||||
| 		colour.g = 0.25 | ||||
| 		colour.b = 0.25 | ||||
| 	} | ||||
| 	generate_cached_draw_list( draw_list, glyph_pack[:], to_cache, colour ) | ||||
| 	profile_end() | ||||
|  | ||||
| 	profile_begin("generate_cached_draw_list: to_cache") | ||||
| 	when ENABLE_DRAW_TYPE_VIS { | ||||
| 		colour.r = 1.0 | ||||
| 		colour.g = 1.0 | ||||
| 		colour.b = 1.0 | ||||
| 	} | ||||
| 	generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour ) | ||||
| 	profile_end() | ||||
| } | ||||
|  | ||||
| // Flush the content of the glyph_buffers draw lists to the main draw list | ||||
| @@ -747,7 +799,6 @@ flush_glyph_buffer_draw_list :: proc( #no_alias draw_list, glyph_buffer_draw_lis | ||||
| 	(allocated_x ^) = 0 | ||||
| } | ||||
|  | ||||
| // ve_fontcache_clear_Draw_List | ||||
| @(optimization_mode="favor_size") | ||||
| clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { | ||||
| 	clear( & draw_list.calls ) | ||||
| @@ -755,7 +806,7 @@ clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { | ||||
| 	clear( & draw_list.vertices ) | ||||
| } | ||||
|  | ||||
| // ve_fontcache_merge_Draw_List | ||||
| // Helper used by flush_glyph_buffer_draw_list. Used to append all the content from the src draw list o the destination. | ||||
| @(optimization_mode="favor_size") | ||||
| merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check | ||||
| { | ||||
| @@ -783,6 +834,8 @@ merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Naive implmentation to merge passes that are equivalent and the following to be merged (b for can_merge_draw_calls) doesn't have a clear todo. | ||||
| // Its intended for optimiztion passes to occur on a per-layer basis. | ||||
| optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int)  #no_bounds_check | ||||
| { | ||||
| 	profile(#procedure) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| when false { | ||||
| // TODO(Ed): Freetype support | ||||
|   | ||||
| @@ -1,4 +1,9 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| /* | ||||
| 	Didn't want to splinter this into more files.. | ||||
| 	Just a bunch of utilities. | ||||
| */ | ||||
|  | ||||
| import "base:runtime" | ||||
| import "core:simd" | ||||
| @@ -6,6 +11,10 @@ import "core:math" | ||||
|  | ||||
| import core_log "core:log" | ||||
|  | ||||
| peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type { | ||||
| 	return self[ len(self) - 1 ] | ||||
| } | ||||
|  | ||||
| reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) { | ||||
| 	raw          := transmute( ^runtime.Raw_Dynamic_Array) self | ||||
| 	raw.allocator = allocator | ||||
| @@ -43,7 +52,8 @@ to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { | ||||
| @(optimization_mode="favor_size") | ||||
| djb8_hash :: #force_inline proc "contextless" ( hash : ^$Type, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + Type(value) } | ||||
|  | ||||
| Colour  :: [4]f32 | ||||
| RGBA8   :: [4]f32 | ||||
| RGBAN   :: [4]f32 | ||||
| Vec2    :: [2]f32 | ||||
| Vec2i   :: [2]i32 | ||||
| Vec2_64 :: [2]f64 | ||||
|   | ||||
| @@ -1,12 +1,18 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| /* | ||||
| Notes: | ||||
| This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future. | ||||
| Otherwise, its essentially 1:1 with it. | ||||
|  | ||||
| Freetype will do memory allocations and has an interface the user can implement. | ||||
| That interface is not exposed from this parser but could be added to parser_init. | ||||
| Freetype isn't really supported and its not a high priority (pretty sure its too slow). | ||||
| ~~Freetype will do memory allocations and has an interface the user can implement.~~ | ||||
| ~~That interface is not exposed from this parser but could be added to parser_init.~~ | ||||
|  | ||||
| STB_Truetype has macros for its allocation unfortuantely | ||||
| STB_Truetype: | ||||
| * Has macros for its allocation unfortuantely.  | ||||
| TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator. | ||||
| Already wanted to do so anyway to evaluate the shape generation implementation. | ||||
| */ | ||||
|  | ||||
| import "base:runtime" | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
| 
 | ||||
| import "base:builtin" | ||||
| import "base:runtime" | ||||
| import "core:hash" | ||||
| 	ginger16 :: hash.ginger16 | ||||
| @@ -50,34 +51,34 @@ append_soa :: proc { | ||||
| } | ||||
| 
 | ||||
| ceil :: proc { | ||||
| 	ceil_f16, | ||||
| 	ceil_f16le, | ||||
| 	ceil_f16be, | ||||
| 	ceil_f32, | ||||
| 	ceil_f32le, | ||||
| 	ceil_f32be, | ||||
| 	ceil_f64, | ||||
| 	ceil_f64le, | ||||
| 	ceil_f64be, | ||||
| 	math.ceil_f16, | ||||
| 	math.ceil_f16le, | ||||
| 	math.ceil_f16be, | ||||
| 	math.ceil_f32, | ||||
| 	math.ceil_f32le, | ||||
| 	math.ceil_f32be, | ||||
| 	math.ceil_f64, | ||||
| 	math.ceil_f64le, | ||||
| 	math.ceil_f64be, | ||||
| 
 | ||||
| 	ceil_vec2, | ||||
| } | ||||
| 
 | ||||
| clear :: proc { | ||||
| 	clear_dynamic_array, | ||||
| 	clear_map, | ||||
| 	builtin.clear_dynamic_array, | ||||
| 	builtin.clear_map, | ||||
| } | ||||
| 
 | ||||
| floor :: proc { | ||||
| 	floor_f16, | ||||
| 	floor_f16le, | ||||
| 	floor_f16be, | ||||
| 	floor_f32, | ||||
| 	floor_f32le, | ||||
| 	floor_f32be, | ||||
| 	floor_f64, | ||||
| 	floor_f64le, | ||||
| 	floor_f64be, | ||||
| 	math.floor_f16, | ||||
| 	math.floor_f16le, | ||||
| 	math.floor_f16be, | ||||
| 	math.floor_f32, | ||||
| 	math.floor_f32le, | ||||
| 	math.floor_f32be, | ||||
| 	math.floor_f64, | ||||
| 	math.floor_f64le, | ||||
| 	math.floor_f64be, | ||||
| 
 | ||||
| 	floor_vec2, | ||||
| } | ||||
| @@ -87,21 +88,30 @@ fill :: proc { | ||||
| } | ||||
| 
 | ||||
| make :: proc { | ||||
| 	make_dynamic_array, | ||||
| 	make_dynamic_array_len, | ||||
| 	make_dynamic_array_len_cap, | ||||
| 	make_slice, | ||||
| 	make_map, | ||||
| 	make_map_cap, | ||||
| 	builtin.make_dynamic_array, | ||||
| 	builtin.make_dynamic_array_len, | ||||
| 	builtin.make_dynamic_array_len_cap, | ||||
| 	builtin.make_slice, | ||||
| 	builtin.make_map, | ||||
| 	builtin.make_map_cap, | ||||
| } | ||||
| 
 | ||||
| make_soa :: proc { | ||||
| 	make_soa_dynamic_array_len_cap, | ||||
| 	make_soa_slice, | ||||
| 	builtin.make_soa_dynamic_array_len_cap, | ||||
| 	builtin.make_soa_slice, | ||||
| } | ||||
| 
 | ||||
| peek :: proc { | ||||
| 	peek_array, | ||||
| } | ||||
| 
 | ||||
| pop_array :: proc { | ||||
| 	builtin.pop, | ||||
| 	// pop_safe, | ||||
| } | ||||
| 
 | ||||
| resize :: proc { | ||||
| 	resize_dynamic_array, | ||||
| 	builtin.resize_dynamic_array, | ||||
| } | ||||
| 
 | ||||
| vec2 :: proc { | ||||
| @@ -1,4 +1,4 @@ | ||||
| package vefontcache | ||||
| package vetext | ||||
| /* | ||||
| Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists and seems to be under active development as an alternative. | ||||
| */ | ||||
| @@ -8,28 +8,51 @@ import "thirdparty:harfbuzz" | ||||
|  | ||||
| Shape_Key :: u32 | ||||
|  | ||||
| /*  A text whose codepoints have had their relevant glyphs and  | ||||
| 	associated data resolved for processing in a draw list generation stage. | ||||
| 	Traditionally a shape only refers to resolving which glyph and  | ||||
| 	its position should be used for rendering. | ||||
|  | ||||
| 	For this library's case it also involes keeping any content  | ||||
| 	that does not have to be resolved up once again in a later stage of | ||||
| 	preparing it for rendering. | ||||
|  | ||||
| 	Ideally the user should resolve this shape once and cache/store it on their side. | ||||
| 	They have the best ability to avoid costly lookups to streamline  | ||||
| 	a hot path to only focusing on draw list generation that must be computed every frame. | ||||
|  | ||||
| 	For ease of use the cache does a relatively good job and only adds a  | ||||
| 	few hundred nano-seconds to resolve a shape's lookup from its source specification. | ||||
| 	If your doing something heavy though (where there is thousands, or tens-of thousands) | ||||
| 	your not going to be satisfied with keeping that in the iteration). | ||||
| */ | ||||
| Shaped_Text :: struct { | ||||
| 	glyphs             : [dynamic]Glyph, | ||||
| 	positions          : [dynamic]Vec2, | ||||
| 	// bounds             : [dynamic]Range2,            // TODO(Ed): Test tracking/resolving the bounds here, its expensive to resolve at the draw list generation stage. | ||||
| 	// region_kinds       : [dynamic]Atlas_Region_Kind, // TODO(ed): test tracking/resolving the assigne atlas region here, for some reason as ^^. | ||||
| 	end_cursor_pos     : Vec2, | ||||
| 	size               : Vec2, | ||||
| 	entry              : ^Entry, | ||||
| 	font               : Font_ID, | ||||
| } | ||||
|  | ||||
| // Ease of use cache, can handle thousands of lookups per frame with ease. | ||||
| // TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Shaped_Text. | ||||
| Shaped_Text_Cache :: struct { | ||||
| 	storage       : [dynamic]Shaped_Text, | ||||
| 	state         : LRU_Cache(Shape_Key), | ||||
| 	next_cache_id : i32, | ||||
| } | ||||
|  | ||||
| // Used by shaper_shape_text_cached, allows user to specify their own proc at compile-time without having to rewrite the caching implementation. | ||||
| Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text ) | ||||
|  | ||||
| // Note(Ed): Not used.. | ||||
| Shaper_Kind :: enum { | ||||
| 	Naive    = 0, | ||||
| 	Latin    = 0, | ||||
| 	Harfbuzz = 1, | ||||
| } | ||||
|  | ||||
| //  Not much here other than just keep track of a harfbuzz var and deciding to keep runtime config here used by the shapers. | ||||
| Shaper_Context :: struct { | ||||
| 	hb_buffer : harfbuzz.Buffer, | ||||
|  | ||||
| @@ -37,6 +60,7 @@ Shaper_Context :: struct { | ||||
| 	adv_snap_small_font_threshold : f32, | ||||
| } | ||||
|  | ||||
| // Only used with harbuzz for now. Resolved during load_font for a font Entry. | ||||
| Shaper_Info :: struct { | ||||
| 	blob : harfbuzz.Blob, | ||||
| 	face : harfbuzz.Face, | ||||
| @@ -71,6 +95,8 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) | ||||
| 	if info.blob != nil do harfbuzz.blob_destroy( info.blob ) | ||||
| } | ||||
|  | ||||
| // Recommended shaper. Very performant. | ||||
| // TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal... | ||||
| @(optimization_mode="favor_size") | ||||
| shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text ) | ||||
| { | ||||
| @@ -141,7 +167,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry | ||||
| 			} | ||||
| 			if abs( font_px_size ) <= adv_snap_small_font_threshold | ||||
| 			{ | ||||
| 				(position^) = ceil( position^ ) | ||||
| 				(position^) =  ceil( position^ ) | ||||
| 			} | ||||
|  | ||||
| 			glyph_pos := position^ | ||||
| @@ -244,6 +270,7 @@ shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context | ||||
| 	shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output ) | ||||
| } | ||||
|  | ||||
| // Basic western alphabet based shaping. Not that much faster than harfbuzz if at all. | ||||
| shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,  | ||||
| 	entry        : Entry,  | ||||
| 	font_px_Size : f32,  | ||||
| @@ -308,6 +335,12 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, | ||||
| 	output.size.y = f32(line_count) * line_height | ||||
| } | ||||
|  | ||||
| // Shapes are tracked by the library's context using the shape cache  | ||||
| // and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped. | ||||
| // Thus this procedures cost will be proporitonal to how muh text it has to sift through. | ||||
| // djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long | ||||
| // (and its very fast). | ||||
| @(optimization_mode="favor_size") | ||||
| shaper_shape_text_cached :: proc( text_utf8 : string,  | ||||
| 	ctx                 : ^Shaper_Context, | ||||
| 	shape_cache         : ^Shaped_Text_Cache,  | ||||
|   | ||||
| @@ -3,12 +3,10 @@ A port of (https://github.com/hypernewbie/VEFontCache) to Odin. | ||||
|  | ||||
| See: https://github.com/Ed94/VEFontCache-Odin | ||||
| */ | ||||
| package vefontcache | ||||
| package vetext | ||||
|  | ||||
| import "base:runtime" | ||||
|  | ||||
| // White: Cached Hit, Red: Cache Miss, Yellow: Oversized | ||||
| ENABLE_DRAW_TYPE_VIS :: false | ||||
| // See: mappings.odin for profiling hookup | ||||
| DISABLE_PROFILING    :: true | ||||
|  | ||||
| @@ -21,12 +19,11 @@ Load_Font_Error :: enum(i32) { | ||||
| } | ||||
|  | ||||
| Entry :: struct { | ||||
| 	parser_info    : Parser_Font_Info, | ||||
| 	shaper_info    : Shaper_Info, | ||||
| 	id             : Font_ID, | ||||
| 	used           : b32, | ||||
| 	curve_quality  : f32, | ||||
| 	// snap_glyph_pos :  | ||||
| 	parser_info   : Parser_Font_Info, | ||||
| 	shaper_info   : Shaper_Info, | ||||
| 	id            : Font_ID, | ||||
| 	used          : b32, | ||||
| 	curve_quality : f32, | ||||
|  | ||||
| 	ascent   : f32, | ||||
| 	descent  : f32, | ||||
| @@ -39,6 +36,23 @@ Entry_Default :: Entry { | ||||
| 	curve_quality = 3, | ||||
| } | ||||
|  | ||||
| // Ease of use encapsulation of common fields for a canvas space | ||||
| Camera :: struct { | ||||
| 	view         : [dynamic]Vec2, | ||||
| 	position     : [dynamic]Vec2, | ||||
| 	zoom         : [dynamic]f32, | ||||
| } | ||||
|  | ||||
| Scope_Stack :: struct { | ||||
| 	font         : [dynamic]Font_ID, | ||||
| 	font_size    : [dynamic]f32, | ||||
| 	colour       : [dynamic]RGBAN, | ||||
| 	view         : [dynamic]Vec2, | ||||
| 	position     : [dynamic]Vec2, | ||||
| 	scale        : [dynamic]Vec2, | ||||
| 	zoom         : [dynamic]f32, | ||||
| } | ||||
|  | ||||
| Context :: struct { | ||||
| 	backing : Allocator, | ||||
|  | ||||
| @@ -48,11 +62,14 @@ Context :: struct { | ||||
| 	// The managed font instances | ||||
| 	entries : [dynamic]Entry, | ||||
|  | ||||
| 	// TODO(Ed): Review these when preparing to handle lifting of working context to a thread context. | ||||
| 	glyph_buffer : Glyph_Draw_Buffer, | ||||
| 	atlas        : Atlas, | ||||
| 	shape_cache  : Shaped_Text_Cache, | ||||
| 	draw_list    : Draw_List, | ||||
|  | ||||
| 	batch_shapes_buffer : [dynamic]Shaped_Text, // Used for the procs that batch a layer of text. | ||||
|  | ||||
| 	// Tracks the offsets for the current layer in a draw_list | ||||
| 	draw_layer : struct { | ||||
| 		vertices_offset : int, | ||||
| @@ -60,25 +77,35 @@ Context :: struct { | ||||
| 		calls_offset    : int, | ||||
| 	}, | ||||
|  | ||||
| 	debug_print         : b32, | ||||
| 	debug_print_verbose : b32, | ||||
| 	// Note(Ed): Not really used anymore. | ||||
| 	// debug_print         : b32, | ||||
| 	// debug_print_verbose : b32, | ||||
|  | ||||
| 	//TODO(Ed):  Add a push/pop stack for the below | ||||
|  | ||||
| 	// Helps with hinting | ||||
| 	snap_width  : f32, | ||||
| 	snap_height : f32, | ||||
|  | ||||
| 	// camera        : Camera, // TODO(Ed): Add camera support | ||||
| 	colour        : Colour, // Color used in draw interface | ||||
| 	cursor_pos    : Vec2, | ||||
| 	alpha_sharpen : f32,    // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. | ||||
| 	// Will enforce even px_size when drawing. | ||||
| 	even_size_only : f32, | ||||
|  | ||||
| 	// Whether or not to snap positioning to the pixel of the view | ||||
| 	// Helps with hinting | ||||
| 	snap_to_view_extent : b32, | ||||
|  | ||||
| 	stack : Scope_Stack, | ||||
|  | ||||
| 	colour        : RGBAN, // Color used in draw interface TODO(Ed): use the stack | ||||
| 	cursor_pos    : Vec2,  // TODO(Ed): Review this, no longer used much at all... (still useful I guess) | ||||
| 	// Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. | ||||
| 	// Has a boldening side-effect. If overblown will look smeared. | ||||
| 	alpha_sharpen : f32, | ||||
| 	// Used by draw interface to super-scale the text by  | ||||
| 	// upscaling px_size with px_scalar and then down-scaling | ||||
| 	// the draw_list result by the same amount. | ||||
| 	px_scalar     : f32,   // Used to upscale which font size is used to render (improves hinting) | ||||
| 	px_scalar      : f32,   // Improves hinting, positioning, etc. Can make zoomed out text too jagged. | ||||
|  | ||||
| 	default_curve_quality : i32, | ||||
| 	// White: Cached Hit, Red: Cache Miss, Yellow: Oversized (Will override user's colors when active) | ||||
| 	enable_draw_type_visualization : f32, | ||||
| 	default_curve_quality          : i32, | ||||
| } | ||||
|  | ||||
| Init_Atlas_Params :: struct { | ||||
| @@ -92,14 +119,22 @@ Init_Atlas_Params_Default :: Init_Atlas_Params { | ||||
| } | ||||
|  | ||||
| Init_Glyph_Draw_Params :: struct { | ||||
| 	// During the draw list generation stage when blitting to atlas, the quad wil be ceil()'d to the closest pixel. | ||||
| 	snap_glyph_height         : b32, | ||||
| 	// Intended to be x16 (4x4) super-sampling from the glyph buffer to the atlas. | ||||
| 	// Oversized glyphs don't use this and instead do 2x or 1x depending on how massive they are. | ||||
| 	over_sample               : u32, | ||||
| 	// Best to just keep this the same as glyph_padding for the atlas.. | ||||
| 	draw_padding              : u32, | ||||
| 	shape_gen_scratch_reserve : u32, | ||||
| 	buffer_glyph_limit        : u32, // How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario) | ||||
| 	batch_glyph_limit         : u32, // How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list | ||||
| 	// How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario) | ||||
| 	buffer_glyph_limit        : u32, | ||||
| 	// How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list | ||||
| 	batch_glyph_limit         : u32, | ||||
| } | ||||
|  | ||||
| Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { | ||||
| 	snap_glyph_height               = true, | ||||
| 	over_sample                     = 4, | ||||
| 	draw_padding                    = Init_Atlas_Params_Default.glyph_padding, | ||||
| 	shape_gen_scratch_reserve       = 10 * 1024, | ||||
| @@ -108,23 +143,28 @@ Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { | ||||
| } | ||||
|  | ||||
| Init_Shaper_Params :: struct { | ||||
| 	// Forces a glyph position to align to a pixel (make sure to use snap_to_view_extent with this or else it won't be preserveds) | ||||
| 	snap_glyph_position           : b32, | ||||
| 	// Will use more signficant advance during shaping for fonts  | ||||
| 	// Note(Ed): Thinking of removing, doesn't look good often and its an extra condition in the hot-loop. | ||||
| 	adv_snap_small_font_threshold : u32, | ||||
| } | ||||
|  | ||||
| Init_Shaper_Params_Default :: Init_Shaper_Params { | ||||
| 	snap_glyph_position           = false, | ||||
| 	snap_glyph_position           = true, | ||||
| 	adv_snap_small_font_threshold = 0, | ||||
| } | ||||
|  | ||||
| Init_Shape_Cache_Params :: struct { | ||||
| 	capacity       : u32, | ||||
| 	reserve_length : u32, | ||||
| 	// Note(Ed): This should mostly just be given the worst-case capacity and reserve at the same time. | ||||
| 	// If memory is a concern it can easily be 256 - 2k if not much text is going to be rendered often. | ||||
| 	capacity : u32, | ||||
| 	reserve  : u32, | ||||
| } | ||||
|  | ||||
| Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { | ||||
| 	capacity       = 10 * 1024, | ||||
| 	reserve_length = 10 * 1024, | ||||
| 	capacity = 10 * 1024, | ||||
| 	reserve  = 10 * 1024, | ||||
| } | ||||
|  | ||||
| //#region("lifetime") | ||||
| @@ -143,6 +183,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, | ||||
| 	// Affects step size for bezier curve passes in generate_glyph_pass_draw_list | ||||
| 	default_curve_quality       : u32 = 3, | ||||
| 	entires_reserve             : u32 = 256, | ||||
| 	scope_stack_reserve         : u32 = 128, | ||||
| ) | ||||
| { | ||||
| 	assert( ctx != nil, "Must provide a valid context" ) | ||||
| @@ -230,10 +271,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, | ||||
| 		for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 { | ||||
| 			stroage_entry := & shape_cache.storage[idx] | ||||
|  | ||||
| 			stroage_entry.glyphs, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve_length ) | ||||
| 			stroage_entry.glyphs, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve ) | ||||
| 			assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" ) | ||||
|  | ||||
| 			stroage_entry.positions, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve_length ) | ||||
| 			stroage_entry.positions, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve ) | ||||
| 			assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) | ||||
| 		} | ||||
| 	} | ||||
| @@ -384,6 +425,34 @@ shutdown :: proc( ctx : ^Context ) | ||||
| 	parser_shutdown( & ctx.parser_ctx ) | ||||
| } | ||||
|  | ||||
| // Can be used with hot-reload | ||||
| clear_atlas_region_caches :: proc(ctx : ^Context) | ||||
| { | ||||
| 	lru_clear(& ctx.atlas.region_a.state) | ||||
| 	lru_clear(& ctx.atlas.region_b.state) | ||||
| 	lru_clear(& ctx.atlas.region_c.state) | ||||
| 	lru_clear(& ctx.atlas.region_d.state) | ||||
|  | ||||
| 	ctx.atlas.region_a.next_idx = 0 | ||||
| 	ctx.atlas.region_b.next_idx = 0 | ||||
| 	ctx.atlas.region_c.next_idx = 0 | ||||
| 	ctx.atlas.region_d.next_idx = 0 | ||||
| } | ||||
|  | ||||
| // Can be used with hot-reload | ||||
| clear_shape_cache :: proc (ctx : ^Context) | ||||
| { | ||||
| 	lru_clear(& ctx.shape_cache.state) | ||||
| 	for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { | ||||
| 		stroage_entry := & ctx.shape_cache.storage[idx] | ||||
| 		stroage_entry.end_cursor_pos = {} | ||||
| 		stroage_entry.size           = {} | ||||
| 		clear(& stroage_entry.glyphs) | ||||
| 		clear(& stroage_entry.positions) | ||||
| 	} | ||||
| 	ctx.shape_cache.next_cache_id = 0 | ||||
| } | ||||
|  | ||||
| load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| @@ -454,7 +523,7 @@ unload_font :: proc( ctx : ^Context, font : Font_ID ) | ||||
|  | ||||
| //#endregion("lifetime") | ||||
|  | ||||
| //#region("drawing") | ||||
| //#region("scoping") | ||||
|  | ||||
| configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { | ||||
| 	assert( ctx != nil ) | ||||
| @@ -462,34 +531,125 @@ configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : | ||||
| 	ctx.snap_height = f32(snap_height) | ||||
| } | ||||
|  | ||||
| get_cursor_pos     :: #force_inline proc( ctx : ^Context                  ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos      } | ||||
| set_alpha_scalar   :: #force_inline proc( ctx : ^Context, scalar : f32    )         { assert(ctx != nil); ctx.alpha_sharpen = scalar } | ||||
| set_px_scalar      :: #force_inline proc( ctx : ^Context, scalar : f32    )         { assert(ctx != nil); ctx.px_scalar     = scalar }  | ||||
| set_colour         :: #force_inline proc( ctx : ^Context, colour : Colour )         { assert(ctx != nil); ctx.colour        = colour } | ||||
| /* Scope stacking ease of use interface. | ||||
|  | ||||
| View: Extents in 2D for the relative space the the text is being drawn within. | ||||
| Used with snap_to_view_extent to enforce position snapping. | ||||
|  | ||||
| Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount. | ||||
| Scale   : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount. | ||||
| Zoom    : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it. | ||||
|  | ||||
| The pkg_mapping.odin provides an explicit set of overloads for these: | ||||
| scope() | ||||
| push() | ||||
| pop() | ||||
| */ | ||||
|  | ||||
| @(deferred_none = pop_font) | ||||
| scope_font      :: #force_inline proc( ctx : ^Context, font      : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } | ||||
| push_font       :: #force_inline proc( ctx : ^Context, font      : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } | ||||
| pop_font        :: #force_inline proc( ctx : ^Context, font      : Font_ID ) { assert(ctx != nil); pop_array(& ctx.stack.font)     } | ||||
|  | ||||
| @(deferred_none = pop_font_size) | ||||
| scope_font_size :: #force_inline proc( ctx : ^Context, px_size   : f32     ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } | ||||
| push_font_size  :: #force_inline proc( ctx : ^Context, px_size   : f32     ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } | ||||
| pop_font_size   :: #force_inline proc( ctx : ^Context, px_size   : f32     ) { assert(ctx != nil); pop_array(& ctx.stack.font_size)       } | ||||
|  | ||||
| @(deferred_none = pop_colour ) | ||||
| scope_colour    :: #force_inline proc( ctx : ^Context, colour    : RGBAN  )  { assert(ctx != nil); append(& ctx.stack.colour, colour) } | ||||
| push_colour     :: #force_inline proc( ctx : ^Context, colour    : RGBAN  )  { assert(ctx != nil); append(& ctx.stack.colour, colour) } | ||||
| pop_colour      :: #force_inline proc( ctx : ^Context                     )  { assert(ctx != nil); pop_array(& ctx.stack.colour)      } | ||||
|  | ||||
| @(deferred_none = pop_view) | ||||
| scope_view      :: #force_inline proc( ctx : ^Context, view      : Vec2    ) { assert(ctx != nil); append(& ctx.stack.view, view) } | ||||
| push_view       :: #force_inline proc( ctx : ^Context, view      : Vec2    ) { assert(ctx != nil); append(& ctx.stack.view, view) } | ||||
| pop_view        :: #force_inline proc( ctx : ^Context                      ) { assert(ctx != nil); pop_array(& ctx.stack.view)    } | ||||
|  | ||||
| @(deferred_none = pop_position) | ||||
| scope_position  :: #force_inline proc( ctx : ^Context, position  : Vec2    ) { assert(ctx != nil); append(& ctx.stack.position, position ) } | ||||
| push_position   :: #force_inline proc( ctx : ^Context, position  : Vec2    ) { assert(ctx != nil); append(& ctx.stack.position, position ) } | ||||
| pop_position    :: #force_inline proc( ctx : ^Context                      ) { assert(ctx != nil); pop_array( & ctx.stack.position)        } | ||||
|  | ||||
| @(deferred_none = pop_scale) | ||||
| scope_scale     :: #force_inline proc( ctx : ^Context, scale     : Vec2    ) { append(& ctx.stack.scale, scale ) } | ||||
| push_scale      :: #force_inline proc( ctx : ^Context, scale     : Vec2    ) { append(& ctx.stack.scale, scale ) } | ||||
| pop_scale       :: #force_inline proc( ctx : ^Context, scale     : Vec2    ) { pop_array(& ctx.stack.scale)      } | ||||
|  | ||||
| @(deferred_none = pop_zoom ) | ||||
| scope_zoom      :: #force_inline proc( ctx : ^Context, zoom      : f32     ) { append(& ctx.stack.zoom, zoom ) } | ||||
| push_zoom       :: #force_inline proc( ctx : ^Context, zoom      : f32     ) { append(& ctx.stack.zoom, zoom)  } | ||||
| pop_zoom        :: #force_inline proc( ctx : ^Context                      ) { pop_array(& ctx.stack.zoom)     } | ||||
|  | ||||
| @(deferred_none = pop_camera) | ||||
| scope_camera :: #force_inline proc( ctx : ^Context, camera : Camera  ) {  | ||||
| 	append(& ctx.stack.view,     camera.view     ) | ||||
| 	append(& ctx.stack.position, camera.position ) | ||||
| 	append(& ctx.stack.zoom,     camera.zoom     ) | ||||
| } | ||||
| push_camera  :: #force_inline proc( ctx : ^Context, camera : Camera  ) {  | ||||
| 	append(& ctx.stack.view,     camera.view     ) | ||||
| 	append(& ctx.stack.position, camera.position ) | ||||
| 	append(& ctx.stack.zoom,     camera.zoom     ) | ||||
| } | ||||
| pop_camera   :: #force_inline proc( ctx : ^Context ) { | ||||
| 	pop_array(& ctx.stack.view    ) | ||||
| 	pop_array(& ctx.stack.position) | ||||
| 	pop_array(& ctx.stack.zoom    ) | ||||
| } | ||||
|  | ||||
| //#endregion("scoping") | ||||
|  | ||||
| //#region("draw_list generation") | ||||
|  | ||||
| get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } | ||||
|  | ||||
| // Does nothing when view is 1 or 0. | ||||
| get_snapped_position :: #force_inline proc "contextless" ( ctx : Context, position : Vec2 ) -> (snapped_position : Vec2) { | ||||
| 	snap_width        := max(ctx.snap_width,  1) | ||||
| 	snap_height       := max(ctx.snap_height, 1) | ||||
| 	snap_quotient     :=  1 / Vec2 { snap_width, snap_height } | ||||
| 	snapped_position   = position  | ||||
| 	snapped_position.x = ceil(position.x * snap_width ) * snap_quotient.x | ||||
| 	snapped_position.y = ceil(position.y * snap_height) * snap_quotient.y | ||||
| 	return | ||||
| } | ||||
|  | ||||
| set_alpha_scalar            :: #force_inline proc( ctx : ^Context, scalar : f32    )      { assert(ctx != nil); ctx.alpha_sharpen                  = scalar } | ||||
| set_colour                  :: #force_inline proc( ctx : ^Context, colour : RGBAN  )      { assert(ctx != nil); ctx.colour                         = colour } | ||||
| set_draw_type_visualization :: #force_inline proc( ctx : ^Context, should_enable : b32 )  { assert(ctx != nil); ctx.enable_draw_type_visualization = cast(f32) i32(should_enable); } | ||||
| set_px_scalar               :: #force_inline proc( ctx : ^Context, scalar : f32    )      { assert(ctx != nil); ctx.px_scalar                      = scalar }  | ||||
|  | ||||
| set_snap_glyph_pos :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {  | ||||
| 	assert(ctx != nil) | ||||
| 	ctx.shaper_ctx.snap_glyph_position = should_snap | ||||
| } | ||||
|  | ||||
| // Non-scoping context. The most fundamental interface-level draw shape procedure. | ||||
| // (everything else is either batching/pipelining or quality of life warppers) | ||||
| // Note: Prefer over draw_text_normalized_space if possible to resolve shape once persistently or at least once per frame or non-dirty state. | ||||
| @(optimization_mode="favor_size") | ||||
| draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) | ||||
| draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, | ||||
| 	font     : Font_ID, | ||||
| 	px_size  : f32,  | ||||
| 	colour   : RGBAN,  | ||||
| 	view     : Vec2, | ||||
| 	position : Vec2, | ||||
| 	scale    : Vec2,  | ||||
| 	zoom     : f32,  | ||||
| 	shape    : Shaped_Text | ||||
| ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
| 	assert( len(text_utf8) > 0 ) | ||||
|  | ||||
| 	adjusted_position := get_snapped_position( ctx, position ) | ||||
|  | ||||
| 	entry := ctx.entries[ font ] | ||||
|  | ||||
| 	ctx.cursor_pos = {} | ||||
|  | ||||
| 	position := position | ||||
| 	position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width | ||||
| 	position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height | ||||
|  | ||||
| 	colour   := ctx.colour | ||||
| 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
| 	adjusted_colour   := colour | ||||
| 	adjusted_colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
|  | ||||
| @@ -497,141 +657,129 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, | ||||
| 	downscale          := scale * (1 / ctx.px_scalar) | ||||
| 	font_scale_upscale := parser_scale( entry.parser_info, px_upscale ) | ||||
|  | ||||
| 	shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,  | ||||
| 		font,  | ||||
| 	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, | ||||
| 		adjusted_colour,  | ||||
| 		entry,  | ||||
| 		px_upscale, | ||||
| 		font_scale_upscale,  | ||||
| 		position,  | ||||
| 		downscale,  | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // Non-scoping context. The most fundamental interface-level draw text procedure. | ||||
| // (everything else is either batching/pipelining or quality of life warppers) | ||||
| // Note: shape lookup can be expensive on high call usage. | ||||
| draw_text_normalized_space :: #force_inline proc( ctx : ^Context,  | ||||
| 	font      : Font_ID, | ||||
| 	px_size   : f32, | ||||
| 	colour    : RGBAN, | ||||
| 	view      : Vec2, | ||||
| 	position  : Vec2, | ||||
| 	scale     : Vec2,  | ||||
| 	zoom      : f32, | ||||
| 	text_utf8 : string | ||||
| ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
| 	assert( len(text_utf8) > 0 ) | ||||
| 	assert( ctx.snap_width > 0 && ctx.snap_height > 0 ) | ||||
|  | ||||
| 	ctx.cursor_pos = {} | ||||
| 	entry := ctx.entries[ font ] | ||||
|  | ||||
| 	adjusted_position := get_snapped_position( ctx, position ) | ||||
|  | ||||
| 	colour   := ctx.colour | ||||
| 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| 	// Does nothing when px_scalar is 1.0 | ||||
| 	target_px_size     := px_size * ctx.px_scalar | ||||
| 	target_scale       := scale * (1 / ctx.px_scalar) | ||||
| 	target_font_scale  := parser_scale( entry.parser_info, px_upscale ) | ||||
|  | ||||
| 	shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,  | ||||
| 		font,  | ||||
| 		entry,  | ||||
| 		target_px_size, | ||||
| 		target_font_scale,  | ||||
| 		shaper_shape_text_uncached_advanced | ||||
| 	) | ||||
| 	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, | ||||
| 		colour,  | ||||
| 		entry,  | ||||
| 		px_upscale, | ||||
| 		font_scale_upscale,  | ||||
| 		position, | ||||
| 		downscale,  | ||||
| 		ctx.snap_width,  | ||||
| 		ctx.snap_height | ||||
| 		target_px_size, | ||||
| 		target_font_scale,  | ||||
| 		adjusted_position, | ||||
| 		target_scale,  | ||||
| 	) | ||||
| } | ||||
|  | ||||
| draw_text_slice :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, texts : []string ) | ||||
|  | ||||
| draw_shape :: #force_inline proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) { | ||||
| 	// peek_position | ||||
| } | ||||
|  | ||||
| draw_text :: #force_inline proc( ctx : ^Context, text_utf8 : string, position, scale : Vec2 ) { | ||||
|  | ||||
| } | ||||
|  | ||||
| Text_Layer_Elem :: struct { | ||||
| 	text     : string, | ||||
| 	view     : Vec2, | ||||
| 	position : Vec2, | ||||
| 	scale    : Vec2, | ||||
| 	font     : Font_ID, | ||||
| 	px_size  : f32, | ||||
| 	zoom     : f32, | ||||
| 	colour   : Colour, | ||||
| } | ||||
|  | ||||
| // Batch a layer of text. Use get_draw_list_layer to process the layer immediately after. | ||||
| draw_text_layer :: #force_inline proc( ctx : ^Context, layer : []Text_Layer_Elem )  | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
| 	assert( len(texts) > 0 ) | ||||
|  | ||||
| 	entry := ctx.entries[ font ] | ||||
| 	for elem in layer | ||||
| 	{ | ||||
| 		entry := ctx.entries[ elem.font ] | ||||
|  | ||||
| 	ctx.cursor_pos = {} | ||||
| 		ctx.cursor_pos = {} | ||||
|  | ||||
| 	position := position | ||||
| 	position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width | ||||
| 	position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height | ||||
| 		adjusted_position := get_snapped_position( ctx, position ) | ||||
|  | ||||
| 	colour   := ctx.colour | ||||
| 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
| 		colour   := ctx.colour | ||||
| 		colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
| 		font_scale := parser_scale( entry.parser_info, px_size ) | ||||
|  | ||||
| 	px_upscale         := px_size * ctx.px_scalar | ||||
| 	downscale          := scale * (1 / ctx.px_scalar) | ||||
| 	font_scale_upscale := parser_scale( entry.parser_info, px_upscale ) | ||||
| 		px_upscale         := px_size * ctx.px_scalar | ||||
| 		downscale          := scale * (1 / ctx.px_scalar) | ||||
| 		font_scale_upscale := parser_scale( entry.parser_info, px_upscale ) | ||||
|  | ||||
| 	shapes := make( []Shaped_Text, len(texts) ) | ||||
| 	for str, id in texts { | ||||
| 		assert( len(str) > 0 ) | ||||
| 		shape := shaper_shape_text_cached( str, & ctx.shaper_ctx, & ctx.shape_cache,  | ||||
| 			font,  | ||||
| 			entry, | ||||
| 			px_upscale, | ||||
| 			font_scale_upscale,  | ||||
| 			shaper_shape_text_uncached_advanced | ||||
| 		) | ||||
| 		shapes[id] = shape | ||||
| 		shapes := make( []Shaped_Text, len(texts) ) | ||||
| 		for str, id in texts | ||||
| 		{ | ||||
| 			assert( len(str) > 0 ) | ||||
| 			shape := shaper_shape_text_cached( str, & ctx.shaper_ctx, & ctx.shape_cache,  | ||||
| 				font,  | ||||
| 				entry, | ||||
| 				px_upscale, | ||||
| 				font_scale_upscale,  | ||||
| 				shaper_shape_text_uncached_advanced | ||||
| 			) | ||||
| 			shapes[id] = shape | ||||
| 		} | ||||
|  | ||||
| 		generate_shapes_draw_list(ctx, font, colour, entry, px_upscale, font_scale_upscale, position, downscale, shapes ) | ||||
| 	} | ||||
|  | ||||
| 	generate_shapes_draw_list(ctx, font, colour, entry, px_upscale, font_scale_upscale, position, downscale, shapes ) | ||||
| } | ||||
|  | ||||
|  | ||||
| // draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) | ||||
| // { | ||||
| // 	profile(#procedure) | ||||
| // 	assert( ctx != nil ) | ||||
| // 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
| // 	assert( len(text_utf8) > 0 ) | ||||
|  | ||||
| // 	ctx.cursor_pos = {} | ||||
|  | ||||
| // 	entry := ctx.entries[ font ] | ||||
|  | ||||
| // 	colour   := ctx.colour | ||||
| // 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| // 	px_size_upscaled   := px_size * ctx.px_scalar | ||||
| // 	scale_downsample   := scale * (1 / ctx.px_scalar) | ||||
| // 	font_scale_upscale := parser_scale( entry.parser_info, px_size_upscaled ) | ||||
|  | ||||
| // 	font_scale    := parser_scale( entry.parser_info, -px_size ) | ||||
| // 	shape         := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin ) | ||||
| // 	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height ) | ||||
| // } | ||||
|  | ||||
| // Resolve the shape and track it to reduce iteration overhead | ||||
| @(optimization_mode="favor_size") | ||||
| draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
| 	position := position | ||||
| 	position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width | ||||
| 	position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height | ||||
|  | ||||
| 	entry := ctx.entries[ font ] | ||||
|  | ||||
| 	colour   := ctx.colour | ||||
| 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
|  | ||||
| 	px_upscale         := px_size * ctx.px_scalar | ||||
| 	downscale          := scale * (1 / ctx.px_scalar) | ||||
| 	font_scale_upscale := parser_scale( entry.parser_info, px_upscale ) | ||||
|  | ||||
| 	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, | ||||
| 		colour,  | ||||
| 		entry,  | ||||
| 		px_upscale, | ||||
| 		font_scale_upscale,  | ||||
| 		position,  | ||||
| 		downscale,  | ||||
| 		ctx.snap_width,  | ||||
| 		ctx.snap_height | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // Resolve the shape and track it to reduce iteration overhead | ||||
| // draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) | ||||
| // { | ||||
| // 	profile(#procedure) | ||||
| // 	assert( ctx != nil ) | ||||
| // 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| // 	colour   := ctx.colour | ||||
| // 	colour.a  = 1.0 + ctx.alpha_sharpen | ||||
|  | ||||
| // 	px_size_upscaled := px_size * ctx.px_scalar | ||||
| // 	scale            := scale * (1 / ctx.px_scalar) | ||||
|  | ||||
| // 	entry := ctx.entries[ font ] | ||||
| // 	font_scale     := parser_scale( entry.parser_info, px_size ) | ||||
| // 	ctx.cursor_pos  = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height ) | ||||
| // } | ||||
|  | ||||
| get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { | ||||
| 	assert( ctx != nil ) | ||||
| 	if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) | ||||
| @@ -662,10 +810,20 @@ flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { | ||||
| 	ctx.draw_layer.calls_offset    = len(ctx.draw_list.calls) | ||||
| } | ||||
|  | ||||
| //#endregion("drawing") | ||||
| //#endregion("draw_list generation") | ||||
|  | ||||
| //#region("metrics") | ||||
|  | ||||
| // The metrics follow the convention for providing their values unscaled from ctx.px_scalar | ||||
| // Where its assumed when utilizing the draw_list generators or shaping procedures that the shape will be affected by it so it must be handled. | ||||
| // If px_scalar is 1.0 no effect is done and its just redundant ops. | ||||
|  | ||||
| measure_shape_size :: #force_inline proc( ctx : ^Context, shape : Shaped_Text ) -> (measured : Vec2) { | ||||
| 	measured = shape.size * (1 / ctx.px_scalar) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Don't use this if you already have the shape instead use measure_shape_size | ||||
| measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| @@ -676,11 +834,12 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size | ||||
|  | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
| 	 | ||||
| 	downscale           := 1 / ctx.px_scalar | ||||
| 	px_size_upscaled    := px_size * ctx.px_scalar | ||||
| 	font_scale_upscaled := parser_scale( entry.parser_info, px_size_upscaled ) | ||||
|  | ||||
| 	shaped     := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size_upscaled, font_scale_upscaled, shaper_shape_text_uncached_advanced ) | ||||
| 	return shaped.size | ||||
| 	shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size_upscaled, font_scale_upscaled, shaper_shape_text_uncached_advanced ) | ||||
| 	return shaped.size * downscale | ||||
| } | ||||
|  | ||||
| get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 ) | ||||
| @@ -689,12 +848,8 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| 	entry := ctx.entries[ font ] | ||||
|  | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
| 	 | ||||
| 	px_size_upscaled    := px_size * ctx.px_scalar | ||||
| 	downscale           := 1 / px_size_upscaled | ||||
| 	font_scale_upscaled := parser_scale( entry.parser_info, px_size_upscaled ) | ||||
| 	font_scale := parser_scale( entry.parser_info, px_size ) | ||||
|  | ||||
| 	ascent   = font_scale * entry.ascent | ||||
| 	descent  = font_scale * entry.descent | ||||
| @@ -720,7 +875,7 @@ shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size | ||||
| 	return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,  | ||||
| 		font,  | ||||
| 		entry,  | ||||
| 		px_size,  | ||||
| 		px_size_upscaled,  | ||||
| 		font_scale_upscaled,  | ||||
| 		shaper_shape_text_latin | ||||
| 	) | ||||
| @@ -740,13 +895,14 @@ shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si | ||||
| 	return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, | ||||
| 		font,  | ||||
| 		entry,  | ||||
| 		px_size,  | ||||
| 		px_size_upscaled,  | ||||
| 		font_scale_upscaled,  | ||||
| 		shaper_shape_text_uncached_advanced | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // User handled shaped text. Will not be cached | ||||
| // @(disabled = true) | ||||
| shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape : ^Shaped_Text ) | ||||
| { | ||||
| 	assert(false) | ||||
| @@ -754,6 +910,7 @@ shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, | ||||
| } | ||||
|  | ||||
| // User handled shaped text. Will not be cached | ||||
| // @(disabled = true) | ||||
| shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape : ^Shaped_Text ) | ||||
| { | ||||
| 	assert(false) | ||||
| @@ -761,31 +918,3 @@ shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_I | ||||
| } | ||||
|  | ||||
| //#endregion("shaping") | ||||
|  | ||||
| // Can be used with hot-reload | ||||
| clear_atlas_region_caches :: proc(ctx : ^Context) | ||||
| { | ||||
| 	lru_clear(& ctx.atlas.region_a.state) | ||||
| 	lru_clear(& ctx.atlas.region_b.state) | ||||
| 	lru_clear(& ctx.atlas.region_c.state) | ||||
| 	lru_clear(& ctx.atlas.region_d.state) | ||||
|  | ||||
| 	ctx.atlas.region_a.next_idx = 0 | ||||
| 	ctx.atlas.region_b.next_idx = 0 | ||||
| 	ctx.atlas.region_c.next_idx = 0 | ||||
| 	ctx.atlas.region_d.next_idx = 0 | ||||
| } | ||||
|  | ||||
| // Can be used with hot-reload | ||||
| clear_shape_cache :: proc (ctx : ^Context) | ||||
| { | ||||
| 	lru_clear(& ctx.shape_cache.state) | ||||
| 	for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { | ||||
| 		stroage_entry := & ctx.shape_cache.storage[idx] | ||||
| 		stroage_entry.end_cursor_pos = {} | ||||
| 		stroage_entry.size           = {} | ||||
| 		clear(& stroage_entry.glyphs) | ||||
| 		clear(& stroage_entry.positions) | ||||
| 	} | ||||
| 	ctx.shape_cache.next_cache_id = 0 | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,9 @@ ui_screen_reload :: proc( screen_ui : ^UI_ScreenState ) { | ||||
| ui_screen_tick :: proc( screen_ui : ^UI_ScreenState ) { | ||||
| 	profile("Screenspace Imgui") | ||||
|  | ||||
| 	font_provider_set_px_scalar( app_config().text_size_screen_scalar ) | ||||
| 	// screen_ui.zoom_scale = 1.0 | ||||
|  | ||||
| 	ui_graph_build( screen_ui ) | ||||
| 	ui_floating_manager( & screen_ui.floating ) | ||||
| 	ui_floating("Menu Bar",      & screen_ui.menu_bar,      ui_screen_menu_bar_builder) | ||||
|   | ||||
| @@ -496,7 +496,7 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise : | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			Font_Size_Screen_Scalar: | ||||
| 			Text_Size_Screen_Scalar: | ||||
| 			{ | ||||
| 				ui_settings_entry_inputbox( & font_size_screen_scalar_input, false, "settings_menu.font_size_screen_scalar", str_intern("Font: Size Screen Scalar"), | ||||
| 					UI_TextInput_Policy { | ||||
| @@ -515,17 +515,46 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise : | ||||
| 					value, success := parse_f32(to_string(array_to_slice(input_str))) | ||||
| 					if success { | ||||
| 						value = clamp(value, 0.001, 9999.0) | ||||
| 						config.font_size_screen_scalar = value | ||||
| 						config.text_size_screen_scalar = value | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					clear( input_str ) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.font_size_screen_scalar))) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.text_size_screen_scalar))) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			Font_Size_Canvas_Scalar: | ||||
| 			Text_Snap_Glyph_Positions: | ||||
| 			{ | ||||
| 				ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"), | ||||
| 				UI_TextInput_Policy { | ||||
| 					digits_only            = true, | ||||
| 					disallow_leading_zeros = false, | ||||
| 					disallow_decimal       = false, | ||||
| 					digit_min              = 0, | ||||
| 					digit_max              = 1, | ||||
| 					max_length             = 1, | ||||
| 				} | ||||
| 				) | ||||
| 				using font_size_canvas_scalar_input | ||||
|  | ||||
| 				if was_active | ||||
| 				{ | ||||
| 					value, success := parse_f32(to_string(array_to_slice(input_str))) | ||||
| 					if success { | ||||
| 						value = clamp(value, 0, 1) | ||||
| 						config.text_snap_glyph_positions = cast(b32) i32(value) | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					clear( input_str ) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar))) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			Text_Size_Canvas_Scalar: | ||||
| 			{ | ||||
| 				ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"), | ||||
| 					UI_TextInput_Policy { | ||||
| @@ -544,19 +573,19 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise : | ||||
| 					value, success := parse_f32(to_string(array_to_slice(input_str))) | ||||
| 					if success { | ||||
| 						value = clamp(value, 0.001, 9999.0) | ||||
| 						config.font_size_canvas_scalar = value | ||||
| 						config.text_size_canvas_scalar = value | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					clear( input_str ) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.font_size_canvas_scalar))) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar))) | ||||
| 				} | ||||
| 			} | ||||
| 		 | ||||
| 			Text_Alpha_Sharpen: | ||||
| 			{ | ||||
| 				ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"), | ||||
| 				ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.text_alpha_sharpen", str_intern("Text: Alpha Sharpen"), | ||||
| 					UI_TextInput_Policy { | ||||
| 						digits_only            = true, | ||||
| 						disallow_leading_zeros = false, | ||||
| @@ -572,14 +601,14 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise : | ||||
| 				{ | ||||
| 					value, success := parse_f32(to_string(array_to_slice(input_str))) | ||||
| 					if success { | ||||
| 						value = clamp(value, 0.001, 9999.0) | ||||
| 						config.font_size_canvas_scalar = value | ||||
| 						value = clamp(value, 0.001, 10.0) | ||||
| 						config.text_alpha_sharpen = value | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					clear( input_str ) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.font_size_canvas_scalar))) | ||||
| 					append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar))) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -161,8 +161,10 @@ AppConfig :: struct { | ||||
|  | ||||
| 	color_theme : AppColorTheme, | ||||
|  | ||||
| 	font_size_screen_scalar : f32, | ||||
| 	font_size_canvas_scalar : f32, | ||||
| 	text_snap_glyph_positions : b32, | ||||
| 	text_size_screen_scalar   : f32, | ||||
| 	text_size_canvas_scalar   : f32, | ||||
| 	text_alpha_sharpen        : f32, | ||||
| } | ||||
|  | ||||
| AppWindow :: struct { | ||||
|   | ||||
| @@ -152,8 +152,10 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
|  | ||||
| 		color_theme = App_Thm_Dusk | ||||
|  | ||||
| 		font_size_screen_scalar = 1.0 | ||||
| 		font_size_canvas_scalar = 1.0 | ||||
| 		text_snap_glyph_positions = true | ||||
| 		text_size_screen_scalar   = 2.0 | ||||
| 		text_size_canvas_scalar   = 2.0 | ||||
| 		text_alpha_sharpen        = 0.25 | ||||
| 	} | ||||
|  | ||||
| 	Desired_OS_Scheduler_MS :: 1 | ||||
| @@ -354,7 +356,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
|  | ||||
| 		// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (197).txt", allocator = persistent_slab_allocator()) | ||||
| 		// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (1022).txt", allocator = persistent_slab_allocator()) | ||||
| 		debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator()) | ||||
| 		// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator()) | ||||
| 		// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/ve_fontcache.h", allocator = persistent_slab_allocator()) | ||||
|  | ||||
| 		alloc_error : AllocatorError; success : bool | ||||
|   | ||||
| @@ -57,7 +57,7 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In | ||||
| 	screen_size    := screen_extent * 2 | ||||
|  | ||||
| 	// TODO(Ed): Eventually will be the viewport extents | ||||
| 	ve.set_px_scalar( ve_ctx, app_config().font_size_canvas_scalar ) | ||||
| 	font_provider_set_px_scalar( app_config().text_size_canvas_scalar ) | ||||
| 	ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) ) | ||||
| 	// ve.configure_snap( ve_ctx, 0, 0 ) | ||||
|  | ||||
| @@ -123,7 +123,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State | ||||
| 	screen_size   := screen_extent * 2 | ||||
| 	screen_ratio  := screen_size.x * ( 1.0 / screen_size.y ) | ||||
|  | ||||
| 	ve.set_px_scalar( ve_ctx, app_config().font_size_screen_scalar ) | ||||
| 	font_provider_set_px_scalar( app_config().text_size_canvas_scalar ) | ||||
| 	ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) ) | ||||
|  | ||||
| 	render_screen_ui( screen_extent, screen_ui, ve_ctx, ve_render ) | ||||
| @@ -265,14 +265,6 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if true { | ||||
| 			zoom_adjust_size := 16 * state.project.workspace.cam.zoom | ||||
| 			over_sample      := f32(state.config.font_size_canvas_scalar) | ||||
| 			debug_text("font_size_canvas_scalar: %v", config.font_size_canvas_scalar) | ||||
| 			ve_id, resolved_size := font_provider_resolve_draw_id( default_font, zoom_adjust_size * over_sample ) | ||||
| 			debug_text("font_size resolved: %v px", resolved_size) | ||||
| 		} | ||||
|  | ||||
| 		render_text_layer( screen_extent, ve_ctx, ve_render ) | ||||
| 	} | ||||
|  | ||||
| @@ -663,6 +655,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_ | ||||
|  | ||||
| 			if cam != nil { | ||||
| 				draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color ) | ||||
| 				// draw_text_shape_pos_extent_zoomed( entry.shape, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color ) | ||||
| 			} | ||||
| 			else { | ||||
| 				draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color ) | ||||
| @@ -987,10 +980,6 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo | ||||
|  | ||||
| 	zoom_adjust_size := size * zoom | ||||
|  | ||||
| 	// Over-sample font-size for any render under a camera | ||||
| 	// over_sample : f32 = f32(config.font_size_canvas_scalar) | ||||
| 	// zoom_adjust_size *= over_sample | ||||
|  | ||||
| 	pos_offset     := (pos + cam_offset) | ||||
| 	render_pos     := ws_view_to_render_pos(pos) | ||||
| 	normalized_pos := render_pos * screen_size_norm | ||||
| @@ -1007,11 +996,7 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo | ||||
| 		text_scale.y = clamp( text_scale.y, 0, screen_size.y ) | ||||
| 	} | ||||
|  | ||||
| 	// Down-sample back | ||||
| 	// text_scale  /= over_sample | ||||
|  | ||||
| 	color_norm := normalize_rgba8(color) | ||||
| 	// ve.set_px_scalar( & get_state().font_provider_ctx.ve_ctx, config.font_size_canvas_scalar ) | ||||
| 	ve.set_colour( & get_state().font_provider_ctx.ve_ctx, color_norm ) | ||||
| 	ve.draw_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), normalized_pos, text_scale, text ) | ||||
| } | ||||
|   | ||||
| @@ -302,19 +302,20 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
|  | ||||
| 	// TODO(Ed): We need input buffer so that we can consume input actions based on the UI with priority | ||||
|  | ||||
| 	font_provider_set_px_scalar( app_config().font_size_screen_scalar ) | ||||
| 	ui_screen_tick( & get_state().screen_ui ) | ||||
|  | ||||
| 	//region WorkspaceImgui Tick | ||||
| 	if true | ||||
| 	{ | ||||
| 		font_provider_set_px_scalar( app_config().font_size_canvas_scalar ) | ||||
| 		font_provider_set_px_scalar( app_config().text_size_canvas_scalar ) | ||||
| 		profile("Workspace Imgui") | ||||
|  | ||||
| 		// Creates the root box node, set its as the first parent. | ||||
| 		ui_graph_build( & state.project.workspace.ui ) | ||||
| 		ui := ui_context | ||||
|  | ||||
| 		ui.zoom_scale = state.project.workspace.cam.zoom | ||||
|  | ||||
| 		frame_style_flags : UI_LayoutFlags = { | ||||
| 			.Fixed_Position_X, .Fixed_Position_Y, | ||||
| 			.Fixed_Width, .Fixed_Height, | ||||
|   | ||||
| @@ -140,6 +140,12 @@ font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_U | ||||
| 	return | ||||
| } | ||||
|  | ||||
| measure_text_shape :: #force_inline proc( shape : ShapedText ) -> Vec2 | ||||
| { | ||||
| 	measured    := ve.measure_shape_size( & get_state().font_provider_ctx.ve_ctx, shape ) | ||||
| 	return measured | ||||
| } | ||||
|  | ||||
| measure_text_size :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 | ||||
| { | ||||
| 	ve_id, size := font_provider_resolve_draw_id( font, font_size ) | ||||
|   | ||||
| @@ -123,6 +123,8 @@ UI_State :: struct { | ||||
| 	// build_arenas : [2]Arena, | ||||
| 	// build_arena  : ^ Arena, | ||||
|  | ||||
| 	zoom_scale : f32, | ||||
|  | ||||
| 	built_box_count : i32, | ||||
|  | ||||
| 	caches     : [2] HMapChained( UI_Box ), | ||||
| @@ -285,15 +287,6 @@ ui_graph_build_end :: proc( ui : ^UI_State ) | ||||
| 			{ | ||||
| 				if len(current.text) > 0 { | ||||
| 					profile("text shape") | ||||
| 					// app_window       := get_state().app_window | ||||
| 					// screen_extent    := app_window.extent | ||||
| 					// screen_size      := screen_extent * 2 | ||||
| 					// screen_size_norm := 1 / screen_size | ||||
|  | ||||
| 					font_size_screen_scalar := app_config().font_size_screen_scalar | ||||
|  | ||||
| 					// over_sample : f32 = f32(get_state().config.font_size_canvas_scalar) | ||||
|  | ||||
| 					current.computed.text_shape = shape_text_cached( current.text, current.style.font, current.layout.font_size, 1.0 ) | ||||
| 				} | ||||
| 				ui_box_compute_layout( current ) | ||||
|   | ||||
| @@ -73,7 +73,7 @@ ui_box_compute_layout :: proc( box : ^UI_Box, | ||||
| 	text_size : Vec2 | ||||
| 	if len(box.text) > 0 | ||||
| 	{ | ||||
| 		text_size = computed.text_shape.size | ||||
| 		text_size = measure_text_shape( computed.text_shape ) | ||||
| 		// if layout.font_size == computed.text_size.y { | ||||
| 		// 	text_size = computed.text_size | ||||
| 		// } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user