diff --git a/code/sectr/app/screen.odin b/code/sectr/app/screen.odin index 0bdcd96..c121311 100644 --- a/code/sectr/app/screen.odin +++ b/code/sectr/app/screen.odin @@ -55,7 +55,7 @@ ui_screen_menu_bar :: proc( captures : rawptr = nil ) -> (should_raise : b32 = f flags = {}, anchor = range2({},{}), // alignment = UI_Align_Presets.text_centered, - text_alignment = {0.0, 1.5}, + text_alignment = {0.0, 0}, font_size = 12, margins = {0, 0, 0, 0}, padding = {0, 0, 0, 0}, @@ -225,7 +225,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b using title layout.anchor.ratio.x = 1.0 layout.margins.left = 10 - layout.text_alignment = {0, 0.5} + layout.text_alignment = {0, 0.0} } input_box := ui_widget("settings_menu.engine_refresh.input_box", {.Mouse_Clickable, .Focusable, .Click_To_Focus}); { diff --git a/code/sectr/app/state.odin b/code/sectr/app/state.odin index 6fc5860..fe807b9 100644 --- a/code/sectr/app/state.odin +++ b/code/sectr/app/state.odin @@ -151,10 +151,10 @@ AppConfig :: struct { cam_zoom_smooth_snappiness : f32, cam_zoom_sensitivity_smooth : f32, cam_zoom_sensitivity_digital : f32, + cam_zoom_scroll_delta_scale : f32, engine_refresh_hz : uint, - timing_fps_moving_avg_alpha : f32, ui_resize_border_width : f32, diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index c5c8467..83fbaa5 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -69,7 +69,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // Setup Persistent Slabs & String Cache { // alignment := uint(mem.DEFAULT_ALIGNMENT) - alignment := uint(16) + alignment := uint(64) policy_ptr := & default_slab_policy push( policy_ptr, SlabSizeClass { 128 * Kilobyte, 1 * Kilobyte, alignment }) @@ -88,9 +88,9 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem push( policy_ptr, SlabSizeClass { 16 * Megabyte, 16 * Megabyte, alignment }) push( policy_ptr, SlabSizeClass { 32 * Megabyte, 32 * Megabyte, alignment }) push( policy_ptr, SlabSizeClass { 64 * Megabyte, 64 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment }) - // push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment }) + push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment }) + push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment }) + push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment }) alloc_error : AllocatorError persistent_slab, alloc_error = slab_init( policy_ptr, allocator = persistent_allocator(), dbg_name = Persistent_Slab_DBG_Name, enable_mem_tracking = false ) @@ -137,11 +137,12 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem resolution_height = 600 refresh_rate = 0 - cam_min_zoom = 0.10 + cam_min_zoom = 0.025 cam_max_zoom = 5.0 - cam_zoom_mode = .Smooth + cam_zoom_mode = .Digital cam_zoom_smooth_snappiness = 4.0 - cam_zoom_sensitivity_digital = 0.05 + cam_zoom_sensitivity_digital = 0.25 + cam_zoom_scroll_delta_scale = 0.25 cam_zoom_sensitivity_smooth = 2.0 engine_refresh_hz = 0 @@ -252,7 +253,10 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // Setup sokol_gp { - desc := sokol_gp.Desc {} + desc := sokol_gp.Desc { + max_vertices = 2 * Mega + 640 * Kilo, + max_commands = 1 * Mega, + } sokol_gp.setup(desc) verify( cast(b32) sokol_gp.is_valid(), "Failed to setup sokol gp (graphics painter)" ) } @@ -329,7 +333,9 @@ 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/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/ve_fontcache.h", allocator = persistent_slab_allocator()) alloc_error : AllocatorError; success : bool debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() ) @@ -502,8 +508,8 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 // rl.PollInputEvents() debug.draw_ui_box_bounds_points = false - debug.draw_ui_padding_bounds = false - debug.draw_ui_content_bounds = false + debug.draw_ui_padding_bounds = false + debug.draw_ui_content_bounds = false // config.engine_refresh_hz = 165 diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index bbb3f2b..94e670f 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -258,7 +258,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State } if true { - state.config.font_size_canvas_scalar = 2 + state.config.font_size_canvas_scalar = 1.5 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) diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index fd93f43..87e67ce 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -153,6 +153,65 @@ update :: proc( delta_time : f64 ) -> b32 //region 2D Camera Manual Nav // TODO(Ed): This should be per workspace view { + Digial_Zoom_Snap_Levels := []f32{ + 0.025, // 0.4px (not practical for text, but allows extreme zoom out) + 0.03125, // 0.5px + 0.0375, // 0.6px + 0.04375, // 0.7px + 0.05, // 0.8px + 0.05625, // 0.9px + 0.0625, // 1px + 0.075, // 1.2px + 0.0875, // 1.4px + 0.1, // 1.6px + 0.1125, // 1.8px + 0.125, // 2px (first practical font size) + 0.15, // + 0.20, // + 0.25, // 4px + 0.375, // 6px + 0.5, // 8px + 0.625, // 10px + 0.75, // 12px + 0.875, // 14px + 1.0, // 16px (base size) + 1.125, // 18px + 1.25, // 20px + 1.375, // 22px + 1.5, // 24px + 1.625, // 26px + 1.75, // 28px + 1.875, // 30px + 2.0, // 32px + 2.125, // 34px + 2.25, // 36px + 2.375, // 38px + 2.5, // 40px + 2.625, // 42px + 2.75, // 44px + 2.875, // 46px + 3.0, // 48px + 3.125, // 50px + 3.25, // 52px + 3.375, // 54px + 3.5, // 56px + 3.625, // 58px + 3.75, // 60px + 3.875, // 62px + 4.0, // 64px + 4.125, // 66px + 4.25, // 68px + 4.375, // 70px + 4.5, // 72px + 4.625, // 74px + 4.75, // 76px + 4.875, // 78px + 5.0, // 80px + } + + Min_Zoom := Digial_Zoom_Snap_Levels[ 0 ] + Max_zoom := Digial_Zoom_Snap_Levels[ len(Digial_Zoom_Snap_Levels) - 1 ] + // profile("Camera Manual Nav") digital_move_speed : f32 = 1000.0 @@ -160,11 +219,30 @@ update :: proc( delta_time : f64 ) -> b32 workspace.zoom_target = cam.zoom } - // config.cam_max_zoom = 10 - // config.cam_min_zoom = 0.05 - // config.cam_zoom_sensitivity_digital = 0.05 - // config.cam_zoom_sensitivity_smooth = 2.0 - // config.cam_zoom_mode = .Smooth + binary_search_closest :: proc(arr: []f32, target: f32) -> int + { + low, high := 0, len(arr) - 1 + + for low <= high { + mid := (low + high) / 2 + if arr[ mid ] == target do return mid + else if arr[ mid ] < target do low = mid + 1 + else do high = mid - 1 + } + + if low == 0 do return 0 + if low == len(arr) do return len(arr) - 1 + + if abs(arr[low-1] - target) < abs(arr[low] - target) { + return low - 1 + } + return low + } + + find_closest_zoom_index :: proc(zoom: f32, levels : []f32) -> int { + return clamp(binary_search_closest(levels, zoom), 0, len(levels) - 1) + } + switch config.cam_zoom_mode { case .Smooth: @@ -177,9 +255,26 @@ update :: proc( delta_time : f64 ) -> b32 cam.zoom += (workspace.zoom_target - cam.zoom) * lerp_factor * f32(delta_time) cam.zoom = clamp(cam.zoom, config.cam_min_zoom, config.cam_max_zoom) // Ensure cam.zoom stays within bounds case .Digital: - zoom_delta := clamp(input.mouse.scroll.y, -1, 1) * config.cam_zoom_sensitivity_digital - workspace.zoom_target = clamp(workspace.zoom_target + zoom_delta, config.cam_min_zoom, config.cam_max_zoom) - cam.zoom = workspace.zoom_target + zoom_delta := input.mouse.scroll.y + + if zoom_delta != 0 { + current_index := find_closest_zoom_index(cam.zoom, Digial_Zoom_Snap_Levels) + scroll_speed := max(1, abs(zoom_delta) * config.cam_zoom_scroll_delta_scale) // Adjust this factor to control sensitivity + target_index := current_index + + if zoom_delta > 0 { + target_index = min(len(Digial_Zoom_Snap_Levels) - 1, current_index + int(scroll_speed)) + } else if zoom_delta < 0 { + target_index = max(0, current_index - int(scroll_speed)) + } + + if target_index != current_index { + workspace.zoom_target = Digial_Zoom_Snap_Levels[target_index] + } + } + + // Smooth transition to target zoom + cam.zoom = lerp(cam.zoom, workspace.zoom_target, cast(f32) config.cam_zoom_sensitivity_digital) } move_velocity : Vec2 = { diff --git a/code/sectr/grime/mappings.odin b/code/sectr/grime/mappings.odin index c5c478d..cc2ef71 100644 --- a/code/sectr/grime/mappings.odin +++ b/code/sectr/grime/mappings.odin @@ -51,6 +51,7 @@ import fmt_io "core:fmt" str_tmp_from_any :: fmt_io.tprint import "core:math" + lerp :: math.lerp import "core:math/bits" u64_max :: bits.U64_MAX diff --git a/code/sectr/math/space.odin b/code/sectr/math/space.odin index 98e999b..f89e876 100644 --- a/code/sectr/math/space.odin +++ b/code/sectr/math/space.odin @@ -205,7 +205,8 @@ render_to_ws_view_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 { screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 { state := get_state(); using state cam := & project.workspace.cam - result := pos - cam.position * cam.zoom + cam_zoom_ratio := 1.0 / cam.zoom + result := pos * cam_zoom_ratio - cam.position return result } @@ -227,8 +228,17 @@ ws_view_extent :: #force_inline proc "contextless"() -> Extents2 { // Workspace view to screen space position // TODO(Ed): Support a position which would not be centered on the screen if in a viewport -ws_view_to_screen_pos :: #force_inline proc "contextless"(position: Vec2) -> Vec2 { - return position +ws_view_to_screen_pos :: proc(ws_pos : Vec2) -> Vec2 { + cam := &get_state().project.workspace.cam + screen_extent := get_state().app_window.extent + + // Apply camera transformation + view_pos := (ws_pos - cam.position) * cam.zoom + + // Convert to screen space + screen_pos := view_pos + + return screen_pos } ws_view_to_render_pos :: #force_inline proc "contextless"(position: Vec2) -> Vec2 { diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index 3435fd3..1c37c84 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -504,15 +504,16 @@ ui_top_ancestor :: #force_inline proc "contextless" ( box : ^UI_Box ) -> (^UI_Bo return ancestor } -ui_view_bounds :: #force_inline proc "contextless" () -> (range : Range2) { - using state := get_state(); - // if ui_context == & screen_ui { - // return screen_get_bounds() - // } - // else { - +ui_view_bounds :: #force_inline proc "contextless" ( ui : ^UI_State = nil ) -> (range : Range2) { + state := get_state(); using state + ui := ui; if ui == nil do ui = ui_context + + if ui == & screen_ui { + return screen_get_bounds() + } + else { return view_get_bounds() - // } + } } ui_context :: #force_inline proc "contextless" () -> ^UI_State { return get_state().ui_context } diff --git a/code/sectr/ui/core/box.odin b/code/sectr/ui/core/box.odin index b66986f..819a7be 100644 --- a/code/sectr/ui/core/box.odin +++ b/code/sectr/ui/core/box.odin @@ -118,36 +118,33 @@ ui_prev_cached_box :: #force_inline proc( box : ^UI_Box ) -> ^UI_Box { return hm // TODO(Ed): Rename to ui_box_tranverse_view_next // Traveral pritorizes immeidate children -ui_box_tranverse_next_depth_first :: #force_inline proc "contextless" ( box : ^ UI_Box, bypass_intersection_test := false ) -> (^ UI_Box) -{ - using state := get_state() - // If current has children, do them first - if box.first != nil - { - // Check to make sure parent is present on the screen, if its not don't bother. - if bypass_intersection_test { - return box.first - } - if intersects_range2( ui_view_bounds(), box.computed.bounds) { - return box.first +ui_box_tranverse_next_depth_first :: #force_inline proc "contextless" (box: ^UI_Box, bypass_intersection_test := false, ctx: ^UI_State = nil) -> ^UI_Box { + state := get_state(); using state + ctx := ctx if ctx != nil else ui_context + + // If current has children, check if we should traverse them + if box.first != nil { + if bypass_intersection_test || intersects_range2(ui_view_bounds(ctx), box.computed.bounds) { + return box.first } } - if box.next != nil do return box.next - // There are no more adjacent nodes + // If no children or children are culled, try next sibling + if box.next != nil { + return box.next + } + // No more siblings, traverse up the tree parent := box.parent - // Attempt to find a parent with a next, otherwise we just return a parent with nil - for ; parent.parent != nil; - { - if parent.next != nil { - break - } - parent = parent.parent + for parent != nil { + if parent.next != nil { + return parent.next + } + parent = parent.parent } - // Lift back up to parent, and set it to its next. - return parent.next + // We've reached the end of the tree + return nil } // Traveral pritorizes traversing a "anestry layer" diff --git a/code/sectr/ui/core/layout_compute.odin b/code/sectr/ui/core/layout_compute.odin index 82de63e..6715aeb 100644 --- a/code/sectr/ui/core/layout_compute.odin +++ b/code/sectr/ui/core/layout_compute.odin @@ -166,9 +166,11 @@ ui_box_compute_layout :: proc( box : ^UI_Box, // 8. Text position & size if len(box.text.str) > 0 { + ascent, descent, line_gap := get_font_vertical_metrics(style.font, layout.font_size) content_size := content_bounds.max - content_bounds.min text_pos : Vec2 - text_pos = content_bounds.min + { 0, text_size.y * 0.5 } + text_pos = content_bounds.min + text_pos += { 0, -descent } text_pos += (content_size - text_size) * layout.text_alignment computed.text_size = text_size diff --git a/code/sectr/ui/tests.odin b/code/sectr/ui/tests.odin index 083e23c..d28eaf8 100644 --- a/code/sectr/ui/tests.odin +++ b/code/sectr/ui/tests.odin @@ -163,17 +163,17 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : text_layout.flags = { // .Origin_At_Anchor_Center, .Fixed_Position_X, .Fixed_Position_Y, - .Fixed_Width, .Fixed_Height, + .Fixed_Width, .Fixed_Height, } text_layout.text_alignment = { 0.0, 0.5 } - text_layout.alignment = { 0.0, 0.0 } - text_layout.size.min = { 1600, 20 } + text_layout.alignment = { 0.0, 1.0 } + text_layout.size.min = { 1600, 14 } text_style := frame_style_default ^ text_style_combo := to_ui_style_combo(text_style) text_style_combo.default.bg_color = Color_Transparent - text_style_combo.disabled.bg_color = Color_Frame_Disabled - text_style_combo.hot.bg_color = Color_Frame_Hover - text_style_combo.active.bg_color = Color_Frame_Select + text_style_combo.disabled.bg_color = Color_Transparent + text_style_combo.hot.bg_color = Color_Transparent + text_style_combo.active.bg_color = Color_Transparent scope( text_layout, text_style ) alloc_error : AllocatorError; success : bool @@ -204,14 +204,14 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : ui_layout( text_layout ) line_hbox := ui_widget(str_fmt( "line %v", line_id ), {.Mouse_Clickable}) - if line_hbox.key == ui.hot + if line_hbox.key == ui.hot && false { line_hbox.text = StrRunesPair {} ui_parent(line_hbox) chunk_layout := text_layout - chunk_layout.alignment = { 0.0, 0.5 } - chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 }) + chunk_layout.alignment = { 0.0, 0.0 } + chunk_layout.anchor = range2({ 0.0, 0.0 }, { 0.0, 0.0 }) chunk_layout.pos = {} chunk_layout.flags = { .Fixed_Position_X, .Size_To_Text } @@ -286,12 +286,12 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : if len(line_hbox.text.str) > 0 { array_append( widgets_ptr, line_hbox ) text_layout.pos.x = text_layout.pos.x - text_layout.pos.y += size_range2(line_hbox.computed.bounds).y - 8 + text_layout.pos.y += size_range2(line_hbox.computed.bounds).y } else { widget := & widgets.data[ widgets.num - 1 ] if widget.box != nil { - text_layout.pos.y += size_range2( widget.computed.bounds ).y - 8 + text_layout.pos.y += size_range2( widget.computed.bounds ).y } } diff --git a/examples/sokol_gp.h b/examples/sokol_gp.h new file mode 100644 index 0000000..114bba5 --- /dev/null +++ b/examples/sokol_gp.h @@ -0,0 +1,2978 @@ +/* +Minimal efficient cross platform 2D graphics painter for Sokol GFX. +sokol_gp - v0.5.0 - 22/Mar/2024 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/sokol_gp + +# Sokol GP + +Minimal efficient cross platform 2D graphics painter in pure C +using modern graphics API through the excellent [Sokol GFX](https://github.com/floooh/sokol) library. + +Sokol GP, or in short SGP, stands for Sokol Graphics Painter. + +![sample-primitives](https://raw.githubusercontent.com/edubart/sokol_gp/master/screenshots/sample-primitives.png) + +## Features + +* Made and optimized only for **2D rendering only**, no 3D support. +* Minimal, in a pure single C header. +* Use modern unfixed pipeline graphics APIs for more efficiency. +* Cross platform (backed by Sokol GFX). +* D3D11/OpenGL 3.3/Metal/WebGPU graphics backends (through Sokol GFX). +* **Automatic batching** (merge recent draw calls into batches automatically). +* **Batch optimizer** (rearranges the ordering of draw calls to batch more). +* Uses preallocated memory (no allocations at runtime). +* Supports drawing basic 2D primitives (rectangles, triangles, lines and points). +* Supports the classic 2D color blending modes (color blend, add, modulate, multiply). +* Supports 2D space transformations and changing 2D space coordinate systems. +* Supports drawing the basic primitives (rectangles, triangles, lines and points). +* Supports multiple texture bindings. +* Supports custom fragment shaders with 2D primitives. +* Can be mixed with projects that are already using Sokol GFX. + +## Why? + +Sokol GFX is an excellent library for rendering using unfixed pipelines +of modern graphics cards, but it is too complex to use for simple 2D drawing, +and it's API is too generic and specialized for 3D rendering. To draw 2D stuff, the programmer +usually needs to setup custom shaders when using Sokol GFX, or use its Sokol GL +extra library, but Sokol GL also has an API with 3D design in mind, which +incurs some costs and limitations. + +This library was created to draw 2D primitives through Sokol GFX with ease, +and by not considering 3D usage it is optimized for 2D rendering only, +furthermore it features an **automatic batch optimizer**, more details of it will be described below. + +## Automatic batch optimizer + +When drawing the library creates a draw command queue of all primitives yet to be drawn, +every time a new draw command is added the batch optimizer looks back up to the last +8 recent draw commands (this is adjustable), and try to rearrange and merge drawing commands +if it finds a previous draw command that meets the following criteria: + +* The new draw command and previous command uses the *same primitive pipeline* +* The new draw command and previous command uses the *same shader uniforms* +* The new draw command and previous command uses the *same texture bindings* +* The new draw command and previous command does not have another intermediary +draw command *that overlaps* in-between them. + +By doing this the batch optimizer is able for example to merge textured draw calls, +even if they were drawn with other intermediary different textures draws between them. +The effect is more efficiency when drawing, because less draw calls will be dispatched +to the GPU, + +This library can avoid a lot of work of making an efficient 2D drawing batching system, +by automatically merging draw calls behind the scenes at runtime, +thus the programmer does not need to manage batched draw calls manually, +nor he needs to sort batched texture draw calls, +the library will do this seamlessly behind the scenes. + +The batching algorithm is fast, but it has `O(n)` CPU complexity for every new draw command added, +where `n` is the `SGP_BATCH_OPTIMIZER_DEPTH` configuration. +In experiments using `8` as the default is a good default, +but you may want to try out different values depending on your case. +Using values that are too high is not recommended, because the algorithm may take too long +scanning previous draw commands, and that may consume more CPU resources. + +The batch optimizer can be disabled by setting `SGP_BATCH_OPTIMIZER_DEPTH` to 0, +you can use that to measure its impact. + +In the samples directory of this repository there is a +benchmark example that tests drawing with the bath optimizer enabled/disabled. +On my machine that benchmark was able to increase performance in a 2.2x factor when it is enabled. +In some private game projects the gains of the batch optimizer proved to increase FPS performance +above 1.5x by just replacing the graphics backend with this library, with no internal +changes to the game itself. + +## Design choices + +The library has some design choices with performance in mind that will be discussed briefly here. + +Like Sokol GFX, Sokol GP will never do any allocation in the draw loop, +so when initializing you must configure beforehand the maximum size of the +draw command queue buffer and the vertices buffer. + +All the 2D space transformation (functions like `sgp_rotate`) are done by the CPU and not by the GPU, +this is intentionally to avoid adding extra overhead in the GPU, because typically the number +of vertices of 2D applications are not that large, and it is more efficient to perform +all the transformation with the CPU right away rather than pushing extra buffers to the GPU +that ends up using more bandwidth of the CPU<->GPU bus. +In contrast 3D applications usually dispatches vertex transformations to the GPU using a vertex shader, +they do this because the amount of vertices of 3D objects can be very large +and it is usually the best choice, but this is not true for 2D rendering. + +Many APIs to transform the 2D space before drawing a primitive are available, such as +translate, rotate and scale. They can be used as similarly as the ones available in 3D graphics APIs, +but they are crafted for 2D only, for example when using 2D we don't need to use a 4x4 or 3x3 matrix +to perform vertex transformation, instead the code is specialized for 2D and can use a 2x3 matrix, +saving extra CPU float computations. + +All pipelines always use a texture associated with it, even when drawing non textured primitives, +because this minimizes graphics pipeline changes when mixing textured calls and non textured calls, +improving efficiency. + +The library is coded in the style of Sokol GFX headers, reusing many macros from there, +you can change some of its semantics such as custom allocator, custom log function, and some +other details, read `sokol_gfx.h` documentation for more on that. + +## Usage + +Copy `sokol_gp.h` along with other Sokol headers to the same folder. Setup Sokol GFX +as you usually would, then add call to `sgp_setup(desc)` just after `sg_setup(desc)`, and +call to `sgp_shutdown()` just before `sg_shutdown()`. Note that you should usually check if +SGP is valid after its creation with `sgp_is_valid()` and exit gracefully with an error if not. + +In your frame draw function add `sgp_begin(width, height)` before calling any SGP +draw function, then draw your primitives. At the end of the frame (or framebuffer) you +should **ALWAYS call** `sgp_flush()` between a Sokol GFX begin/end render pass, +the `sgp_flush()` will dispatch all draw commands to Sokol GFX. Then call `sgp_end()` immediately +to discard the draw command queue. + +An actual example of this setup will be shown below. + +## Quick usage example + +The following is a quick example on how to this library with Sokol GFX and Sokol APP: + +```c +// This is an example on how to set up and use Sokol GP to draw a filled rectangle. + +// Includes Sokol GFX, Sokol GP and Sokol APP, doing all implementations. +#define SOKOL_IMPL +#include "sokol_gfx.h" +#include "sokol_gp.h" +#include "sokol_app.h" +#include "sokol_glue.h" +#include "sokol_log.h" + +#include // for fprintf() +#include // for exit() +#include // for sinf() and cosf() + +// Called on every frame of the application. +static void frame(void) { + // Get current window size. + int width = sapp_width(), height = sapp_height(); + float ratio = width/(float)height; + + // Begin recording draw commands for a frame buffer of size (width, height). + sgp_begin(width, height); + // Set frame buffer drawing region to (0,0,width,height). + sgp_viewport(0, 0, width, height); + // Set drawing coordinate space to (left=-ratio, right=ratio, top=1, bottom=-1). + sgp_project(-ratio, ratio, 1.0f, -1.0f); + + // Clear the frame buffer. + sgp_set_color(0.1f, 0.1f, 0.1f, 1.0f); + sgp_clear(); + + // Draw an animated rectangle that rotates and changes its colors. + float time = sapp_frame_count() * sapp_frame_duration(); + float r = sinf(time)*0.5+0.5, g = cosf(time)*0.5+0.5; + sgp_set_color(r, g, 0.3f, 1.0f); + sgp_rotate_at(time, 0.0f, 0.0f); + sgp_draw_filled_rect(-0.5f, -0.5f, 1.0f, 1.0f); + + // Begin a render pass. + sg_pass pass = {.swapchain = sglue_swapchain()}; + sg_begin_pass(&pass); + // Dispatch all draw commands to Sokol GFX. + sgp_flush(); + // Finish a draw command queue, clearing it. + sgp_end(); + // End render pass. + sg_end_pass(); + // Commit Sokol render. + sg_commit(); +} + +// Called when the application is initializing. +static void init(void) { + // Initialize Sokol GFX. + sg_desc sgdesc = { + .environment = sglue_environment(), + .logger.func = slog_func + }; + sg_setup(&sgdesc); + if (!sg_isvalid()) { + fprintf(stderr, "Failed to create Sokol GFX context!\n"); + exit(-1); + } + + // Initialize Sokol GP, adjust the size of command buffers for your own use. + sgp_desc sgpdesc = {0}; + sgp_setup(&sgpdesc); + if (!sgp_is_valid()) { + fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); + exit(-1); + } +} + +// Called when the application is shutting down. +static void cleanup(void) { + // Cleanup Sokol GP and Sokol GFX resources. + sgp_shutdown(); + sg_shutdown(); +} + +// Implement application main through Sokol APP. +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + return (sapp_desc){ + .init_cb = init, + .frame_cb = frame, + .cleanup_cb = cleanup, + .window_title = "Rectangle (Sokol GP)", + .logger.func = slog_func, + }; +} +``` + +To run this example, first copy the `sokol_gp.h` header alongside with other Sokol headers +to the same folder, then compile with any C compiler using the proper linking flags (read `sokol_gfx.h`). + +## Complete Examples + +In folder `samples` you can find the following complete examples covering all APIs of the library: + +* [sample-primitives.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-primitives.c): This is an example showing all drawing primitives and transformations APIs. +* [sample-blend.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-blend.c): This is an example showing all blend modes between 3 rectangles. +* [sample-framebuffer.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-framebuffer.c): This is an example showing how to use multiple `sgp_begin()` with frame buffers. +* [sample-sdf.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-sdf.c): This is an example on how to create custom shaders. +* [sample-effect.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-effect.c): This is an example on how to use custom shaders for 2D drawing. +* [sample-bench.c](https://github.com/edubart/sokol_gp/blob/master/samples/sample-bench.c): This is a heavy example used for benchmarking purposes. + +These examples are used as the test suite for the library, you can build them by typing `make`. + +## Error handling + +It is possible that after many draw calls the command or vertex buffer may overflow, +in that case the library will set an error error state and will continue to operate normally, +but when flushing the drawing command queue with `sgp_flush()` no draw command will be dispatched. +This can happen because the library uses pre allocated buffers, in such +cases the issue can be fixed by increasing the prefixed command queue buffer and the vertices buffer +when calling `sgp_setup()`. + +Making invalid number of push/pops of `sgp_push_transform()` and `sgp_pop_transform()`, +or nesting too many `sgp_begin()` and `sgp_end()` may also lead to errors, that +is a usage mistake. + +You can enable the `SOKOL_DEBUG` macro in such cases to debug, or handle +the error programmatically by reading `sgp_get_last_error()` after calling `sgp_end()`. +It is also advised to leave `SOKOL_DEBUG` enabled when developing with Sokol, so you can +catch mistakes early. + +## Blend modes + +The library supports the most usual blend modes used in 2D, which are the following: + +- `SGP_BLENDMODE_NONE` - No blending (`dstRGBA = srcRGBA`). +- `SGP_BLENDMODE_BLEND` - Alpha blending (`dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))` and `dstA = srcA + (dstA * (1-srcA))`) +- `SGP_BLENDMODE_ADD` - Color add (`dstRGB = (srcRGB * srcA) + dstRGB` and `dstA = dstA`) +- `SGP_BLENDMODE_MOD` - Color modulate (`dstRGB = srcRGB * dstRGB` and `dstA = dstA`) +- `SGP_BLENDMODE_MUL` - Color multiply (`dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA))` and `dstA = (srcA * dstA) + (dstA * (1-srcA))`) + +## Changing 2D coordinate system + +You can change the screen area to draw by calling `sgp_viewport(x, y, width, height)`. +You can change the coordinate system of the 2D space by calling `sgp_project(left, right, top, bottom)`, +with it. + +## Transforming 2D space + +You can translate, rotate or scale the 2D space before a draw call, by using the transformation +functions the library provides, such as `sgp_translate(x, y)`, `sgp_rotate(theta)`, etc. +Check the cheat sheet or the header for more. + +To save and restore the transformation state you should call `sgp_push_transform()` and +later `sgp_pop_transform()`. + +## Drawing primitives + +The library provides drawing functions for all the basic primitives, that is, +for points, lines, triangles and rectangles, such as `sgp_draw_line()` and `sgp_draw_filled_rect()`. +Check the cheat sheet or the header for more. +All of them have batched variations. + +## Drawing textured primitives + +To draw textured rectangles you can use `sgp_set_image(0, img)` and then sgp_draw_filled_rect()`, +this will draw an entire texture into a rectangle. +You should later reset the image with `sgp_reset_image(0)` to restore the bound image to default white image, +otherwise you will have glitches when drawing a solid color. + +In case you want to draw a specific source from the texture, +you should use `sgp_draw_textured_rect()` instead. + +By default textures are drawn using a simple nearest filter sampler, +you can change the sampler with `sgp_set_sampler(0, smp)` before drawing a texture, +it's recommended to restore the default sampler using `sgp_reset_sampler(0)`. + +## Color modulation + +All common pipelines have color modulation, and you can modulate +a color before a draw by setting the current state color with `sgp_set_color(r,g,b,a)`, +later you should reset the color to default (white) with `sgp_reset_color()`. + +## Custom shaders + +When using a custom shader, you must create a pipeline for it with `sgp_make_pipeline(desc)`, +using shader, blend mode and a draw primitive associated with it. Then you should +call `sgp_set_pipeline()` before the shader draw call. You are responsible for using +the same blend mode and drawing primitive as the created pipeline. + +Custom uniforms can be passed to the shader with `sgp_set_uniform(data, size)`, +where you should always pass a pointer to a struct with exactly the same schema and size +as the one defined in the shader. + +Although you can create custom shaders for each graphics backend manually, +it is advised should use the Sokol shader compiler [SHDC](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md), +because it can generate shaders for multiple backends from a single `.glsl` file, +and this usually works well. + +By default the library uniform buffer per draw call has just 4 float uniforms +(`SGP_UNIFORM_CONTENT_SLOTS` configuration), and that may be too low to use with custom shaders. +This is the default because typically newcomers may not want to use custom 2D shaders, +and increasing a larger value means more overhead. +If you are using custom shaders please increase this value to be large enough to hold +the number of uniforms of your largest shader. + +## Library configuration + +The following macros can be defined before including to change the library behavior: + +- `SGP_BATCH_OPTIMIZER_DEPTH` - Number of draw commands that the batch optimizer looks back at. Default is 8. +- `SGP_UNIFORM_CONTENT_SLOTS` - Maximum number of floats that can be stored in each draw call uniform buffer. Default is 4. +- `SGP_TEXTURE_SLOTS` - Maximum number of textures that can be bound per draw call. Default is 4. + +## License + +MIT, see LICENSE file or the end of `sokol_gp.h` file. +*/ + +#if defined(SOKOL_IMPL) && !defined(SOKOL_GP_IMPL) +#define SOKOL_GP_IMPL +#endif + +#ifndef SOKOL_GP_INCLUDED +#define SOKOL_GP_INCLUDED 1 + +#ifndef SOKOL_GFX_INCLUDED +#error "Please include sokol_gfx.h before sokol_gp.h" +#endif + +/* Number of draw commands that the batch optimizer looks back at. +8 is a fair default value, but could be tuned per application. +1 makes the batch optimizer try to merge only the very last draw call. +0 disables the batch optimizer +*/ +#ifndef SGP_BATCH_OPTIMIZER_DEPTH +#define SGP_BATCH_OPTIMIZER_DEPTH 8 +#endif + +/* Number of uniform floats (4-bytes) slots that can be set in a shader. +Increase this value if you need to use shader with many uniforms. +*/ +#ifndef SGP_UNIFORM_CONTENT_SLOTS +#define SGP_UNIFORM_CONTENT_SLOTS 4 +#endif + +/* Number of texture slots that can be bound in a pipeline. */ +#ifndef SGP_TEXTURE_SLOTS +#define SGP_TEXTURE_SLOTS 4 +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GP_API_DECL) +#define SOKOL_GP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GP_IMPL) +#define SOKOL_GP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GP_API_DECL __declspec(dllimport) +#else +#define SOKOL_GP_API_DECL extern +#endif +#endif + +#ifndef SOKOL_LOG + #ifdef SOKOL_DEBUG + #include + #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } + #else + #define SOKOL_LOG(s) + #endif +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* List of possible error codes. */ +typedef enum sgp_error { + SGP_NO_ERROR = 0, + SGP_ERROR_SOKOL_INVALID, + SGP_ERROR_VERTICES_FULL, + SGP_ERROR_UNIFORMS_FULL, + SGP_ERROR_COMMANDS_FULL, + SGP_ERROR_VERTICES_OVERFLOW, + SGP_ERROR_TRANSFORM_STACK_OVERFLOW, + SGP_ERROR_TRANSFORM_STACK_UNDERFLOW, + SGP_ERROR_STATE_STACK_OVERFLOW, + SGP_ERROR_STATE_STACK_UNDERFLOW, + SGP_ERROR_ALLOC_FAILED, + SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED, + SGP_ERROR_MAKE_WHITE_IMAGE_FAILED, + SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED, + SGP_ERROR_MAKE_COMMON_SHADER_FAILED, + SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED, +} sgp_error; + +/* Blend modes. */ +typedef enum sgp_blend_mode { + SGP_BLENDMODE_NONE = 0, /* No blending. + dstRGBA = srcRGBA */ + SGP_BLENDMODE_BLEND, /* Alpha blending. + dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)) + dstA = srcA + (dstA * (1-srcA)) */ + SGP_BLENDMODE_ADD, /* Color add. + dstRGB = (srcRGB * srcA) + dstRGB + dstA = dstA */ + SGP_BLENDMODE_MOD, /* Color modulate. + dstRGB = srcRGB * dstRGB + dstA = dstA */ + SGP_BLENDMODE_MUL, /* Color multiply. + dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA)) + dstA = (srcA * dstA) + (dstA * (1-srcA)) */ + _SGP_BLENDMODE_NUM +} sgp_blend_mode; + +typedef enum sgp_vs_attr_location { + SGP_VS_ATTR_COORD = 0, + SGP_VS_ATTR_COLOR = 1 +} sgp_vs_attr_location; + +typedef struct sgp_isize { + int w, h; +} sgp_isize; + +typedef struct sgp_irect { + int x, y, w, h; +} sgp_irect; + +typedef struct sgp_rect { + float x, y, w, h; +} sgp_rect; + +typedef struct sgp_textured_rect { + sgp_rect dst; + sgp_rect src; +} sgp_textured_rect; + +typedef struct sgp_vec2 { + float x, y; +} sgp_vec2; + +typedef sgp_vec2 sgp_point; + +typedef struct sgp_line { + sgp_point a, b; +} sgp_line; + +typedef struct sgp_triangle { + sgp_point a, b, c; +} sgp_triangle; + +typedef struct sgp_mat2x3 { + float v[2][3]; +} sgp_mat2x3; + +typedef struct sgp_color { + float r, g, b, a; +} sgp_color; + +typedef struct sgp_color_ub4 { + uint8_t r, g, b, a; +} sgp_color_ub4; + +typedef struct sgp_vertex { + sgp_vec2 position; + sgp_vec2 texcoord; + sgp_color_ub4 color; +} sgp_vertex; + +typedef struct sgp_uniform { + uint32_t size; + float content[SGP_UNIFORM_CONTENT_SLOTS]; +} sgp_uniform; + +typedef struct sgp_textures_uniform { + uint32_t count; + sg_image images[SGP_TEXTURE_SLOTS]; + sg_sampler samplers[SGP_TEXTURE_SLOTS]; +} sgp_textures_uniform; + +/* SGP draw state. */ +typedef struct sgp_state { + sgp_isize frame_size; + sgp_irect viewport; + sgp_irect scissor; + sgp_mat2x3 proj; + sgp_mat2x3 transform; + sgp_mat2x3 mvp; + float thickness; + sgp_color_ub4 color; + sgp_textures_uniform textures; + sgp_uniform uniform; + sgp_blend_mode blend_mode; + sg_pipeline pipeline; + uint32_t _base_vertex; + uint32_t _base_uniform; + uint32_t _base_command; +} sgp_state; + +/* Structure that defines SGP setup parameters. */ +typedef struct sgp_desc { + uint32_t max_vertices; + uint32_t max_commands; + sg_pixel_format color_format; /* Color format for creating pipelines, defaults to the same as the Sokol GFX context. */ + sg_pixel_format depth_format; /* Depth format for creating pipelines, defaults to the same as the Sokol GFX context. */ + int sample_count; /* Sample count for creating pipelines, defaults to the same as the Sokol GFX context. */ +} sgp_desc; + +/* Structure that defines SGP custom pipeline creation parameters. */ +typedef struct sgp_pipeline_desc { + sg_shader shader; /* Sokol shader. */ + sg_primitive_type primitive_type; /* Draw primitive type (triangles, lines, points, etc). Default is triangles. */ + sgp_blend_mode blend_mode; /* Color blend mode. Default is no blend. */ + sg_pixel_format color_format; /* Color format, defaults to the value used when creating Sokol GP context. */ + sg_pixel_format depth_format; /* Depth format, defaults to the value used when creating Sokol GP context. */ + int sample_count; /* Sample count, defaults to the value used when creating Sokol GP context. */ + bool has_vs_color; /* If true, the current color state will be passed as an attribute to the vertex shader. */ +} sgp_pipeline_desc; + +/* Initialization and de-initialization. */ +SOKOL_GP_API_DECL void sgp_setup(const sgp_desc* desc); /* Initializes the SGP context, and should be called after `sg_setup`. */ +SOKOL_GP_API_DECL void sgp_shutdown(void); /* Destroys the SGP context. */ +SOKOL_GP_API_DECL bool sgp_is_valid(void); /* Checks if SGP context is valid, should be checked after `sgp_setup`. */ + +/* Error handling. */ +SOKOL_GP_API_DECL sgp_error sgp_get_last_error(void); /* Returns last SGP error. */ +SOKOL_GP_API_DECL const char* sgp_get_error_message(sgp_error error); /* Returns a message with SGP error description. */ + +/* Custom pipeline creation. */ +SOKOL_GP_API_DECL sg_pipeline sgp_make_pipeline(const sgp_pipeline_desc* desc); /* Creates a custom shader pipeline to be used with SGP. */ + +/* Draw command queue management. */ +SOKOL_GP_API_DECL void sgp_begin(int width, int height); /* Begins a new SGP draw command queue. */ +SOKOL_GP_API_DECL void sgp_flush(void); /* Dispatch current Sokol GFX draw commands. */ +SOKOL_GP_API_DECL void sgp_end(void); /* End current draw command queue, discarding it. */ + +/* 2D coordinate space projection */ +SOKOL_GP_API_DECL void sgp_project(float left, float right, float top, float bottom); /* Set the coordinate space boundary in the current viewport. */ +SOKOL_GP_API_DECL void sgp_reset_project(void); /* Resets the coordinate space to default (coordinate of the viewport). */ + +/* 2D coordinate space transformation. */ +SOKOL_GP_API_DECL void sgp_push_transform(void); /* Saves current transform matrix, to be restored later with a pop. */ +SOKOL_GP_API_DECL void sgp_pop_transform(void); /* Restore transform matrix to the same value of the last push. */ +SOKOL_GP_API_DECL void sgp_reset_transform(void); /* Resets the transform matrix to identity (no transform). */ +SOKOL_GP_API_DECL void sgp_translate(float x, float y); /* Translates the 2D coordinate space. */ +SOKOL_GP_API_DECL void sgp_rotate(float theta); /* Rotates the 2D coordinate space around the origin. */ +SOKOL_GP_API_DECL void sgp_rotate_at(float theta, float x, float y); /* Rotates the 2D coordinate space around a point. */ +SOKOL_GP_API_DECL void sgp_scale(float sx, float sy); /* Scales the 2D coordinate space around the origin. */ +SOKOL_GP_API_DECL void sgp_scale_at(float sx, float sy, float x, float y); /* Scales the 2D coordinate space around a point. */ + +/* State change for custom pipelines. */ +SOKOL_GP_API_DECL void sgp_set_pipeline(sg_pipeline pipeline); /* Sets current draw pipeline. */ +SOKOL_GP_API_DECL void sgp_reset_pipeline(void); /* Resets to the current draw pipeline to default (builtin pipelines). */ +SOKOL_GP_API_DECL void sgp_set_uniform(const void* data, uint32_t size); /* Sets uniform buffer for a custom pipeline. */ +SOKOL_GP_API_DECL void sgp_reset_uniform(void); /* Resets uniform buffer to default (current state color). */ + +/* State change functions for the common pipelines. */ +SOKOL_GP_API_DECL void sgp_set_blend_mode(sgp_blend_mode blend_mode); /* Sets current blend mode. */ +SOKOL_GP_API_DECL void sgp_reset_blend_mode(void); /* Resets current blend mode to default (no blending). */ +SOKOL_GP_API_DECL void sgp_set_color(float r, float g, float b, float a); /* Sets current color modulation. */ +SOKOL_GP_API_DECL void sgp_reset_color(void); /* Resets current color modulation to default (white). */ +SOKOL_GP_API_DECL void sgp_set_image(int channel, sg_image image); /* Sets current bound image in a texture channel. */ +SOKOL_GP_API_DECL void sgp_unset_image(int channel); /* Remove current bound image in a texture channel (no texture). */ +SOKOL_GP_API_DECL void sgp_reset_image(int channel); /* Resets current bound image in a texture channel to default (white texture). */ +SOKOL_GP_API_DECL void sgp_set_sampler(int channel, sg_sampler sampler); /* Sets current bound sampler in a texture channel. */ +SOKOL_GP_API_DECL void sgp_reset_sampler(int channel); /* Resets current bound sampler in a texture channel to default (nearest sampler). */ + +/* State change functions for all pipelines. */ +SOKOL_GP_API_DECL void sgp_viewport(int x, int y, int w, int h); /* Sets the screen area to draw into. */ +SOKOL_GP_API_DECL void sgp_reset_viewport(void); /* Reset viewport to default values (0, 0, width, height). */ +SOKOL_GP_API_DECL void sgp_scissor(int x, int y, int w, int h); /* Set clip rectangle in the viewport. */ +SOKOL_GP_API_DECL void sgp_reset_scissor(void); /* Resets clip rectangle to default (viewport bounds). */ +SOKOL_GP_API_DECL void sgp_reset_state(void); /* Reset all state to default values. */ + +/* Drawing functions. */ +SOKOL_GP_API_DECL void sgp_clear(void); /* Clears the current viewport using the current state color. */ +SOKOL_GP_API_DECL void sgp_draw(sg_primitive_type primitive_type, const sgp_vertex* vertices, uint32_t count); /* Low level drawing function, capable of drawing any primitive. */ +SOKOL_GP_API_DECL void sgp_draw_points(const sgp_point* points, uint32_t count); /* Draws points in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_point(float x, float y); /* Draws a single point. */ +SOKOL_GP_API_DECL void sgp_draw_lines(const sgp_line* lines, uint32_t count); /* Draws lines in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_line(float ax, float ay, float bx, float by); /* Draws a single line. */ +SOKOL_GP_API_DECL void sgp_draw_lines_strip(const sgp_point* points, uint32_t count); /* Draws a strip of lines. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangles(const sgp_triangle* triangles, uint32_t count); /* Draws triangles in a batch. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangle(float ax, float ay, float bx, float by, float cx, float cy); /* Draws a single triangle. */ +SOKOL_GP_API_DECL void sgp_draw_filled_triangles_strip(const sgp_point* points, uint32_t count); /* Draws strip of triangles. */ +SOKOL_GP_API_DECL void sgp_draw_filled_rects(const sgp_rect* rects, uint32_t count); /* Draws a batch of rectangles. */ +SOKOL_GP_API_DECL void sgp_draw_filled_rect(float x, float y, float w, float h); /* Draws a single rectangle. */ +SOKOL_GP_API_DECL void sgp_draw_textured_rects(int channel, const sgp_textured_rect* rects, uint32_t count); /* Draws a batch textured rectangle, each from a source region. */ +SOKOL_GP_API_DECL void sgp_draw_textured_rect(int channel, sgp_rect dest_rect, sgp_rect src_rect); /* Draws a single textured rectangle from a source region. */ + +/* Querying functions. */ +SOKOL_GP_API_DECL sgp_state* sgp_query_state(void); /* Returns the current draw state. */ +SOKOL_GP_API_DECL sgp_desc sgp_query_desc(void); /* Returns description of the current SGP context. */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SOKOL_GP_INCLUDED + +#ifdef SOKOL_GP_IMPL +#ifndef SOKOL_GP_IMPL_INCLUDED +#define SOKOL_GP_IMPL_INCLUDED + +#ifndef SOKOL_GFX_IMPL_INCLUDED +#error "Please include sokol_gfx.h implementation before sokol_gp.h implementation" +#endif + +#include +#include +#include + +#ifndef SOKOL_LIKELY +#ifdef __GNUC__ +#define SOKOL_LIKELY(x) __builtin_expect(x, 1) +#define SOKOL_UNLIKELY(x) __builtin_expect(x, 0) +#else +#define SOKOL_LIKELY(x) (x) +#define SOKOL_UNLIKELY(x) (x) +#endif +#endif + +#define _SGP_IMPOSSIBLE_ID 0xffffffffU + +enum { + _SGP_INIT_COOKIE = 0xCAFED0D, + _SGP_DEFAULT_MAX_VERTICES = 65536, + _SGP_DEFAULT_MAX_COMMANDS = 16384, + _SGP_MAX_MOVE_VERTICES = 96, + _SGP_MAX_STACK_DEPTH = 64 +}; + +typedef struct _sgp_region { + float x1, y1, x2, y2; +} _sgp_region; + +typedef struct _sgp_draw_args { + sg_pipeline pip; + sgp_textures_uniform textures; + _sgp_region region; + uint32_t uniform_index; + uint32_t vertex_index; + uint32_t num_vertices; +} _sgp_draw_args; + +typedef union _sgp_command_args { + _sgp_draw_args draw; + sgp_irect viewport; + sgp_irect scissor; +} _sgp_command_args; + +typedef enum _sgp_command_type { + SGP_COMMAND_NONE = 0, + SGP_COMMAND_DRAW, + SGP_COMMAND_VIEWPORT, + SGP_COMMAND_SCISSOR +} _sgp_command_type; + +typedef struct _sgp_command { + _sgp_command_type cmd; + _sgp_command_args args; +} _sgp_command; + +typedef struct _sgp_context { + uint32_t init_cookie; + sgp_error last_error; + sgp_desc desc; + + // resources + sg_shader shader; + sg_buffer vertex_buf; + sg_image white_img; + sg_sampler nearest_smp; + sg_pipeline pipelines[_SG_PRIMITIVETYPE_NUM * _SGP_BLENDMODE_NUM]; + + // command queue + uint32_t cur_vertex; + uint32_t cur_uniform; + uint32_t cur_command; + uint32_t num_vertices; + uint32_t num_uniforms; + uint32_t num_commands; + sgp_vertex* vertices; + sgp_uniform* uniforms; + _sgp_command* commands; + + // state tracking + sgp_state state; + + // matrix stack + uint32_t cur_transform; + uint32_t cur_state; + sgp_mat2x3 transform_stack[_SGP_MAX_STACK_DEPTH]; + sgp_state state_stack[_SGP_MAX_STACK_DEPTH]; +} _sgp_context; + +static _sgp_context _sgp; + +static const sgp_mat2x3 _sgp_mat3_identity = {{ + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f} +}}; + +static const sgp_color_ub4 _sgp_white_color = {255, 255, 255, 255}; + +//////////////////////////////////////////////////////////////////////////////// +// Shaders + +/* + #version 330 + + layout(location = 0) in vec4 coord; + out vec2 texUV; + out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + texUV = coord.zw; + iColor = color; + } + +*/ +static const char sgp_vs_source_glsl330[224] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64, + 0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x55,0x56, + 0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69, + 0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x63,0x6f,0x6f, + 0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x63,0x6f, + 0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + +}; +/* + #version 330 + + uniform sampler2D iTexChannel0_iSmpChannel0; + + layout(location = 0) out vec4 fragColor; + in vec2 texUV; + in vec4 iColor; + + void main() + { + fragColor = texture(iTexChannel0_iSmpChannel0, texUV) * iColor; + } + +*/ +static const char sgp_fs_source_glsl330[219] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d, + 0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f, + 0x6c,0x6f,0x72,0x3b,0x0a,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78, + 0x55,0x56,0x3b,0x0a,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43, + 0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e, + 0x6e,0x65,0x6c,0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69, + 0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 300 es + + layout(location = 0) in vec4 coord; + out vec2 texUV; + out vec4 iColor; + layout(location = 1) in vec4 color; + + void main() + { + gl_Position = vec4(coord.xy, 0.0, 1.0); + texUV = coord.zw; + iColor = color; + } + +*/ +static const char sgp_vs_source_glsl300es[227] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f, + 0x6f,0x72,0x64,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63, + 0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65, + 0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d, + 0x20,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69, + 0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d, + 0x0a,0x0a,0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D iTexChannel0_iSmpChannel0; + + layout(location = 0) out highp vec4 fragColor; + in highp vec2 texUV; + in highp vec4 iColor; + + void main() + { + fragColor = texture(iTexChannel0_iSmpChannel0, texUV) * iColor; + } + +*/ +static const char sgp_fs_source_glsl300es[292] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e, + 0x65,0x6c,0x30,0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30, + 0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72, + 0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20, + 0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f, + 0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30, + 0x5f,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x74, + 0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + static float4 gl_Position; + static float4 coord; + static float2 texUV; + static float4 iColor; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 coord : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = float4(coord.xy, 0.0f, 1.0f); + texUV = coord.zw; + iColor = color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + coord = stage_input.coord; + color = stage_input.color; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.texUV = texUV; + stage_output.iColor = iColor; + return stage_output; + } +*/ +static const char sgp_vs_source_hlsl4[758] = { + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a, + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65, + 0x78,0x55,0x56,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x3a,0x20,0x54, + 0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f, + 0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f, + 0x52,0x44,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x53,0x56, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a, + 0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x63,0x6f,0x6f,0x72,0x64, + 0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x63,0x6f, + 0x6f,0x72,0x64,0x2e,0x7a,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6f,0x72, + 0x64,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74, + 0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x74,0x65,0x78,0x55,0x56,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + Texture2D iTexChannel0 : register(t0); + SamplerState iSmpChannel0 : register(s0); + + static float4 fragColor; + static float2 texUV; + static float4 iColor; + + struct SPIRV_Cross_Input + { + float2 texUV : TEXCOORD0; + float4 iColor : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 fragColor : SV_Target0; + }; + + void frag_main() + { + fragColor = iTexChannel0.Sample(iSmpChannel0, texUV) * iColor; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + texUV = stage_input.texUV; + iColor = stage_input.iColor; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.fragColor = fragColor; + return stage_output; + } +*/ +static const char sgp_fs_source_hlsl4[650] = { + 0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x3e,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a, + 0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x74,0x30,0x29,0x3b,0x0a,0x53, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61,0x74,0x65,0x20,0x69,0x53,0x6d,0x70, + 0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73, + 0x74,0x65,0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73, + 0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43, + 0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63, + 0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75, + 0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56, + 0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f, + 0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x53,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69,0x43,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75, + 0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const char sgp_vs_source_metal_macos[497] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x5b,0x5b,0x75,0x73, + 0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b, + 0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d iTexChannel0 [[texture(0)]], sampler iSmpChannel0 [[sampler(0)]]) + { + main0_out out = {}; + out.fragColor = iTexChannel0.sample(iSmpChannel0, in.texUV) * in.iColor; + return out; + } + +*/ +static const char sgp_fs_source_metal_macos[478] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69, + 0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74, + 0x65,0x78,0x55,0x56,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c, + 0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61, + 0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65, + 0x6c,0x30,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d, + 0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 coord [[attribute(0)]]; + float4 color [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.coord.xy, 0.0, 1.0); + out.texUV = in.coord.zw; + out.iColor = in.color; + return out; + } + +*/ +static const char sgp_vs_source_metal_ios[497] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x5b,0x5b,0x75,0x73, + 0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b, + 0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b, + 0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e, + 0x63,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x55,0x56,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6f,0x72,0x64,0x2e,0x7a,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 fragColor [[color(0)]]; + }; + + struct main0_in + { + float2 texUV [[user(locn0)]]; + float4 iColor [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d iTexChannel0 [[texture(0)]], sampler iSmpChannel0 [[sampler(0)]]) + { + main0_out out = {}; + out.fragColor = iTexChannel0.sample(iSmpChannel0, in.texUV) * in.iColor; + return out; + } + +*/ +static const char sgp_fs_source_metal_ios[478] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69, + 0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74, + 0x65,0x78,0x55,0x56,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c, + 0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61, + 0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x5b,0x5b, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x72,0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65, + 0x6c,0x30,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d, + 0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2e,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x28,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x2c,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x55,0x56,0x29,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var coord : vec4f; + + var texUV : vec2f; + + var iColor : vec4f; + + var color : vec4f; + + var gl_Position : vec4f; + + fn main_1() { + let x_19 : vec4f = coord; + let x_20 : vec2f = vec2f(x_19.x, x_19.y); + gl_Position = vec4f(x_20.x, x_20.y, 0.0f, 1.0f); + let x_30 : vec4f = coord; + texUV = vec2f(x_30.z, x_30.w); + let x_34 : vec4f = color; + iColor = x_34; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + texUV_1 : vec2f, + @location(1) + iColor_1 : vec4f, + } + + @vertex + fn main(@location(0) coord_param : vec4f, @location(1) color_param : vec4f) -> main_out { + coord = coord_param; + color = color_param; + main_1(); + return main_out(gl_Position, texUV, iColor); + } + +*/ +static const char sgp_vs_source_wgsl[790] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6f,0x72,0x64,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74, + 0x65,0x3e,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a, + 0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61, + 0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a, + 0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x32,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x31,0x39,0x2e,0x78,0x2c,0x20,0x78,0x5f, + 0x31,0x39,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x32, + 0x30,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x30,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30, + 0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x33,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63, + 0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x33,0x30,0x2e,0x7a,0x2c,0x20,0x78,0x5f, + 0x33,0x30,0x2e,0x77,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33, + 0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f, + 0x33,0x34,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74, + 0x20,0x7b,0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20, + 0x74,0x65,0x78,0x55,0x56,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c, + 0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a, + 0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66, + 0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x28,0x30,0x29,0x20,0x63,0x6f,0x6f,0x72,0x64,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e,0x20,0x6d,0x61, + 0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x63,0x6f,0x6f,0x72,0x64, + 0x20,0x3d,0x20,0x63,0x6f,0x6f,0x72,0x64,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28, + 0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2c,0x20,0x74,0x65,0x78,0x55,0x56,0x2c,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x29, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var fragColor : vec4f; + + @group(1) @binding(32) var iTexChannel0 : texture_2d; + + @group(1) @binding(48) var iSmpChannel0 : sampler; + + var texUV : vec2f; + + var iColor : vec4f; + + fn main_1() { + let x_23 : vec2f = texUV; + let x_24 : vec4f = textureSample(iTexChannel0, iSmpChannel0, x_23); + let x_27 : vec4f = iColor; + fragColor = (x_24 * x_27); + return; + } + + struct main_out { + @location(0) + fragColor_1 : vec4f, + } + + @fragment + fn main(@location(0) texUV_param : vec2f, @location(1) iColor_param : vec4f) -> main_out { + texUV = texUV_param; + iColor = iColor_param; + main_1(); + return main_out(fragColor); + } + +*/ +static const char sgp_fs_source_wgsl[682] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70, + 0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x33,0x32,0x29, + 0x20,0x76,0x61,0x72,0x20,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c, + 0x30,0x20,0x3a,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66, + 0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20, + 0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x34,0x38,0x29,0x20,0x76,0x61,0x72, + 0x20,0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x20,0x3a,0x20, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x55,0x56,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29, + 0x20,0x7b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x33,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c, + 0x65,0x28,0x69,0x54,0x65,0x78,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20, + 0x69,0x53,0x6d,0x70,0x43,0x68,0x61,0x6e,0x6e,0x65,0x6c,0x30,0x2c,0x20,0x78,0x5f, + 0x32,0x33,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x37,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72, + 0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20, + 0x28,0x78,0x5f,0x32,0x34,0x20,0x2a,0x20,0x78,0x5f,0x32,0x37,0x29,0x3b,0x0a,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20, + 0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x66, + 0x72,0x61,0x67,0x43,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74, + 0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x28,0x30,0x29,0x20,0x74,0x65,0x78,0x55,0x56,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x74,0x65, + 0x78,0x55,0x56,0x20,0x3d,0x20,0x74,0x65,0x78,0x55,0x56,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x3b,0x0a,0x20,0x20,0x69,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x43, + 0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61, + 0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x43,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; + +//////////////////////////////////////////////////////////////////////////////// + +static void _sgp_set_error(sgp_error error) { + _sgp.last_error = error; + SOKOL_LOG(sgp_get_error_message(error)); +} + +static sg_blend_state _sgp_blend_state(sgp_blend_mode blend_mode) { + sg_blend_state blend; + memset(&blend, 0, sizeof(sg_blend_state)); + switch (blend_mode) { + case SGP_BLENDMODE_NONE: + blend.enabled = false; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_BLEND: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_ADD: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_MOD: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_DST_COLOR; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + blend.op_alpha = SG_BLENDOP_ADD; + break; + case SGP_BLENDMODE_MUL: + blend.enabled = true; + blend.src_factor_rgb = SG_BLENDFACTOR_DST_COLOR; + blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_DST_ALPHA; + blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.op_alpha = SG_BLENDOP_ADD; + break; + default: + blend.enabled = false; + blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + blend.op_rgb = SG_BLENDOP_ADD; + blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + blend.op_alpha = SG_BLENDOP_ADD; + SOKOL_UNREACHABLE; + break; + } + return blend; +} + +static sg_pipeline _sgp_make_pipeline(sg_shader shader, sg_primitive_type primitive_type, sgp_blend_mode blend_mode, + sg_pixel_format color_format, sg_pixel_format depth_format, int sample_count, bool has_vs_color) { + // create pipeline + sg_pipeline_desc pip_desc; + memset(&pip_desc, 0, sizeof(sg_pipeline_desc)); + pip_desc.shader = shader; + pip_desc.layout.buffers[0].stride = sizeof(sgp_vertex); + pip_desc.layout.attrs[SGP_VS_ATTR_COORD].offset = offsetof(sgp_vertex, position); + pip_desc.layout.attrs[SGP_VS_ATTR_COORD].format = SG_VERTEXFORMAT_FLOAT4; + if (has_vs_color) { + pip_desc.layout.attrs[SGP_VS_ATTR_COLOR].offset = offsetof(sgp_vertex, color); + pip_desc.layout.attrs[SGP_VS_ATTR_COLOR].format = SG_VERTEXFORMAT_UBYTE4N; + } + pip_desc.sample_count = sample_count; + pip_desc.depth.pixel_format = depth_format; + pip_desc.colors[0].pixel_format = color_format; + pip_desc.colors[0].blend = _sgp_blend_state(blend_mode); + pip_desc.primitive_type = primitive_type; + + sg_pipeline pip = sg_make_pipeline(&pip_desc); + if (pip.id != SG_INVALID_ID && sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID) { + sg_destroy_pipeline(pip); + pip.id = SG_INVALID_ID; + } + return pip; +} + +static sg_pipeline _sgp_lookup_pipeline(sg_primitive_type primitive_type, sgp_blend_mode blend_mode) { + uint32_t pip_index = (primitive_type * _SGP_BLENDMODE_NUM) + blend_mode; + if (_sgp.pipelines[pip_index].id != SG_INVALID_ID) { + return _sgp.pipelines[pip_index]; + } + + sg_pipeline pip = _sgp_make_pipeline(_sgp.shader, primitive_type, blend_mode, _sgp.desc.color_format, _sgp.desc.depth_format, _sgp.desc.sample_count, true); + if (pip.id != SG_INVALID_ID) { + _sgp.pipelines[pip_index] = pip; + } + return pip; +} + +static sg_shader _sgp_make_common_shader(void) { + sg_backend backend = sg_query_backend(); + sg_shader_desc desc; + memset(&desc, 0, sizeof(desc)); + desc.fs.images[0].used = true; + desc.fs.images[0].multisampled = false; + desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + desc.fs.samplers[0].used = true; + desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + desc.fs.image_sampler_pairs[0].used = true; + desc.fs.image_sampler_pairs[0].image_slot = 0; + desc.fs.image_sampler_pairs[0].sampler_slot = 0; + + // GLCORE33 / GLES3 only + desc.attrs[SGP_VS_ATTR_COORD].name = "coord"; + desc.attrs[SGP_VS_ATTR_COLOR].name = "color"; + desc.fs.image_sampler_pairs[0].glsl_name = "iTexChannel0_iSmpChannel0"; + + // D3D11 only + desc.attrs[SGP_VS_ATTR_COORD].sem_name = "TEXCOORD"; + desc.attrs[SGP_VS_ATTR_COORD].sem_index = 0; + desc.attrs[SGP_VS_ATTR_COLOR].sem_name = "TEXCOORD"; + desc.attrs[SGP_VS_ATTR_COLOR].sem_index = 1; + desc.vs.d3d11_target = "vs_4_0"; + desc.fs.d3d11_target = "ps_4_0"; + + // entry + switch (backend) { + case SG_BACKEND_METAL_MACOS: + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + desc.vs.entry = "main0"; + desc.fs.entry = "main0"; + break; + default: + desc.vs.entry = "main"; + desc.fs.entry = "main"; + break; + } + + // source + switch (backend) { + // case SG_BACKEND_GLCORE33: + // desc.vs.source = sgp_vs_source_glsl330; + // desc.fs.source = sgp_fs_source_glsl330; + // break; + case SG_BACKEND_GLES3: + desc.vs.source = sgp_vs_source_glsl300es; + desc.fs.source = sgp_fs_source_glsl300es; + break; + case SG_BACKEND_D3D11: + desc.vs.source = sgp_vs_source_hlsl4; + desc.fs.source = sgp_fs_source_hlsl4; + break; + case SG_BACKEND_METAL_MACOS: + desc.vs.source = sgp_vs_source_metal_macos; + desc.fs.source = sgp_fs_source_metal_macos; + break; + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + desc.vs.source = sgp_vs_source_metal_ios; + desc.fs.source = sgp_fs_source_metal_ios; + break; + case SG_BACKEND_WGPU: + desc.vs.source = sgp_vs_source_wgsl; + desc.fs.source = sgp_fs_source_wgsl; + break; + case SG_BACKEND_DUMMY: + desc.vs.source = ""; + desc.fs.source = ""; + break; + default: { + // Unsupported backend + sg_shader shd; + shd.id = SG_INVALID_ID; + return shd; + } + } + + return sg_make_shader(&desc); +} + +void sgp_setup(const sgp_desc* desc) { + SOKOL_ASSERT(_sgp.init_cookie == 0); + + if (!sg_isvalid()) { + _sgp_set_error(SGP_ERROR_SOKOL_INVALID); + return; + } + + // init + _sgp.init_cookie = _SGP_INIT_COOKIE; + _sgp.last_error = SGP_NO_ERROR; + + // set desc default values + _sgp.desc = *desc; + _sgp.desc.max_vertices = _sg_def(desc->max_vertices, _SGP_DEFAULT_MAX_VERTICES); + _sgp.desc.max_commands = _sg_def(desc->max_commands, _SGP_DEFAULT_MAX_COMMANDS); + _sgp.desc.color_format = _sg_def(desc->color_format, _sg.desc.environment.defaults.color_format); + _sgp.desc.depth_format = _sg_def(desc->depth_format, _sg.desc.environment.defaults.depth_format); + _sgp.desc.sample_count = _sg_def(desc->sample_count, _sg.desc.environment.defaults.sample_count); + + // allocate buffers + _sgp.num_vertices = _sgp.desc.max_vertices; + _sgp.num_commands = _sgp.desc.max_commands; + _sgp.num_uniforms = _sgp.desc.max_commands; + _sgp.vertices = (sgp_vertex*) _sg_malloc(_sgp.num_vertices * sizeof(sgp_vertex)); + _sgp.uniforms = (sgp_uniform*) _sg_malloc(_sgp.num_uniforms * sizeof(sgp_uniform)); + _sgp.commands = (_sgp_command*) _sg_malloc(_sgp.num_commands * sizeof(_sgp_command)); + if (!_sgp.commands || !_sgp.uniforms || !_sgp.commands) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_ALLOC_FAILED); + return; + } + memset(_sgp.vertices, 0, _sgp.num_vertices * sizeof(sgp_vertex)); + memset(_sgp.uniforms, 0, _sgp.num_uniforms * sizeof(sgp_uniform)); + memset(_sgp.commands, 0, _sgp.num_commands * sizeof(_sgp_command)); + + // create vertex buffer + sg_buffer_desc vertex_buf_desc; + memset(&vertex_buf_desc, 0, sizeof(sg_buffer_desc)); + vertex_buf_desc.size = (size_t)(_sgp.num_vertices * sizeof(sgp_vertex)); + vertex_buf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vertex_buf_desc.usage = SG_USAGE_STREAM; + + _sgp.vertex_buf = sg_make_buffer(&vertex_buf_desc); + if (sg_query_buffer_state(_sgp.vertex_buf) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED); + return; + } + + // create white texture + uint32_t pixels[4]; + memset(pixels, 0xFF, sizeof(pixels)); + sg_image_desc white_img_desc; + memset(&white_img_desc, 0, sizeof(sg_image_desc)); + white_img_desc.type = SG_IMAGETYPE_2D; + white_img_desc.width = 2; + white_img_desc.height = 2; + white_img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + white_img_desc.data.subimage[0][0].ptr = pixels; + white_img_desc.data.subimage[0][0].size = sizeof(pixels); + white_img_desc.label = "sgp-white-texture"; + _sgp.white_img = sg_make_image(&white_img_desc); + if (sg_query_image_state(_sgp.white_img) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_WHITE_IMAGE_FAILED); + return; + } + + // create nearest sampler + sg_sampler_desc nearest_smp_desc; + memset(&nearest_smp_desc, 0, sizeof(sg_sampler_desc)); + nearest_smp_desc.label = "sgp-nearest-sampler"; + _sgp.nearest_smp = sg_make_sampler(&nearest_smp_desc); + if (sg_query_sampler_state(_sgp.nearest_smp) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED); + return; + } + + // create common shader + _sgp.shader = _sgp_make_common_shader(); + if (sg_query_shader_state(_sgp.shader) != SG_RESOURCESTATE_VALID) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_COMMON_SHADER_FAILED); + return; + } + + // create common pipelines + bool pips_ok = true; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_POINTS, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_POINTS, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINES, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINES, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLE_STRIP, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLE_STRIP, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINE_STRIP, SGP_BLENDMODE_NONE).id != SG_INVALID_ID; + pips_ok = pips_ok && _sgp_lookup_pipeline(SG_PRIMITIVETYPE_LINE_STRIP, SGP_BLENDMODE_BLEND).id != SG_INVALID_ID; + if (!pips_ok) { + sgp_shutdown(); + _sgp_set_error(SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED); + return; + } +} + +void sgp_shutdown(void) { + if (_sgp.init_cookie == 0) { + return; // not initialized + } + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state == 0); + if (_sgp.vertices) { + _sg_free(_sgp.vertices); + } + if (_sgp.uniforms) { + _sg_free(_sgp.uniforms); + } + if (_sgp.commands) { + _sg_free(_sgp.commands); + } + for (uint32_t i=0;i<_SG_PRIMITIVETYPE_NUM*_SGP_BLENDMODE_NUM;++i) { + sg_pipeline pip = _sgp.pipelines[i]; + if (pip.id != SG_INVALID_ID) { + sg_destroy_pipeline(pip); + } + } + if (_sgp.shader.id != SG_INVALID_ID) { + sg_destroy_shader(_sgp.shader); + } + if (_sgp.vertex_buf.id != SG_INVALID_ID) { + sg_destroy_buffer(_sgp.vertex_buf); + } + if (_sgp.white_img.id != SG_INVALID_ID) { + sg_destroy_image(_sgp.white_img); + } + if (_sgp.nearest_smp.id != SG_INVALID_ID) { + sg_destroy_sampler(_sgp.nearest_smp); + } + memset(&_sgp, 0, sizeof(_sgp_context)); +} + +bool sgp_is_valid(void) { + return _sgp.init_cookie == _SGP_INIT_COOKIE; +} + +sgp_error sgp_get_last_error(void) { + return _sgp.last_error; +} + +const char* sgp_get_error_message(sgp_error error_code) { + switch (error_code) { + case SGP_NO_ERROR: + return "No error"; + case SGP_ERROR_SOKOL_INVALID: + return "Sokol is not initialized"; + case SGP_ERROR_VERTICES_FULL: + return "SGP vertices buffer is full"; + case SGP_ERROR_UNIFORMS_FULL: + return "SGP uniform buffer is full"; + case SGP_ERROR_COMMANDS_FULL: + return "SGP command buffer is full"; + case SGP_ERROR_VERTICES_OVERFLOW: + return "SGP vertices buffer overflow"; + case SGP_ERROR_TRANSFORM_STACK_OVERFLOW: + return "SGP transform stack overflow"; + case SGP_ERROR_TRANSFORM_STACK_UNDERFLOW: + return "SGP transform stack underflow"; + case SGP_ERROR_STATE_STACK_OVERFLOW: + return "SGP state stack overflow"; + case SGP_ERROR_STATE_STACK_UNDERFLOW: + return "SGP state stack underflow"; + case SGP_ERROR_ALLOC_FAILED: + return "SGP failed to allocate buffers"; + case SGP_ERROR_MAKE_VERTEX_BUFFER_FAILED: + return "SGP failed to create vertex buffer"; + case SGP_ERROR_MAKE_WHITE_IMAGE_FAILED: + return "SGP failed to create white image"; + case SGP_ERROR_MAKE_NEAREST_SAMPLER_FAILED: + return "SGP failed to create nearest sampler"; + case SGP_ERROR_MAKE_COMMON_SHADER_FAILED: + return "SGP failed to create the common shader"; + case SGP_ERROR_MAKE_COMMON_PIPELINE_FAILED: + return "SGP failed to create the common pipeline"; + default: + return "Invalid error code"; + } +} + +sg_pipeline sgp_make_pipeline(const sgp_pipeline_desc* desc) { + sg_primitive_type primitive_type = _sg_def(desc->primitive_type, SG_PRIMITIVETYPE_TRIANGLES); + sgp_blend_mode blend_mode = _sg_def(desc->blend_mode, SGP_BLENDMODE_NONE); + sg_pixel_format color_format = _sg_def(desc->color_format, _sgp.desc.color_format); + sg_pixel_format depth_format = _sg_def(desc->depth_format, _sgp.desc.depth_format); + int sample_count = _sg_def(desc->sample_count, _sgp.desc.sample_count); + return _sgp_make_pipeline(desc->shader, primitive_type, blend_mode, color_format, depth_format, sample_count, desc->has_vs_color); +} + +static inline sgp_mat2x3 _sgp_default_proj(int width, int height) { + // matrix to convert screen coordinate system + // to the usual the coordinate system used on the backends + sgp_mat2x3 mat = {{ + {2.0f/(float)width, 0.0f, -1.0f}, + { 0.0f, -2.0f/(float)height, 1.0f} + }}; + return mat; +} + +void sgp_begin(int width, int height) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (SOKOL_UNLIKELY(_sgp.cur_state >= _SGP_MAX_STACK_DEPTH)) { + _sgp_set_error(SGP_ERROR_STATE_STACK_OVERFLOW); + return; + } + + // begin reset last error + _sgp.last_error = SGP_NO_ERROR; + + // save current state + _sgp.state_stack[_sgp.cur_state++] = _sgp.state; + + // reset to default state + _sgp.state.frame_size.w = width; _sgp.state.frame_size.h = height; + _sgp.state.viewport.x = 0; _sgp.state.viewport.y = 0; + _sgp.state.viewport.w = width; _sgp.state.viewport.h = height; + _sgp.state.scissor.x = 0; _sgp.state.scissor.y = 0; + _sgp.state.scissor.w = -1; _sgp.state.scissor.h = -1; + _sgp.state.proj = _sgp_default_proj(width, height); + _sgp.state.transform = _sgp_mat3_identity; + _sgp.state.mvp = _sgp.state.proj; + _sgp.state.thickness = _sg_max(1.0f / width, 1.0f / height); + _sgp.state.color = _sgp_white_color; + memset(&_sgp.state.uniform, 0, sizeof(sgp_uniform)); + _sgp.state.uniform.size = 0; + _sgp.state.blend_mode = SGP_BLENDMODE_NONE; + _sgp.state._base_vertex = _sgp.cur_vertex; + _sgp.state._base_uniform = _sgp.cur_uniform; + _sgp.state._base_command = _sgp.cur_command; + + _sgp.state.textures.count = 1; + _sgp.state.textures.images[0] = _sgp.white_img; + _sgp.state.textures.samplers[0] = _sgp.nearest_smp; + sg_image img = {SG_INVALID_ID}; + for (int i=1;ishader->cmn.stage[SG_SHADERSTAGE_VS].num_uniform_blocks : 0; + *fs_uniform_count = p ? p->shader->cmn.stage[SG_SHADERSTAGE_FS].num_uniform_blocks : 0; +} + +void sgp_flush(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + + uint32_t end_command = _sgp.cur_command; + uint32_t end_vertex = _sgp.cur_vertex; + + // rewind indexes + _sgp.cur_vertex = _sgp.state._base_vertex; + _sgp.cur_uniform = _sgp.state._base_uniform; + _sgp.cur_command = _sgp.state._base_command; + + // draw nothing on errors + if (_sgp.last_error != SGP_NO_ERROR) { + return; + } + + // nothing to be drawn + if (end_command <= _sgp.state._base_command) { + return; + } + + // upload vertices + uint32_t base_vertex = _sgp.state._base_vertex; + uint32_t num_vertices = (end_vertex - base_vertex) * sizeof(sgp_vertex); + sg_range vertex_range = {&_sgp.vertices[base_vertex], num_vertices}; + int offset = sg_append_buffer(_sgp.vertex_buf, &vertex_range); + if (sg_query_buffer_overflow(_sgp.vertex_buf)) { + _sgp_set_error(SGP_ERROR_VERTICES_OVERFLOW); + return; + } + + uint32_t cur_pip_id = _SGP_IMPOSSIBLE_ID; + uint32_t cur_uniform_index = _SGP_IMPOSSIBLE_ID; + uint32_t cur_imgs_id[SGP_TEXTURE_SLOTS]; + for (int i=0;icmd) { + case SGP_COMMAND_VIEWPORT: { + sgp_irect* args = &cmd->args.viewport; + sg_apply_viewport(args->x, args->y, args->w, args->h, true); + break; + } + case SGP_COMMAND_SCISSOR: { + sgp_irect* args = &cmd->args.scissor; + sg_apply_scissor_rect(args->x, args->y, args->w, args->h, true); + break; + } + case SGP_COMMAND_DRAW: { + _sgp_draw_args* args = &cmd->args.draw; + if (args->num_vertices == 0) { + break; + } + bool apply_bindings = false; + // pipeline + if (args->pip.id != cur_pip_id) { + // when pipeline changes we need to re-apply uniforms and bindings + cur_uniform_index = _SGP_IMPOSSIBLE_ID; + apply_bindings = true; + cur_pip_id = args->pip.id; + sg_apply_pipeline(args->pip); + } + // bindings + for (uint32_t j=0;jtextures.count) { + img_id = args->textures.images[j].id; + if (img_id != SG_INVALID_ID) { + smp_id = args->textures.samplers[j].id; + } + } + if (cur_imgs_id[j] != img_id) { + // when an image binding change we need to re-apply bindings + cur_imgs_id[j] = img_id; + bind.fs.images[j].id = img_id; + bind.fs.samplers[j].id = smp_id; + apply_bindings = true; + } + } + if (apply_bindings) { + sg_apply_bindings(&bind); + } + // uniforms + if (cur_uniform_index != args->uniform_index) { + cur_uniform_index = args->uniform_index; + sgp_uniform* uniform = &_sgp.uniforms[cur_uniform_index]; + if (uniform->size > 0) { + sg_range uniform_range = {&uniform->content, uniform->size}; + int vs_uniform_count, fs_uniform_count; + _sgp_get_pipeline_uniform_count(args->pip, &vs_uniform_count, &fs_uniform_count); + // apply uniforms on vertex shader only when needed + if (vs_uniform_count > 0) { + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &uniform_range); + } + // apply uniforms on fragment shader + if (fs_uniform_count > 0) { + sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, &uniform_range); + } + } + } + // draw + sg_draw((int)(args->vertex_index - base_vertex), (int)args->num_vertices, 1); + break; + } + case SGP_COMMAND_NONE: { + // this command was optimized away + break; + } + } + } +} + +void sgp_end(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (SOKOL_UNLIKELY(_sgp.cur_state <= 0)) { + _sgp_set_error(SGP_ERROR_STATE_STACK_UNDERFLOW); + return; + } + + // restore old state + _sgp.state = _sgp.state_stack[--_sgp.cur_state]; +} + +static inline sgp_mat2x3 _sgp_mul_proj_transform(sgp_mat2x3* proj, sgp_mat2x3* transform) { + // this actually multiply matrix projection and transform matrix in an optimized way + float x = proj->v[0][0], y = proj->v[1][1]; + sgp_mat2x3 m = {{ + {x*transform->v[0][0], x*transform->v[0][1], x*transform->v[0][2]+proj->v[0][2]}, + {y*transform->v[1][0], y*transform->v[1][1], y*transform->v[1][2]+proj->v[1][2]} + }}; + return m; +} + +void sgp_project(float left, float right, float top, float bottom) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + float w = right - left; + float h = top - bottom; + sgp_mat2x3 proj = {{ + {2.0f/w, 0.0f, -(right+left)/w}, + {0.0f, 2.0f/h, -(top+bottom)/h} + }}; + _sgp.state.proj = proj; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_project(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.proj = _sgp_default_proj(_sgp.state.viewport.w, _sgp.state.viewport.h); + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_push_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(_sgp.cur_transform >= _SGP_MAX_STACK_DEPTH)) { + _sgp_set_error(SGP_ERROR_TRANSFORM_STACK_OVERFLOW); + return; + } + _sgp.transform_stack[_sgp.cur_transform++] = _sgp.state.transform; +} + +void sgp_pop_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(_sgp.cur_transform <= 0)) { + _sgp_set_error(SGP_ERROR_TRANSFORM_STACK_UNDERFLOW); + return; + } + _sgp.state.transform = _sgp.transform_stack[--_sgp.cur_transform]; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_transform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.transform = _sgp_mat3_identity; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_translate(float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + // multiply by translate matrix: + // 1.0f, 0.0f, x, + // 0.0f, 1.0f, y, + // 0.0f, 0.0f, 1.0f, + _sgp.state.transform.v[0][2] += x*_sgp.state.transform.v[0][0] + y*_sgp.state.transform.v[0][1]; + _sgp.state.transform.v[1][2] += x*_sgp.state.transform.v[1][0] + y*_sgp.state.transform.v[1][1]; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_rotate(float theta) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + float sint = sinf(theta), cost = cosf(theta); + // multiply by rotation matrix: + // cost, -sint, 0.0f, + // sint, cost, 0.0f, + // 0.0f, 0.0f, 1.0f, + sgp_mat2x3 transform = {{ + {cost*_sgp.state.transform.v[0][0]+sint*_sgp.state.transform.v[0][1], -sint*_sgp.state.transform.v[0][0]+cost*_sgp.state.transform.v[0][1], _sgp.state.transform.v[0][2]}, + {cost*_sgp.state.transform.v[1][0]+sint*_sgp.state.transform.v[1][1], -sint*_sgp.state.transform.v[1][0]+cost*_sgp.state.transform.v[1][1], _sgp.state.transform.v[1][2]} + }}; + _sgp.state.transform = transform; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_rotate_at(float theta, float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_translate(x, y); + sgp_rotate(theta); + sgp_translate(-x, -y); +} + +void sgp_scale(float sx, float sy) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + // multiply by scale matrix: + // sx, 0.0f, 0.0f, + // 0.0f, sy, 0.0f, + // 0.0f, 0.0f, 1.0f, + _sgp.state.transform.v[0][0] *= sx; + _sgp.state.transform.v[1][0] *= sx; + _sgp.state.transform.v[0][1] *= sy; + _sgp.state.transform.v[1][1] *= sy; + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_scale_at(float sx, float sy, float x, float y) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_translate(x, y); + sgp_scale(sx, sy); + sgp_translate(-x, -y); +} + +void sgp_set_pipeline(sg_pipeline pipeline) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + _sgp.state.pipeline = pipeline; + + // reset uniforms + memset(&_sgp.state.uniform, 0, sizeof(sgp_uniform)); +} + +void sgp_reset_pipeline(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sg_pipeline pip = {SG_INVALID_ID}; + sgp_set_pipeline(pip); +} + +void sgp_set_uniform(const void* data, uint32_t size) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.state.pipeline.id != SG_INVALID_ID); + SOKOL_ASSERT(size <= sizeof(float) * SGP_UNIFORM_CONTENT_SLOTS); + if (size > 0) { + SOKOL_ASSERT(data); + memcpy(&_sgp.state.uniform.content, data, size); + } + if (size < _sgp.state.uniform.size) { + // zero old uniform data + memset((uint8_t*)(&_sgp.state.uniform) + size, 0, _sgp.state.uniform.size - size); + } + _sgp.state.uniform.size = size; +} + +void sgp_reset_uniform(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.state.pipeline.id != SG_INVALID_ID); + sgp_set_uniform(NULL, 0); +} + +void sgp_set_blend_mode(sgp_blend_mode blend_mode) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + _sgp.state.blend_mode = blend_mode; +} + +void sgp_reset_blend_mode(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sgp_set_blend_mode(SGP_BLENDMODE_NONE); +} + +void sgp_set_color(float r, float g, float b, float a) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.color = (sgp_color_ub4){ + (uint8_t)_sg_clamp(r*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(g*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(b*255.0f, 0.0f, 255.0f), + (uint8_t)_sg_clamp(a*255.0f, 0.0f, 255.0f) + }; +} + +void sgp_reset_color(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + _sgp.state.color = _sgp_white_color; +} + +void sgp_set_image(int channel, sg_image image) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + if (_sgp.state.textures.images[channel].id == image.id) { + return; + } + + _sgp.state.textures.images[channel] = image; + + // recalculate textures count + int textures_count = (int)_sgp.state.textures.count; + for (int i=_sg_max(channel, textures_count-1);i>=0;--i) { + if (_sgp.state.textures.images[i].id != SG_INVALID_ID) { + textures_count = i + 1; + break; + } + } + _sgp.state.textures.count = (uint32_t)textures_count; +} + +void sgp_unset_image(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sg_image img = {SG_INVALID_ID}; + sgp_set_image(channel, img); +} + +void sgp_reset_image(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + if (channel == 0) { + // channel 0 always use white image + sgp_set_image(channel, _sgp.white_img); + } else { + sg_image img = {SG_INVALID_ID}; + sgp_set_image(channel, img); + } +} + +void sgp_set_sampler(int channel, sg_sampler sampler) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + _sgp.state.textures.samplers[channel] = sampler; +} + +void sgp_reset_sampler(int channel) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + sgp_set_sampler(channel, _sgp.nearest_smp); +} + +static sgp_vertex* _sgp_next_vertices(uint32_t count) { + if (SOKOL_LIKELY(_sgp.cur_vertex + count <= _sgp.num_vertices)) { + sgp_vertex *vertices = &_sgp.vertices[_sgp.cur_vertex]; + _sgp.cur_vertex += count; + return vertices; + } else { + _sgp_set_error(SGP_ERROR_VERTICES_FULL); + return NULL; + } +} + +static sgp_uniform* _sgp_prev_uniform(void) { + if (SOKOL_LIKELY(_sgp.cur_uniform > 0)) { + return &_sgp.uniforms[_sgp.cur_uniform-1]; + } else { + return NULL; + } +} + +static sgp_uniform* _sgp_next_uniform(void) { + if (SOKOL_LIKELY(_sgp.cur_uniform < _sgp.num_uniforms)) { + return &_sgp.uniforms[_sgp.cur_uniform++]; + } else { + _sgp_set_error(SGP_ERROR_UNIFORMS_FULL); + return NULL; + } +} + +static _sgp_command* _sgp_prev_command(uint32_t count) { + if (SOKOL_LIKELY((_sgp.cur_command - _sgp.state._base_command) >= count)) { + return &_sgp.commands[_sgp.cur_command-count]; + } else { + return NULL; + } +} + +static _sgp_command* _sgp_next_command(void) { + if (SOKOL_LIKELY(_sgp.cur_command < _sgp.num_commands)) { + return &_sgp.commands[_sgp.cur_command++]; + } else { + _sgp_set_error(SGP_ERROR_COMMANDS_FULL); + return NULL; + } +} + +void sgp_viewport(int x, int y, int w, int h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + + // skip in case of the same viewport + if (_sgp.state.viewport.x == x && _sgp.state.viewport.y == y && + _sgp.state.viewport.w == w && _sgp.state.viewport.h == h) { + return; + } + + // try to reuse last command otherwise use the next one + _sgp_command* cmd = _sgp_prev_command(1); + if (!cmd || cmd->cmd != SGP_COMMAND_VIEWPORT) { + cmd = _sgp_next_command(); + } + if (SOKOL_UNLIKELY(!cmd)) { + return; + } + + sgp_irect viewport = {x, y, w, h}; + + memset(cmd, 0, sizeof(_sgp_command)); + cmd->cmd = SGP_COMMAND_VIEWPORT; + cmd->args.viewport = viewport; + + // adjust current scissor relative offset + if (!(_sgp.state.scissor.w < 0 && _sgp.state.scissor.h < 0)) { + _sgp.state.scissor.x += x - _sgp.state.viewport.x; + _sgp.state.scissor.y += y - _sgp.state.viewport.y; + } + + _sgp.state.viewport = viewport; + _sgp.state.thickness = _sg_max(1.0f / w, 1.0f / h); + _sgp.state.proj = _sgp_default_proj(w, h); + _sgp.state.mvp = _sgp_mul_proj_transform(&_sgp.state.proj, &_sgp.state.transform); +} + +void sgp_reset_viewport(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_viewport(0, 0, _sgp.state.frame_size.w, _sgp.state.frame_size.h); +} + +void sgp_scissor(int x, int y, int w, int h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + + // skip in case of the same scissor + if (_sgp.state.scissor.x == x && _sgp.state.scissor.y == y && + _sgp.state.scissor.w == w && _sgp.state.scissor.h == h) { + return; + } + + // try to reuse last command otherwise use the next one + _sgp_command* cmd = _sgp_prev_command(1); + if (!cmd || cmd->cmd != SGP_COMMAND_SCISSOR) { + cmd = _sgp_next_command(); + } + if (SOKOL_UNLIKELY(!cmd)) { + return; + } + + // coordinate scissor in viewport subspace + sgp_irect viewport_scissor = {_sgp.state.viewport.x + x, _sgp.state.viewport.y + y, w, h}; + + // reset scissor + if (w < 0 && h < 0) { + viewport_scissor.x = 0; viewport_scissor.y = 0; + viewport_scissor.w = _sgp.state.frame_size.w; viewport_scissor.h = _sgp.state.frame_size.h; + } + + memset(cmd, 0, sizeof(_sgp_command)); + cmd->cmd = SGP_COMMAND_SCISSOR; + cmd->args.scissor = viewport_scissor; + + sgp_irect scissor = {x, y, w, h}; + _sgp.state.scissor = scissor; +} + +void sgp_reset_scissor(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_scissor(0, 0, -1, -1); +} + +void sgp_reset_state(void) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_reset_viewport(); + sgp_reset_scissor(); + sgp_reset_project(); + sgp_reset_transform(); + sgp_reset_blend_mode(); + sgp_reset_color(); + sgp_reset_uniform(); + sgp_reset_pipeline(); +} + +static inline bool _sgp_region_overlaps(_sgp_region a, _sgp_region b) { + return !(a.x2 <= b.x1 || b.x2 <= a.x1 || a.y2 <= b.y1 || b.y2 <= a.y1); +} + +static bool _sgp_merge_batch_command(sg_pipeline pip, sgp_textures_uniform textures, sgp_uniform* uniform, _sgp_region region, uint32_t vertex_index, uint32_t num_vertices) { +#if SGP_BATCH_OPTIMIZER_DEPTH > 0 + _sgp_command* prev_cmd = NULL; + _sgp_command* inter_cmds[SGP_BATCH_OPTIMIZER_DEPTH]; + uint32_t inter_cmd_count = 0; + + // find a command that is a good candidate to batch + uint32_t lookup_depth = SGP_BATCH_OPTIMIZER_DEPTH; + for (uint32_t depth=0;depthcmd == SGP_COMMAND_NONE) { + lookup_depth++; + continue; + } + + // stop on scissor/viewport + if (cmd->cmd != SGP_COMMAND_DRAW) { + break; + } + + // can only batch commands with the same bindings and uniforms + if (cmd->args.draw.pip.id == pip.id && + memcmp(&textures, &cmd->args.draw.textures, sizeof(sgp_textures_uniform)) == 0 && + (!uniform || memcmp(uniform, &_sgp.uniforms[cmd->args.draw.uniform_index], sizeof(sgp_uniform)) == 0)) { + prev_cmd = cmd; + break; + } else { + inter_cmds[inter_cmd_count] = cmd; + inter_cmd_count++; + } + } + if (!prev_cmd) { + return false; + } + + // allow batching only if the region of the current or previous draw + // is not touched by intermediate commands + bool overlaps_next = false; + bool overlaps_prev = false; + _sgp_region prev_region = prev_cmd->args.draw.region; + for (uint32_t i=0;iargs.draw.region; + if (_sgp_region_overlaps(region, inter_region)) { + overlaps_next = true; + if (overlaps_prev) { + return false; + } + } + if (_sgp_region_overlaps(prev_region, inter_region)) { + overlaps_prev = true; + if (overlaps_next) { + return false; + } + } + } + + if (!overlaps_next) { // batch in the previous draw command + if (inter_cmd_count > 0) { + // not enough vertices space, can't do this batch + if (SOKOL_UNLIKELY(_sgp.cur_vertex + num_vertices > _sgp.num_vertices)) { + return false; + } + + uint32_t prev_end_vertex = prev_cmd->args.draw.vertex_index + prev_cmd->args.draw.num_vertices; + uint32_t prev_num_vertices = _sgp.cur_vertex - prev_end_vertex; + + // avoid moving too much memory, to not downgrade performance + if (prev_num_vertices > _SGP_MAX_MOVE_VERTICES) { + return false; + } + + // rearrange vertices memory for the batch + memmove(&_sgp.vertices[prev_end_vertex + num_vertices], &_sgp.vertices[prev_end_vertex], prev_num_vertices * sizeof(sgp_vertex)); + memcpy(&_sgp.vertices[prev_end_vertex], &_sgp.vertices[vertex_index + num_vertices], num_vertices * sizeof(sgp_vertex)); + + // offset vertices of intermediate draw commands + for (uint32_t i=0;iargs.draw.vertex_index += num_vertices; + } + } + + // update draw region and vertices + prev_region.x1 = _sg_min(prev_region.x1, region.x1); + prev_region.y1 = _sg_min(prev_region.y1, region.y1); + prev_region.x2 = _sg_max(prev_region.x2, region.x2); + prev_region.y2 = _sg_max(prev_region.y2, region.y2); + prev_cmd->args.draw.num_vertices += num_vertices; + prev_cmd->args.draw.region = prev_region; + } else { // batch in the next draw command + SOKOL_ASSERT(inter_cmd_count > 0); + + // append new draw command + _sgp_command* cmd = _sgp_next_command(); + if (SOKOL_UNLIKELY(!cmd)) { + return false; + } + + uint32_t prev_num_vertices = prev_cmd->args.draw.num_vertices; + + // not enough vertices space, can't do this batch + if (SOKOL_UNLIKELY(_sgp.cur_vertex + prev_num_vertices > _sgp.num_vertices)) { + return false; + } + + // avoid moving too much memory, to not downgrade performance + if (num_vertices > _SGP_MAX_MOVE_VERTICES) { + return false; + } + + // rearrange vertices memory for the batch + memmove(&_sgp.vertices[vertex_index + prev_num_vertices], &_sgp.vertices[vertex_index], num_vertices * sizeof(sgp_vertex)); + memcpy(&_sgp.vertices[vertex_index], &_sgp.vertices[prev_cmd->args.draw.vertex_index], prev_num_vertices * sizeof(sgp_vertex)); + + // update draw region and vertices + prev_region.x1 = _sg_min(prev_region.x1, region.x1); + prev_region.y1 = _sg_min(prev_region.y1, region.y1); + prev_region.x2 = _sg_max(prev_region.x2, region.x2); + prev_region.y2 = _sg_max(prev_region.y2, region.y2); + _sgp.cur_vertex += prev_num_vertices; + num_vertices += prev_num_vertices; + + // configure the draw command + cmd->cmd = SGP_COMMAND_DRAW; + cmd->args.draw.pip = pip; + cmd->args.draw.textures = textures; + cmd->args.draw.region = prev_region; + cmd->args.draw.uniform_index = prev_cmd->args.draw.uniform_index; + cmd->args.draw.vertex_index = vertex_index; + cmd->args.draw.num_vertices = num_vertices; + + // force skipping the previous draw command + prev_cmd->cmd = SGP_COMMAND_NONE; + } + return true; +#else + _SOKOL_UNUSED(pip); + _SOKOL_UNUSED(textures); + _SOKOL_UNUSED(uniform); + _SOKOL_UNUSED(region); + _SOKOL_UNUSED(vertex_index); + _SOKOL_UNUSED(num_vertices); + return false; +#endif // SGP_BATCH_OPTIMIZER_DEPTH > 0 +} + +static void _sgp_queue_draw(sg_pipeline pip, _sgp_region region, uint32_t vertex_index, uint32_t num_vertices, sg_primitive_type primitive_type) { + // override pipeline + sgp_uniform* uniform = NULL; + if (_sgp.state.pipeline.id != SG_INVALID_ID) { + pip = _sgp.state.pipeline; + uniform = &_sgp.state.uniform; + } + + // invalid pipeline + if (SOKOL_UNLIKELY(pip.id == SG_INVALID_ID)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + + // region is out of screen bounds + if (region.x1 > 1.0f || region.y1 > 1.0f || region.x2 < -1.0f || region.y2 < -1.0f) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + + // try to merge on previous command to draw in a batch + if (primitive_type != SG_PRIMITIVETYPE_TRIANGLE_STRIP && primitive_type != SG_PRIMITIVETYPE_LINE_STRIP && + _sgp_merge_batch_command(pip, _sgp.state.textures, uniform, region, vertex_index, num_vertices)) { + return; + } + + // setup uniform, try to reuse previous uniform when possible + uint32_t uniform_index = _SGP_IMPOSSIBLE_ID; + if (uniform) { + sgp_uniform *prev_uniform = _sgp_prev_uniform(); + bool reuse_uniform = prev_uniform && (memcmp(prev_uniform, uniform, sizeof(sgp_uniform)) == 0); + if (!reuse_uniform) { + // append new uniform + sgp_uniform *next_uniform = _sgp_next_uniform(); + if (SOKOL_UNLIKELY(!next_uniform)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + *next_uniform = _sgp.state.uniform; + } + uniform_index = _sgp.cur_uniform - 1; + } + + // append new draw command + _sgp_command* cmd = _sgp_next_command(); + if (SOKOL_UNLIKELY(!cmd)) { + _sgp.cur_vertex -= num_vertices; // rollback allocated vertices + return; + } + cmd->cmd = SGP_COMMAND_DRAW; + cmd->args.draw.pip = pip; + cmd->args.draw.textures = _sgp.state.textures; + cmd->args.draw.region = region; + cmd->args.draw.uniform_index = uniform_index; + cmd->args.draw.vertex_index = vertex_index; + cmd->args.draw.num_vertices = num_vertices; +} + +static inline sgp_vec2 _sgp_mat3_vec2_mul(const sgp_mat2x3* m, const sgp_vec2* v) { + sgp_vec2 u = { + m->v[0][0]*v->x + m->v[0][1]*v->y + m->v[0][2], + m->v[1][0]*v->x + m->v[1][1]*v->y + m->v[1][2] + }; + return u; +} + +static void _sgp_transform_vec2(sgp_mat2x3* matrix, sgp_vec2* dst, const sgp_vec2 *src, uint32_t count) { + for (uint32_t i=0;i 0); + + // setup vertices + uint32_t num_vertices = 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute vertices + sgp_vertex* v = vertices; + const sgp_vec2 quad[4] = { + {-1.0f, -1.0f}, // bottom left + { 1.0f, -1.0f}, // bottom right + { 1.0f, 1.0f}, // top right + {-1.0f, 1.0f}, // top left + }; + const sgp_vec2 texcoord = {0.0f, 0.0f}; + sgp_color_ub4 color = _sgp.state.color; + + // make a quad composed of 2 triangles + v[0].position = quad[0]; v[0].texcoord = texcoord; v[0].color = color; + v[1].position = quad[1]; v[1].texcoord = texcoord; v[1].color = color; + v[2].position = quad[2]; v[2].texcoord = texcoord; v[2].color = color; + v[3].position = quad[3]; v[3].texcoord = texcoord; v[3].color = color; + v[4].position = quad[0]; v[4].texcoord = texcoord; v[4].color = color; + v[5].position = quad[2]; v[5].texcoord = texcoord; v[5].color = color; + + _sgp_region region = {-1.0f, -1.0f, 1.0f, 1.0f}; + + sg_pipeline pip = _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, SGP_BLENDMODE_NONE); + _sgp_queue_draw(pip, region, vertex_index, num_vertices, SG_PRIMITIVETYPE_TRIANGLES); +} + +void sgp_draw(sg_primitive_type primitive_type, const sgp_vertex* vertices, uint32_t count) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + if (SOKOL_UNLIKELY(count == 0)) { + return; + } + + // setup vertices + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* v = _sgp_next_vertices(count); + if (SOKOL_UNLIKELY(!v)) { + return; + } + + // fill vertices + float thickness = (primitive_type == SG_PRIMITIVETYPE_POINTS || primitive_type == SG_PRIMITIVETYPE_LINES || primitive_type == SG_PRIMITIVETYPE_LINE_STRIP) ? _sgp.state.thickness : 0.0f; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + if (SOKOL_UNLIKELY(num_vertices == 0)) { + return; + } + + // setup vertices + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* v = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!v)) { + return; + } + + // fill vertices + float thickness = (primitive_type == SG_PRIMITIVETYPE_POINTS || primitive_type == SG_PRIMITIVETYPE_LINES || primitive_type == SG_PRIMITIVETYPE_LINE_STRIP) ? _sgp.state.thickness : 0.0f; + sgp_color_ub4 color = _sgp.state.color; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + if (SOKOL_UNLIKELY(count == 0)) { + return; + } + + // setup vertices + uint32_t num_vertices = count * 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute vertices + sgp_vertex* v = vertices; + const sgp_rect* rect = rects; + sgp_color_ub4 color = _sgp.state.color; + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;ix, rect->y + rect->h}, // bottom left + {rect->x + rect->w, rect->y + rect->h}, // bottom right + {rect->x + rect->w, rect->y}, // top right + {rect->x, rect->y}, // top left + }; + _sgp_transform_vec2(&mvp, quad, quad, 4); + + for (uint32_t j=0;j<4;++j) { + region.x1 = _sg_min(region.x1, quad[j].x); + region.y1 = _sg_min(region.y1, quad[j].y); + region.x2 = _sg_max(region.x2, quad[j].x); + region.y2 = _sg_max(region.y2, quad[j].y); + } + + const sgp_vec2 vtexquad[4] = { + {0.0f, 1.0f}, // bottom left + {1.0f, 1.0f}, // bottom right + {1.0f, 0.0f}, // top right + {0.0f, 0.0f}, // top left + }; + + // make a quad composed of 2 triangles + v[0].position = quad[0]; v[0].texcoord = vtexquad[0]; v[0].color = color; + v[1].position = quad[1]; v[1].texcoord = vtexquad[1]; v[1].color = color; + v[2].position = quad[2]; v[2].texcoord = vtexquad[2]; v[2].color = color; + v[3].position = quad[3]; v[3].texcoord = vtexquad[3]; v[3].color = color; + v[4].position = quad[0]; v[4].texcoord = vtexquad[0]; v[4].color = color; + v[5].position = quad[2]; v[5].texcoord = vtexquad[2]; v[5].color = color; + } + + // queue draw + sg_pipeline pip = _sgp_lookup_pipeline(SG_PRIMITIVETYPE_TRIANGLES, _sgp.state.blend_mode); + _sgp_queue_draw(pip, region, vertex_index, num_vertices, SG_PRIMITIVETYPE_TRIANGLES); +} + +void sgp_draw_filled_rect(float x, float y, float w, float h) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + sgp_rect rect = {x,y,w,h}; + sgp_draw_filled_rects(&rect, 1); +} + +static sgp_isize _sgp_query_image_size(sg_image img_id) { + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + SOKOL_ASSERT(img); + sgp_isize size = {img ? img->cmn.width : 0, img ? img->cmn.height : 0}; + return size; +} + +void sgp_draw_textured_rects(int channel, const sgp_textured_rect* rects, uint32_t count) { + SOKOL_ASSERT(_sgp.init_cookie == _SGP_INIT_COOKIE); + SOKOL_ASSERT(_sgp.cur_state > 0); + SOKOL_ASSERT(channel >= 0 && channel < SGP_TEXTURE_SLOTS); + sg_image image = _sgp.state.textures.images[channel]; + if (SOKOL_UNLIKELY(count == 0 || image.id == SG_INVALID_ID)) { + return; + } + + // setup vertices + uint32_t num_vertices = count * 6; + uint32_t vertex_index = _sgp.cur_vertex; + sgp_vertex* vertices = _sgp_next_vertices(num_vertices); + if (SOKOL_UNLIKELY(!vertices)) { + return; + } + + // compute image values used for texture coords transform + sgp_isize image_size = _sgp_query_image_size(image); + if (SOKOL_UNLIKELY(image_size.w == 0 || image_size.h == 0)) { + return; + } + float iw = 1.0f/(float)image_size.w, ih = 1.0f/(float)image_size.h; + + // compute vertices + sgp_mat2x3 mvp = _sgp.state.mvp; // copy to stack for more efficiency + _sgp_region region = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (uint32_t i=0;i 0); + sgp_textured_rect rect = {dest_rect, src_rect}; + sgp_draw_textured_rects(channel, &rect, 1); +} + +sgp_desc sgp_query_desc(void) { + return _sgp.desc; +} + +sgp_state* sgp_query_state(void) { + return &_sgp.state; +} + +#endif // SOKOL_GP_IMPL_INCLUDED +#endif // SOKOL_GP_IMPL + +/* +Copyright (c) 2020-2024 Eduardo Bart (https://github.com/edubart/sokol_gp) + +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, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ diff --git a/examples/ve_fontcache.h b/examples/ve_fontcache.h new file mode 100644 index 0000000..88d96a8 --- /dev/null +++ b/examples/ve_fontcache.h @@ -0,0 +1,1799 @@ +/* + -- Vertex Engine GPU Font Cache -- + + Copyright 2020 Xi Chen + + 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, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* +---------------------------------- How to plug into rendering API ---------------------------------------- + +1. Create these simple shaders ( GLSL example provided ): + + // vs_source_shared + #version 330 core + in vec2 vpos; + in vec2 vtex; + out vec2 uv; + void main( void ) { + uv = vtex; + gl_Position = vec4( vpos.xy, 0.0, 1.0 ); + } + + // fs_source_render_glyph + #version 330 core + out vec4 fragc; + void main( void ) { + fragc = vec4( 1.0, 1.0, 1.0, 1.0 ); + } + + // fs_source_blit_atlas + #version 330 core + in vec2 uv; + out vec4 fragc; + uniform uint region; + uniform sampler2D src_texture; + float downsample( vec2 uv, vec2 texsz ) + { + float v = + texture( src_texture, uv + vec2( 0.0f, 0.0f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2( 0.0f, 1.0f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2( 1.0f, 0.0f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2( 1.0f, 1.0f ) * texsz ).x * 0.25f; + return v; + } + void main( void ) { + const vec2 texsz = 1.0f / vec2( 2048 , 512 ); // VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH/HEIGHT + if ( region == 0u || region == 1u || region == 2u ) { + float v = + downsample( uv + vec2( -1.5f, -1.5f ) * texsz, texsz ) * 0.25f + + downsample( uv + vec2( 0.5f, -1.5f ) * texsz, texsz ) * 0.25f + + downsample( uv + vec2( -1.5f, 0.5f ) * texsz, texsz ) * 0.25f + + downsample( uv + vec2( 0.5f, 0.5f ) * texsz, texsz ) * 0.25f; + fragc = vec4( 1, 1, 1, v ); + } else { + fragc = vec4( 0, 0, 0, 1 ); + } + } + + // vs_source_draw_text + #version 330 core + in vec2 vpos; + in vec2 vtex; + out vec2 uv; + void main( void ) { + uv = vtex; + gl_Position = vec4( vpos.xy * 2.0f - 1.0f, 0.0, 1.0 ); + } + + // fs_source_draw_text + #version 330 core + in vec2 uv; + out vec4 fragc; + uniform sampler2D src_texture; + uniform uint downsample; + uniform vec4 colour; + void main( void ) { + float v = texture( src_texture, uv ).x; + if ( downsample == 1u ) { + const vec2 texsz = 1.0f / vec2( 2048 , 512 ); // VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH/HEIGHT + v = + texture( src_texture, uv + vec2(-0.5f,-0.5f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2(-0.5f, 0.5f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2( 0.5f,-0.5f ) * texsz ).x * 0.25f + + texture( src_texture, uv + vec2( 0.5f, 0.5f ) * texsz ).x * 0.25f; + } + fragc = vec4( colour.xyz, colour.a * v ); + } + + fontcache_shader_render_glyph = compile_shader( vs_source_shared, fs_source_render_glyph ); + fontcache_shader_blit_atlas = compile_shader( vs_source_shared, fs_source_blit_atlas ); + fontcache_shader_draw_text = compile_shader( vs_source_draw_text, fs_source_draw_text ); + +2. Set up these 2 render target textures ( OpenGL example ): + + // First render target is 2k x 512 single-channel 8-byte red. + glBindFramebuffer( GL_FRAMEBUFFER, fontcache_fbo[ 0 ] ); + glBindTexture( GL_TEXTURE_2D, fontcache_fbo_texture[0] ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fontcache_fbo_texture[ 0 ], 0 ); + + // Second render target is 4k x 2k single-channel 8-byte red. + glBindFramebuffer( GL_FRAMEBUFFER, fontcache_fbo[ 1 ] ); + glBindTexture( GL_TEXTURE_2D, fontcache_fbo_texture[ 1 ] ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fontcache_fbo_texture[ 1 ], 0 ); + +3. Implement drawlist execute method ( OpenGL example ): + + glDisable( GL_CULL_FACE ); + glEnable( GL_BLEND ); + glBlendEquation( GL_FUNC_ADD ); + + for ( auto& dcall : drawlist->dcalls ) { + if ( dcall.pass == VE_FONTCACHE_FRAMEBUFFER_PASS_GLYPH ) { + glUseProgram( fontcache_shader_render_glyph ); + glBindFramebuffer( GL_FRAMEBUFFER, fontcache_fbo[ 0 ] ); + glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR ); + glViewport( 0, 0, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + glScissor( 0, 0, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + glDisable( GL_FRAMEBUFFER_SRGB ); + } else if ( dcall.pass == VE_FONTCACHE_FRAMEBUFFER_PASS_ATLAS ) { + glUseProgram( fontcache_shader_blit_atlas ); + glBindFramebuffer( GL_FRAMEBUFFER, fontcache_fbo[ 1 ] ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glViewport( 0, 0, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT ); + glScissor( 0, 0, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT ); + glUniform1i( glGetUniformLocation( fontcache_shader_blit_atlas, "src_texture" ), 0 ); + glUniform1ui( glGetUniformLocation( fontcache_shader_blit_atlas, "region" ), dcall.region ); + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, fontcache_fbo_texture[0] ); + glDisable( GL_FRAMEBUFFER_SRGB ); + } else { + glUseProgram( fontcache_shader_draw_text ); + glBindFramebuffer( GL_FRAMEBUFFER, 0 ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glViewport( 0, 0, window_size.width, window_size.height ); + glScissor( 0, 0, window_size.width, window_size.height ); + glUniform1i( glGetUniformLocation( fontcache_shader_draw_text, "src_texture" ), 0 ); + glUniform1ui( glGetUniformLocation( fontcache_shader_draw_text, "downsample" ), dcall.pass == VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET_UNCACHED ? 1 : 0 ); + glUniform4fv( glGetUniformLocation( fontcache_shader_draw_text, "colour" ), 1, dcall.colour ); + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, dcall.pass == VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET_UNCACHED ? fontcache_fbo_texture[0] : fontcache_fbo_texture[1] ); + glEnable( GL_FRAMEBUFFER_SRGB ); + } + if ( dcall.clear_before_draw ) { + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + glClear( GL_COLOR_BUFFER_BIT ); + } + if ( dcall.end_index - dcall.start_index == 0 ) + continue; + glDrawElements( GL_TRIANGLES, dcall.end_index - dcall.start_index, GL_UNSIGNED_INT, ( GLvoid* ) ( dcall.start_index * sizeof( uint32_t ) ) ); + } +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#ifndef VE_STBTT_NOIMPL + #define STB_TRUETYPE_IMPLEMENTATION +#endif // VE_STBTT_NOIMPL + +#ifndef VE_STBTT_INCLUDED + #define VE_STBTT_INCLUDED + #include "stb_truetype.h" +#endif // VE_STBTT_INCLUDED + +#ifndef VE_FONTCACHE_CURVE_QUALITY + #define VE_FONTCACHE_CURVE_QUALITY 6 +#endif // VE_FONTCACHE_CURVE_QUALITY + +#ifndef VE_FONTCACHE_HARFBUZZ + #pragma message( "WARNING: Please include Harfbuzz and define VE_FONTCACHE_HARFBUZZ for production. Using default fallback dumbass non-portable unoptimised text shaper." ) +#endif // VE_FONTCACHE_HARFBUZZ +#include "utf8.h" + +/* +---------------------------------- Font Atlas Caching Strategy -------------------------------------------- + + 2k + -------------------- + | | | + | A | | + | | | 2 + |---------| C | k + | | | + 1k | B | | + | | | + -------------------- + | | + | | + | | 2 + | D | k + | | + | | + | | + -------------------- + + Region A = 32x32 caches, 1024 glyphs + Region B = 32x64 caches, 512 glyphs + Region C = 64x64 caches, 512 glyphs + Region D = 128x128 caches, 256 glyphs +*/ + +#define VE_FONTCACHE_ATLAS_WIDTH 4096 +#define VE_FONTCACHE_ATLAS_HEIGHT 2048 +#define VE_FONTCACHE_ATLAS_GLYPH_PADDING 1 + +#define VE_FONTCACHE_ATLAS_REGION_A_WIDTH 32 +#define VE_FONTCACHE_ATLAS_REGION_A_HEIGHT 32 +#define VE_FONTCACHE_ATLAS_REGION_A_XSIZE ( VE_FONTCACHE_ATLAS_WIDTH / 4 ) +#define VE_FONTCACHE_ATLAS_REGION_A_YSIZE ( VE_FONTCACHE_ATLAS_HEIGHT / 2 ) +#define VE_FONTCACHE_ATLAS_REGION_A_XCAPACITY ( VE_FONTCACHE_ATLAS_REGION_A_XSIZE / VE_FONTCACHE_ATLAS_REGION_A_WIDTH ) +#define VE_FONTCACHE_ATLAS_REGION_A_YCAPACITY ( VE_FONTCACHE_ATLAS_REGION_A_YSIZE / VE_FONTCACHE_ATLAS_REGION_A_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_A_CAPACITY ( VE_FONTCACHE_ATLAS_REGION_A_XCAPACITY * VE_FONTCACHE_ATLAS_REGION_A_YCAPACITY ) +#define VE_FONTCACHE_ATLAS_REGION_A_XOFFSET 0 +#define VE_FONTCACHE_ATLAS_REGION_A_YOFFSET 0 + +#define VE_FONTCACHE_ATLAS_REGION_B_WIDTH 32 +#define VE_FONTCACHE_ATLAS_REGION_B_HEIGHT 64 +#define VE_FONTCACHE_ATLAS_REGION_B_XSIZE ( VE_FONTCACHE_ATLAS_WIDTH / 4 ) +#define VE_FONTCACHE_ATLAS_REGION_B_YSIZE ( VE_FONTCACHE_ATLAS_HEIGHT / 2 ) +#define VE_FONTCACHE_ATLAS_REGION_B_XCAPACITY ( VE_FONTCACHE_ATLAS_REGION_B_XSIZE / VE_FONTCACHE_ATLAS_REGION_B_WIDTH ) +#define VE_FONTCACHE_ATLAS_REGION_B_YCAPACITY ( VE_FONTCACHE_ATLAS_REGION_B_YSIZE / VE_FONTCACHE_ATLAS_REGION_B_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_B_CAPACITY ( VE_FONTCACHE_ATLAS_REGION_B_XCAPACITY * VE_FONTCACHE_ATLAS_REGION_B_YCAPACITY ) +#define VE_FONTCACHE_ATLAS_REGION_B_XOFFSET 0 +#define VE_FONTCACHE_ATLAS_REGION_B_YOFFSET VE_FONTCACHE_ATLAS_REGION_A_YSIZE + +#define VE_FONTCACHE_ATLAS_REGION_C_WIDTH 64 +#define VE_FONTCACHE_ATLAS_REGION_C_HEIGHT 64 +#define VE_FONTCACHE_ATLAS_REGION_C_XSIZE ( VE_FONTCACHE_ATLAS_WIDTH / 4 ) +#define VE_FONTCACHE_ATLAS_REGION_C_YSIZE ( VE_FONTCACHE_ATLAS_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_C_XCAPACITY ( VE_FONTCACHE_ATLAS_REGION_C_XSIZE / VE_FONTCACHE_ATLAS_REGION_C_WIDTH ) +#define VE_FONTCACHE_ATLAS_REGION_C_YCAPACITY ( VE_FONTCACHE_ATLAS_REGION_C_YSIZE / VE_FONTCACHE_ATLAS_REGION_C_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_C_CAPACITY ( VE_FONTCACHE_ATLAS_REGION_C_XCAPACITY * VE_FONTCACHE_ATLAS_REGION_C_YCAPACITY ) +#define VE_FONTCACHE_ATLAS_REGION_C_XOFFSET VE_FONTCACHE_ATLAS_REGION_A_XSIZE +#define VE_FONTCACHE_ATLAS_REGION_C_YOFFSET 0 + +#define VE_FONTCACHE_ATLAS_REGION_D_WIDTH 128 +#define VE_FONTCACHE_ATLAS_REGION_D_HEIGHT 128 +#define VE_FONTCACHE_ATLAS_REGION_D_XSIZE ( VE_FONTCACHE_ATLAS_WIDTH / 2 ) +#define VE_FONTCACHE_ATLAS_REGION_D_YSIZE ( VE_FONTCACHE_ATLAS_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_D_XCAPACITY ( VE_FONTCACHE_ATLAS_REGION_D_XSIZE / VE_FONTCACHE_ATLAS_REGION_D_WIDTH ) +#define VE_FONTCACHE_ATLAS_REGION_D_YCAPACITY ( VE_FONTCACHE_ATLAS_REGION_D_YSIZE / VE_FONTCACHE_ATLAS_REGION_D_HEIGHT ) +#define VE_FONTCACHE_ATLAS_REGION_D_CAPACITY ( VE_FONTCACHE_ATLAS_REGION_D_XCAPACITY * VE_FONTCACHE_ATLAS_REGION_D_YCAPACITY ) +#define VE_FONTCACHE_ATLAS_REGION_D_XOFFSET ( VE_FONTCACHE_ATLAS_WIDTH / 2 ) +#define VE_FONTCACHE_ATLAS_REGION_D_YOFFSET 0 + +static_assert( VE_FONTCACHE_ATLAS_REGION_A_CAPACITY == 1024, "VE FontCache Atlas Sanity check fail. Please update this assert if you changed atlas packing strategy." ); +static_assert( VE_FONTCACHE_ATLAS_REGION_B_CAPACITY == 512, "VE FontCache Atlas Sanity check fail. Please update this assert if you changed atlas packing strategy." ); +static_assert( VE_FONTCACHE_ATLAS_REGION_C_CAPACITY == 512, "VE FontCache Atlas Sanity check fail. Please update this assert if you changed atlas packing strategy." ); +static_assert( VE_FONTCACHE_ATLAS_REGION_D_CAPACITY == 256, "VE FontCache Atlas Sanity check fail. Please update this assert if you changed atlas packing strategy." ); + +#define VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_X 4 +#define VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_Y 4 +#define VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH 4 +#define VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH ( VE_FONTCACHE_ATLAS_REGION_D_WIDTH * VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_X * VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH ) +#define VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ( VE_FONTCACHE_ATLAS_REGION_D_HEIGHT * VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_Y ) + +// Set to same value as VE_FONTCACHE_ATLAS_GLYPH_PADDING for best results! +#define VE_FONTCACHE_GLYPHDRAW_PADDING VE_FONTCACHE_ATLAS_GLYPH_PADDING + +#define VE_FONTCACHE_FRAMEBUFFER_PASS_GLYPH 1 +#define VE_FONTCACHE_FRAMEBUFFER_PASS_ATLAS 2 +#define VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET 3 +#define VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET_UNCACHED 4 + +// How many to store in text shaping cache. Shaping cache is also stored in LRU format. +#define VE_FONTCACHE_SHAPECACHE_SIZE 256 + +// How much to reserve for each shape cache. This adds up to a ~0.768mb cache. +#define VE_FONTCACHE_SHAPECACHE_RESERVE_LENGTH 64 + +// Max. text size for caching. This means the cache has ~3.072mb upper bound. +#define VE_FONTCACHE_SHAPECACHE_MAX_LENGTH 256 + +// Sizes below this snap their advance to next pixel boundary. +#define VE_FONTCACHE_ADVANCE_SNAP_SMALLFONT_SIZE 12 + +// --------------------------------------------------------------- Data Types --------------------------------------------------- + +typedef int64_t ve_font_id; +typedef int32_t ve_codepoint; +typedef int32_t ve_glyph; +typedef char ve_atlas_region; + +struct ve_fontcache_entry +{ + ve_font_id font_id = 0; + stbtt_fontinfo info; + bool used = false; + float size = 24.0f; + float size_scale = 1.0f; +#ifdef VE_FONTCACHE_HARFBUZZ + hb_blob_t* hb_blob = nullptr; + hb_face_t* hb_face = nullptr; + hb_font_t* hb_font = nullptr; +#endif // VE_FONTCACHE_HARFBUZZ +}; + +struct ve_fontcache_vertex +{ + float x; float y; + float u; float v; +}; + +struct ve_fontcache_vec2 +{ + float x; float y; +}; +inline ve_fontcache_vec2 ve_fontcache_make_vec2( float x_, float y_ ) { ve_fontcache_vec2 v; v.x = x_; v.y = y_; return v; } + +struct ve_fontcache_draw +{ + uint32_t pass = 0; // One of VE_FONTCACHE_FRAMEBUFFER_PASS_* values. + uint32_t start_index = 0; + uint32_t end_index = 0; + bool clear_before_draw = false; + uint32_t region = 0; + float colour[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +}; + +struct ve_fontcache_drawlist +{ + std::vector< ve_fontcache_vertex > vertices; + std::vector< uint32_t > indices; + std::vector< ve_fontcache_draw > dcalls; +}; + +typedef uint32_t ve_fontcache_poollist_itr; +typedef uint64_t ve_fontcache_poollist_value; + +struct ve_fontcache_poollist_item +{ + ve_fontcache_poollist_itr prev = -1; + ve_fontcache_poollist_itr next = -1; + ve_fontcache_poollist_value value = 0; +}; + +struct ve_fontcache_poollist +{ + std::vector< ve_fontcache_poollist_item > pool; + std::vector< ve_fontcache_poollist_itr > freelist; + ve_fontcache_poollist_itr front = -1; + ve_fontcache_poollist_itr back = -1; + size_t size = 0; + size_t capacity = 0; +}; + +struct ve_fontcache_LRU_link +{ + int value = 0; + ve_fontcache_poollist_itr ptr; +}; + +struct ve_fontcache_LRU +{ + int capacity = 0; + std::unordered_map< uint64_t, ve_fontcache_LRU_link > cache; + ve_fontcache_poollist key_queue; +}; + +struct ve_fontcache_atlas +{ + uint32_t next_atlas_idx_A = 0; + uint32_t next_atlas_idx_B = 0; + uint32_t next_atlas_idx_C = 0; + uint32_t next_atlas_idx_D = 0; + + ve_fontcache_LRU stateA; + ve_fontcache_LRU stateB; + ve_fontcache_LRU stateC; + ve_fontcache_LRU stateD; + + uint32_t glyph_update_batch_x = 0; + ve_fontcache_drawlist glyph_update_batch_clear_drawlist; + ve_fontcache_drawlist glyph_update_batch_drawlist; +}; + +struct ve_fontcache_shaped_text +{ + std::vector< ve_glyph > glyphs; + std::vector< ve_fontcache_vec2 > pos; + ve_fontcache_vec2 end_cursor_pos; +}; + +struct ve_fontcache_shaped_text_cache +{ + std::vector< ve_fontcache_shaped_text > storage; + ve_fontcache_LRU state; + uint32_t next_cache_idx = 0; +}; + +struct ve_fontcache +{ + std::vector< ve_fontcache_entry > entry; + + std::vector< ve_fontcache_vec2 > temp_path; + std::unordered_map< uint64_t, bool > temp_codepoint_seen; + + uint32_t snap_width = 0; + uint32_t snap_height = 0; + float colour[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + ve_fontcache_vec2 cursor_pos; + + ve_fontcache_drawlist drawlist; + ve_fontcache_atlas atlas; + ve_fontcache_shaped_text_cache shape_cache; + + bool text_shape_advanced = true; +#ifdef VE_FONTCACHE_HARFBUZZ + hb_buffer_t* hb_text_buffer = nullptr; +#endif // VE_FONTCACHE_HARFBUZZ +}; + +// --------------------------------------------------------------------- VEFontCache Function Declarations ------------------------------------------------------------- + +// Call this to initialise a fontcache. +void ve_fontcache_init( ve_fontcache* cache ); + +// Call this to shutdown everything. +void ve_fontcache_shutdown( ve_fontcache* cache ); + +// Load hb_font from in-memory buffer. Supports otf, ttf, everything STB_truetype supports. +// Caller still owns data and must hold onto it; This buffer cannot be freed while hb_font is still being used. +// SBTTT will keep track of weak pointers into this memory. +// If you're loading the same hb_font at different size_px values, it is OK to share the same data buffer amongst them. +// +ve_font_id ve_fontcache_load( ve_fontcache* cache, const void* data, size_t data_size, float size_px = 24.0f ); + +// Load hb_font from file. Supports otf, ttf, everything STB_truetype supports. +// Caller still owns given buffer and must hold onto it. This buffer cannot be freed while hb_font is still being used. +// SBTTT will keep track of weak pointers into this memory. +// If you're loading the same hb_font at different size_px values, it is OK to share the same buffer amongst them. +// +ve_font_id ve_fontcache_loadfile( ve_fontcache* cache, const char* filename, std::vector< uint8_t >& buffer, float size_px = 24.0f ); + +// Unload a font and relase memory. Calling ve_fontcache_shutdown already does this on all loaded fonts. +void ve_fontcache_unload( ve_fontcache* cache, ve_font_id id ); + +// Configure snapping glyphs to pixel border when hb_font is rendered to 2D screen. May affect kerning. This may be changed at any time. +// Set both to zero to disable pixel snapping. +void ve_fontcache_configure_snap( ve_fontcache* cache, uint32_t snap_width = 0, uint32_t snap_height = 0 ); + +// Call this per-frame after draw list has been executed. This will clear the drawlist for next frame. +void ve_fontcache_flush_drawlist( ve_fontcache* cache ); + +// Main draw text function. This batches caches both shape and glyphs, and uses fallback path when not available. +// Note that this function immediately appends everything needed to render the next to the cache->drawlist. +// If you want to draw to multiple unrelated targets, simply draw_text, then loop through execute draw list, draw_text again, and loop through execute draw list again. +// Suggest scalex = 1 / screen_width and scaley = 1 / screen height! scalex and scaley will need to account for aspect ratio. +// +bool ve_fontcache_draw_text( ve_fontcache* cache, ve_font_id font, const std::string& text_utf8, float posx = 0.0f, float posy = 0.0f, float scalex = 1.0f, float scaley = 1.0f ); + +// Get where the last ve_fontcache_draw_text call left off. +ve_fontcache_vec2 ve_fontcache_get_cursor_pos( ve_fontcache* cache ); + +// Merges drawcalls. Significantly improves drawcall overhead, highly recommended. Call this before looping through and executing drawlist. +void ve_fontcache_optimise_drawlist( ve_fontcache* cache ); + +// Retrieves current drawlist from cache. +ve_fontcache_drawlist* ve_fontcache_get_drawlist( ve_fontcache* cache ); + +// Set text colour of subsequent text draws. +void ve_fontcache_set_colour( ve_fontcache* cache, float c[4] ); + +inline void ve_fontcache_enable_advanced_text_shaping( ve_fontcache* cache, bool enabled = true ) +{ + cache->text_shape_advanced = enabled; +} + +// --------------------------------------------------------------------- Generic Data Structure Declarations ------------------------------------------------------------- + + +// Generic pool list for alloc-free LRU implementation. +void ve_fontcache_poollist_init( ve_fontcache_poollist& plist, int capacity ); +void ve_fontcache_poollist_push_front( ve_fontcache_poollist& plist, ve_fontcache_poollist_value v ); +void ve_fontcache_poollist_erase( ve_fontcache_poollist& plist, ve_fontcache_poollist_itr it ); +ve_fontcache_poollist_value ve_fontcache_poollist_peek_back( ve_fontcache_poollist& plist ); +ve_fontcache_poollist_value ve_fontcache_poollist_pop_back( ve_fontcache_poollist& plist ); + +// Generic LRU ( Least-Recently-Used ) cache implementation, reused for both atlas and shape cache. +void ve_fontcache_LRU_init( ve_fontcache_LRU& LRU, int capacity ); +int ve_fontcache_LRU_get( ve_fontcache_LRU& LRU, uint64_t key ); +int ve_fontcache_LRU_peek( ve_fontcache_LRU& LRU, uint64_t key ); +uint64_t ve_fontcache_LRU_put( ve_fontcache_LRU& LRU, uint64_t key, int val ); +void ve_fontcache_LRU_refresh( ve_fontcache_LRU& LRU, uint64_t key ); +uint64_t ve_fontcache_LRU_get_next_evicted( ve_fontcache_LRU& LRU ); + +// --------------------------------------------------------------------- VEFontCache Function Implementations ------------------------------------------------------------- + +#ifdef VE_FONTCACHE_IMPL + +void ve_fontcache_init( ve_fontcache* cache ) +{ + STBTT_assert( cache ); + + // Reserve global context data. + cache->entry.reserve( 8 ); + cache->temp_path.reserve( 256 ); + cache->temp_codepoint_seen.reserve( 512 ); + cache->drawlist.vertices.reserve( 4096 ); + cache->drawlist.indices.reserve( 8192 ); + cache->drawlist.dcalls.reserve( 512 ); + + // Reserve data atlas LRU regions. + cache->atlas.next_atlas_idx_A = 0; + cache->atlas.next_atlas_idx_B = 0; + cache->atlas.next_atlas_idx_C = 0; + cache->atlas.next_atlas_idx_D = 0; + ve_fontcache_LRU_init( cache->atlas.stateA, VE_FONTCACHE_ATLAS_REGION_A_CAPACITY ); + ve_fontcache_LRU_init( cache->atlas.stateB, VE_FONTCACHE_ATLAS_REGION_B_CAPACITY ); + ve_fontcache_LRU_init( cache->atlas.stateC, VE_FONTCACHE_ATLAS_REGION_C_CAPACITY ); + ve_fontcache_LRU_init( cache->atlas.stateD, VE_FONTCACHE_ATLAS_REGION_D_CAPACITY ); + + // Reserve data for shape cache. This is pretty big! + ve_fontcache_LRU_init( cache->shape_cache.state, VE_FONTCACHE_SHAPECACHE_SIZE ); + cache->shape_cache.storage.resize( VE_FONTCACHE_SHAPECACHE_SIZE ); + for ( int i = 0; i < VE_FONTCACHE_SHAPECACHE_SIZE; i++ ) { + cache->shape_cache.storage[ i ].glyphs.reserve( VE_FONTCACHE_SHAPECACHE_RESERVE_LENGTH ); + cache->shape_cache.storage[ i ].pos.reserve( VE_FONTCACHE_SHAPECACHE_RESERVE_LENGTH ); + } + + // We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing! + cache->atlas.glyph_update_batch_drawlist.dcalls.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 ); + cache->atlas.glyph_update_batch_drawlist.vertices.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 * 4 ); + cache->atlas.glyph_update_batch_drawlist.indices.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 * 6 ); + cache->atlas.glyph_update_batch_clear_drawlist.dcalls.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 ); + cache->atlas.glyph_update_batch_clear_drawlist.vertices.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 * 4 ); + cache->atlas.glyph_update_batch_clear_drawlist.indices.reserve( VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH * 2 * 6 ); + +#ifdef VE_FONTCACHE_HARFBUZZ + cache->hb_text_buffer = hb_buffer_create(); +#endif // VE_FONTCACHE_HARFBUZZ +} + +void ve_fontcache_shutdown( ve_fontcache* cache ) +{ + STBTT_assert( cache ); + for ( ve_fontcache_entry& et : cache->entry ) { + ve_fontcache_unload( cache, et.font_id ); + } +#ifdef VE_FONTCACHE_HARFBUZZ + if ( cache->hb_text_buffer ) + hb_buffer_destroy( cache->hb_text_buffer ); +#endif // VE_FONTCACHE_HARFBUZZ +} + +ve_font_id ve_fontcache_load( ve_fontcache* cache, const void* data, size_t data_size, float size_px ) +{ + STBTT_assert( cache ); + if ( !data ) return -1; + + // Allocate cache entry. + int id = -1; + for( int i = 0; i < cache->entry.size(); i++ ) { + if ( !cache->entry[i].used ) { + id = i; + break; + } + } + if ( id == -1 ) { + cache->entry.push_back( ve_fontcache_entry() ); + id = cache->entry.size() - 1; + } + STBTT_assert( id >= 0 && id < cache->entry.size() ); + + // Load hb_font from memory. + auto& et = cache->entry[id]; + int success = stbtt_InitFont( &et.info, ( const unsigned char* ) data, 0 ); + if ( !success ) { + return -1; + } + et.font_id = id; + et.size = size_px; + et.size_scale = size_px < 0.0f ? stbtt_ScaleForPixelHeight( &et.info, -size_px ) : stbtt_ScaleForMappingEmToPixels( &et.info, size_px ); + et.used = true; + +#ifdef VE_FONTCACHE_HARFBUZZ + et.hb_blob = hb_blob_create( ( const char* ) data, data_size, HB_MEMORY_MODE_READONLY, ( void* )( uintptr_t ) id, nullptr ); + et.hb_face = hb_face_create( et.hb_blob, 0 ); + et.hb_font = hb_font_create( et.hb_face ); +#endif // VE_FONTCACHE_HARFBUZZ + + return id; +} + +ve_font_id ve_fontcache_loadfile( ve_fontcache* cache, const char* filename, std::vector< uint8_t >& buffer, float size_px ) +{ + FILE *fp = fopen( filename, "rb" ); + if ( !fp ) return -1; + + fseek( fp, 0, SEEK_END ); + size_t sz = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + buffer.resize( sz ); + + if ( fread( buffer.data(), 1, sz, fp ) != sz ) { + buffer.clear(); + fclose( fp ); + return -1; + } + fclose( fp ); + + ve_font_id ret = ve_fontcache_load( cache, buffer.data(), buffer.size(), size_px ); + return ret; +} + +void ve_fontcache_unload( ve_fontcache* cache, ve_font_id font ) +{ + STBTT_assert( cache ); + STBTT_assert( font >= 0 && font < cache->entry.size() ); + + auto& et = cache->entry[ font ]; + et.used = false; + +#ifdef VE_FONTCACHE_HARFBUZZ + if ( et.hb_font ) { + hb_font_destroy( et.hb_font ); + et.hb_font = nullptr; + } + if ( et.hb_face ) { + hb_face_destroy( et.hb_face ); + et.hb_face = nullptr; + } + if ( et.hb_blob ) { + hb_blob_destroy( et.hb_blob ); + et.hb_blob = nullptr; + } +#endif // VE_FONTCACHE_HARFBUZZ +} + +void ve_fontcache_configure_snap( ve_fontcache* cache, uint32_t snap_width, uint32_t snap_height ) +{ + STBTT_assert( cache ); + cache->snap_width = snap_width; + cache->snap_height = snap_height; +} + +ve_fontcache_drawlist* ve_fontcache_get_drawlist( ve_fontcache* cache ) +{ + STBTT_assert( cache ); + return &cache->drawlist; +} + +void ve_fontcache_clear_drawlist( ve_fontcache_drawlist& drawlist ) +{ + drawlist.dcalls.clear(); + drawlist.indices.clear(); + drawlist.vertices.clear(); +} + +void ve_fontcache_merge_drawlist( ve_fontcache_drawlist& dest, const ve_fontcache_drawlist& src ) +{ + int voffset = dest.vertices.size(); + for ( int i = 0; i < src.vertices.size(); i++ ) { + dest.vertices.push_back( src.vertices[i] ); + } + int ioffset = dest.indices.size(); + for ( int i = 0; i < src.indices.size(); i++ ) { + dest.indices.push_back( src.indices[i] + voffset ); + } + for ( int i = 0; i < src.dcalls.size(); i++ ) { + ve_fontcache_draw dcall = src.dcalls[i]; + dcall.start_index += ioffset; + dcall.end_index += ioffset; + dest.dcalls.push_back( dcall ); + } +} + +void ve_fontcache_flush_drawlist( ve_fontcache* cache ) +{ + STBTT_assert( cache ); + ve_fontcache_clear_drawlist( cache->drawlist ); +} + +inline ve_fontcache_vec2 ve_fontcache_eval_bezier( ve_fontcache_vec2 p0, ve_fontcache_vec2 p1, ve_fontcache_vec2 p2, float t ) +{ + float t2 = t * t, c0 = ( 1.0f - t ) * ( 1.0f - t ), c1 = 2.0f * ( 1.0f - t ) * t, c2 = t2; + return ve_fontcache_make_vec2( + c0 * p0.x + c1 * p1.x + c2 * p2.x, + c0 * p0.y + c1 * p1.y + c2 * p2.y + ); +} + +inline ve_fontcache_vec2 ve_fontcache_eval_bezier( ve_fontcache_vec2 p0, ve_fontcache_vec2 p1, ve_fontcache_vec2 p2, ve_fontcache_vec2 p3, float t ) +{ + float t2 = t * t, t3 = t2 * t; + float c0 = ( 1.0f - t ) * ( 1.0f - t ) * ( 1.0f - t ), + c1 = 3.0f * ( 1.0f - t ) * ( 1.0f - t ) * t, + c2 = 3.0f * ( 1.0f - t ) * t2, + c3 = t3; + return ve_fontcache_make_vec2( + c0 * p0.x + c1 * p1.x + c2 * p2.x + c3 * p3.x, + c0 * p0.y + c1 * p1.y + c2 * p2.y + c3 * p3.y + ); +} + +// WARNING: doesn't actually append drawcall; caller is responsible for actually appending the drawcall. +void ve_fontcache_draw_filled_path( ve_fontcache_drawlist& drawlist, ve_fontcache_vec2 outside, std::vector< ve_fontcache_vec2 >& path, + float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f ) +{ +#ifdef VE_FONTCACHE_DEBUGPRINT_VERBOSE + printf( "outline_path: \n" ); + for ( int i = 0; i < path.size(); i++ ) { + printf( " %.2f %.2f\n", path[i].x * scaleX + translateX, path[i].y * scaleY + + translateY ); + } +#endif // VE_FONTCACHE_DEBUGPRINT_VERBOSE + + int voffset = drawlist.vertices.size(); + for ( int i = 0; i < path.size(); i++ ) { + ve_fontcache_vertex vertex; + vertex.x = path[i].x * scaleX + translateX; + vertex.y = path[i].y * scaleY + translateY; + vertex.u = 0.0f; + vertex.v = 0.0f; + drawlist.vertices.push_back( vertex ); + } + int voutside = drawlist.vertices.size(); + { + ve_fontcache_vertex vertex; + vertex.x = outside.x * scaleX + translateX; + vertex.y = outside.y * scaleY + translateY; + vertex.u = 0.0f; + vertex.v = 0.0f; + drawlist.vertices.push_back( vertex ); + } + for ( int i = 1; i < path.size(); i++ ) { + drawlist.indices.push_back( voutside ); + drawlist.indices.push_back( voffset + i - 1 ); + drawlist.indices.push_back( voffset + i ); + } +} + +// WARNING: doesn't actually append drawcall; caller is responsible for actually appending the drawcall. +void ve_fontcache_blit_quad( ve_fontcache_drawlist& drawlist, float x0 = 0.0f, float y0 = 0.0f, float x1 = 1.0f, float y1 = 1.0f, float u0 = 0.0f, float v0 = 0.0f, float u1 = 1.0f, float v1 = 1.0f ) +{ + //printf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f\n", + //x0, y0, x1, y1, u0, v0, u1, v1); + int voffset = drawlist.vertices.size(); + + ve_fontcache_vertex vertex; + vertex.x = x0; vertex.y = y0; + vertex.u = u0; vertex.v = v0; + drawlist.vertices.push_back( vertex ); + + vertex.x = x0; vertex.y = y1; + vertex.u = u0; vertex.v = v1; + drawlist.vertices.push_back( vertex ); + + vertex.x = x1; vertex.y = y0; + vertex.u = u1; vertex.v = v0; + drawlist.vertices.push_back( vertex ); + + vertex.x = x1; vertex.y = y1; + vertex.u = u1; vertex.v = v1; + drawlist.vertices.push_back( vertex ); + + static uint32_t quad_indices[] = { + 0, 1, 2, + 2, 1, 3 + }; + for ( int i = 0; i < 6; i++ ) { + drawlist.indices.push_back( voffset + quad_indices[i] ); + } +} + +bool ve_fontcache_cache_glyph( + ve_fontcache* cache, ve_font_id font, ve_glyph glyph_index, + float scaleX = 1.0f, float scaleY = 1.0f, float translateX = 0.0f, float translateY = 0.0f ) +{ + STBTT_assert( cache ); + STBTT_assert( font >= 0 && font < cache->entry.size() ); + ve_fontcache_entry& entry = cache->entry[ font ]; + + if ( !glyph_index ) { + // Glyph not in current hb_font. + return false; + } + + // Retrieve the shape definition from STB TrueType. + if ( stbtt_IsGlyphEmpty( &entry.info, glyph_index ) ) + return true; + + stbtt_vertex* shape = nullptr; + int nverts = stbtt_GetGlyphShape( &entry.info, glyph_index, &shape ); + if ( !nverts || !shape ) { + return false; + } + +#ifdef VE_FONTCACHE_DEBUGPRINT_VERBOSE + printf( "shape: \n" ); + for ( int i = 0; i < nverts; i++ ) { + if ( shape[i].type == STBTT_vmove ) { + printf(" move_to %d %d\n", shape[i].x, shape[i].y ); + } else if ( shape[i].type == STBTT_vline ) { + printf(" line_to %d %d\n", shape[i].x, shape[i].y ); + } else if ( shape[i].type == STBTT_vcurve ) { + printf(" curve_to %d %d through %d %d\n", shape[i].x, shape[i].y, shape[i].cx, shape[i].cy ); + } else if ( shape[i].type == STBTT_vcubic ) { + printf(" cubic_to %d %d through %d %d and %d %d\n", shape[i].x, shape[i].y, shape[i].cx, shape[i].cy, shape[i].cx1, shape[i].cy1 ); + } + } +#endif // VE_FONTCACHE_DEBUGPRINT_VERBOSE + + // We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner. + // Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here. + int bounds_x0, bounds_x1, bounds_y0, bounds_y1; + int success = stbtt_GetGlyphBox( &entry.info, glyph_index, &bounds_x0, &bounds_y0, &bounds_x1, &bounds_y1 ); + STBTT_assert( success ); + ve_fontcache_vec2 outside = ve_fontcache_make_vec2( bounds_x0 - 21, bounds_y0 - 33 ); + + // Figure out scaling so it fits within our box. + ve_fontcache_draw draw; + draw.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_GLYPH; + draw.start_index = cache->drawlist.indices.size(); + + // Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac. + // Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions. + // + std::vector< ve_fontcache_vec2 >& path = cache->temp_path; + path.clear(); + for ( int i = 0; i < nverts; i++ ) { + stbtt_vertex& edge = shape[i]; + switch ( edge.type ) { + case STBTT_vmove: + if ( path.size() > 0 ) { + ve_fontcache_draw_filled_path( cache->drawlist, outside, path, scaleX, scaleY, translateX, translateY ); + } + path.clear(); + // Fallthrough. + case STBTT_vline: + path.push_back( ve_fontcache_make_vec2( shape[i].x, shape[i].y ) ); + break; + case STBTT_vcurve: { + STBTT_assert( path.size() > 0 ); + ve_fontcache_vec2 p0 = path[ path.size() - 1 ]; + ve_fontcache_vec2 p1 = ve_fontcache_make_vec2( shape[i].cx, shape[i].cy ); + ve_fontcache_vec2 p2 = ve_fontcache_make_vec2( shape[i].x, shape[i].y ); + + float step = 1.0f / VE_FONTCACHE_CURVE_QUALITY, t = step; + for ( int i = 0; i < VE_FONTCACHE_CURVE_QUALITY; i++ ) { + path.push_back( ve_fontcache_eval_bezier( p0, p1, p2, t ) ); + t += step; + } + break; + } + case STBTT_vcubic: { + STBTT_assert( path.size() > 0 ); + ve_fontcache_vec2 p0 = path[ path.size() - 1 ]; + ve_fontcache_vec2 p1 = ve_fontcache_make_vec2( shape[i].cx, shape[i].cy ); + ve_fontcache_vec2 p2 = ve_fontcache_make_vec2( shape[i].cx1, shape[i].cy1 ); + ve_fontcache_vec2 p3 = ve_fontcache_make_vec2( shape[i].x, shape[i].y ); + + float step = 1.0f / VE_FONTCACHE_CURVE_QUALITY, t = step; + for ( int i = 0; i < VE_FONTCACHE_CURVE_QUALITY; i++ ) { + path.push_back( ve_fontcache_eval_bezier( p0, p1, p2, p3, t ) ); + t += step; + } + break; + } + default: + STBTT_assert( !"Unknown shape edge type." ); + } + } + if ( path.size() > 0 ) { + ve_fontcache_draw_filled_path( cache->drawlist, outside, path, scaleX, scaleY, translateX, translateY ); + } + + // Append the draw call. + draw.end_index = cache->drawlist.indices.size(); + if ( draw.end_index > draw.start_index ) { + cache->drawlist.dcalls.push_back( draw ); + } + + stbtt_FreeShape( &entry.info, shape ); + return true; +} + +static ve_atlas_region ve_fontcache_decide_codepoint_region( ve_fontcache* cache, ve_fontcache_entry& entry, int glyph_index, + ve_fontcache_LRU*& state, uint32_t*& next_idx, float& oversample_x, float& oversample_y ) +{ + if ( stbtt_IsGlyphEmpty( &entry.info, glyph_index ) ) + return '\0'; + + // Get hb_font text metrics. These are unscaled! + int bounds_x0, bounds_x1, bounds_y0, bounds_y1; + int success = stbtt_GetGlyphBox( &entry.info, glyph_index, &bounds_x0, &bounds_y0, &bounds_x1, &bounds_y1 ); + int bounds_width = bounds_x1 - bounds_x0; + int bounds_height = bounds_y1 - bounds_y0; + STBTT_assert( success ); + + // Decide which atlas to target. This logic should work well for reasonable on-screen text sizes of around 24px. + // For 4k+ displays, caching hb_font at a lower pt and drawing it upscaled at a higher pt is recommended. + // + ve_atlas_region region; + float bwidth_scaled = bounds_width * entry.size_scale + 2.0f * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + float bheight_scaled = bounds_height * entry.size_scale + 2.0f * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + + if ( bwidth_scaled <= VE_FONTCACHE_ATLAS_REGION_A_WIDTH && bheight_scaled <= VE_FONTCACHE_ATLAS_REGION_A_HEIGHT ) + { + // Region A for small glyphs. These are good for things such as punctuation. + region = 'A'; + state = &cache->atlas.stateA; + next_idx = &cache->atlas.next_atlas_idx_A; + } + else if ( bwidth_scaled <= VE_FONTCACHE_ATLAS_REGION_B_WIDTH && bheight_scaled > VE_FONTCACHE_ATLAS_REGION_B_HEIGHT ) + { + // Region B for tall glyphs. These are good for things such as european alphabets. + region = 'B'; + state = &cache->atlas.stateB; + next_idx = &cache->atlas.next_atlas_idx_B; + } + else if ( bwidth_scaled <= VE_FONTCACHE_ATLAS_REGION_C_WIDTH && bheight_scaled <= VE_FONTCACHE_ATLAS_REGION_C_HEIGHT ) + { + // Region C for big glyphs. These are good for things such as asian typography. + region = 'C'; + state = &cache->atlas.stateC; + next_idx = &cache->atlas.next_atlas_idx_C; + } + else if ( bwidth_scaled <= VE_FONTCACHE_ATLAS_REGION_D_WIDTH && bheight_scaled <= VE_FONTCACHE_ATLAS_REGION_D_HEIGHT ) + { + // Region D for huge glyphs. These are good for things such as titles and 4k. + region = 'D'; + state = &cache->atlas.stateD; + next_idx = &cache->atlas.next_atlas_idx_D; + } + else if ( bwidth_scaled <= VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH && bheight_scaled <= VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ) + { + // Region 'E' for massive glyphs. These are rendered uncached and un-oversampled. + region = 'E'; + state = nullptr; + next_idx = nullptr; + if ( bwidth_scaled <= VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH / 2 && bheight_scaled <= VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT / 2 ) { + oversample_x = 2.0f; oversample_y = 2.0f; + } else { + oversample_x = 1.0f; oversample_y = 1.0f; + } + return region; + } + else + { + return '\0'; + } + + STBTT_assert( state ); + STBTT_assert( next_idx ); + + return region; +} + +static void ve_fontcache_flush_glyph_buffer_to_atlas( ve_fontcache* cache ) +{ + // Flush drawcalls to draw list. + ve_fontcache_merge_drawlist( cache->drawlist, cache->atlas.glyph_update_batch_clear_drawlist ); + ve_fontcache_merge_drawlist( cache->drawlist, cache->atlas.glyph_update_batch_drawlist ); + ve_fontcache_clear_drawlist( cache->atlas.glyph_update_batch_clear_drawlist ); + ve_fontcache_clear_drawlist( cache->atlas.glyph_update_batch_drawlist ); + + // Clear glyph_update_FBO. + if ( cache->atlas.glyph_update_batch_x != 0 ) { + ve_fontcache_draw dcall; + dcall.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_GLYPH; + dcall.start_index = 0; + dcall.end_index = 0; + dcall.clear_before_draw = true; + + cache->drawlist.dcalls.push_back( dcall ); + cache->atlas.glyph_update_batch_x = 0; + } +} + +static void ve_fontcache_screenspace_xform( float& x, float& y, float& scalex, float& scaley, float width, float height ) +{ + scalex /= width; + scaley /= height; + scalex *= 2.0f; + scaley *= 2.0f; + x *= ( 2.0f / width ); + y *= ( 2.0f / height ); + x -= 1.0f; + y -= 1.0f; +} + +static void ve_fontcache_texspace_xform( float& x, float& y, float& scalex, float& scaley, float width, float height ) +{ + x /= width; + y /= height; + scalex /= width; + scaley /= height; +} + +static void ve_fontcache_atlas_bbox( ve_atlas_region region, int local_idx, float& x, float& y, float& width, float& height ) +{ + if ( region == 'A' ) { + width = VE_FONTCACHE_ATLAS_REGION_A_WIDTH; + height = VE_FONTCACHE_ATLAS_REGION_A_HEIGHT; + x = ( local_idx % VE_FONTCACHE_ATLAS_REGION_A_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_A_WIDTH; + y = ( local_idx / VE_FONTCACHE_ATLAS_REGION_A_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_A_HEIGHT; + x += VE_FONTCACHE_ATLAS_REGION_A_XOFFSET; + y += VE_FONTCACHE_ATLAS_REGION_A_YOFFSET; + } else if ( region == 'B' ) { + width = VE_FONTCACHE_ATLAS_REGION_B_WIDTH; + height = VE_FONTCACHE_ATLAS_REGION_B_HEIGHT; + x = ( local_idx % VE_FONTCACHE_ATLAS_REGION_B_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_B_WIDTH; + y = ( local_idx / VE_FONTCACHE_ATLAS_REGION_B_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_B_HEIGHT; + x += VE_FONTCACHE_ATLAS_REGION_B_XOFFSET; + y += VE_FONTCACHE_ATLAS_REGION_B_YOFFSET; + } else if ( region == 'C' ) { + width = VE_FONTCACHE_ATLAS_REGION_C_WIDTH; + height = VE_FONTCACHE_ATLAS_REGION_C_HEIGHT; + x = ( local_idx % VE_FONTCACHE_ATLAS_REGION_C_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_C_WIDTH; + y = ( local_idx / VE_FONTCACHE_ATLAS_REGION_C_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_C_HEIGHT; + x += VE_FONTCACHE_ATLAS_REGION_C_XOFFSET; + y += VE_FONTCACHE_ATLAS_REGION_C_YOFFSET; + } else { + STBTT_assert( region == 'D' ); + width = VE_FONTCACHE_ATLAS_REGION_D_WIDTH; + height = VE_FONTCACHE_ATLAS_REGION_D_HEIGHT; + x = ( local_idx % VE_FONTCACHE_ATLAS_REGION_D_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_D_WIDTH; + y = ( local_idx / VE_FONTCACHE_ATLAS_REGION_D_XCAPACITY ) * VE_FONTCACHE_ATLAS_REGION_D_HEIGHT; + x += VE_FONTCACHE_ATLAS_REGION_D_XOFFSET; + y += VE_FONTCACHE_ATLAS_REGION_D_YOFFSET; + } +} + +void ve_fontcache_cache_glyph_to_atlas( ve_fontcache* cache, ve_font_id font, ve_glyph glyph_index ) +{ + STBTT_assert( cache ); + STBTT_assert( font >= 0 && font < cache->entry.size() ); + ve_fontcache_entry& entry = cache->entry[ font ]; + + if ( !glyph_index ) { + // Glyph not in current hb_font. + return; + } + if ( stbtt_IsGlyphEmpty( &entry.info, glyph_index ) ) + return; + + // Get hb_font text metrics. These are unscaled! + int bounds_x0, bounds_x1, bounds_y0, bounds_y1; + int success = stbtt_GetGlyphBox( &entry.info, glyph_index, &bounds_x0, &bounds_y0, &bounds_x1, &bounds_y1 ); + int bounds_width = bounds_x1 - bounds_x0, + bounds_height = bounds_y1 - bounds_y0; + STBTT_assert( success ); + + // Decide which atlas to target. + ve_fontcache_LRU* state = nullptr; uint32_t* next_idx = nullptr; + float oversample_x = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_X, + oversample_y = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_Y; + ve_atlas_region region = ve_fontcache_decide_codepoint_region( cache, entry, glyph_index, state, next_idx, oversample_x, oversample_y ); + + // E region is special case and not cached to atlas. + if ( region == '\0' || region == 'E' ) return; + + // Grab an atlas LRU cache slot. + uint64_t lru_code = glyph_index + ( ( 0x100000000ULL * font ) & 0xFFFFFFFF00000000ULL ); + int atlas_index = ve_fontcache_LRU_get( *state, lru_code ); + if ( atlas_index == -1 ) + { + if ( *next_idx < state->capacity ) + { + uint64_t evicted = ve_fontcache_LRU_put( *state, lru_code, *next_idx ); + atlas_index = *next_idx; + ( *next_idx )++; + STBTT_assert( evicted == lru_code ); + } + else + { + uint64_t next_evict_codepoint = ve_fontcache_LRU_get_next_evicted( *state ); + STBTT_assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFFULL ); + + atlas_index = ve_fontcache_LRU_peek( *state, next_evict_codepoint ); + STBTT_assert( atlas_index != -1 ); + + uint64_t evicted = ve_fontcache_LRU_put( *state, lru_code, atlas_index ); + STBTT_assert( evicted == next_evict_codepoint ); + } + + STBTT_assert( ve_fontcache_LRU_get( *state, lru_code ) != -1 ); + } + +#ifdef VE_FONTCACHE_DEBUGPRINT + static int debug_total_cached = 0; + //printf( "glyph 0x%x( %c ) caching to atlas region %c at idx %d. %d total glyphs cached.\n", unicode, (char) unicode, (char) region, atlas_index, debug_total_cached++ ); +#endif // VE_FONTCACHE_DEBUGPRINT + + // Draw oversized glyph to update FBO. + float glyph_draw_scale_x = entry.size_scale * oversample_x; + float glyph_draw_scale_y = entry.size_scale * oversample_y; + float glyph_draw_translate_x = -bounds_x0 * glyph_draw_scale_x + VE_FONTCACHE_GLYPHDRAW_PADDING; + float glyph_draw_translate_y = -bounds_y0 * glyph_draw_scale_y + VE_FONTCACHE_GLYPHDRAW_PADDING; + + glyph_draw_translate_x = ( int ) ( glyph_draw_translate_x + 0.9999999f ); + glyph_draw_translate_y = ( int ) ( glyph_draw_translate_y + 0.9999999f ); + + // Allocate a glyph_update_FBO region. + int gdwidth_scaled_px = ( int )( bounds_width * glyph_draw_scale_x + 1.0f ) + 2 * oversample_x * VE_FONTCACHE_GLYPHDRAW_PADDING; + if( cache->atlas.glyph_update_batch_x + gdwidth_scaled_px >= VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH ) { + ve_fontcache_flush_glyph_buffer_to_atlas( cache ); + } + + // Calculate the src and destination regions. + + float destx, desty, destw, desth, srcx, srcy, srcw, srch; + ve_fontcache_atlas_bbox( region, atlas_index, destx, desty, destw, desth ); + float dest_glyph_x = destx + VE_FONTCACHE_ATLAS_GLYPH_PADDING, + dest_glyph_y = desty + VE_FONTCACHE_ATLAS_GLYPH_PADDING, + dest_glyph_w = bounds_width * entry.size_scale, + dest_glyph_h = bounds_height * entry.size_scale; + dest_glyph_x -= VE_FONTCACHE_GLYPHDRAW_PADDING; + dest_glyph_y -= VE_FONTCACHE_GLYPHDRAW_PADDING; + dest_glyph_w += 2 * VE_FONTCACHE_GLYPHDRAW_PADDING; + dest_glyph_h += 2 * VE_FONTCACHE_GLYPHDRAW_PADDING; + ve_fontcache_screenspace_xform( dest_glyph_x, dest_glyph_y, dest_glyph_w, dest_glyph_h, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT ); + ve_fontcache_screenspace_xform( destx, desty, destw, desth, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT ); + + srcx = cache->atlas.glyph_update_batch_x; + srcy = 0.0; + srcw = bounds_width * glyph_draw_scale_x; + srch = bounds_height * glyph_draw_scale_y; + srcw += 2 * oversample_x * VE_FONTCACHE_GLYPHDRAW_PADDING; + srch += 2 * oversample_y * VE_FONTCACHE_GLYPHDRAW_PADDING; + ve_fontcache_texspace_xform( srcx, srcy, srcw, srch, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + + // Advance glyph_update_batch_x and calcuate final glyph drawing transform. + glyph_draw_translate_x += cache->atlas.glyph_update_batch_x; + cache->atlas.glyph_update_batch_x += gdwidth_scaled_px; + ve_fontcache_screenspace_xform( glyph_draw_translate_x, glyph_draw_translate_y, glyph_draw_scale_x, glyph_draw_scale_y, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + + // Queue up clear on target region on atlas. + ve_fontcache_draw dcall; + dcall.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_ATLAS; + dcall.region = ( uint32_t )( -1 ); + dcall.start_index = cache->atlas.glyph_update_batch_clear_drawlist.indices.size(); + ve_fontcache_blit_quad( cache->atlas.glyph_update_batch_clear_drawlist, destx, desty, destx + destw, desty + desth, 1.0f, 1.0f, 1.0f, 1.0f ); + dcall.end_index = cache->atlas.glyph_update_batch_clear_drawlist.indices.size(); + cache->atlas.glyph_update_batch_clear_drawlist.dcalls.push_back( dcall ); + + // Queue up a blit from glyph_update_FBO to the atlas. + dcall.region = ( uint32_t )( 0 ); + dcall.start_index = cache->atlas.glyph_update_batch_drawlist.indices.size(); + ve_fontcache_blit_quad( cache->atlas.glyph_update_batch_drawlist, dest_glyph_x, dest_glyph_y, destx + dest_glyph_w, desty + dest_glyph_h, srcx, srcy, srcx + srcw, srcy + srch ); + dcall.end_index = cache->atlas.glyph_update_batch_drawlist.indices.size(); + cache->atlas.glyph_update_batch_drawlist.dcalls.push_back( dcall ); + + // Render glyph to glyph_update_FBO. + ve_fontcache_cache_glyph( + cache, font, glyph_index, + glyph_draw_scale_x, glyph_draw_scale_y, + glyph_draw_translate_x, glyph_draw_translate_y + ); +} + +void ve_fontcache_shape_text_uncached( ve_fontcache* cache, ve_font_id font, ve_fontcache_shaped_text& output, const std::string& text_utf8 ) +{ + STBTT_assert( cache ); + STBTT_assert( font >= 0 && font < cache->entry.size() ); + + bool use_full_text_shape = cache->text_shape_advanced; + ve_fontcache_entry& entry = cache->entry[ font ]; + output.glyphs.clear(); + output.pos.clear(); + + int ascent = 0, descent = 0, line_gap = 0; + stbtt_GetFontVMetrics( &entry.info, &ascent, &descent, &line_gap ); + +#ifdef VE_FONTCACHE_HARFBUZZ + if ( use_full_text_shape ) { + + // Use HarfBuzz for text shaping. Yay! This is good. + hb_script_t current_script = HB_SCRIPT_UNKNOWN; + hb_unicode_funcs_t* hb_ucfunc = hb_unicode_funcs_get_default(); + hb_buffer_clear_contents( cache->hb_text_buffer ); + STBTT_assert( entry.hb_font ); + + // Lambda to shape a run using HarfBuzz library. + float pos = 0.0f, vpos = 0.0f; + auto ve_fontcache_shape_hb_run = [&]( hb_buffer_t * buf, hb_script_t script ) + { + // Set script and direction. We use the system's default language. + //script = HB_SCRIPT_LATIN; + hb_buffer_set_script( buf, script ); + hb_buffer_set_direction( buf, hb_script_get_horizontal_direction( script ) ); + hb_buffer_set_language( buf, hb_language_get_default() ); + + // Perform the actual shaping of this run using HarfBuzz. + hb_shape( entry.hb_font, buf, nullptr, 0 ); + + // Loop over glyphs and append to output buffer. + unsigned int glyph_count; + hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos( buf, &glyph_count ); + hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions( buf, &glyph_count ); + + for ( int i = 0; i < glyph_count; i++ ) { + ve_glyph glyph_id = ( ve_glyph ) glyph_info[i].codepoint; + if ( glyph_info[i].cluster ) { + pos = 0.0f; + vpos -= ( ascent - descent + line_gap ) * entry.size_scale; + vpos = ( int ) ( vpos + 0.5f ); + continue; + } + if ( std::abs( entry.size ) <= VE_FONTCACHE_ADVANCE_SNAP_SMALLFONT_SIZE ) { + // Expand advance to closest pixel for hb_font small sizes. + pos = std::ceilf( pos ); + } + + output.glyphs.push_back( glyph_id ); + float offset_x = glyph_pos[i].x_offset * entry.size_scale, offset_y = glyph_pos[i].y_offset * entry.size_scale; + output.pos.push_back( ve_fontcache_make_vec2( int( pos + offset_x + 0.5 ), vpos + offset_y ) ); + + pos += glyph_pos[i].x_advance * entry.size_scale; + vpos += glyph_pos[i].y_advance * entry.size_scale; + } + + output.end_cursor_pos.x = pos; + output.end_cursor_pos.y = vpos; + hb_buffer_clear_contents( buf ); + }; + + // We first start with simple bidi and run logic. + // True CTL is pretty hard and we don't fully support that; patches welcome! + + void* v = nullptr; + utf8_int32_t codepoint; + size_t u32_length = utf8len( text_utf8.data() ); + for ( v = utf8codepoint( text_utf8.data(), &codepoint ); codepoint; v = utf8codepoint( v, &codepoint ) ) { + hb_script_t script = hb_unicode_script( hb_ucfunc, ( hb_codepoint_t ) codepoint ); + + // Can we continue the current run? + bool special_script = ( script == HB_SCRIPT_UNKNOWN || script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON ); + if ( special_script || script == current_script ) { + hb_buffer_add( cache->hb_text_buffer, ( hb_codepoint_t ) codepoint, codepoint == '\n' ? 1 : 0 ); + current_script = special_script ? current_script : script; + continue; + } + + // End current run since we've encountered a script change. + ve_fontcache_shape_hb_run( cache->hb_text_buffer, current_script ); + hb_buffer_add( cache->hb_text_buffer, ( hb_codepoint_t ) codepoint, codepoint == '\n' ? 1 : 0 ); + current_script = script; + } + + // End the last run if needed. + ve_fontcache_shape_hb_run( cache->hb_text_buffer, current_script ); + return; + } +#else // VE_FONTCACHE_HARFBUZZ + cache->text_shape_advanced = false; +#endif // VE_FONTCACHE_HARFBUZZ + + // We use our own fallback dumbass text shaping. + // WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. + + utf8_int32_t codepoint, prev_codepoint = 0; + size_t u32_length = utf8len( text_utf8.data() ); + output.glyphs.reserve( u32_length ); + output.pos.reserve( u32_length ); + + float pos = 0.0f, + vpos = 0.0f; + int advance = 0, + to_left_side_glyph = 0; + + // Loop through text and shape. + for ( void* v = utf8codepoint( text_utf8.data(), &codepoint ); codepoint; v = utf8codepoint( v, &codepoint ) ) + { + if ( prev_codepoint ) + { + int kern = stbtt_GetCodepointKernAdvance( &entry.info, prev_codepoint, codepoint ); + pos += kern * entry.size_scale; + } + if ( codepoint == '\n' ) + { + pos = 0.0f; + vpos -= ( ascent - descent + line_gap ) * entry.size_scale; + vpos = ( int ) ( vpos + 0.5f ); + prev_codepoint = 0; + continue; + } + if ( std::abs( entry.size ) <= VE_FONTCACHE_ADVANCE_SNAP_SMALLFONT_SIZE ) + { + // Expand advance to closest pixel for hb_font small sizes. + pos = std::ceilf( pos ); + } + + output.glyphs.push_back( stbtt_FindGlyphIndex( &entry.info, codepoint ) ); + stbtt_GetCodepointHMetrics( &entry.info, codepoint, &advance, &to_left_side_glyph ); + output.pos.push_back( ve_fontcache_make_vec2( int( pos + 0.5 ), vpos ) ); + + float adv = advance * entry.size_scale; + + pos += adv; + prev_codepoint = codepoint; + } + + output.end_cursor_pos.x = pos; + output.end_cursor_pos.y = vpos; +} + +template < typename T > +void ve_fontcache_ELFhash64( uint64_t& hash, const T* ptr, size_t count = 1 ) +{ + uint64_t x = 0; + auto bytes = reinterpret_cast< const uint8_t* >( ptr ); + + for ( int i = 0; i < sizeof( T ) * count; i++ ) { + hash = ( hash << 4 ) + bytes[i]; + if( ( x = hash & 0xF000000000000000ULL ) != 0 ) { + hash ^= ( x >> 24 ); + } + hash &= ~x; + } +} + +static ve_fontcache_shaped_text& ve_fontcache_shape_text_cached( ve_fontcache* cache, ve_font_id font, const std::string& text_utf8 ) +{ + uint64_t hash = 0x9f8e00d51d263c24ULL; + ve_fontcache_ELFhash64( hash, ( const uint8_t* ) text_utf8.data(), text_utf8.size() ); + ve_fontcache_ELFhash64( hash, &font ); + + ve_fontcache_LRU& state = cache->shape_cache.state; + int shape_cache_idx = ve_fontcache_LRU_get( state, hash ); + if ( shape_cache_idx == -1 ) + { + if ( cache->shape_cache.next_cache_idx < state.capacity ) + { + shape_cache_idx = cache->shape_cache.next_cache_idx++; + ve_fontcache_LRU_put( state, hash, shape_cache_idx ); + } + else + { + uint64_t next_evict_idx = ve_fontcache_LRU_get_next_evicted( state ); + STBTT_assert( next_evict_idx != 0xFFFFFFFFFFFFFFFFULL ); + + shape_cache_idx = ve_fontcache_LRU_peek( state, next_evict_idx ); + STBTT_assert( shape_cache_idx != -1 ); + + ve_fontcache_LRU_put( state, hash, shape_cache_idx ); + } + + ve_fontcache_shape_text_uncached( cache, font, cache->shape_cache.storage[ shape_cache_idx ], text_utf8 ); + } + + return cache->shape_cache.storage[ shape_cache_idx ]; +} + +static void ve_fontcache_directly_draw_massive_glyph( ve_fontcache* cache, ve_fontcache_entry& entry, ve_glyph glyph, int bounds_x0, int bounds_y0, int bounds_width, int bounds_height, + float oversample_x = 1.0f, float oversample_y = 1.0f, float posx = 0.0f, float posy = 0.0f, float scalex = 1.0f, float scaley = 1.0f ) +{ + // Flush out whatever was in the glyph buffer beforehand to atlas. + ve_fontcache_flush_glyph_buffer_to_atlas( cache ); + + // Draw un-antialiased glyph to update FBO. + float glyph_draw_scale_x = entry.size_scale * oversample_x; + float glyph_draw_scale_y = entry.size_scale * oversample_y; + float glyph_draw_translate_x = -bounds_x0 * glyph_draw_scale_x + VE_FONTCACHE_GLYPHDRAW_PADDING; + float glyph_draw_translate_y = -bounds_y0 * glyph_draw_scale_y + VE_FONTCACHE_GLYPHDRAW_PADDING; + ve_fontcache_screenspace_xform( glyph_draw_translate_x, glyph_draw_translate_y, + glyph_draw_scale_x, glyph_draw_scale_y, + VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + + // Render glyph to glyph_update_FBO. + ve_fontcache_cache_glyph( + cache, entry.font_id, glyph, + glyph_draw_scale_x, glyph_draw_scale_y, + glyph_draw_translate_x, glyph_draw_translate_y + ); + + // Figure out the source rect. + float glyph_x = 0.0f, + glyph_y = 0.0f, + glyph_w = bounds_width * entry.size_scale * oversample_x, + glyph_h = bounds_height * entry.size_scale * oversample_y; + float glyph_dest_w = bounds_width * entry.size_scale, + glyph_dest_h = bounds_height * entry.size_scale; + glyph_w += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + glyph_h += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + glyph_dest_w += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + glyph_dest_h += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + + // Figure out the destination rect. + float bounds_x0_scaled = int( bounds_x0 * entry.size_scale - 0.5f ); + float bounds_y0_scaled = int( bounds_y0 * entry.size_scale - 0.5f ); + float dest_x = posx + scalex * bounds_x0_scaled; + float dest_y = posy + scaley * bounds_y0_scaled; + float dest_w = scalex * glyph_dest_w, + dest_h = scaley * glyph_dest_h; + dest_x -= scalex * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + dest_y -= scaley * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + ve_fontcache_texspace_xform( glyph_x, glyph_y, glyph_w, glyph_h, VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH, VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT ); + + // Add the glyph drawcall. + ve_fontcache_draw dcall; + dcall.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET_UNCACHED; + dcall.colour[0] = cache->colour[0]; dcall.colour[1] = cache->colour[1]; dcall.colour[2] = cache->colour[2]; dcall.colour[3] = cache->colour[3]; + dcall.start_index = cache->drawlist.indices.size(); + ve_fontcache_blit_quad( cache->drawlist, + dest_x, dest_y, + dest_x + dest_w, dest_y + dest_h, + glyph_x, glyph_y, + glyph_x + glyph_w, glyph_y + glyph_h ); + dcall.end_index = cache->drawlist.indices.size(); + cache->drawlist.dcalls.push_back( dcall ); + + // Clear glyph_update_FBO. + dcall.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_GLYPH; + dcall.start_index = 0; + dcall.end_index = 0; + dcall.clear_before_draw = true; + cache->drawlist.dcalls.push_back( dcall ); +} + +static bool ve_fontcache_empty( ve_fontcache* cache, ve_fontcache_entry& entry, ve_glyph glyph_index ) +{ + if ( !glyph_index ) { + // Glyph not in current hb_font. + return true; + } + if ( stbtt_IsGlyphEmpty( &entry.info, glyph_index ) ) + return true; + return false; +} + +// This function only draws codepoints that have been drawn. Returns false without drawing anything if uncached. +bool ve_fontcache_draw_cached_glyph( ve_fontcache* cache, ve_fontcache_entry& entry, ve_glyph glyph_index, float posx = 0.0f, float posy = 0.0f, float scalex = 1.0f, float scaley = 1.0f ) +{ + if ( !glyph_index ) { + // Glyph not in current hb_font. + return true; + } + if ( stbtt_IsGlyphEmpty( &entry.info, glyph_index ) ) + return true; + + // Get hb_font text metrics. These are unscaled! + int bounds_x0, bounds_x1, bounds_y0, bounds_y1; + int success = stbtt_GetGlyphBox( &entry.info, glyph_index, &bounds_x0, &bounds_y0, &bounds_x1, &bounds_y1 ); + int bounds_width = bounds_x1 - bounds_x0, + bounds_height = bounds_y1 - bounds_y0; + STBTT_assert( success ); + + // Decide which atlas to target. + ve_fontcache_LRU* state = nullptr; + uint32_t* next_idx = nullptr; + float oversample_x = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_X, + oversample_y = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_Y; + ve_atlas_region region = ve_fontcache_decide_codepoint_region( cache, entry, glyph_index, state, next_idx, oversample_x, oversample_y ); + + // E region is special case and not cached to atlas. + if ( region == 'E' ) { + ve_fontcache_directly_draw_massive_glyph( + cache, entry, glyph_index, + bounds_x0, bounds_y0, bounds_width, bounds_height, + oversample_x, oversample_y, + posx, posy, scalex, scaley + ); + return true; + } + + // Is this codepoint cached? + uint64_t lru_code = glyph_index + ( ( 0x100000000ULL * entry.font_id ) & 0xFFFFFFFF00000000ULL ); + int atlas_index = ve_fontcache_LRU_get( *state, lru_code ); + if( atlas_index == -1 ) { + return false; + } + + // Figure out the source bounding box in atlas texture. + float atlas_x, atlas_y, atlas_w, atlas_h; + ve_fontcache_atlas_bbox( region, atlas_index, atlas_x, atlas_y, atlas_w, atlas_h ); + float glyph_x = atlas_x, + glyph_y = atlas_y, + glyph_w = bounds_width * entry.size_scale, + glyph_h = bounds_height * entry.size_scale; + glyph_w += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + glyph_h += 2 * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + + // Figure out the destination rect. + float bounds_x0_scaled = int( bounds_x0 * entry.size_scale - 0.5f ); + float bounds_y0_scaled = int( bounds_y0 * entry.size_scale - 0.5f ); + float dest_x = posx + scalex * bounds_x0_scaled; + float dest_y = posy + scaley * bounds_y0_scaled; + float dest_w = scalex * glyph_w, + dest_h = scaley * glyph_h; + dest_x -= scalex * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + dest_y -= scaley * VE_FONTCACHE_ATLAS_GLYPH_PADDING; + ve_fontcache_texspace_xform( glyph_x, glyph_y, glyph_w, glyph_h, VE_FONTCACHE_ATLAS_WIDTH, VE_FONTCACHE_ATLAS_HEIGHT ); + + // Add the glyph drawcall. + ve_fontcache_draw dcall; + dcall.pass = VE_FONTCACHE_FRAMEBUFFER_PASS_TARGET; + dcall.colour[0] = cache->colour[0]; dcall.colour[1] = cache->colour[1]; dcall.colour[2] = cache->colour[2]; dcall.colour[3] = cache->colour[3]; + dcall.start_index = cache->drawlist.indices.size(); + ve_fontcache_blit_quad( cache->drawlist, dest_x, dest_y, dest_x + dest_w, dest_y + dest_h, glyph_x, glyph_y, glyph_x + glyph_w, glyph_y + glyph_h ); + dcall.end_index = cache->drawlist.indices.size(); + cache->drawlist.dcalls.push_back( dcall ); + + return true; +} + +static void ve_fontcache_reset_batch_codepoint_state( ve_fontcache* cache ) +{ + cache->temp_codepoint_seen.clear(); + cache->temp_codepoint_seen.reserve( 256 ); +} + +static bool ve_fontcache_can_batch_glyph( ve_fontcache* cache, ve_font_id font, ve_fontcache_entry& entry, ve_glyph glyph_index ) +{ + STBTT_assert( cache ); + STBTT_assert( entry.font_id == font ); + + // Decide which atlas to target. + STBTT_assert( glyph_index != -1 ); + ve_fontcache_LRU* state = nullptr; uint32_t* next_idx = nullptr; + float oversample_x = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_X, oversample_y = VE_FONTCACHE_GLYPHDRAW_OVERSAMPLE_Y; + ve_atlas_region region = ve_fontcache_decide_codepoint_region( cache, entry, glyph_index, state, next_idx, oversample_x, oversample_y ); + + // E region can't batch. + if ( region == 'E' || region == '\0' ) return false; + if ( cache->temp_codepoint_seen.size() > 1024 ) return false; + + // Is this glyph cached? + uint64_t lru_code = glyph_index + ( ( 0x100000000ULL * entry.font_id ) & 0xFFFFFFFF00000000ULL ); + int atlas_index = ve_fontcache_LRU_get( *state, lru_code ); + if ( atlas_index == -1 ) { + if ( *next_idx >= state->capacity ) { + // We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch. + uint64_t next_evict_codepoint = ve_fontcache_LRU_get_next_evicted( *state ); + if ( cache->temp_codepoint_seen[ next_evict_codepoint ] ) { + return false; + } + } + ve_fontcache_cache_glyph_to_atlas( cache, font, glyph_index ); + } + + STBTT_assert( ve_fontcache_LRU_get( *state, lru_code ) != -1 ); + cache->temp_codepoint_seen[ lru_code ] = true; + + return true; +} + +static void ve_fontcache_draw_text_batch( ve_fontcache* cache, ve_fontcache_entry& entry, ve_fontcache_shaped_text& shaped, + int batch_start_idx, int batch_end_idx, float posx, float posy, float scalex, float scaley ) +{ + ve_fontcache_flush_glyph_buffer_to_atlas( cache ); + for( int j = batch_start_idx; j < batch_end_idx; j++ ) { + ve_glyph glyph_index = shaped.glyphs[j]; + float glyph_translate_x = posx + shaped.pos[j].x * scalex, + glyph_translate_y = posy + shaped.pos[j].y * scaley; + bool glyph_cached = ve_fontcache_draw_cached_glyph( cache, entry, glyph_index, glyph_translate_x, glyph_translate_y, scalex, scaley ); + STBTT_assert( glyph_cached ); + } +} + +bool ve_fontcache_draw_text( ve_fontcache* cache, ve_font_id font, const std::string& text_utf8, float posx, float posy, float scalex, float scaley ) +{ + STBTT_assert(cache); + STBTT_assert(font >= 0 && font < cache->entry.size()); + ve_fontcache_shaped_text& shaped = ve_fontcache_shape_text_cached( cache, font, text_utf8 ); + + if ( cache->snap_width ) posx = ( ( int ) ( posx * cache->snap_width + 0.5f ) ) / ( float ) cache->snap_width; + if ( cache->snap_height ) posy = ( ( int ) ( posy * cache->snap_height + 0.5f ) ) / ( float ) cache->snap_height; + + ve_fontcache_entry& entry = cache->entry[ font ]; + + int batch_start_idx = 0; + for( int i = 0; i < shaped.glyphs.size(); i++ ) + { + ve_glyph glyph_index = shaped.glyphs[i]; + if ( ve_fontcache_empty( cache, entry, glyph_index ) ) + continue; + + if ( ve_fontcache_can_batch_glyph( cache, font, entry, glyph_index ) ) { + continue; + } + + ve_fontcache_draw_text_batch( cache, entry, shaped, batch_start_idx, i, posx, posy, scalex, scaley ); + ve_fontcache_reset_batch_codepoint_state( cache ); + + ve_fontcache_cache_glyph_to_atlas( cache, font, glyph_index ); + + uint64_t lru_code = glyph_index + ( ( 0x100000000ULL * font ) & 0xFFFFFFFF00000000ULL ); + cache->temp_codepoint_seen[ lru_code ] = true; + + batch_start_idx = i; + } + + ve_fontcache_draw_text_batch( cache, entry, shaped, batch_start_idx, shaped.glyphs.size(), posx, posy, scalex, scaley ); + ve_fontcache_reset_batch_codepoint_state( cache ); + cache->cursor_pos.x = posx + shaped.end_cursor_pos.x * scalex; + cache->cursor_pos.y = posy + shaped.end_cursor_pos.x * scaley; + + return true; +} + +ve_fontcache_vec2 ve_fontcache_get_cursor_pos( ve_fontcache* cache ) +{ + return cache->cursor_pos; +} + +void ve_fontcache_optimise_drawlist( ve_fontcache* cache ) +{ + STBTT_assert( cache ); + + int write_idx = 0; + for ( int i = 1; i < cache->drawlist.dcalls.size(); i++ ) { + + STBTT_assert( write_idx <= i ); + ve_fontcache_draw& draw0 = cache->drawlist.dcalls[ write_idx ]; + ve_fontcache_draw& draw1 = cache->drawlist.dcalls[ i ]; + + bool merge = true; + if ( draw0.pass != draw1.pass ) merge = false; + if ( draw0.end_index != draw1.start_index ) merge = false; + if ( draw0.region != draw1.region ) merge = false; + if ( draw1.clear_before_draw ) merge = false; + if ( draw0.colour[0] != draw1.colour[0] + || draw0.colour[1] != draw1.colour[1] + || draw0.colour[2] != draw1.colour[2] + || draw0.colour[3] != draw1.colour[3] ) merge = false; + + if ( merge ) { + draw0.end_index = draw1.end_index; + draw1.start_index = draw1.end_index = 0; + } else { + ve_fontcache_draw& draw2 = cache->drawlist.dcalls[ ++write_idx ]; + if ( write_idx != i ) draw2 = draw1; + } + } + cache->drawlist.dcalls.resize( write_idx + 1 ); +} + +void ve_fontcache_set_colour( ve_fontcache* cache, float c[4] ) +{ + cache->colour[0] = c[0]; + cache->colour[1] = c[1]; + cache->colour[2] = c[2]; + cache->colour[3] = c[3]; +} + +// ------------------------------------------------------ Generic Data Structure Implementations ------------------------------------------ + + +void ve_fontcache_poollist_init( ve_fontcache_poollist& plist, int capacity ) +{ + plist.pool.resize( capacity ); + plist.freelist.resize( capacity ); + plist.capacity = capacity; + for ( int i = 0; i < capacity; i++ ) plist.freelist[ i ] = i; +} + +void ve_fontcache_poollist_push_front( ve_fontcache_poollist& plist, ve_fontcache_poollist_value v ) +{ + if ( plist.size >= plist.capacity ) return; + assert( plist.freelist.size() > 0 ); + assert( plist.freelist.size() == plist.capacity - plist.size ); + + ve_fontcache_poollist_itr idx = plist.freelist.back(); + plist.freelist.pop_back(); + plist.pool[ idx ].prev = -1; + plist.pool[ idx ].next = plist.front; + plist.pool[ idx ].value = v; + + if ( plist.front != -1 ) plist.pool[ plist.front ].prev = idx; + if ( plist.back == -1 ) plist.back = idx; + plist.front = idx; + plist.size++; +} + +void ve_fontcache_poollist_erase( ve_fontcache_poollist& plist, ve_fontcache_poollist_itr it ) +{ + if ( plist.size <= 0 ) return; + assert( it >= 0 && it < plist.capacity ); + assert( plist.freelist.size() == plist.capacity - plist.size ); + + if ( plist.pool[ it ].prev != -1 ) + plist.pool[ plist.pool[ it ].prev ].next = plist.pool[ it ].next; + if ( plist.pool[ it ].next != -1 ) + plist.pool[ plist.pool[ it ].next ].prev = plist.pool[ it ].prev; + + if ( plist.front == it ) plist.front = plist.pool[ it ].next; + if ( plist.back == it ) plist.back = plist.pool[ it ].prev; + + plist.pool[ it ].prev = -1; + plist.pool[ it ].next = -1; + plist.pool[ it ].value = 0; + plist.freelist.push_back( it ); + + if ( --plist.size == 0 ) plist.back = plist.front = -1; +} + +ve_fontcache_poollist_value ve_fontcache_poollist_peek_back( ve_fontcache_poollist& plist ) +{ + assert( plist.back != -1 ); + return plist.pool[ plist.back ].value; +} + +ve_fontcache_poollist_value ve_fontcache_poollist_pop_back( ve_fontcache_poollist& plist ) +{ + if ( plist.size <= 0 ) return 0; + assert( plist.back != -1 ); + ve_fontcache_poollist_value v = plist.pool[ plist.back ].value; + ve_fontcache_poollist_erase( plist, plist.back ); + return v; +} + +void ve_fontcache_LRU_init( ve_fontcache_LRU& LRU, int capacity ) +{ + // Thanks to dfsbfs from leetcode! This code is eavily based on that with simplifications made on top. + // ref: https://leetcode.com/problems/lru-cache/discuss/968703/c%2B%2B + // https://leetcode.com/submissions/detail/436667816/ + LRU.capacity = capacity; + LRU.cache.reserve( capacity ); + ve_fontcache_poollist_init( LRU.key_queue, capacity ); +} + +void ve_fontcache_LRU_refresh( ve_fontcache_LRU& LRU, uint64_t key ) +{ + auto it = LRU.cache.find( key ); + ve_fontcache_poollist_erase( LRU.key_queue, it->second.ptr ); + ve_fontcache_poollist_push_front( LRU.key_queue, key ); + it->second.ptr = LRU.key_queue.front; +} + +int ve_fontcache_LRU_get( ve_fontcache_LRU& LRU, uint64_t key ) +{ + auto it = LRU.cache.find( key ); + if ( it == LRU.cache.end() ) { + return -1; + } + ve_fontcache_LRU_refresh( LRU, key ); + return it->second.value; +} + +int ve_fontcache_LRU_peek( ve_fontcache_LRU& LRU, uint64_t key ) +{ + auto it = LRU.cache.find( key ); + if ( it == LRU.cache.end() ) { + return -1; + } + return it->second.value; +} + +uint64_t ve_fontcache_LRU_put( ve_fontcache_LRU& LRU, uint64_t key, int val ) +{ + auto it = LRU.cache.find( key ); + if ( it != LRU.cache.end() ) { + ve_fontcache_LRU_refresh( LRU, key ); + it->second.value = val; + return key; + } + + uint64_t evict = key; + if ( LRU.key_queue.size >= LRU.capacity ) { + evict = ve_fontcache_poollist_pop_back( LRU.key_queue ); + LRU.cache.erase( evict ); + } + + ve_fontcache_poollist_push_front( LRU.key_queue, key ); + LRU.cache[ key ].value = val; + LRU.cache[ key ].ptr = LRU.key_queue.front; + return evict; +} + +uint64_t ve_fontcache_LRU_get_next_evicted( ve_fontcache_LRU& LRU ) +{ + if ( LRU.key_queue.size >= LRU.capacity ) { + uint64_t evict = ve_fontcache_poollist_peek_back( LRU.key_queue );; + return evict; + } + return 0xFFFFFFFFFFFFFFFFULL; +} + +#endif // VE_FONTCACHE_IMPL \ No newline at end of file diff --git a/scripts/build.ps1 b/scripts/build.ps1 index b845a53..71b3dbd 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -204,10 +204,10 @@ push-location $path_root # $build_args += $flag_micro_architecture_native $build_args += $flag_use_separate_modules $build_args += $flag_thread_count + $CoreCount_Physical - $build_args += $flag_optimize_none + # $build_args += $flag_optimize_none # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed - # $build_args += $falg_optimize_aggressive + $build_args += $falg_optimize_aggressive $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows'