From 346979a21ad8f530a401dca00e423a1aec528c64 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Tue, 25 Apr 2023 19:13:15 +0200 Subject: [PATCH] [canvas] change surface/canvas APIs to treat canvas a 'just another kind of surface', and split canvas implementation (managed by surface) from canvas _command buffer_ and implicit context (represented by mg_canvas) --- examples/canvas/main.c | 113 +-- examples/perf_text/main.c | 33 +- examples/tiger/main.c | 43 +- src/graphics.c | 1952 ++++--------------------------------- src/graphics.h | 67 +- src/graphics_internal.h | 6 +- src/milepost.c | 19 +- src/milepost.m | 16 +- src/mp_app_internal.h | 4 +- src/mtl_renderer.m | 559 +++++++---- src/platform/platform.h | 2 +- src/platform/std_log.c | 2 +- src/tmp_gl_canvas.c | 1573 ++++++++++++++++++++++++++++++ src/ui.c | 2 +- 14 files changed, 2277 insertions(+), 2114 deletions(-) create mode 100644 src/tmp_gl_canvas.c diff --git a/examples/canvas/main.c b/examples/canvas/main.c index c376c03..f433359 100644 --- a/examples/canvas/main.c +++ b/examples/canvas/main.c @@ -52,8 +52,6 @@ mg_font create_font() int main() { - LogLevel(LOG_LEVEL_WARNING); - mp_init(); mp_clock_init(); //TODO put that in mp_init()? @@ -63,11 +61,10 @@ int main() mp_rect contentRect = mp_window_get_content_rect(window); //NOTE: create surface - mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT); + mg_surface surface = mg_surface_create_for_window(window, MG_CANVAS); mg_surface_swap_interval(surface, 0); - //TODO: create canvas - mg_canvas canvas = mg_canvas_create(surface); + mg_canvas canvas = mg_canvas_create(); if(mg_canvas_is_nil(canvas)) { @@ -91,10 +88,10 @@ int main() f64 startTime = mp_get_time(MP_CLOCK_MONOTONIC); mp_pump_events(0); - mp_event event = {0}; - while(mp_next_event(&event)) + mp_event* event = 0; + while((event = mp_next_event(mem_scratch())) != 0) { - switch(event.type) + switch(event->type) { case MP_EVENT_WINDOW_CLOSE: { @@ -103,23 +100,23 @@ int main() case MP_EVENT_KEYBOARD_KEY: { - if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT) + if(event->key.action == MP_KEY_PRESS || event->key.action == MP_KEY_REPEAT) { - f32 factor = (event.key.mods & MP_KEYMOD_SHIFT) ? 10 : 1; + f32 factor = (event->key.mods & MP_KEYMOD_SHIFT) ? 10 : 1; - if(event.key.code == MP_KEY_LEFT) + if(event->key.code == MP_KEY_LEFT) { x-=0.3*factor; } - else if(event.key.code == MP_KEY_RIGHT) + else if(event->key.code == MP_KEY_RIGHT) { x+=0.3*factor; } - else if(event.key.code == MP_KEY_UP) + else if(event->key.code == MP_KEY_UP) { y-=0.3*factor; } - else if(event.key.code == MP_KEY_DOWN) + else if(event->key.code == MP_KEY_DOWN) { y+=0.3*factor; } @@ -154,55 +151,47 @@ int main() x += dx; y += dy; + // background + mg_set_color_rgba(0, 1, 1, 1); + mg_clear(); + + // head + mg_set_color_rgba(1, 1, 0, 1); + + mg_circle_fill(x, y, 200); + + // smile + f32 frown = frameTime > 0.033 ? -100 : 0; + + mg_set_color_rgba(0, 0, 0, 1); + mg_set_width(20); + mg_move_to(x-100, y+100); + mg_cubic_to(x-50, y+150+frown, x+50, y+150+frown, x+100, y+100); + mg_stroke(); + + // eyes + mg_ellipse_fill(x-70, y-50, 30, 50); + mg_ellipse_fill(x+70, y-50, 30, 50); + + // text + mg_set_color_rgba(0, 0, 1, 1); + mg_set_font(font); + mg_set_font_size(12); + mg_move_to(50, 600-50); + + str8 text = str8_pushf(mem_scratch(), + "Milepost vector graphics test program (frame time = %fs, fps = %f)...", + frameTime, + 1./frameTime); + mg_text_outlines(text); + mg_fill(); + + printf("Milepost vector graphics test program (frame time = %fs, fps = %f)...\n", + frameTime, + 1./frameTime); + mg_surface_prepare(surface); - - // background - mg_set_color_rgba(0, 1, 1, 1); - mg_clear(); - - mg_set_color_rgba(1, 0, 0, 1); - mg_set_width(2); - mg_rectangle_stroke(304, 100, 300, 250); - - mg_clip_push(304, 100, 300, 250); - // head - mg_set_color_rgba(1, 1, 0, 1); - - mg_circle_fill(x, y, 200); - - // smile - f32 frown = frameTime > 0.033 ? -100 : 0; - - mg_set_color_rgba(0, 0, 0, 1); - mg_set_width(20); - mg_move_to(x-100, y+100); - mg_cubic_to(x-50, y+150+frown, x+50, y+150+frown, x+100, y+100); - mg_stroke(); - - // eyes - mg_ellipse_fill(x-70, y-50, 30, 50); - mg_ellipse_fill(x+70, y-50, 30, 50); - - mg_clip_pop(); - - // text - mg_set_color_rgba(0, 0, 1, 1); - mg_set_font(font); - mg_set_font_size(12); - mg_move_to(50, 600-50); - - str8 text = str8_pushf(mem_scratch(), - "Milepost vector graphics test program (frame time = %fs, fps = %f)...", - frameTime, - 1./frameTime); - mg_text_outlines(text); - mg_fill(); - - printf("Milepost vector graphics test program (frame time = %fs, fps = %f)...\n", - frameTime, - 1./frameTime); - - mg_flush(); + mg_flush(surface); mg_surface_present(surface); mem_arena_clear(mem_scratch()); diff --git a/examples/perf_text/main.c b/examples/perf_text/main.c index 0baeae6..103b251 100644 --- a/examples/perf_text/main.c +++ b/examples/perf_text/main.c @@ -94,7 +94,6 @@ mg_font create_font(const char* path) int main() { - LogLevel(LOG_LEVEL_MESSAGE); mp_init(); mp_clock_init(); @@ -105,10 +104,10 @@ int main() //NOTE: create surface, canvas and font - mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT); + mg_surface surface = mg_surface_create_for_window(window, MG_CANVAS); mg_surface_swap_interval(surface, 0); - mg_canvas canvas = mg_canvas_create(surface); + mg_canvas canvas = mg_canvas_create(); const int fontCount = 3; int fontIndex = 0; @@ -153,15 +152,19 @@ int main() f32 startX = 10; f32 startY = 10 + lineHeights[fontIndex]; + mp_input_state inputState = {0}; + while(!mp_should_quit()) { f64 startFrameTime = mp_get_time(MP_CLOCK_MONOTONIC); mp_pump_events(0); - mp_event event = {0}; - while(mp_next_event(&event)) + mp_event* event = 0; + while((event = mp_next_event(mem_scratch())) != 0) { - switch(event.type) + mp_input_process_event(&inputState, event); + + switch(event->type) { case MP_EVENT_WINDOW_CLOSE: { @@ -170,12 +173,12 @@ int main() case MP_EVENT_MOUSE_BUTTON: { - if(event.key.code == MP_MOUSE_LEFT) + if(event->key.code == MP_MOUSE_LEFT) { - if(event.key.action == MP_KEY_PRESS) + if(event->key.action == MP_KEY_PRESS) { tracked = true; - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); trackPoint.x = mousePos.x/zoom - startX; trackPoint.y = mousePos.y/zoom - startY; } @@ -188,11 +191,11 @@ int main() case MP_EVENT_MOUSE_WHEEL: { - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); f32 trackX = mousePos.x/zoom - startX; f32 trackY = mousePos.y/zoom - startY; - zoom *= 1 + event.move.deltaY * 0.01; + zoom *= 1 + event->move.deltaY * 0.01; zoom = Clamp(zoom, 0.2, 10); startX = mousePos.x/zoom - trackX; @@ -201,7 +204,7 @@ int main() case MP_EVENT_KEYBOARD_KEY: { - if(event.key.code == MP_KEY_SPACE && event.key.action == MP_KEY_PRESS) + if(event->key.code == MP_KEY_SPACE && event->key.action == MP_KEY_PRESS) { fontIndex = (fontIndex+1)%fontCount; } @@ -214,7 +217,7 @@ int main() if(tracked) { - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); startX = mousePos.x/zoom - trackPoint.x; startY = mousePos.y/zoom - trackPoint.y; } @@ -289,8 +292,7 @@ int main() f64 startFlushTime = mp_get_time(MP_CLOCK_MONOTONIC); mg_surface_prepare(surface); - - mg_flush(); + mg_flush(surface); f64 startPresentTime = mp_get_time(MP_CLOCK_MONOTONIC); mg_surface_present(surface); @@ -306,6 +308,7 @@ int main() (startPresentTime - startFlushTime)*1000, (endFrameTime - startPresentTime)*1000); + mp_input_next_frame(&inputState); mem_arena_clear(mem_scratch()); } diff --git a/examples/tiger/main.c b/examples/tiger/main.c index b71efb1..92c0e32 100644 --- a/examples/tiger/main.c +++ b/examples/tiger/main.c @@ -15,8 +15,6 @@ #include"milepost.h" -#define LOG_SUBSYSTEM "Main" - #include"tiger.c" mg_font create_font() @@ -53,8 +51,6 @@ mg_font create_font() int main() { - LogLevel(LOG_LEVEL_WARNING); - mp_init(); mp_clock_init(); //TODO put that in mp_init()? @@ -64,11 +60,11 @@ int main() mp_rect contentRect = mp_window_get_content_rect(window); //NOTE: create surface - mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT); + mg_surface surface = mg_surface_create_for_window(window, MG_CANVAS); mg_surface_swap_interval(surface, 0); //TODO: create canvas - mg_canvas canvas = mg_canvas_create(surface); + mg_canvas canvas = mg_canvas_create(); if(mg_canvas_is_nil(canvas)) { @@ -92,15 +88,19 @@ int main() f64 frameTime = 0; + mp_input_state inputState = {0}; + while(!mp_should_quit()) { f64 startTime = mp_get_time(MP_CLOCK_MONOTONIC); mp_pump_events(0); - mp_event event = {0}; - while(mp_next_event(&event)) + mp_event* event = 0; + while((event = mp_next_event(mem_scratch())) != 0) { - switch(event.type) + mp_input_process_event(&inputState, event); + + switch(event->type) { case MP_EVENT_WINDOW_CLOSE: { @@ -109,18 +109,18 @@ int main() case MP_EVENT_WINDOW_RESIZE: { - mp_rect frame = {0, 0, event.frame.rect.w, event.frame.rect.h}; + mp_rect frame = {0, 0, event->frame.rect.w, event->frame.rect.h}; mg_surface_set_frame(surface, frame); } break; case MP_EVENT_MOUSE_BUTTON: { - if(event.key.code == MP_MOUSE_LEFT) + if(event->key.code == MP_MOUSE_LEFT) { - if(event.key.action == MP_KEY_PRESS) + if(event->key.action == MP_KEY_PRESS) { tracked = true; - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); trackPoint.x = (mousePos.x - startX)/zoom; trackPoint.y = (mousePos.y - startY)/zoom; } @@ -133,11 +133,11 @@ int main() case MP_EVENT_MOUSE_WHEEL: { - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); f32 pinX = (mousePos.x - startX)/zoom; f32 pinY = (mousePos.y - startY)/zoom; - zoom *= 1 + event.move.deltaY * 0.01; + zoom *= 1 + event->move.deltaY * 0.01; zoom = Clamp(zoom, 0.5, 5); startX = mousePos.x - pinX*zoom; @@ -146,9 +146,9 @@ int main() case MP_EVENT_KEYBOARD_KEY: { - if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT) + if(event->key.action == MP_KEY_PRESS || event->key.action == MP_KEY_REPEAT) { - switch(event.key.code) + switch(event->key.code) { case MP_KEY_SPACE: singlePath = !singlePath; @@ -156,7 +156,7 @@ int main() case MP_KEY_UP: { - if(event.key.mods & MP_KEYMOD_SHIFT) + if(event->key.mods & MP_KEYMOD_SHIFT) { singlePathIndex++; } @@ -168,7 +168,7 @@ int main() case MP_KEY_DOWN: { - if(event.key.mods & MP_KEYMOD_SHIFT) + if(event->key.mods & MP_KEYMOD_SHIFT) { singlePathIndex--; } @@ -188,7 +188,7 @@ int main() if(tracked) { - vec2 mousePos = mp_mouse_position(); + vec2 mousePos = mp_mouse_position(&inputState); startX = mousePos.x - trackPoint.x*zoom; startY = mousePos.y - trackPoint.y*zoom; } @@ -228,9 +228,10 @@ int main() frameTime, 1./frameTime); - mg_flush(); + mg_flush(surface); mg_surface_present(surface); + mp_input_next_frame(&inputState); mem_arena_clear(mem_scratch()); frameTime = mp_get_time(MP_CLOCK_MONOTONIC) - startTime; } diff --git a/src/graphics.c b/src/graphics.c index 7656e25..b5185de 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -134,10 +134,6 @@ typedef struct mg_canvas_data u32 vertexCount; u32 indexCount; - mg_surface surface; - mg_canvas_backend* backend; - - int splitCount; } mg_canvas_data; @@ -280,59 +276,48 @@ mg_image_data* mg_image_data_from_handle(mg_image handle) // surface API //--------------------------------------------------------------- -#if MG_COMPILE_BACKEND_GL - #if defined(PLATFORM_WIN64) +#if MG_COMPILE_GL + #if PLATFORM_WINDOWS #include"wgl_surface.h" #define gl_surface_create_for_window mg_wgl_surface_create_for_window - #elif defined(PLATFORM_MACOS) -/* - #include"nsgl_surface.h" - #define gl_surface_create_for_window nsgl_surface_create_for_window -*/ #endif #endif -#if MG_COMPILE_BACKEND_GLES +#if MG_COMPILE_GLES #include"egl_surface.h" #endif -#if MG_COMPILE_BACKEND_METAL +#if MG_COMPILE_METAL #include"mtl_surface.h" #endif +#if MG_COMPILE_CANVAS + #if PLATFORM_MACOS + mg_surface_data* mtl_canvas_surface_create_for_window(mp_window window); + #elif PLATFORM_WINDOWS + //TODO + #endif +#endif + bool mg_is_surface_backend_available(mg_surface_api api) { bool result = false; switch(api) { - #if MG_COMPILE_BACKEND_METAL + #if MG_COMPILE_METAL case MG_METAL: #endif - #if MG_COMPILE_BACKEND_GL + + #if MG_COMPILE_GL case MG_GL: #endif - #if MG_COMPILE_BACKEND_GLES + + #if MG_COMPILE_GLES case MG_GLES: #endif - result = true; - break; - default: - break; - } - return(result); -} - -bool mg_is_canvas_backend_available(mg_surface_api api) -{ - bool result = false; - switch(api) - { - #if MG_COMPILE_BACKEND_METAL - case MG_METAL: - #endif - #if MG_COMPILE_BACKEND_GL && defined(PLATFORM_WIN64) - case MG_GL: + #if MG_COMPILE_CANVAS + case MG_CANVAS: #endif result = true; break; @@ -357,24 +342,35 @@ mg_surface mg_surface_create_for_window(mp_window window, mg_surface_api api) switch(api) { - #if MG_COMPILE_BACKEND_GL + #if MG_COMPILE_GL case MG_GL: surface = gl_surface_create_for_window(window); break; #endif - #if MG_COMPILE_BACKEND_GLES + #if MG_COMPILE_GLES case MG_GLES: surface = mg_egl_surface_create_for_window(window); break; #endif - #if MG_COMPILE_BACKEND_METAL + #if MG_COMPILE_METAL case MG_METAL: surface = mg_mtl_surface_create_for_window(window); break; #endif + #if MG_COMPILE_CANVAS + case MG_CANVAS: + + #if PLATFORM_MACOS + surface = mtl_canvas_surface_create_for_window(window); + #elif PLATFORM_WINDOWS + surface = gl_canvas_surface_create_for_window(window); + #endif + break; + #endif + default: break; } @@ -396,7 +392,7 @@ mg_surface mg_surface_create_remote(u32 width, u32 height, mg_surface_api api) switch(api) { - #if MG_COMPILE_BACKEND_GLES + #if MG_COMPILE_GLES case MG_GLES: surface = mg_egl_surface_create_remote(width, height); break; @@ -422,7 +418,7 @@ mg_surface mg_surface_create_host(mp_window window) mg_surface_data* surface = 0; #if PLATFORM_MACOS surface = mg_osx_surface_create_host(window); - #elif PLATFORM_WIN64 + #elif PLATFORM_WINDOWS surface = mg_win32_surface_create_host(window); #endif @@ -439,6 +435,10 @@ void mg_surface_destroy(mg_surface handle) mg_surface_data* surface = mg_surface_data_from_handle(handle); if(surface) { + if(surface->backend && surface->backend->destroy) + { + surface->backend->destroy(surface->backend); + } surface->destroy(surface); mg_handle_recycle(handle.h); } @@ -569,7 +569,28 @@ void mg_surface_host_connect(mg_surface handle, mg_surface_id remoteID) mp_thread_local mg_canvas_data* __mgCurrentCanvas = 0; mp_thread_local mg_canvas __mgCurrentCanvasHandle = {0}; -//TODO: move elsewhere? +//TODO put these elsewhere +bool vec2_equal(vec2 v0, vec2 v1) +{ + return(v0.x == v1.x && v0.y == v1.y); +} + +bool vec2_close(vec2 p0, vec2 p1, f32 tolerance) +{ + f32 norm2 = (p1.x - p0.x)*(p1.x - p0.x) + (p1.y - p0.y)*(p1.y - p0.y); + return(fabs(norm2) < tolerance); +} + +vec2 vec2_mul(f32 f, vec2 v) +{ + return((vec2){f*v.x, f*v.y}); +} + +vec2 vec2_add(vec2 v0, vec2 v1) +{ + return((vec2){v0.x + v1.x, v0.y + v1.y}); +} + mg_mat2x3 mg_mat2x3_mul_m(mg_mat2x3 lhs, mg_mat2x3 rhs) { mg_mat2x3 res; @@ -712,1459 +733,6 @@ void mg_path_push_element(mg_canvas_data* canvas, mg_path_elt elt) mg_path_push_elements(canvas, 1, &elt); } -/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// - -void mg_reset_shape_index(mg_canvas_data* canvas) -{ - canvas->nextShapeIndex = 0; - canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; -} - -void mg_finalize_shape(mg_canvas_data* canvas) -{ - if(canvas->nextShapeIndex) - { - //NOTE: set shape's uv transform for the _current_ shape - vec2 texSize = mg_image_size(canvas->image); - - mp_rect srcRegion = canvas->srcRegion; - - mp_rect destRegion = {canvas->shapeExtents.x, - canvas->shapeExtents.y, - canvas->shapeExtents.z - canvas->shapeExtents.x, - canvas->shapeExtents.w - canvas->shapeExtents.y}; - - mg_mat2x3 srcRegionToImage = {1/texSize.x, 0, srcRegion.x/texSize.x, - 0, 1/texSize.y, srcRegion.y/texSize.y}; - mg_mat2x3 destRegionToSrcRegion = {srcRegion.w/destRegion.w, 0, 0, - 0, srcRegion.h/destRegion.h, 0}; - mg_mat2x3 userToDestRegion = {1, 0, -destRegion.x, - 0, 1, -destRegion.y}; - - mg_mat2x3 screenToUser = mg_mat2x3_inv(canvas->transform); - - mg_mat2x3 uvTransform = srcRegionToImage; - uvTransform = mg_mat2x3_mul_m(uvTransform, destRegionToSrcRegion); - uvTransform = mg_mat2x3_mul_m(uvTransform, userToDestRegion); - uvTransform = mg_mat2x3_mul_m(uvTransform, screenToUser); - - int index = canvas->nextShapeIndex-1; - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - *(mg_mat2x3*)(layout->uvTransformBuffer + index*layout->uvTransformStride) = uvTransform; - - //TODO: transform extents before clipping - mp_rect clip = {maximum(canvas->clip.x, canvas->shapeScreenExtents.x), - maximum(canvas->clip.y, canvas->shapeScreenExtents.y), - minimum(canvas->clip.x + canvas->clip.w, canvas->shapeScreenExtents.z), - minimum(canvas->clip.y + canvas->clip.h, canvas->shapeScreenExtents.w)}; - - *(mp_rect*)(((char*)layout->clipBuffer) + index*layout->clipStride) = clip; - } -} - -u32 mg_next_shape(mg_canvas_data* canvas, mg_attributes* attributes) -{ - mg_finalize_shape(canvas); - - canvas->clip = attributes->clip; - canvas->transform = attributes->transform; - canvas->srcRegion = attributes->srcRegion; - canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; - canvas->shapeScreenExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; - - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - int index = canvas->nextShapeIndex; - canvas->nextShapeIndex++; - - *(mg_color*)(((char*)layout->colorBuffer) + index*layout->colorStride) = attributes->color; - *(bool*)(((char*)layout->texturedBuffer) + index*layout->texturedStride) = !mg_image_is_nil(attributes->image); - - return(index); -} - -//TODO(martin): rename with something more explicit -u32 mg_vertices_base_index(mg_canvas_data* canvas) -{ - return(canvas->vertexCount); -} - -int* mg_reserve_indices(mg_canvas_data* canvas, u32 indexCount) -{ - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - - //TODO: do something here... - ASSERT(canvas->indexCount + indexCount < layout->maxIndexCount); - - int* base = ((int*)layout->indexBuffer) + canvas->indexCount; - canvas->indexCount += indexCount; - return(base); -} - -void mg_push_vertex_cubic(mg_canvas_data* canvas, vec2 pos, vec4 cubic) -{ - canvas->shapeExtents.x = minimum(canvas->shapeExtents.x, pos.x); - canvas->shapeExtents.y = minimum(canvas->shapeExtents.y, pos.y); - canvas->shapeExtents.z = maximum(canvas->shapeExtents.z, pos.x); - canvas->shapeExtents.w = maximum(canvas->shapeExtents.w, pos.y); - - vec2 screenPos = mg_mat2x3_mul(canvas->transform, pos); - - canvas->shapeScreenExtents.x = minimum(canvas->shapeScreenExtents.x, screenPos.x); - canvas->shapeScreenExtents.y = minimum(canvas->shapeScreenExtents.y, screenPos.y); - canvas->shapeScreenExtents.z = maximum(canvas->shapeScreenExtents.z, screenPos.x); - canvas->shapeScreenExtents.w = maximum(canvas->shapeScreenExtents.w, screenPos.y); - - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - ASSERT(canvas->vertexCount < layout->maxVertexCount); - ASSERT(canvas->nextShapeIndex > 0); - - int shapeIndex = maximum(0, canvas->nextShapeIndex-1); - u32 index = canvas->vertexCount; - canvas->vertexCount++; - - *(vec2*)(((char*)layout->posBuffer) + index*layout->posStride) = screenPos; - *(vec4*)(((char*)layout->cubicBuffer) + index*layout->cubicStride) = cubic; - *(u32*)(((char*)layout->shapeIndexBuffer) + index*layout->shapeIndexStride) = shapeIndex; -} - -void mg_push_vertex(mg_canvas_data* canvas, vec2 pos) -{ - mg_push_vertex_cubic(canvas, pos, (vec4){1, 1, 1, 1}); -} -//----------------------------------------------------------------------------------------------------------- -// Path Filling -//----------------------------------------------------------------------------------------------------------- -//NOTE(martin): forward declarations -void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4]); - -//NOTE(martin): quadratics filling - -void mg_render_fill_quadratic(mg_canvas_data* canvas, vec2 p[3]) -{ - u32 baseIndex = mg_vertices_base_index(canvas); - - i32* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex_cubic(canvas, (vec2){p[0].x, p[0].y}, (vec4){0, 0, 0, 1}); - mg_push_vertex_cubic(canvas, (vec2){p[1].x, p[1].y}, (vec4){0.5, 0, 0.5, 1}); - mg_push_vertex_cubic(canvas, (vec2){p[2].x, p[2].y}, (vec4){1, 1, 1, 1}); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; -} - -//NOTE(martin): cubic filling - -void mg_split_and_fill_cubic(mg_canvas_data* canvas, vec2 p[4], f32 tSplit) -{ - //DEBUG - __mgCurrentCanvas->splitCount++; - - int subVertexCount = 0; - int subIndexCount = 0; - - f32 OneMinusTSplit = 1-tSplit; - - vec2 q0 = {OneMinusTSplit*p[0].x + tSplit*p[1].x, - OneMinusTSplit*p[0].y + tSplit*p[1].y}; - - vec2 q1 = {OneMinusTSplit*p[1].x + tSplit*p[2].x, - OneMinusTSplit*p[1].y + tSplit*p[2].y}; - - vec2 q2 = {OneMinusTSplit*p[2].x + tSplit*p[3].x, - OneMinusTSplit*p[2].y + tSplit*p[3].y}; - - vec2 r0 = {OneMinusTSplit*q0.x + tSplit*q1.x, - OneMinusTSplit*q0.y + tSplit*q1.y}; - - vec2 r1 = {OneMinusTSplit*q1.x + tSplit*q2.x, - OneMinusTSplit*q1.y + tSplit*q2.y}; - - vec2 split = {OneMinusTSplit*r0.x + tSplit*r1.x, - OneMinusTSplit*r0.y + tSplit*r1.y};; - - vec2 subPointsLow[4] = {p[0], q0, r0, split}; - vec2 subPointsHigh[4] = {split, r1, q2, p[3]}; - - //NOTE(martin): add base triangle - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex(canvas, (vec2){p[0].x, p[0].y}); - mg_push_vertex(canvas, (vec2){split.x, split.y}); - mg_push_vertex(canvas, (vec2){p[3].x, p[3].y}); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - - mg_render_fill_cubic(canvas, subPointsLow); - mg_render_fill_cubic(canvas, subPointsHigh); -} - -int mg_cubic_outside_test(vec4 c) -{ - int res = (c.x*c.x*c.x - c.y*c.z < 0) ? -1 : 1; - return(res); -} - -void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4]) -{ - vec4 testCoords[4]; - - /*NOTE(martin): first convert the control points to power basis, multiplying by M3 - - | 1 0 0 0| - M3 = |-3 3 0 0| - | 3 -6 3 0| - |-1 3 -3 1| - ie: - c0 = p0 - c1 = -3*p0 + 3*p1 - c2 = 3*p0 - 6*p1 + 3*p2 - c3 = -p0 + 3*p1 - 3*p2 + p3 - */ - f32 c1x = 3.0*p[1].x - 3.0*p[0].x; - f32 c1y = 3.0*p[1].y - 3.0*p[0].y; - - f32 c2x = 3.0*p[0].x + 3.0*p[2].x - 6.0*p[1].x; - f32 c2y = 3.0*p[0].y + 3.0*p[2].y - 6.0*p[1].y; - - f32 c3x = 3.0*p[1].x - 3.0*p[2].x + p[3].x - p[0].x; - f32 c3y = 3.0*p[1].y - 3.0*p[2].y + p[3].y - p[0].y; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //TODO(martin): we shouldn't need scaling here since now we're doing our shader math in fixed point? - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - c1x /= 10; - c1y /= 10; - c2x /= 10; - c2y /= 10; - c3x /= 10; - c3y /= 10; - - /*NOTE(martin): - now, compute determinants d0, d1, d2, d3, which gives the coefficients of the - inflection points polynomial: - - I(t, s) = d0*t^3 - 3*d1*t^2*s + 3*d2*t*s^2 - d3*s^3 - - The roots of this polynomial are the inflection points of the parametric curve, in homogeneous - coordinates (ie we can have an inflection point at inifinity with s=0). - - |x3 y3 w3| |x3 y3 w3| |x3 y3 w3| |x2 y2 w2| - d0 = det |x2 y2 w2| d1 = -det |x2 y2 w2| d2 = det |x1 y1 w1| d3 = -det |x1 y1 w1| - |x1 y1 w1| |x0 y0 w0| |x0 y0 w0| |x0 y0 w0| - - In our case, the pi.w equal 1 (no point at infinity), so _in_the_power_basis_, w1 = w2 = w3 = 0 and w0 = 1 - (which also means d0 = 0) - */ - - f32 d1 = c3y*c2x - c3x*c2y; - f32 d2 = c3x*c1y - c3y*c1x; - f32 d3 = c2y*c1x - c2x*c1y; - - //NOTE(martin): compute the second factor of the discriminant discr(I) = d1^2*(3*d2^2 - 4*d3*d1) - f32 discrFactor2 = 3.0*Square(d2) - 4.0*d3*d1; - - //NOTE(martin): each following case gives the number of roots, hence the category of the parametric curve - if(fabs(d1) < 0.1 && fabs(d2) < 0.1 && d3 != 0) - { - //NOTE(martin): quadratic degenerate case - //NOTE(martin): compute quadratic curve control point, which is at p0 + 1.5*(p1-p0) = 1.5*p1 - 0.5*p0 - vec2 quadControlPoints[3] = { p[0], - {1.5*p[1].x - 0.5*p[0].x, 1.5*p[1].y - 0.5*p[0].y}, - p[3]}; - - mg_render_fill_quadratic(canvas, quadControlPoints); - return; - } - else if( (discrFactor2 > 0 && d1 != 0) - ||(discrFactor2 == 0 && d1 != 0)) - { - //NOTE(martin): serpentine curve or cusp with inflection at infinity - // (these two cases are handled the same way). - //NOTE(martin): compute the solutions (tl, sl), (tm, sm), and (tn, sn) of the inflection point equation - f32 tl = d2 + sqrt(discrFactor2/3); - f32 sl = 2*d1; - f32 tm = d2 - sqrt(discrFactor2/3); - f32 sm = sl; - - /*NOTE(martin): - the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: - - | tl*tm tl^3 tm^3 1 | - | -sm*tl - sl*tm -3sl*tl^2 -3*sm*tm^2 0 | - | sl*sm 3*sl^2*tl 3*sm^2*tm 0 | - | 0 -sl^3 -sm^3 0 | - - This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n - which are assigned as a 4D image coordinates to control points. - - - | 1 0 0 0 | - M3^(-1) = | 1 1/3 0 0 | - | 1 2/3 1/3 0 | - | 1 1 1 1 | - */ - testCoords[0].x = tl*tm; - testCoords[0].y = Cube(tl); - testCoords[0].z = Cube(tm); - - testCoords[1].x = tl*tm - (sm*tl + sl*tm)/3; - testCoords[1].y = Cube(tl) - sl*Square(tl); - testCoords[1].z = Cube(tm) - sm*Square(tm); - - testCoords[2].x = tl*tm - (sm*tl + sl*tm)*2/3 + sl*sm/3; - testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; - testCoords[2].z = Cube(tm) - 2*sm*Square(tm) + Square(sm)*tm; - - testCoords[3].x = tl*tm - (sm*tl + sl*tm) + sl*sm; - testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); - testCoords[3].z = Cube(tm) - 3*sm*Square(tm) + 3*Square(sm)*tm - Cube(sm); - } - else if(discrFactor2 < 0 && d1 != 0) - { - //NOTE(martin): loop curve - f32 td = d2 + sqrt(-discrFactor2); - f32 sd = 2*d1; - f32 te = d2 - sqrt(-discrFactor2); - f32 se = sd; - - //NOTE(martin): if one of the parameters (td/sd) or (te/se) is in the interval [0,1], the double point - // is inside the control points convex hull and would cause a shading anomaly. If this is - // the case, subdivide the curve at that point - - //TODO: study edge case where td/sd ~ 1 or 0 (which causes an infinite recursion in split and fill). - // quick fix for now is adding a little slop in the check... - - if(sd != 0 && td/sd < 0.99 && td/sd > 0.01) - { - mg_split_and_fill_cubic(canvas, p, td/sd); - return; - } - if(se != 0 && te/se < 0.99 && te/se > 0.01) - { - mg_split_and_fill_cubic(canvas, p, te/se); - return; - } - - /*NOTE(martin): - the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: - - | td*te td^2*te td*te^2 1 | - | -se*td - sd*te -se*td^2 - 2sd*te*td -sd*te^2 - 2*se*td*te 0 | - | sd*se te*sd^2 + 2*se*td*sd td*se^2 + 2*sd*te*se 0 | - | 0 -sd^2*se -sd*se^2 0 | - - This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n - which are assigned as a 4D image coordinates to control points. - - - | 1 0 0 0 | - M3^(-1) = | 1 1/3 0 0 | - | 1 2/3 1/3 0 | - | 1 1 1 1 | - */ - testCoords[0].x = td*te; - testCoords[0].y = Square(td)*te; - testCoords[0].z = td*Square(te); - - testCoords[1].x = td*te - (se*td + sd*te)/3.0; - testCoords[1].y = Square(td)*te - (se*Square(td) + 2.*sd*te*td)/3.0; - testCoords[1].z = td*Square(te) - (sd*Square(te) + 2*se*td*te)/3.0; - - testCoords[2].x = td*te - 2.0*(se*td + sd*te)/3.0 + sd*se/3.0; - testCoords[2].y = Square(td)*te - 2.0*(se*Square(td) + 2.0*sd*te*td)/3.0 + (te*Square(sd) + 2.0*se*td*sd)/3.0; - testCoords[2].z = td*Square(te) - 2.0*(sd*Square(te) + 2.0*se*td*te)/3.0 + (td*Square(se) + 2.0*sd*te*se)/3.0; - - testCoords[3].x = td*te - (se*td + sd*te) + sd*se; - testCoords[3].y = Square(td)*te - (se*Square(td) + 2.0*sd*te*td) + (te*Square(sd) + 2.0*se*td*sd) - Square(sd)*se; - testCoords[3].z = td*Square(te) - (sd*Square(te) + 2.0*se*td*te) + (td*Square(se) + 2.0*sd*te*se) - sd*Square(se); - } - else if(d1 == 0 && d2 != 0) - { - //NOTE(martin): cusp with cusp at infinity - - f32 tl = d3; - f32 sl = 3*d2; - - /*NOTE(martin): - the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: - - | tl tl^3 1 1 | - | -sl -3sl*tl^2 0 0 | - | 0 3*sl^2*tl 0 0 | - | 0 -sl^3 0 0 | - - This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n - which are assigned as a 4D image coordinates to control points. - - - | 1 0 0 0 | - M3^(-1) = | 1 1/3 0 0 | - | 1 2/3 1/3 0 | - | 1 1 1 1 | - */ - - testCoords[0].x = tl; - testCoords[0].y = Cube(tl); - testCoords[0].z = 1; - - testCoords[1].x = tl - sl/3; - testCoords[1].y = Cube(tl) - sl*Square(tl); - testCoords[1].z = 1; - - testCoords[2].x = tl - sl*2/3; - testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; - testCoords[2].z = 1; - - testCoords[3].x = tl - sl; - testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); - testCoords[3].z = 1; - } - else if(d1 == 0 && d2 == 0 && d3 == 0) - { - //NOTE(martin): line or point degenerate case, ignored - return; - } - else - { - //TODO(martin): handle error ? put some epsilon slack on the conditions ? - ASSERT(0, "not implemented yet !"); - return; - } - - //NOTE(martin): compute convex hull indices using Gift wrapping / Jarvis' march algorithm - int convexHullIndices[4]; - int leftMostPointIndex = 0; - - for(int i=0; i<4; i++) - { - if(p[i].x < p[leftMostPointIndex].x) - { - leftMostPointIndex = i; - } - } - int currentPointIndex = leftMostPointIndex; - int i=0; - int convexHullCount = 0; - - do - { - convexHullIndices[i] = currentPointIndex; - convexHullCount++; - int bestGuessIndex = 0; - - for(int j=0; j<4; j++) - { - vec2 bestGuessEdge = {.x = p[bestGuessIndex].x - p[currentPointIndex].x, - .y = p[bestGuessIndex].y - p[currentPointIndex].y}; - - vec2 nextGuessEdge = {.x = p[j].x - p[currentPointIndex].x, - .y = p[j].y - p[currentPointIndex].y}; - - //NOTE(martin): if control point j is on the right of current edge, it is a best guess - // (in case of colinearity we choose the point which is farthest from the current point) - - f32 crossProduct = bestGuessEdge.x*nextGuessEdge.y - bestGuessEdge.y*nextGuessEdge.x; - - if( bestGuessIndex == currentPointIndex - || crossProduct < 0) - { - bestGuessIndex = j; - } - else if(crossProduct == 0) - { - - //NOTE(martin): if vectors v1, v2 are colinear and distinct, and ||v1|| > ||v2||, - // either abs(v1.x) > abs(v2.x) or abs(v1.y) > abs(v2.y) - // so we don't actually need to compute their norm to select the greatest - // (and if v1 and v2 are equal we don't have to update our best guess.) - - //TODO(martin): in case of colinearity we should rather select the edge that has the greatest dot product with last edge ?? - - if(fabs(nextGuessEdge.x) > fabs(bestGuessEdge.x) - || fabs(nextGuessEdge.y) > fabs(bestGuessEdge.y)) - { - bestGuessIndex = j; - } - } - } - i++; - currentPointIndex = bestGuessIndex; - - } while(currentPointIndex != leftMostPointIndex && i<4); - - //NOTE(martin): triangulation and inside/outside tests. In the shader, the outside is defined by s*(k^3 - lm) > 0 - // ie the 4th coordinate s flips the inside/outside test. - // We affect s such that the covered are is between the curve and the line joining p0 and p3. - - //TODO: quick fix, maybe later cull degenerate hulls beforehand - if(convexHullCount <= 2) - { - //NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing. - return; - } - else if(convexHullCount == 3) - { - /*NOTE(martin): - We have 3 case here: - 1) Endpoints are coincidents. We push on triangle, and test an intermediate point for orientation. - 2) The point not on the hull is an endpoint. We push two triangle (p0, p3, p1) and (p0, p3, p2). We test the intermediate - points to know if we must flip the orientation of the curve. - 3) The point not on the hull is an intermediate point: we emit one triangle. We test the intermediate point on the hull - to know if we must flip the orientation of the curve. - */ - if( p[0].x == p[3].x - && p[0].y == p[3].y) - { - //NOTE: case 1: endpoints are coincidents - int outsideTest = mg_cubic_outside_test(testCoords[1]); - - //NOTE: push triangle - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest}); - mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest}); - mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest}); - - for(int i=0; i<3; i++) - { - indices[i] = baseIndex + i; - } - } - else - { - //NOTE: find point not on the hull - int insidePointIndex = -1; - { - bool present[4] = {0}; - for(int i=0; i<3; i++) - { - present[convexHullIndices[i]] = true; - } - for(int i=0; i<4; i++) - { - if(!present[i]) - { - insidePointIndex = i; - break; - } - } - } - DEBUG_ASSERT(insidePointIndex >= 0 && insidePointIndex < 4); - - if(insidePointIndex == 0 || insidePointIndex == 3) - { - //NOTE: case 2: the point inside the hull is an endpoint - - int outsideTest0 = mg_cubic_outside_test(testCoords[1]); - int outsideTest1 = mg_cubic_outside_test(testCoords[2]); - - //NOTE: push triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest0}); - mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest0}); - mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest0}); - mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest1}); - mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest1}); - mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest1}); - - for(int i=0; i<6; i++) - { - indices[i] = baseIndex + i; - } - } - else - { - int testIndex = (insidePointIndex == 1) ? 2 : 1; - int outsideTest = mg_cubic_outside_test(testCoords[testIndex]); - - //NOTE: push triangle - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 3); - - for(int i=0; i<3; i++) - { - mg_push_vertex_cubic(canvas, - p[convexHullIndices[i]], - (vec4){vec4_expand_xyz(testCoords[convexHullIndices[i]]), - outsideTest}); - } - - for(int i=0; i<3; i++) - { - indices[i] = baseIndex + i; - } - } - } - } - else - { - DEBUG_ASSERT(convexHullCount == 4); - /*NOTE(martin): - We build a fan from the hull, starting from an endpoint. For each triangle, we test the vertex that is an intermediate - control point for orientation - */ - int endPointIndex = -1; - for(int i=0; i<4; i++) - { - if(convexHullIndices[i] == 0 || convexHullIndices[i] == 3) - { - endPointIndex = i; - break; - } - } - ASSERT(endPointIndex >= 0); - - int fanIndices[6] = {convexHullIndices[endPointIndex], - convexHullIndices[(endPointIndex + 1)%4], - convexHullIndices[(endPointIndex + 2)%4], - convexHullIndices[endPointIndex], - convexHullIndices[(endPointIndex + 2)%4], - convexHullIndices[(endPointIndex + 3)%4]}; - - //NOTE: fan indices on the hull are (0,1,2)(0,2,3). So if the 3rd vertex of the hull is an intermediate point it works - // as a test vertex for both triangles. Otherwise, the test vertices on the fan are 1 and 5. - int outsideTest0 = 1; - int outsideTest1 = 1; - - if( fanIndices[2] == 1 - ||fanIndices[2] == 2) - { - outsideTest0 = outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[2]]); - } - else - { - DEBUG_ASSERT(fanIndices[1] == 1 || fanIndices[1] == 2); - DEBUG_ASSERT(fanIndices[5] == 1 || fanIndices[5] == 2); - - outsideTest0 = mg_cubic_outside_test(testCoords[fanIndices[1]]); - outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[5]]); - } - - //NOTE: push triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - for(int i=0; i<3; i++) - { - mg_push_vertex_cubic(canvas, p[fanIndices[i]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i]]), outsideTest0}); - } - for(int i=0; i<3; i++) - { - mg_push_vertex_cubic(canvas, p[fanIndices[i+3]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i+3]]), outsideTest1}); - } - - for(int i=0; i<6; i++) - { - indices[i] = baseIndex + i; - } - } -} - -//NOTE(martin): global path fill - -void mg_render_fill(mg_canvas_data* canvas, mg_path_elt* elements, mg_path_descriptor* path) -{ - u32 eltCount = path->count; - vec2 startPoint = path->startPoint; - vec2 endPoint = path->startPoint; - vec2 currentPoint = path->startPoint; - - for(int eltIndex=0; eltIndexp[0], elt->p[1], elt->p[2]}; - - switch(elt->type) - { - case MG_PATH_MOVE: - { - startPoint = elt->p[0]; - endPoint = elt->p[0]; - currentPoint = endPoint; - continue; - } break; - - case MG_PATH_LINE: - { - endPoint = controlPoints[1]; - } break; - - case MG_PATH_QUADRATIC: - { - mg_render_fill_quadratic(canvas, controlPoints); - endPoint = controlPoints[2]; - - } break; - - case MG_PATH_CUBIC: - { - mg_render_fill_cubic(canvas, controlPoints); - endPoint = controlPoints[3]; - } break; - } - - //NOTE(martin): now fill interior triangle - u32 baseIndex = mg_vertices_base_index(canvas); - int* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex(canvas, startPoint); - mg_push_vertex(canvas, currentPoint); - mg_push_vertex(canvas, endPoint); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - - currentPoint = endPoint; - } -} - -//----------------------------------------------------------------------------------------------------------- -// Path Stroking -//----------------------------------------------------------------------------------------------------------- - -void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], mg_attributes* attributes) -{ - //NOTE(martin): get normals multiplied by halfWidth - f32 halfW = attributes->width/2; - - vec2 n0 = {p[0].y - p[1].y, - p[1].x - p[0].x}; - f32 norm0 = sqrt(n0.x*n0.x + n0.y*n0.y); - n0.x *= halfW/norm0; - n0.y *= halfW/norm0; - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, (vec2){p[0].x + n0.x, p[0].y + n0.y}); - mg_push_vertex(canvas, (vec2){p[1].x + n0.x, p[1].y + n0.y}); - mg_push_vertex(canvas, (vec2){p[1].x - n0.x, p[1].y - n0.y}); - mg_push_vertex(canvas, (vec2){p[0].x - n0.x, p[0].y - n0.y}); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -//TODO put these elsewhere -bool vec2_equal(vec2 v0, vec2 v1) -{ - return(v0.x == v1.x && v0.y == v1.y); -} - -bool vec2_close(vec2 p0, vec2 p1, f32 tolerance) -{ - f32 norm2 = (p1.x - p0.x)*(p1.x - p0.x) + (p1.y - p0.y)*(p1.y - p0.y); - return(fabs(norm2) < tolerance); -} - -vec2 vec2_mul(f32 f, vec2 v) -{ - return((vec2){f*v.x, f*v.y}); -} - -vec2 vec2_add(vec2 v0, vec2 v1) -{ - return((vec2){v0.x + v1.x, v0.y + v1.y}); -} - -bool mg_intersect_hull_legs(vec2 p0, vec2 p1, vec2 p2, vec2 p3, vec2* intersection) -{ - /*NOTE: check intersection of lines (p0-p1) and (p2-p3) - - P = p0 + u(p1-p0) - P = p2 + w(p3-p2) - */ - bool found = false; - - f32 den = (p0.x - p1.x)*(p2.y - p3.y) - (p0.y - p1.y)*(p2.x - p3.x); - if(fabs(den) > 0.0001) - { - f32 u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/den; - f32 w = ((p0.x - p2.x)*(p0.y - p1.y) - (p0.y - p2.y)*(p0.x - p1.x))/den; - - intersection->x = p0.x + u*(p1.x - p0.x); - intersection->y = p0.y + u*(p1.y - p0.y); - found = true; - } - return(found); -} - -bool mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) -{ - //NOTE: we should have no more than two coincident points here. This means the leg between - // those two points can't be offset, but we can set a double point at the start of first leg, - // end of first leg, or we can join the first and last leg to create a missing middle one - - vec2 legs[3][2] = {0}; - bool valid[3] = {0}; - - for(int i=0; i= 1e-6) - { - n = vec2_mul(offset/norm, n); - legs[i][0] = vec2_add(p[i], n); - legs[i][1] = vec2_add(p[i+1], n); - valid[i] = true; - } - } - - //NOTE: now we find intersections - - // first point is either the start of the first or second leg - if(valid[0]) - { - result[0] = legs[0][0]; - } - else - { - ASSERT(valid[1]); - result[0] = legs[1][0]; - } - - for(int i=1; isplitCount++; - - //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm - // the q_n are the points along the hull's segments at parameter t - // s is the split point. - - f32 oneMt = 1-t; - - vec2 q0 = {oneMt*p[0].x + t*p[1].x, - oneMt*p[0].y + t*p[1].y}; - - vec2 q1 = {oneMt*p[1].x + t*p[2].x, - oneMt*p[1].y + t*p[2].y}; - - vec2 s = {oneMt*q0.x + t*q1.x, - oneMt*q0.y + t*q1.y}; - - outLeft[0] = p[0]; - outLeft[1] = q0; - outLeft[2] = s; - - outRight[0] = s; - outRight[1] = q1; - outRight[2] = p[2]; -} - - -void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[3], mg_attributes* attributes) -{ - //NOTE: check for degenerate line case - const f32 equalEps = 1e-3; - if(vec2_close(p[0], p[1], equalEps)) - { - mg_render_stroke_line(canvas, p+1, attributes); - return; - } - else if(vec2_close(p[1], p[2], equalEps)) - { - mg_render_stroke_line(canvas, p, attributes); - return; - } - - #define CHECK_SAMPLE_COUNT 5 - f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6}; - - vec2 positiveOffsetHull[3]; - vec2 negativeOffsetHull[3]; - - if( !mg_offset_hull(3, p, positiveOffsetHull, 0.5 * attributes->width) - ||!mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width)) - { - //NOTE: offsetting the hull failed, split the curve - vec2 splitLeft[3]; - vec2 splitRight[3]; - mg_quadratic_split(p, 0.5, splitLeft, splitRight); - mg_render_stroke_quadratic(canvas, splitLeft, attributes); - mg_render_stroke_quadratic(canvas, splitRight, attributes); - } - else - { - //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance - // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: - // - // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 - // - // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter - - //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot - f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); - f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); - f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); - - f32 maxOvershoot = 0; - f32 maxOvershootParameter = 0; - - for(int i=0; i maxOvershoot) - { - maxOvershoot = overshoot; - maxOvershootParameter = t; - } - } - - if(maxOvershoot > 0) - { - vec2 splitLeft[3]; - vec2 splitRight[3]; - mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); - mg_render_stroke_quadratic(canvas, splitLeft, attributes); - mg_render_stroke_quadratic(canvas, splitRight, attributes); - } - else - { - //NOTE(martin): push the actual fill commands for the offset contour - - mg_next_shape(canvas, attributes); - - mg_render_fill_quadratic(canvas, positiveOffsetHull); - mg_render_fill_quadratic(canvas, negativeOffsetHull); - - //NOTE(martin): add base triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, positiveOffsetHull[0]); - mg_push_vertex(canvas, positiveOffsetHull[2]); - mg_push_vertex(canvas, negativeOffsetHull[2]); - mg_push_vertex(canvas, negativeOffsetHull[0]); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - } - } - #undef CHECK_SAMPLE_COUNT -} - -vec2 mg_cubic_get_point(vec2 p[4], f32 t) -{ - vec2 r; - - f32 oneMt = 1-t; - f32 oneMt2 = Square(oneMt); - f32 oneMt3 = oneMt2*oneMt; - f32 t2 = Square(t); - f32 t3 = t2*t; - - r.x = oneMt3*p[0].x + 3*oneMt2*t*p[1].x + 3*oneMt*t2*p[2].x + t3*p[3].x; - r.y = oneMt3*p[0].y + 3*oneMt2*t*p[1].y + 3*oneMt*t2*p[2].y + t3*p[3].y; - - return(r); -} - -void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4]) -{ - //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm - // the q_n are the points along the hull's segments at parameter t - // the r_n are the points along the (q_n, q_n+1) segments at parameter t - // s is the split point. - - vec2 q0 = {(1-t)*p[0].x + t*p[1].x, - (1-t)*p[0].y + t*p[1].y}; - - vec2 q1 = {(1-t)*p[1].x + t*p[2].x, - (1-t)*p[1].y + t*p[2].y}; - - vec2 q2 = {(1-t)*p[2].x + t*p[3].x, - (1-t)*p[2].y + t*p[3].y}; - - vec2 r0 = {(1-t)*q0.x + t*q1.x, - (1-t)*q0.y + t*q1.y}; - - vec2 r1 = {(1-t)*q1.x + t*q2.x, - (1-t)*q1.y + t*q2.y}; - - vec2 s = {(1-t)*r0.x + t*r1.x, - (1-t)*r0.y + t*r1.y};; - - outLeft[0] = p[0]; - outLeft[1] = q0; - outLeft[2] = r0; - outLeft[3] = s; - - outRight[0] = s; - outRight[1] = r1; - outRight[2] = q2; - outRight[3] = p[3]; -} - -void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes) -{ - //NOTE: check degenerate line cases - f32 equalEps = 1e-3; - - if( (vec2_close(p[0], p[1], equalEps) && vec2_close(p[2], p[3], equalEps)) - ||(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[2], equalEps)) - ||(vec2_close(p[1], p[2], equalEps) && vec2_close(p[2], p[3], equalEps))) - { - vec2 line[2] = {p[0], p[3]}; - mg_render_stroke_line(canvas, line, attributes); - return; - } - else if(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[3], equalEps)) - { - vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[2]))}; - mg_render_stroke_line(canvas, line, attributes); - return; - } - else if(vec2_close(p[0], p[2], equalEps) && vec2_close(p[2], p[3], equalEps)) - { - vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[1]))}; - mg_render_stroke_line(canvas, line, attributes); - return; - } - - #define CHECK_SAMPLE_COUNT 5 - f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6}; - - vec2 positiveOffsetHull[4]; - vec2 negativeOffsetHull[4]; - - if( !mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width) - || !mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width)) - { - vec2 splitLeft[4]; - vec2 splitRight[4]; - mg_cubic_split(p, 0.5, splitLeft, splitRight); - mg_render_stroke_cubic(canvas, splitLeft, attributes); - mg_render_stroke_cubic(canvas, splitRight, attributes); - return; - } - - //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance - // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: - // - // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 - // - // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter - - //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot - f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); - f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); - f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); - - f32 maxOvershoot = 0; - f32 maxOvershootParameter = 0; - - for(int i=0; i maxOvershoot) - { - maxOvershoot = overshoot; - maxOvershootParameter = t; - } - } - - if(maxOvershoot > 0) - { - vec2 splitLeft[4]; - vec2 splitRight[4]; - mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); - mg_render_stroke_cubic(canvas, splitLeft, attributes); - mg_render_stroke_cubic(canvas, splitRight, attributes); - - //TODO: render joint between the split curves - } - else - { - //NOTE(martin): push the actual fill commands for the offset contour - mg_next_shape(canvas, attributes); - - mg_render_fill_cubic(canvas, positiveOffsetHull); - mg_render_fill_cubic(canvas, negativeOffsetHull); - - //NOTE(martin): add base triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, positiveOffsetHull[0]); - mg_push_vertex(canvas, positiveOffsetHull[3]); - mg_push_vertex(canvas, negativeOffsetHull[3]); - mg_push_vertex(canvas, negativeOffsetHull[0]); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - } - #undef CHECK_SAMPLE_COUNT -} - -void mg_stroke_cap(mg_canvas_data* canvas, vec2 p0, vec2 direction, mg_attributes* attributes) -{ - //NOTE(martin): compute the tangent and normal vectors (multiplied by half width) at the cap point - - f32 dn = sqrt(Square(direction.x) + Square(direction.y)); - f32 alpha = 0.5 * attributes->width/dn; - - vec2 n0 = {-alpha*direction.y, - alpha*direction.x}; - - vec2 m0 = {alpha*direction.x, - alpha*direction.y}; - - mg_next_shape(canvas, attributes); - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, (vec2){p0.x + n0.x, p0.y + n0.y}); - mg_push_vertex(canvas, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y}); - mg_push_vertex(canvas, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y}); - mg_push_vertex(canvas, (vec2){p0.x - n0.x, p0.y - n0.y}); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -void mg_stroke_joint(mg_canvas_data* canvas, - vec2 p0, - vec2 t0, - vec2 t1, - mg_attributes* attributes) -{ - //NOTE(martin): compute the normals at the joint point - f32 norm_t0 = sqrt(Square(t0.x) + Square(t0.y)); - f32 norm_t1 = sqrt(Square(t1.x) + Square(t1.y)); - - vec2 n0 = {-t0.y, t0.x}; - n0.x /= norm_t0; - n0.y /= norm_t0; - - vec2 n1 = {-t1.y, t1.x}; - n1.x /= norm_t1; - n1.y /= norm_t1; - - //NOTE(martin): the sign of the cross product determines if the normals are facing outwards or inwards the angle. - // we flip them to face outwards if needed - f32 crossZ = n0.x*n1.y - n0.y*n1.x; - if(crossZ > 0) - { - n0.x *= -1; - n0.y *= -1; - n1.x *= -1; - n1.y *= -1; - } - - mg_next_shape(canvas, attributes); - - //NOTE(martin): use the same code as hull offset to find mitter point... - /*NOTE(martin): let vector u = (n0+n1) and vector v = pIntersect - p1 - then v = u * (2*offset / norm(u)^2) - (this can be derived from writing the pythagoras theorems in the triangles of the joint) - */ - f32 halfW = 0.5 * attributes->width; - vec2 u = {n0.x + n1.x, n0.y + n1.y}; - f32 uNormSquare = u.x*u.x + u.y*u.y; - f32 alpha = attributes->width / uNormSquare; - vec2 v = {u.x * alpha, u.y * alpha}; - - f32 excursionSquare = uNormSquare * Square(alpha - attributes->width/4); - - if( attributes->joint == MG_JOINT_MITER - && excursionSquare <= Square(attributes->maxJointExcursion)) - { - vec2 mitterPoint = {p0.x + v.x, p0.y + v.y}; - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, p0); - mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}); - mg_push_vertex(canvas, mitterPoint); - mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - } - else - { - //NOTE(martin): add a bevel joint - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex(canvas, p0); - mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}); - mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}); - - DEBUG_ASSERT(!isnan(n0.x) && !isnan(n0.y) && !isnan(n1.x) && !isnan(n1.y)); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - } -} - -void mg_render_stroke_element(mg_canvas_data* canvas, - mg_path_elt* element, - mg_attributes* attributes, - vec2 currentPoint, - vec2* startTangent, - vec2* endTangent, - vec2* endPoint) -{ - vec2 controlPoints[4] = {currentPoint, element->p[0], element->p[1], element->p[2]}; - int endPointIndex = 0; - mg_next_shape(canvas, attributes); - - switch(element->type) - { - case MG_PATH_LINE: - mg_render_stroke_line(canvas, controlPoints, attributes); - endPointIndex = 1; - break; - - case MG_PATH_QUADRATIC: - mg_render_stroke_quadratic(canvas, controlPoints, attributes); - endPointIndex = 2; - break; - - case MG_PATH_CUBIC: - mg_render_stroke_cubic(canvas, controlPoints, attributes); - endPointIndex = 3; - break; - - case MG_PATH_MOVE: - ASSERT(0, "should be unreachable"); - break; - } - - //NOTE: ensure tangents are properly computed even in presence of coincident points - //TODO: see if we can do this in a less hacky way - - for(int i=1; i<4; i++) - { - if( controlPoints[i].x != controlPoints[0].x - || controlPoints[i].y != controlPoints[0].y) - { - *startTangent = (vec2){.x = controlPoints[i].x - controlPoints[0].x, - .y = controlPoints[i].y - controlPoints[0].y}; - break; - } - } - *endPoint = controlPoints[endPointIndex]; - - for(int i=endPointIndex-1; i>=0; i++) - { - if( controlPoints[i].x != endPoint->x - || controlPoints[i].y != endPoint->y) - { - *endTangent = (vec2){.x = endPoint->x - controlPoints[i].x, - .y = endPoint->y - controlPoints[i].y}; - break; - } - } - - DEBUG_ASSERT(startTangent->x != 0 || startTangent->y != 0); -} - -u32 mg_render_stroke_subpath(mg_canvas_data* canvas, - mg_path_elt* elements, - mg_path_descriptor* path, - mg_attributes* attributes, - u32 startIndex, - vec2 startPoint) -{ - u32 eltCount = path->count; - DEBUG_ASSERT(startIndex < eltCount); - - vec2 currentPoint = startPoint; - vec2 endPoint = {0, 0}; - vec2 previousEndTangent = {0, 0}; - vec2 firstTangent = {0, 0}; - vec2 startTangent = {0, 0}; - vec2 endTangent = {0, 0}; - - //NOTE(martin): render first element and compute first tangent - mg_render_stroke_element(canvas, elements + startIndex, attributes, currentPoint, &startTangent, &endTangent, &endPoint); - - firstTangent = startTangent; - previousEndTangent = endTangent; - currentPoint = endPoint; - - //NOTE(martin): render subsequent elements along with their joints - u32 eltIndex = startIndex + 1; - for(; - eltIndexjoint != MG_JOINT_NONE) - { - mg_stroke_joint(canvas, currentPoint, previousEndTangent, startTangent, attributes); - } - previousEndTangent = endTangent; - currentPoint = endPoint; - } - u32 subPathEltCount = eltIndex - (startIndex+1); - - //NOTE(martin): draw end cap / joint. We ensure there's at least two segments to draw a closing joint - if( subPathEltCount > 1 - && startPoint.x == endPoint.x - && startPoint.y == endPoint.y) - { - if(attributes->joint != MG_JOINT_NONE) - { - //NOTE(martin): add a closing joint if the path is closed - mg_stroke_joint(canvas, endPoint, endTangent, firstTangent, attributes); - } - } - else if(attributes->cap == MG_CAP_SQUARE) - { - //NOTE(martin): add start and end cap - mg_stroke_cap(canvas, startPoint, (vec2){-startTangent.x, -startTangent.y}, attributes); - mg_stroke_cap(canvas, endPoint, startTangent, attributes); - } - - return(eltIndex); -} - - -void mg_render_stroke(mg_canvas_data* canvas, - mg_path_elt* elements, - mg_path_descriptor* path, - mg_attributes* attributes) -{ - u32 eltCount = path->count; - DEBUG_ASSERT(eltCount); - - vec2 startPoint = path->startPoint; - u32 startIndex = 0; - - while(startIndex < eltCount) - { - //NOTE(martin): eliminate leading moves - while(startIndex < eltCount && elements[startIndex].type == MG_PATH_MOVE) - { - startPoint = elements[startIndex].p[0]; - startIndex++; - } - - if(startIndex < eltCount) - { - startIndex = mg_render_stroke_subpath(canvas, elements, path, attributes, startIndex, startPoint); - } - } -} - //------------------------------------------------------------------------------------------ //NOTE(martin): fonts //------------------------------------------------------------------------------------------ @@ -2621,62 +1189,26 @@ mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text) mg_canvas mg_canvas_nil() { return((mg_canvas){.h = 0}); } bool mg_canvas_is_nil(mg_canvas canvas) { return(canvas.h == 0); } -#if MG_COMPILE_BACKEND_METAL - mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface); -#endif - -#if MG_COMPILE_BACKEND_GL - mg_canvas_backend* mg_gl_canvas_create(mg_surface surface); -#endif - -mg_canvas mg_canvas_create(mg_surface surface) +mg_canvas mg_canvas_create() { mg_canvas canvasHandle = mg_canvas_nil(); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) + mg_canvas_data* canvas = list_pop_entry(&__mgData.canvasFreeList, mg_canvas_data, freeListElt); + if(!canvas) { - mg_canvas_backend* backend = 0; - switch(surfaceData->api) - { - #if MG_COMPILE_BACKEND_METAL - case MG_METAL: - backend = mg_mtl_canvas_create(surface); - break; - #endif + canvas = mem_arena_alloc_type(&__mgData.resourceArena, mg_canvas_data); + } + if(canvas) + { + memset(canvas, 0, sizeof(mg_canvas_data)); - #if MG_COMPILE_BACKEND_GL - case MG_GL: - backend = mg_gl_canvas_create(surface); - break; - #endif + canvas->attributes.color = (mg_color){0, 0, 0, 1}; + canvas->attributes.tolerance = 1; + canvas->attributes.width = 10; + canvas->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - default: - break; - } + canvasHandle = mg_canvas_handle_alloc(canvas); - if(backend) - { - mg_canvas_data* canvas = list_pop_entry(&__mgData.canvasFreeList, mg_canvas_data, freeListElt); - if(!canvas) - { - canvas = mem_arena_alloc_type(&__mgData.resourceArena, mg_canvas_data); - } - if(canvas) - { - memset(canvas, 0, sizeof(mg_canvas_data)); - } - - canvas->surface = surface; - canvas->backend = backend; - - canvas->attributes.color = (mg_color){0, 0, 0, 1}; - canvas->attributes.tolerance = 1; - canvas->attributes.width = 10; - canvas->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - - canvasHandle = mg_canvas_handle_alloc(canvas); - mg_canvas_prepare(canvasHandle); - } + mg_canvas_set_current(canvasHandle); } return(canvasHandle); } @@ -2691,179 +1223,39 @@ void mg_canvas_destroy(mg_canvas handle) __mgCurrentCanvas = 0; __mgCurrentCanvasHandle = mg_canvas_nil(); } - - if(canvas->backend && canvas->backend->destroy) - { - canvas->backend->destroy(canvas->backend); - } list_push(&__mgData.canvasFreeList, &canvas->freeListElt); mg_handle_recycle(handle.h); } } -mg_canvas mg_canvas_prepare(mg_canvas canvas) +mg_canvas mg_canvas_set_current(mg_canvas canvas) { mg_canvas old = __mgCurrentCanvasHandle; - __mgCurrentCanvasHandle = canvas; __mgCurrentCanvas = mg_canvas_data_from_handle(canvas); - - if(__mgCurrentCanvas) - { - mg_surface_prepare(__mgCurrentCanvas->surface); - } return(old); } -vec2 mg_canvas_size(void) -{ - vec2 res = {0}; - if(__mgCurrentCanvas) - { - mp_rect frame = mg_surface_get_frame(__mgCurrentCanvas->surface); - res = (vec2){frame.w, frame.h}; - } - return(res); -} -//////////////////////////////////////////////////////////// - -void mg_draw_batch(mg_canvas_data* canvas, mg_image_data* image) -{ - mg_finalize_shape(canvas); - - if(canvas->backend && canvas->backend->drawBatch && canvas->indexCount) - { - canvas->backend->drawBatch(canvas->backend, image, canvas->nextShapeIndex, canvas->vertexCount, canvas->indexCount); - } - mg_reset_shape_index(canvas); - - canvas->vertexCount = 0; - canvas->indexCount = 0; -} - -void mg_flush_commands(int primitiveCount, mg_primitive* primitives, mg_path_elt* pathElements) +void mg_flush(mg_surface surface) { mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - - - if(canvas->backend && canvas->backend->render) + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(canvas && surfaceData && surfaceData->backend) { int eltCount = canvas->path.startIndex + canvas->path.count; - canvas->backend->render(canvas->backend, canvas->clearColor, primitiveCount, primitives, eltCount, pathElements); - return; - } + surfaceData->backend->render(surfaceData->backend, + canvas->clearColor, + canvas->primitiveCount, + canvas->primitives, + eltCount, + canvas->pathElements); - u32 nextIndex = 0; - - mg_reset_shape_index(canvas); - - canvas->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - canvas->image = mg_image_nil(); - - canvas->backend->begin(canvas->backend, canvas->clearColor); - - //DEBUG - canvas->splitCount = 0; - - for(int i=0; i= primitiveCount) - { - log_error("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex); - break; - } - mg_primitive* primitive = &(primitives[nextIndex]); - nextIndex++; - - if(i && primitive->attributes.image.h != canvas->image.h) - { - mg_image_data* imageData = mg_image_data_from_handle(canvas->image); - mg_draw_batch(canvas, imageData); - canvas->image = primitive->attributes.image; - } - - switch(primitive->cmd) - { - case MG_CMD_FILL: - { - mg_next_shape(canvas, &primitive->attributes); - mg_render_fill(canvas, - pathElements + primitive->path.startIndex, - &primitive->path); - } break; - - case MG_CMD_STROKE: - { - mg_render_stroke(canvas, - pathElements + primitive->path.startIndex, - &primitive->path, - &primitive->attributes); - } break; - - case MG_CMD_JUMP: - { - if(primitive->jump == ~0) - { - //NOTE(martin): normal end of stream marker - goto exit_command_loop; - } - else if(primitive->jump >= primitiveCount) - { - log_error("invalid jump location '%i' in graphics command buffer\n", primitive->jump); - goto exit_command_loop; - } - else - { - nextIndex = primitive->jump; - } - } break; - } - } - exit_command_loop: ; - - printf("path elements: %i, splitCount = %i\n", canvas->path.startIndex + canvas->path.count, canvas->splitCount); - - - mg_image_data* imageData = mg_image_data_from_handle(canvas->image); - mg_draw_batch(canvas, imageData); - - canvas->backend->end(canvas->backend); - - //NOTE(martin): clear buffers - canvas->vertexCount = 0; - canvas->indexCount = 0; -} - -void mg_flush() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_flush_commands(canvas->primitiveCount, canvas->primitives, canvas->pathElements); canvas->primitiveCount = 0; canvas->path.startIndex = 0; canvas->path.count = 0; } } -void mg_present() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_flush_commands(canvas->primitiveCount, canvas->primitives, canvas->pathElements); - canvas->primitiveCount = 0; - canvas->path.startIndex = 0; - canvas->path.count = 0; - - mg_surface_present(canvas->surface); - } -} - //------------------------------------------------------------------------------------------ //NOTE(martin): transform, viewport and clipping //------------------------------------------------------------------------------------------ @@ -3501,95 +1893,9 @@ void mg_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle) //NOTE(martin): images //------------------------------------------------------------------------------------------ - mg_image mg_image_nil() { return((mg_image){.h = 0}); } bool mg_image_is_nil(mg_image image) { return(image.h == 0); } -mg_image mg_image_create(u32 width, u32 height) -{ - mg_image image = mg_image_nil(); - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_image_data* imageData = canvas->backend->imageCreate(canvas->backend, (vec2){width, height}); - if(imageData) - { - image = mg_image_handle_alloc(imageData); - } - } - return(image); -} - -mg_image mg_image_create_from_rgba8(u32 width, u32 height, u8* pixels) -{ - mg_image image = mg_image_create(width, height); - if(!mg_image_is_nil(image)) - { - mg_image_upload_region_rgba8(image, (mp_rect){0, 0, width, height}, pixels); - } - return(image); -} - -mg_image mg_image_create_from_data(str8 data, bool flip) -{ - mg_image image = mg_image_nil(); - int width, height, channels; - - stbi_set_flip_vertically_on_load(flip ? 1 : 0); - u8* pixels = stbi_load_from_memory((u8*)data.ptr, data.len, &width, &height, &channels, 4); - - if(pixels) - { - image = mg_image_create_from_rgba8(width, height, pixels); - free(pixels); - } - return(image); -} - -mg_image mg_image_create_from_file(str8 path, bool flip) -{ - mg_image image = mg_image_nil(); - int width, height, channels; - - const char* cpath = str8_to_cstring(mem_scratch(), path); - - stbi_set_flip_vertically_on_load(flip ? 1 : 0); - u8* pixels = stbi_load(cpath, &width, &height, &channels, 4); - if(pixels) - { - image = mg_image_create_from_rgba8(width, height, pixels); - free(pixels); - } - return(image); -} - -void mg_image_destroy(mg_image image) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_image_data* imageData = mg_image_data_from_handle(image); - if(imageData) - { - canvas->backend->imageDestroy(canvas->backend, imageData); - mg_handle_recycle(image.h); - } - } -} - -void mg_image_upload_region_rgba8(mg_image image, mp_rect region, u8* pixels) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_image_data* imageData = mg_image_data_from_handle(image); - if(imageData) - { - canvas->backend->imageUploadRegion(canvas->backend, imageData, region, pixels); - } - } -} - vec2 mg_image_size(mg_image image) { vec2 res = {0}; @@ -3605,6 +1911,94 @@ vec2 mg_image_size(mg_image image) return(res); } +mg_image mg_image_create(mg_surface surface, u32 width, u32 height) +{ + mg_image image = mg_image_nil(); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData && surfaceData->backend) + { + DEBUG_ASSERT(surfaceData->api == MG_CANVAS); + + mg_image_data* imageData = surfaceData->backend->imageCreate(surfaceData->backend, (vec2){width, height}); + if(imageData) + { + image = mg_image_handle_alloc(imageData); + } + } + return(image); +} + +mg_image mg_image_create_from_rgba8(mg_surface surface, u32 width, u32 height, u8* pixels) +{ + mg_image image = mg_image_create(surface, width, height); + if(!mg_image_is_nil(image)) + { + mg_image_upload_region_rgba8(image, (mp_rect){0, 0, width, height}, pixels); + } + return(image); +} + +mg_image mg_image_create_from_data(mg_surface surface, str8 data, bool flip) +{ + mg_image image = mg_image_nil(); + int width, height, channels; + + stbi_set_flip_vertically_on_load(flip ? 1 : 0); + u8* pixels = stbi_load_from_memory((u8*)data.ptr, data.len, &width, &height, &channels, 4); + + if(pixels) + { + image = mg_image_create_from_rgba8(surface, width, height, pixels); + free(pixels); + } + return(image); +} + +mg_image mg_image_create_from_file(mg_surface surface, str8 path, bool flip) +{ + mg_image image = mg_image_nil(); + int width, height, channels; + + const char* cpath = str8_to_cstring(mem_scratch(), path); + + stbi_set_flip_vertically_on_load(flip ? 1 : 0); + u8* pixels = stbi_load(cpath, &width, &height, &channels, 4); + if(pixels) + { + image = mg_image_create_from_rgba8(surface, width, height, pixels); + free(pixels); + } + return(image); +} + +void mg_image_destroy(mg_image image) +{ + mg_image_data* imageData = mg_image_data_from_handle(image); + if(imageData) + { + mg_surface_data* surface = mg_surface_data_from_handle(imageData->surface); + if(surface && surface->backend) + { + surface->backend->imageDestroy(surface->backend, imageData); + mg_handle_recycle(image.h); + } + } +} + +void mg_image_upload_region_rgba8(mg_image image, mp_rect region, u8* pixels) +{ + mg_image_data* imageData = mg_image_data_from_handle(image); + if(imageData) + { + mg_surface_data* surfaceData = mg_surface_data_from_handle(imageData->surface); + if(surfaceData) + { + DEBUG_ASSERT(surfaceData->backend); + surfaceData->backend->imageUploadRegion(surfaceData->backend, imageData, region, pixels); + } + } +} + void mg_image_draw_region(mg_image image, mp_rect srcRegion, mp_rect dstRegion) { mg_canvas_data* canvas = __mgCurrentCanvas; diff --git a/src/graphics.h b/src/graphics.h index f1a098b..19219a2 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -27,43 +27,51 @@ typedef enum { //NOTE: these macros are used to select which backend to include when building milepost // they can be overridden by passing them to the compiler command line -#if defined(PLATFORM_MACOS) - #ifndef MG_COMPILE_BACKEND_METAL - #define MG_COMPILE_BACKEND_METAL 1 +#if PLATFORM_MACOS + #ifndef MG_COMPILE_METAL + #define MG_COMPILE_METAL 1 #endif - #ifndef MG_COMPILE_BACKEND_GLES - #define MG_COMPILE_BACKEND_GLES 1 + #ifndef MG_COMPILE_GLES + #define MG_COMPILE_GLES 1 #endif - #ifndef MG_COMPILE_BACKEND_CANVAS - #define MG_COMPILE_BACKEND_CANVAS 1 + #ifndef MG_COMPILE_CANVAS + #if !MG_COMPILE_METAL + #error "Canvas surface requires a Metal backend on macOS. Make sure you define MG_COMPILE_METAL to 1." + #endif + #define MG_COMPILE_CANVAS 1 #endif - #define MG_COMPILE_BACKEND_GL 0 + #define MG_COMPILE_GL 0 -#elif defined(PLATFORM_WIN64) - #ifndef MG_COMPILE_BACKEND_GL - #define MG_COMPILE_BACKEND_GL 1 +#elif PLATFORM_WINDOWS + #ifndef MG_COMPILE_GL + #define MG_COMPILE_GL 1 #endif - #ifndef MG_COMPILE_BACKEND_GLES - #define MG_COMPILE_BACKEND_GLES 1 + #ifndef MG_COMPILE_GLES + #define MG_COMPILE_GLES 1 #endif - #ifndef MG_COMPILE_BACKEND_CANVAS - #define MG_COMPILE_BACKEND_CANVAS 1 + #ifndef MG_COMPILE_CANVAS + #if !MG_COMPILE_GL + #error "Canvas surface requires an OpenGL backend on Windows. Make sure you define MG_COMPILE_GL to 1." + #endif + #define MG_COMPILE_CANVAS 1 #endif -#elif defined(PLATFORM_LINUX) - #ifndef MG_COMPILE_BACKEND_GL - #define MG_COMPILE_BACKEND_GL 1 +#elif PLATFORM_LINUX + #ifndef MG_COMPILE_GL + #define MG_COMPILE_GL 1 #endif - #ifndef MG_COMPILE_BACKEND_CANVAS - #define MG_COMPILE_BACKEND_CANVAS 1 + #ifndef MG_COMPILE_CANVAS + #if !MG_COMPILE_GL + #error "Canvas surface requires an OpenGL backend on Linux. Make sure you define MG_COMPILE_GL to 1." + #endif + #define MG_COMPILE_CANVAS 1 #endif - #endif //NOTE: these macros are used to select backend-specific APIs to include when using milepost @@ -170,14 +178,11 @@ typedef struct mg_text_extents MP_API mg_canvas mg_canvas_nil(void); MP_API bool mg_canvas_is_nil(mg_canvas canvas); -MP_API mg_canvas mg_canvas_create(mg_surface surface); +MP_API mg_canvas mg_canvas_create(); MP_API void mg_canvas_destroy(mg_canvas canvas); +mg_canvas mg_canvas_set_current(mg_canvas canvas); +MP_API void mg_flush(mg_surface surface); //TODO change to mg_canvas_render(mg_surface surface, mg_canvas canvas); -MP_API mg_canvas mg_canvas_prepare(mg_canvas canvas); -MP_API void mg_flush(void); -MP_API void mg_present(void); - -MP_API vec2 mg_canvas_size(void); //------------------------------------------------------------------------------------------ //NOTE(martin): fonts //------------------------------------------------------------------------------------------ @@ -212,10 +217,10 @@ MP_API mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text); MP_API mg_image mg_image_nil(); MP_API bool mg_image_is_nil(mg_image a); -MP_API mg_image mg_image_create(u32 width, u32 height); -MP_API mg_image mg_image_create_from_rgba8(u32 width, u32 height, u8* pixels); -MP_API mg_image mg_image_create_from_data(str8 data, bool flip); -MP_API mg_image mg_image_create_from_file(str8 path, bool flip); +MP_API mg_image mg_image_create(mg_surface surface, u32 width, u32 height); +MP_API mg_image mg_image_create_from_rgba8(mg_surface surface, u32 width, u32 height, u8* pixels); +MP_API mg_image mg_image_create_from_data(mg_surface surface, str8 data, bool flip); +MP_API mg_image mg_image_create_from_file(mg_surface surface, str8 path, bool flip); MP_API void mg_image_destroy(mg_image image); diff --git a/src/graphics_internal.h b/src/graphics_internal.h index 5eda6f2..ce11556 100644 --- a/src/graphics_internal.h +++ b/src/graphics_internal.h @@ -20,6 +20,7 @@ extern "C" { // surface interface //--------------------------------------------------------------- typedef struct mg_surface_data mg_surface_data; +typedef struct mg_canvas_backend mg_canvas_backend; typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface); typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface); @@ -51,6 +52,9 @@ typedef struct mg_surface_data mg_surface_native_layer_proc nativeLayer; mg_surface_remote_id_proc remoteID; mg_surface_host_connect_proc hostConnect; + + mg_canvas_backend* backend; + } mg_surface_data; mg_surface mg_surface_alloc_handle(mg_surface_data* surface); @@ -69,6 +73,7 @@ typedef struct mg_image_data { list_elt listElt; u32 generation; + mg_surface surface; vec2 size; } mg_image_data; @@ -104,7 +109,6 @@ typedef struct mg_vertex_layout } mg_vertex_layout; -typedef struct mg_canvas_backend mg_canvas_backend; typedef void (*mg_canvas_backend_destroy_proc)(mg_canvas_backend* backend); typedef void (*mg_canvas_backend_begin_proc)(mg_canvas_backend* backend, mg_color clearColor); diff --git a/src/milepost.c b/src/milepost.c index a2b0d3f..e49f106 100644 --- a/src/milepost.c +++ b/src/milepost.c @@ -14,11 +14,11 @@ #include"platform/std_log.c" -#if defined(PLATFORM_WIN64) +#if PLATFORM_WINDOWS #include"platform/win32_memory.c" #include"platform/win32_clock.c" //TODO -#elif defined(PLATFORM_MACOS) +#elif PLATFORM_MACOS #include"platform/unix_memory.c" #include"platform/osx_clock.c" /* @@ -27,7 +27,7 @@ #include"platform/posix_socket.c" */ -#elif defined(PLATFORM_LINUX) +#elif PLATFORM_LINUX #include"platform/unix_base_memory.c" #include"platform/linux_clock.c" /* @@ -52,24 +52,27 @@ // app/graphics layer //--------------------------------------------------------------- -#if defined(PLATFORM_WIN64) +#if PLATFORM_WINDOWS #include"win32_app.c" #include"graphics.c" - #if MG_COMPILE_BACKEND_GL || MG_COMPILE_BACKEND_GLES + #if MG_COMPILE_GL || MG_COMPILE_GLES #include"gl_loader.c" #endif - #if MG_COMPILE_BACKEND_GL + #if MG_COMPILE_GL #include"wgl_surface.c" + #endif + + #if MG_COMPILE_CANVAS #include"gl_canvas.c" #endif - #if MG_COMPILE_BACKEND_GLES + #if MG_COMPILE_GLES #include"egl_surface.c" #endif -#elif defined(PLATFORM_MACOS) +#elif PLATFORM_MACOS //NOTE: macos application layer and graphics backends are defined in milepost.m #else #error "Unsupported platform" diff --git a/src/milepost.m b/src/milepost.m index df7dd7a..584ece4 100644 --- a/src/milepost.m +++ b/src/milepost.m @@ -10,21 +10,15 @@ #include"osx_app.m" #include"graphics.c" -#if MG_COMPILE_BACKEND_METAL +#if MG_COMPILE_METAL #include"mtl_surface.m" -// #include"mtl_canvas.m" +#endif + +#if MG_COMPILE_CANVAS #include"mtl_renderer.m" #endif -#if MG_COMPILE_BACKEND_GLES +#if MG_COMPILE_GLES #include"gl_loader.c" #include"egl_surface.c" #endif - -/* -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - #include"osx_gles_surface.m" -#pragma clang diagnostic pop -*/ -//#include"osx_surface_client.m" diff --git a/src/mp_app_internal.h b/src/mp_app_internal.h index 97b75a5..e801a96 100644 --- a/src/mp_app_internal.h +++ b/src/mp_app_internal.h @@ -14,9 +14,9 @@ #include"platform.h" #include"ringbuffer.h" -#if defined(PLATFORM_WIN64) || defined(PLATFORM_WIN32) +#if PLATFORM_WINDOWS #include"win32_app.h" -#elif defined(PLATFORM_MACOS) +#elif PLATFORM_MACOS #include"osx_app.h" #else #error "platform not supported yet" diff --git a/src/mtl_renderer.m b/src/mtl_renderer.m index 2216769..497d7af 100644 --- a/src/mtl_renderer.m +++ b/src/mtl_renderer.m @@ -23,7 +23,7 @@ const int MG_MTL_INPUT_BUFFERS_COUNT = 3, typedef struct mg_mtl_canvas_backend { mg_canvas_backend interface; - mg_surface surface; + mg_mtl_surface* surface; id pathPipeline; id segmentPipeline; @@ -64,15 +64,6 @@ typedef struct mg_mtl_image_data id texture; } mg_mtl_image_data; - -static void mg_update_path_extents(vec4* extents, vec2 p) -{ - extents->x = minimum(extents->x, p.x); - extents->y = minimum(extents->y, p.y); - extents->z = maximum(extents->z, p.x); - extents->w = maximum(extents->w, p.y); -} - void mg_mtl_print_log(int bufferIndex, id logBuffer, id logOffsetBuffer) { char* log = [logBuffer contents]; @@ -106,6 +97,14 @@ typedef struct mg_mtl_encoding_context } mg_mtl_encoding_context; +static void mg_update_path_extents(vec4* extents, vec2 p) +{ + extents->x = minimum(extents->x, p.x); + extents->y = minimum(extents->y, p.y); + extents->z = maximum(extents->z, p.x); + extents->w = maximum(extents->w, p.y); +} + void mg_mtl_canvas_encode_element(mg_mtl_encoding_context* context, mg_path_elt_type kind, vec2* p) { mg_mtl_path_elt* mtlElt = &context->elementBufferData[context->mtlEltCount]; @@ -147,6 +146,198 @@ void mg_mtl_canvas_encode_element(mg_mtl_encoding_context* context, mg_path_elt_ } } + +bool mg_intersect_hull_legs(vec2 p0, vec2 p1, vec2 p2, vec2 p3, vec2* intersection) +{ + /*NOTE: check intersection of lines (p0-p1) and (p2-p3) + + P = p0 + u(p1-p0) + P = p2 + w(p3-p2) + */ + bool found = false; + + f32 den = (p0.x - p1.x)*(p2.y - p3.y) - (p0.y - p1.y)*(p2.x - p3.x); + if(fabs(den) > 0.0001) + { + f32 u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/den; + f32 w = ((p0.x - p2.x)*(p0.y - p1.y) - (p0.y - p2.y)*(p0.x - p1.x))/den; + + intersection->x = p0.x + u*(p1.x - p0.x); + intersection->y = p0.y + u*(p1.y - p0.y); + found = true; + } + return(found); +} + +bool mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) +{ + //NOTE: we should have no more than two coincident points here. This means the leg between + // those two points can't be offset, but we can set a double point at the start of first leg, + // end of first leg, or we can join the first and last leg to create a missing middle one + + vec2 legs[3][2] = {0}; + bool valid[3] = {0}; + + for(int i=0; i= 1e-6) + { + n = vec2_mul(offset/norm, n); + legs[i][0] = vec2_add(p[i], n); + legs[i][1] = vec2_add(p[i+1], n); + valid[i] = true; + } + } + + //NOTE: now we find intersections + + // first point is either the start of the first or second leg + if(valid[0]) + { + result[0] = legs[0][0]; + } + else + { + ASSERT(valid[1]); + result[0] = legs[1][0]; + } + + for(int i=1; isplitCount++; + + //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm + // the q_n are the points along the hull's segments at parameter t + // s is the split point. + + f32 oneMt = 1-t; + + vec2 q0 = {oneMt*p[0].x + t*p[1].x, + oneMt*p[0].y + t*p[1].y}; + + vec2 q1 = {oneMt*p[1].x + t*p[2].x, + oneMt*p[1].y + t*p[2].y}; + + vec2 s = {oneMt*q0.x + t*q1.x, + oneMt*q0.y + t*q1.y}; + + outLeft[0] = p[0]; + outLeft[1] = q0; + outLeft[2] = s; + + outRight[0] = s; + outRight[1] = q1; + outRight[2] = p[2]; +} + +vec2 mg_cubic_get_point(vec2 p[4], f32 t) +{ + vec2 r; + + f32 oneMt = 1-t; + f32 oneMt2 = Square(oneMt); + f32 oneMt3 = oneMt2*oneMt; + f32 t2 = Square(t); + f32 t3 = t2*t; + + r.x = oneMt3*p[0].x + 3*oneMt2*t*p[1].x + 3*oneMt*t2*p[2].x + t3*p[3].x; + r.y = oneMt3*p[0].y + 3*oneMt2*t*p[1].y + 3*oneMt*t2*p[2].y + t3*p[3].y; + + return(r); +} + +void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4]) +{ + //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm + // the q_n are the points along the hull's segments at parameter t + // the r_n are the points along the (q_n, q_n+1) segments at parameter t + // s is the split point. + + vec2 q0 = {(1-t)*p[0].x + t*p[1].x, + (1-t)*p[0].y + t*p[1].y}; + + vec2 q1 = {(1-t)*p[1].x + t*p[2].x, + (1-t)*p[1].y + t*p[2].y}; + + vec2 q2 = {(1-t)*p[2].x + t*p[3].x, + (1-t)*p[2].y + t*p[3].y}; + + vec2 r0 = {(1-t)*q0.x + t*q1.x, + (1-t)*q0.y + t*q1.y}; + + vec2 r1 = {(1-t)*q1.x + t*q2.x, + (1-t)*q1.y + t*q2.y}; + + vec2 s = {(1-t)*r0.x + t*r1.x, + (1-t)*r0.y + t*r1.y};; + + outLeft[0] = p[0]; + outLeft[1] = q0; + outLeft[2] = r0; + outLeft[3] = s; + + outRight[0] = s; + outRight[1] = r1; + outRight[2] = q2; + outRight[3] = p[3]; +} + void mg_mtl_render_stroke_line(mg_mtl_encoding_context* context, vec2* p) { f32 width = context->primitive->attributes.width; @@ -771,40 +962,36 @@ void mg_mtl_render_batch(mg_mtl_canvas_backend* backend, void mg_mtl_canvas_resize(mg_mtl_canvas_backend* backend, vec2 size) { - mg_mtl_surface* surface = (mg_mtl_surface*)mg_surface_data_from_handle(backend->surface); - if(surface) + @autoreleasepool { - @autoreleasepool + if(backend->screenTilesBuffer) { - if(backend->screenTilesBuffer) - { - [backend->screenTilesBuffer release]; - backend->screenTilesBuffer = nil; - } - int tileSize = MG_MTL_TILE_SIZE; - int nTilesX = (int)(size.x + tileSize - 1)/tileSize; - int nTilesY = (int)(size.y + tileSize - 1)/tileSize; - MTLResourceOptions bufferOptions = MTLResourceStorageModePrivate; - backend->screenTilesBuffer = [surface->device newBufferWithLength: nTilesX*nTilesY*sizeof(int) - options: bufferOptions]; - - if(backend->outTexture) - { - [backend->outTexture release]; - backend->outTexture = nil; - } - MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModePrivate; - texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; - texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; - texDesc.width = size.x; - texDesc.height = size.y; - - backend->outTexture = [surface->device newTextureWithDescriptor:texDesc]; - - backend->frameSize = size; + [backend->screenTilesBuffer release]; + backend->screenTilesBuffer = nil; } + int tileSize = MG_MTL_TILE_SIZE; + int nTilesX = (int)(size.x + tileSize - 1)/tileSize; + int nTilesY = (int)(size.y + tileSize - 1)/tileSize; + MTLResourceOptions bufferOptions = MTLResourceStorageModePrivate; + backend->screenTilesBuffer = [backend->surface->device newBufferWithLength: nTilesX*nTilesY*sizeof(int) + options: bufferOptions]; + + if(backend->outTexture) + { + [backend->outTexture release]; + backend->outTexture = nil; + } + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; + texDesc.width = size.x; + texDesc.height = size.y; + + backend->outTexture = [backend->surface->device newTextureWithDescriptor:texDesc]; + + backend->frameSize = size; } } @@ -829,10 +1016,10 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface, ///////////////////////////////////////////////////////////////////////////////////// //NOTE: prepare rendering - mg_mtl_surface* surface = (mg_mtl_surface*)mg_surface_data_from_handle(backend->surface); - ASSERT(surface && surface->interface.api == MG_METAL); + mg_mtl_surface* surface = backend->surface; + + mp_rect frame = surface->interface.getFrame((mg_surface_data*)surface); - mp_rect frame = mg_surface_get_frame(backend->surface); f32 scale = surface->mtlLayer.contentsScale; vec2 viewportSize = {frame.w * scale, frame.h * scale}; int tileSize = MG_MTL_TILE_SIZE; @@ -1068,34 +1255,31 @@ mg_image_data* mg_mtl_canvas_image_create(mg_canvas_backend* interface, vec2 siz { mg_mtl_image_data* image = 0; mg_mtl_canvas_backend* backend = (mg_mtl_canvas_backend*)interface; - mg_mtl_surface* surface = (mg_mtl_surface*)mg_surface_data_from_handle(backend->surface); + mg_mtl_surface* surface = backend->surface; - if(surface && surface->interface.api == MG_METAL) + @autoreleasepool { - @autoreleasepool{ + image = malloc_type(mg_mtl_image_data); + if(image) + { + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModeManaged; + texDesc.usage = MTLTextureUsageShaderRead; + texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; + texDesc.width = size.x; + texDesc.height = size.y; - image = malloc_type(mg_mtl_image_data); - if(image) + image->texture = [surface->device newTextureWithDescriptor:texDesc]; + if(image->texture != nil) { - MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModeManaged; - texDesc.usage = MTLTextureUsageShaderRead; - texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; - texDesc.width = size.x; - texDesc.height = size.y; - - image->texture = [surface->device newTextureWithDescriptor:texDesc]; - if(image->texture != nil) - { - [image->texture retain]; - image->interface.size = size; - } - else - { - free(image); - image = 0; - } + [image->texture retain]; + image->interface.size = size; + } + else + { + free(image); + image = 0; } } } @@ -1129,153 +1313,166 @@ const u32 MG_MTL_PATH_BUFFER_SIZE = (4<<20)*sizeof(mg_mtl_path), MG_MTL_TILE_QUEUE_BUFFER_SIZE = (4<<20)*sizeof(mg_mtl_tile_queue), MG_MTL_TILE_OP_BUFFER_SIZE = (4<<20)*sizeof(mg_mtl_tile_op); -mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface) +mg_canvas_backend* mtl_canvas_backend_create(mg_mtl_surface* surface) { mg_mtl_canvas_backend* backend = 0; - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData && surfaceData->api == MG_METAL) - { - mg_mtl_surface* metalSurface = (mg_mtl_surface*)surfaceData; + backend = malloc_type(mg_mtl_canvas_backend); + memset(backend, 0, sizeof(mg_mtl_canvas_backend)); - backend = malloc_type(mg_mtl_canvas_backend); - memset(backend, 0, sizeof(mg_mtl_canvas_backend)); + backend->msaaCount = MG_MTL_MSAA_COUNT; + backend->surface = surface; - backend->msaaCount = MG_MTL_MSAA_COUNT; - backend->surface = surface; + //NOTE(martin): setup interface functions + backend->interface.destroy = mg_mtl_canvas_destroy; + backend->interface.render = mg_mtl_canvas_render; + backend->interface.imageCreate = mg_mtl_canvas_image_create; + backend->interface.imageDestroy = mg_mtl_canvas_image_destroy; + backend->interface.imageUploadRegion = mg_mtl_canvas_image_upload_region; - //NOTE(martin): setup interface functions - backend->interface.destroy = mg_mtl_canvas_destroy; - backend->interface.render = mg_mtl_canvas_render; - backend->interface.imageCreate = mg_mtl_canvas_image_create; - backend->interface.imageDestroy = mg_mtl_canvas_image_destroy; - backend->interface.imageUploadRegion = mg_mtl_canvas_image_upload_region; + @autoreleasepool{ + //NOTE: load metal library + str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "mtl_renderer.metallib"); + NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding]; + NSError* err = 0; + id library = [surface->device newLibraryWithFile: metalFileName error:&err]; + if(err != nil) + { + const char* errStr = [[err localizedDescription] UTF8String]; + log_error("error : %s\n", errStr); + return(0); + } + id pathFunction = [library newFunctionWithName:@"mtl_path_setup"]; + id segmentFunction = [library newFunctionWithName:@"mtl_segment_setup"]; + id backpropFunction = [library newFunctionWithName:@"mtl_backprop"]; + id mergeFunction = [library newFunctionWithName:@"mtl_merge"]; + id rasterFunction = [library newFunctionWithName:@"mtl_raster"]; + id vertexFunction = [library newFunctionWithName:@"mtl_vertex_shader"]; + id fragmentFunction = [library newFunctionWithName:@"mtl_fragment_shader"]; - @autoreleasepool{ - //NOTE: load metal library - str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "mtl_renderer.metallib"); - NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding]; - NSError* err = 0; - id library = [metalSurface->device newLibraryWithFile: metalFileName error:&err]; - if(err != nil) - { - const char* errStr = [[err localizedDescription] UTF8String]; - log_error("error : %s\n", errStr); - return(0); - } - id pathFunction = [library newFunctionWithName:@"mtl_path_setup"]; - id segmentFunction = [library newFunctionWithName:@"mtl_segment_setup"]; - id backpropFunction = [library newFunctionWithName:@"mtl_backprop"]; - id mergeFunction = [library newFunctionWithName:@"mtl_merge"]; - id rasterFunction = [library newFunctionWithName:@"mtl_raster"]; - id vertexFunction = [library newFunctionWithName:@"mtl_vertex_shader"]; - id fragmentFunction = [library newFunctionWithName:@"mtl_fragment_shader"]; + //NOTE: create pipelines + NSError* error = NULL; - //NOTE: create pipelines - NSError* error = NULL; + backend->pathPipeline = [surface->device newComputePipelineStateWithFunction: pathFunction + error:&error]; - backend->pathPipeline = [metalSurface->device newComputePipelineStateWithFunction: pathFunction - error:&error]; + backend->segmentPipeline = [surface->device newComputePipelineStateWithFunction: segmentFunction + error:&error]; - backend->segmentPipeline = [metalSurface->device newComputePipelineStateWithFunction: segmentFunction - error:&error]; + backend->backpropPipeline = [surface->device newComputePipelineStateWithFunction: backpropFunction + error:&error]; - backend->backpropPipeline = [metalSurface->device newComputePipelineStateWithFunction: backpropFunction - error:&error]; + backend->mergePipeline = [surface->device newComputePipelineStateWithFunction: mergeFunction + error:&error]; - backend->mergePipeline = [metalSurface->device newComputePipelineStateWithFunction: mergeFunction - error:&error]; + backend->rasterPipeline = [surface->device newComputePipelineStateWithFunction: rasterFunction + error:&error]; - backend->rasterPipeline = [metalSurface->device newComputePipelineStateWithFunction: rasterFunction - error:&error]; + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"blit pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = surface->mtlLayer.pixelFormat; + pipelineStateDescriptor.colorAttachments[0].blendingEnabled = YES; + pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; + pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; - MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; - pipelineStateDescriptor.label = @"blit pipeline"; - pipelineStateDescriptor.vertexFunction = vertexFunction; - pipelineStateDescriptor.fragmentFunction = fragmentFunction; - pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->mtlLayer.pixelFormat; - pipelineStateDescriptor.colorAttachments[0].blendingEnabled = YES; - pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; - pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; - pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; - pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; - pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + backend->blitPipeline = [surface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err]; - backend->blitPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err]; + //NOTE: create textures + mp_rect frame = surface->interface.getFrame((mg_surface_data*)surface); + f32 scale = surface->mtlLayer.contentsScale; - //NOTE: create textures - mp_rect frame = mg_surface_get_frame(surface); - f32 scale = metalSurface->mtlLayer.contentsScale; + backend->frameSize = (vec2){frame.w*scale, frame.h*scale}; - backend->frameSize = (vec2){frame.w*scale, frame.h*scale}; + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; + texDesc.width = backend->frameSize.x; + texDesc.height = backend->frameSize.y; - MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModePrivate; - texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; - texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; - texDesc.width = backend->frameSize.x; - texDesc.height = backend->frameSize.y; + backend->outTexture = [surface->device newTextureWithDescriptor:texDesc]; - backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc]; + //NOTE: create buffers - //NOTE: create buffers + backend->bufferSemaphore = dispatch_semaphore_create(MG_MTL_INPUT_BUFFERS_COUNT); + backend->bufferIndex = 0; - backend->bufferSemaphore = dispatch_semaphore_create(MG_MTL_INPUT_BUFFERS_COUNT); - backend->bufferIndex = 0; + MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined + | MTLResourceStorageModeShared; - MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined - | MTLResourceStorageModeShared; + for(int i=0; ipathBuffer[i] = [surface->device newBufferWithLength: MG_MTL_PATH_BUFFER_SIZE + options: bufferOptions]; - for(int i=0; ipathBuffer[i] = [metalSurface->device newBufferWithLength: MG_MTL_PATH_BUFFER_SIZE - options: bufferOptions]; + backend->elementBuffer[i] = [surface->device newBufferWithLength: MG_MTL_ELEMENT_BUFFER_SIZE + options: bufferOptions]; + } - backend->elementBuffer[i] = [metalSurface->device newBufferWithLength: MG_MTL_ELEMENT_BUFFER_SIZE - options: bufferOptions]; - } + bufferOptions = MTLResourceStorageModePrivate; + backend->segmentBuffer = [surface->device newBufferWithLength: MG_MTL_SEGMENT_BUFFER_SIZE + options: bufferOptions]; - bufferOptions = MTLResourceStorageModePrivate; - backend->segmentBuffer = [metalSurface->device newBufferWithLength: MG_MTL_SEGMENT_BUFFER_SIZE - options: bufferOptions]; + backend->segmentCountBuffer = [surface->device newBufferWithLength: sizeof(int) + options: bufferOptions]; - backend->segmentCountBuffer = [metalSurface->device newBufferWithLength: sizeof(int) - options: bufferOptions]; + backend->pathQueueBuffer = [surface->device newBufferWithLength: MG_MTL_PATH_QUEUE_BUFFER_SIZE + options: bufferOptions]; + backend->tileQueueBuffer = [surface->device newBufferWithLength: MG_MTL_TILE_QUEUE_BUFFER_SIZE + options: bufferOptions]; - backend->pathQueueBuffer = [metalSurface->device newBufferWithLength: MG_MTL_PATH_QUEUE_BUFFER_SIZE - options: bufferOptions]; + backend->tileQueueCountBuffer = [surface->device newBufferWithLength: sizeof(int) + options: bufferOptions]; - backend->tileQueueBuffer = [metalSurface->device newBufferWithLength: MG_MTL_TILE_QUEUE_BUFFER_SIZE - options: bufferOptions]; + backend->tileOpBuffer = [surface->device newBufferWithLength: MG_MTL_TILE_OP_BUFFER_SIZE + options: bufferOptions]; - backend->tileQueueCountBuffer = [metalSurface->device newBufferWithLength: sizeof(int) - options: bufferOptions]; + backend->tileOpCountBuffer = [surface->device newBufferWithLength: sizeof(int) + options: bufferOptions]; - backend->tileOpBuffer = [metalSurface->device newBufferWithLength: MG_MTL_TILE_OP_BUFFER_SIZE - options: bufferOptions]; + int tileSize = MG_MTL_TILE_SIZE; + int nTilesX = (int)(frame.w * scale + tileSize - 1)/tileSize; + int nTilesY = (int)(frame.h * scale + tileSize - 1)/tileSize; + backend->screenTilesBuffer = [surface->device newBufferWithLength: nTilesX*nTilesY*sizeof(int) + options: bufferOptions]; - backend->tileOpCountBuffer = [metalSurface->device newBufferWithLength: sizeof(int) - options: bufferOptions]; + bufferOptions = MTLResourceStorageModeShared; + for(int i=0; ilogBuffer[i] = [surface->device newBufferWithLength: 1<<20 + options: bufferOptions]; - int tileSize = MG_MTL_TILE_SIZE; - int nTilesX = (int)(frame.w * scale + tileSize - 1)/tileSize; - int nTilesY = (int)(frame.h * scale + tileSize - 1)/tileSize; - backend->screenTilesBuffer = [metalSurface->device newBufferWithLength: nTilesX*nTilesY*sizeof(int) - options: bufferOptions]; - - bufferOptions = MTLResourceStorageModeShared; - for(int i=0; ilogBuffer[i] = [metalSurface->device newBufferWithLength: 1<<20 - options: bufferOptions]; - - backend->logOffsetBuffer[i] = [metalSurface->device newBufferWithLength: sizeof(int) - options: bufferOptions]; - } + backend->logOffsetBuffer[i] = [surface->device newBufferWithLength: sizeof(int) + options: bufferOptions]; } } return((mg_canvas_backend*)backend); } + +mg_surface_data* mtl_canvas_surface_create_for_window(mp_window window) +{ + mg_mtl_surface* surface = (mg_mtl_surface*)mg_mtl_surface_create_for_window(window); + + if(surface) + { + surface->interface.backend = mtl_canvas_backend_create(surface); + if(surface->interface.backend) + { + surface->interface.api = MG_CANVAS; + } + else + { + surface->interface.destroy((mg_surface_data*)surface); + surface = 0; + } + } + return((mg_surface_data*)surface); +} diff --git a/src/platform/platform.h b/src/platform/platform.h index f89df8c..471081a 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -33,7 +33,7 @@ // OS identification //----------------------------------------------------------------- #if defined(_WIN64) - #define PLATFORM_WIN64 1 + #define PLATFORM_WINDOWS 1 #elif defined(_WIN32) #error "Unsupported OS (32bit only version of Windows)" #elif defined(__APPLE__) && defined(__MACH__) diff --git a/src/platform/std_log.c b/src/platform/std_log.c index 1094942..38d32a9 100644 --- a/src/platform/std_log.c +++ b/src/platform/std_log.c @@ -8,7 +8,7 @@ #include #include"platform_log.c" -#if PLATFORM_WIN32 || PLATFORM_WIN64 +#if PLATFORM_WINDOWS #include #define isatty _isatty #define fileno _fileno diff --git a/src/tmp_gl_canvas.c b/src/tmp_gl_canvas.c new file mode 100644 index 0000000..546f7df --- /dev/null +++ b/src/tmp_gl_canvas.c @@ -0,0 +1,1573 @@ +/************************************************************//** +* +* @file: tmp_gl_canvas.c +* @author: Martin Fouilleul +* @date: 25/04/2023 +* +*****************************************************************/ + + +void mg_reset_shape_index(mg_canvas_data* canvas) +{ + canvas->nextShapeIndex = 0; + canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; +} + +void mg_finalize_shape(mg_canvas_data* canvas) +{ + if(canvas->nextShapeIndex) + { + //NOTE: set shape's uv transform for the _current_ shape + vec2 texSize = mg_image_size(canvas->image); + + mp_rect srcRegion = canvas->srcRegion; + + mp_rect destRegion = {canvas->shapeExtents.x, + canvas->shapeExtents.y, + canvas->shapeExtents.z - canvas->shapeExtents.x, + canvas->shapeExtents.w - canvas->shapeExtents.y}; + + mg_mat2x3 srcRegionToImage = {1/texSize.x, 0, srcRegion.x/texSize.x, + 0, 1/texSize.y, srcRegion.y/texSize.y}; + mg_mat2x3 destRegionToSrcRegion = {srcRegion.w/destRegion.w, 0, 0, + 0, srcRegion.h/destRegion.h, 0}; + mg_mat2x3 userToDestRegion = {1, 0, -destRegion.x, + 0, 1, -destRegion.y}; + + mg_mat2x3 screenToUser = mg_mat2x3_inv(canvas->transform); + + mg_mat2x3 uvTransform = srcRegionToImage; + uvTransform = mg_mat2x3_mul_m(uvTransform, destRegionToSrcRegion); + uvTransform = mg_mat2x3_mul_m(uvTransform, userToDestRegion); + uvTransform = mg_mat2x3_mul_m(uvTransform, screenToUser); + + int index = canvas->nextShapeIndex-1; + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + *(mg_mat2x3*)(layout->uvTransformBuffer + index*layout->uvTransformStride) = uvTransform; + + //TODO: transform extents before clipping + mp_rect clip = {maximum(canvas->clip.x, canvas->shapeScreenExtents.x), + maximum(canvas->clip.y, canvas->shapeScreenExtents.y), + minimum(canvas->clip.x + canvas->clip.w, canvas->shapeScreenExtents.z), + minimum(canvas->clip.y + canvas->clip.h, canvas->shapeScreenExtents.w)}; + + *(mp_rect*)(((char*)layout->clipBuffer) + index*layout->clipStride) = clip; + } +} + +u32 mg_next_shape(mg_canvas_data* canvas, mg_attributes* attributes) +{ + mg_finalize_shape(canvas); + + canvas->clip = attributes->clip; + canvas->transform = attributes->transform; + canvas->srcRegion = attributes->srcRegion; + canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + canvas->shapeScreenExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; + + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + int index = canvas->nextShapeIndex; + canvas->nextShapeIndex++; + + *(mg_color*)(((char*)layout->colorBuffer) + index*layout->colorStride) = attributes->color; + *(bool*)(((char*)layout->texturedBuffer) + index*layout->texturedStride) = !mg_image_is_nil(attributes->image); + + return(index); +} + +//TODO(martin): rename with something more explicit +u32 mg_vertices_base_index(mg_canvas_data* canvas) +{ + return(canvas->vertexCount); +} + +int* mg_reserve_indices(mg_canvas_data* canvas, u32 indexCount) +{ + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + + //TODO: do something here... + ASSERT(canvas->indexCount + indexCount < layout->maxIndexCount); + + int* base = ((int*)layout->indexBuffer) + canvas->indexCount; + canvas->indexCount += indexCount; + return(base); +} + +void mg_push_vertex_cubic(mg_canvas_data* canvas, vec2 pos, vec4 cubic) +{ + canvas->shapeExtents.x = minimum(canvas->shapeExtents.x, pos.x); + canvas->shapeExtents.y = minimum(canvas->shapeExtents.y, pos.y); + canvas->shapeExtents.z = maximum(canvas->shapeExtents.z, pos.x); + canvas->shapeExtents.w = maximum(canvas->shapeExtents.w, pos.y); + + vec2 screenPos = mg_mat2x3_mul(canvas->transform, pos); + + canvas->shapeScreenExtents.x = minimum(canvas->shapeScreenExtents.x, screenPos.x); + canvas->shapeScreenExtents.y = minimum(canvas->shapeScreenExtents.y, screenPos.y); + canvas->shapeScreenExtents.z = maximum(canvas->shapeScreenExtents.z, screenPos.x); + canvas->shapeScreenExtents.w = maximum(canvas->shapeScreenExtents.w, screenPos.y); + + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + ASSERT(canvas->vertexCount < layout->maxVertexCount); + ASSERT(canvas->nextShapeIndex > 0); + + int shapeIndex = maximum(0, canvas->nextShapeIndex-1); + u32 index = canvas->vertexCount; + canvas->vertexCount++; + + *(vec2*)(((char*)layout->posBuffer) + index*layout->posStride) = screenPos; + *(vec4*)(((char*)layout->cubicBuffer) + index*layout->cubicStride) = cubic; + *(u32*)(((char*)layout->shapeIndexBuffer) + index*layout->shapeIndexStride) = shapeIndex; +} + +void mg_push_vertex(mg_canvas_data* canvas, vec2 pos) +{ + mg_push_vertex_cubic(canvas, pos, (vec4){1, 1, 1, 1}); +} +//----------------------------------------------------------------------------------------------------------- +// Path Filling +//----------------------------------------------------------------------------------------------------------- +//NOTE(martin): forward declarations +void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4]); + +//NOTE(martin): quadratics filling + +void mg_render_fill_quadratic(mg_canvas_data* canvas, vec2 p[3]) +{ + u32 baseIndex = mg_vertices_base_index(canvas); + + i32* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex_cubic(canvas, (vec2){p[0].x, p[0].y}, (vec4){0, 0, 0, 1}); + mg_push_vertex_cubic(canvas, (vec2){p[1].x, p[1].y}, (vec4){0.5, 0, 0.5, 1}); + mg_push_vertex_cubic(canvas, (vec2){p[2].x, p[2].y}, (vec4){1, 1, 1, 1}); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; +} + +//NOTE(martin): cubic filling + +void mg_split_and_fill_cubic(mg_canvas_data* canvas, vec2 p[4], f32 tSplit) +{ + //DEBUG + __mgCurrentCanvas->splitCount++; + + int subVertexCount = 0; + int subIndexCount = 0; + + f32 OneMinusTSplit = 1-tSplit; + + vec2 q0 = {OneMinusTSplit*p[0].x + tSplit*p[1].x, + OneMinusTSplit*p[0].y + tSplit*p[1].y}; + + vec2 q1 = {OneMinusTSplit*p[1].x + tSplit*p[2].x, + OneMinusTSplit*p[1].y + tSplit*p[2].y}; + + vec2 q2 = {OneMinusTSplit*p[2].x + tSplit*p[3].x, + OneMinusTSplit*p[2].y + tSplit*p[3].y}; + + vec2 r0 = {OneMinusTSplit*q0.x + tSplit*q1.x, + OneMinusTSplit*q0.y + tSplit*q1.y}; + + vec2 r1 = {OneMinusTSplit*q1.x + tSplit*q2.x, + OneMinusTSplit*q1.y + tSplit*q2.y}; + + vec2 split = {OneMinusTSplit*r0.x + tSplit*r1.x, + OneMinusTSplit*r0.y + tSplit*r1.y};; + + vec2 subPointsLow[4] = {p[0], q0, r0, split}; + vec2 subPointsHigh[4] = {split, r1, q2, p[3]}; + + //NOTE(martin): add base triangle + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex(canvas, (vec2){p[0].x, p[0].y}); + mg_push_vertex(canvas, (vec2){split.x, split.y}); + mg_push_vertex(canvas, (vec2){p[3].x, p[3].y}); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + + mg_render_fill_cubic(canvas, subPointsLow); + mg_render_fill_cubic(canvas, subPointsHigh); +} + +int mg_cubic_outside_test(vec4 c) +{ + int res = (c.x*c.x*c.x - c.y*c.z < 0) ? -1 : 1; + return(res); +} + +void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4]) +{ + vec4 testCoords[4]; + + /*NOTE(martin): first convert the control points to power basis, multiplying by M3 + + | 1 0 0 0| + M3 = |-3 3 0 0| + | 3 -6 3 0| + |-1 3 -3 1| + ie: + c0 = p0 + c1 = -3*p0 + 3*p1 + c2 = 3*p0 - 6*p1 + 3*p2 + c3 = -p0 + 3*p1 - 3*p2 + p3 + */ + f32 c1x = 3.0*p[1].x - 3.0*p[0].x; + f32 c1y = 3.0*p[1].y - 3.0*p[0].y; + + f32 c2x = 3.0*p[0].x + 3.0*p[2].x - 6.0*p[1].x; + f32 c2y = 3.0*p[0].y + 3.0*p[2].y - 6.0*p[1].y; + + f32 c3x = 3.0*p[1].x - 3.0*p[2].x + p[3].x - p[0].x; + f32 c3y = 3.0*p[1].y - 3.0*p[2].y + p[3].y - p[0].y; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //TODO(martin): we shouldn't need scaling here since now we're doing our shader math in fixed point? + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + c1x /= 10; + c1y /= 10; + c2x /= 10; + c2y /= 10; + c3x /= 10; + c3y /= 10; + + /*NOTE(martin): + now, compute determinants d0, d1, d2, d3, which gives the coefficients of the + inflection points polynomial: + + I(t, s) = d0*t^3 - 3*d1*t^2*s + 3*d2*t*s^2 - d3*s^3 + + The roots of this polynomial are the inflection points of the parametric curve, in homogeneous + coordinates (ie we can have an inflection point at inifinity with s=0). + + |x3 y3 w3| |x3 y3 w3| |x3 y3 w3| |x2 y2 w2| + d0 = det |x2 y2 w2| d1 = -det |x2 y2 w2| d2 = det |x1 y1 w1| d3 = -det |x1 y1 w1| + |x1 y1 w1| |x0 y0 w0| |x0 y0 w0| |x0 y0 w0| + + In our case, the pi.w equal 1 (no point at infinity), so _in_the_power_basis_, w1 = w2 = w3 = 0 and w0 = 1 + (which also means d0 = 0) + */ + + f32 d1 = c3y*c2x - c3x*c2y; + f32 d2 = c3x*c1y - c3y*c1x; + f32 d3 = c2y*c1x - c2x*c1y; + + //NOTE(martin): compute the second factor of the discriminant discr(I) = d1^2*(3*d2^2 - 4*d3*d1) + f32 discrFactor2 = 3.0*Square(d2) - 4.0*d3*d1; + + //NOTE(martin): each following case gives the number of roots, hence the category of the parametric curve + if(fabs(d1) < 0.1 && fabs(d2) < 0.1 && d3 != 0) + { + //NOTE(martin): quadratic degenerate case + //NOTE(martin): compute quadratic curve control point, which is at p0 + 1.5*(p1-p0) = 1.5*p1 - 0.5*p0 + vec2 quadControlPoints[3] = { p[0], + {1.5*p[1].x - 0.5*p[0].x, 1.5*p[1].y - 0.5*p[0].y}, + p[3]}; + + mg_render_fill_quadratic(canvas, quadControlPoints); + return; + } + else if( (discrFactor2 > 0 && d1 != 0) + ||(discrFactor2 == 0 && d1 != 0)) + { + //NOTE(martin): serpentine curve or cusp with inflection at infinity + // (these two cases are handled the same way). + //NOTE(martin): compute the solutions (tl, sl), (tm, sm), and (tn, sn) of the inflection point equation + f32 tl = d2 + sqrt(discrFactor2/3); + f32 sl = 2*d1; + f32 tm = d2 - sqrt(discrFactor2/3); + f32 sm = sl; + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | tl*tm tl^3 tm^3 1 | + | -sm*tl - sl*tm -3sl*tl^2 -3*sm*tm^2 0 | + | sl*sm 3*sl^2*tl 3*sm^2*tm 0 | + | 0 -sl^3 -sm^3 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D image coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + testCoords[0].x = tl*tm; + testCoords[0].y = Cube(tl); + testCoords[0].z = Cube(tm); + + testCoords[1].x = tl*tm - (sm*tl + sl*tm)/3; + testCoords[1].y = Cube(tl) - sl*Square(tl); + testCoords[1].z = Cube(tm) - sm*Square(tm); + + testCoords[2].x = tl*tm - (sm*tl + sl*tm)*2/3 + sl*sm/3; + testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; + testCoords[2].z = Cube(tm) - 2*sm*Square(tm) + Square(sm)*tm; + + testCoords[3].x = tl*tm - (sm*tl + sl*tm) + sl*sm; + testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); + testCoords[3].z = Cube(tm) - 3*sm*Square(tm) + 3*Square(sm)*tm - Cube(sm); + } + else if(discrFactor2 < 0 && d1 != 0) + { + //NOTE(martin): loop curve + f32 td = d2 + sqrt(-discrFactor2); + f32 sd = 2*d1; + f32 te = d2 - sqrt(-discrFactor2); + f32 se = sd; + + //NOTE(martin): if one of the parameters (td/sd) or (te/se) is in the interval [0,1], the double point + // is inside the control points convex hull and would cause a shading anomaly. If this is + // the case, subdivide the curve at that point + + //TODO: study edge case where td/sd ~ 1 or 0 (which causes an infinite recursion in split and fill). + // quick fix for now is adding a little slop in the check... + + if(sd != 0 && td/sd < 0.99 && td/sd > 0.01) + { + mg_split_and_fill_cubic(canvas, p, td/sd); + return; + } + if(se != 0 && te/se < 0.99 && te/se > 0.01) + { + mg_split_and_fill_cubic(canvas, p, te/se); + return; + } + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | td*te td^2*te td*te^2 1 | + | -se*td - sd*te -se*td^2 - 2sd*te*td -sd*te^2 - 2*se*td*te 0 | + | sd*se te*sd^2 + 2*se*td*sd td*se^2 + 2*sd*te*se 0 | + | 0 -sd^2*se -sd*se^2 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D image coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + testCoords[0].x = td*te; + testCoords[0].y = Square(td)*te; + testCoords[0].z = td*Square(te); + + testCoords[1].x = td*te - (se*td + sd*te)/3.0; + testCoords[1].y = Square(td)*te - (se*Square(td) + 2.*sd*te*td)/3.0; + testCoords[1].z = td*Square(te) - (sd*Square(te) + 2*se*td*te)/3.0; + + testCoords[2].x = td*te - 2.0*(se*td + sd*te)/3.0 + sd*se/3.0; + testCoords[2].y = Square(td)*te - 2.0*(se*Square(td) + 2.0*sd*te*td)/3.0 + (te*Square(sd) + 2.0*se*td*sd)/3.0; + testCoords[2].z = td*Square(te) - 2.0*(sd*Square(te) + 2.0*se*td*te)/3.0 + (td*Square(se) + 2.0*sd*te*se)/3.0; + + testCoords[3].x = td*te - (se*td + sd*te) + sd*se; + testCoords[3].y = Square(td)*te - (se*Square(td) + 2.0*sd*te*td) + (te*Square(sd) + 2.0*se*td*sd) - Square(sd)*se; + testCoords[3].z = td*Square(te) - (sd*Square(te) + 2.0*se*td*te) + (td*Square(se) + 2.0*sd*te*se) - sd*Square(se); + } + else if(d1 == 0 && d2 != 0) + { + //NOTE(martin): cusp with cusp at infinity + + f32 tl = d3; + f32 sl = 3*d2; + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | tl tl^3 1 1 | + | -sl -3sl*tl^2 0 0 | + | 0 3*sl^2*tl 0 0 | + | 0 -sl^3 0 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D image coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + + testCoords[0].x = tl; + testCoords[0].y = Cube(tl); + testCoords[0].z = 1; + + testCoords[1].x = tl - sl/3; + testCoords[1].y = Cube(tl) - sl*Square(tl); + testCoords[1].z = 1; + + testCoords[2].x = tl - sl*2/3; + testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; + testCoords[2].z = 1; + + testCoords[3].x = tl - sl; + testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); + testCoords[3].z = 1; + } + else if(d1 == 0 && d2 == 0 && d3 == 0) + { + //NOTE(martin): line or point degenerate case, ignored + return; + } + else + { + //TODO(martin): handle error ? put some epsilon slack on the conditions ? + ASSERT(0, "not implemented yet !"); + return; + } + + //NOTE(martin): compute convex hull indices using Gift wrapping / Jarvis' march algorithm + int convexHullIndices[4]; + int leftMostPointIndex = 0; + + for(int i=0; i<4; i++) + { + if(p[i].x < p[leftMostPointIndex].x) + { + leftMostPointIndex = i; + } + } + int currentPointIndex = leftMostPointIndex; + int i=0; + int convexHullCount = 0; + + do + { + convexHullIndices[i] = currentPointIndex; + convexHullCount++; + int bestGuessIndex = 0; + + for(int j=0; j<4; j++) + { + vec2 bestGuessEdge = {.x = p[bestGuessIndex].x - p[currentPointIndex].x, + .y = p[bestGuessIndex].y - p[currentPointIndex].y}; + + vec2 nextGuessEdge = {.x = p[j].x - p[currentPointIndex].x, + .y = p[j].y - p[currentPointIndex].y}; + + //NOTE(martin): if control point j is on the right of current edge, it is a best guess + // (in case of colinearity we choose the point which is farthest from the current point) + + f32 crossProduct = bestGuessEdge.x*nextGuessEdge.y - bestGuessEdge.y*nextGuessEdge.x; + + if( bestGuessIndex == currentPointIndex + || crossProduct < 0) + { + bestGuessIndex = j; + } + else if(crossProduct == 0) + { + + //NOTE(martin): if vectors v1, v2 are colinear and distinct, and ||v1|| > ||v2||, + // either abs(v1.x) > abs(v2.x) or abs(v1.y) > abs(v2.y) + // so we don't actually need to compute their norm to select the greatest + // (and if v1 and v2 are equal we don't have to update our best guess.) + + //TODO(martin): in case of colinearity we should rather select the edge that has the greatest dot product with last edge ?? + + if(fabs(nextGuessEdge.x) > fabs(bestGuessEdge.x) + || fabs(nextGuessEdge.y) > fabs(bestGuessEdge.y)) + { + bestGuessIndex = j; + } + } + } + i++; + currentPointIndex = bestGuessIndex; + + } while(currentPointIndex != leftMostPointIndex && i<4); + + //NOTE(martin): triangulation and inside/outside tests. In the shader, the outside is defined by s*(k^3 - lm) > 0 + // ie the 4th coordinate s flips the inside/outside test. + // We affect s such that the covered are is between the curve and the line joining p0 and p3. + + //TODO: quick fix, maybe later cull degenerate hulls beforehand + if(convexHullCount <= 2) + { + //NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing. + return; + } + else if(convexHullCount == 3) + { + /*NOTE(martin): + We have 3 case here: + 1) Endpoints are coincidents. We push on triangle, and test an intermediate point for orientation. + 2) The point not on the hull is an endpoint. We push two triangle (p0, p3, p1) and (p0, p3, p2). We test the intermediate + points to know if we must flip the orientation of the curve. + 3) The point not on the hull is an intermediate point: we emit one triangle. We test the intermediate point on the hull + to know if we must flip the orientation of the curve. + */ + if( p[0].x == p[3].x + && p[0].y == p[3].y) + { + //NOTE: case 1: endpoints are coincidents + int outsideTest = mg_cubic_outside_test(testCoords[1]); + + //NOTE: push triangle + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest}); + mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest}); + mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest}); + + for(int i=0; i<3; i++) + { + indices[i] = baseIndex + i; + } + } + else + { + //NOTE: find point not on the hull + int insidePointIndex = -1; + { + bool present[4] = {0}; + for(int i=0; i<3; i++) + { + present[convexHullIndices[i]] = true; + } + for(int i=0; i<4; i++) + { + if(!present[i]) + { + insidePointIndex = i; + break; + } + } + } + DEBUG_ASSERT(insidePointIndex >= 0 && insidePointIndex < 4); + + if(insidePointIndex == 0 || insidePointIndex == 3) + { + //NOTE: case 2: the point inside the hull is an endpoint + + int outsideTest0 = mg_cubic_outside_test(testCoords[1]); + int outsideTest1 = mg_cubic_outside_test(testCoords[2]); + + //NOTE: push triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest0}); + mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest0}); + mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest0}); + mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest1}); + mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest1}); + mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest1}); + + for(int i=0; i<6; i++) + { + indices[i] = baseIndex + i; + } + } + else + { + int testIndex = (insidePointIndex == 1) ? 2 : 1; + int outsideTest = mg_cubic_outside_test(testCoords[testIndex]); + + //NOTE: push triangle + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 3); + + for(int i=0; i<3; i++) + { + mg_push_vertex_cubic(canvas, + p[convexHullIndices[i]], + (vec4){vec4_expand_xyz(testCoords[convexHullIndices[i]]), + outsideTest}); + } + + for(int i=0; i<3; i++) + { + indices[i] = baseIndex + i; + } + } + } + } + else + { + DEBUG_ASSERT(convexHullCount == 4); + /*NOTE(martin): + We build a fan from the hull, starting from an endpoint. For each triangle, we test the vertex that is an intermediate + control point for orientation + */ + int endPointIndex = -1; + for(int i=0; i<4; i++) + { + if(convexHullIndices[i] == 0 || convexHullIndices[i] == 3) + { + endPointIndex = i; + break; + } + } + ASSERT(endPointIndex >= 0); + + int fanIndices[6] = {convexHullIndices[endPointIndex], + convexHullIndices[(endPointIndex + 1)%4], + convexHullIndices[(endPointIndex + 2)%4], + convexHullIndices[endPointIndex], + convexHullIndices[(endPointIndex + 2)%4], + convexHullIndices[(endPointIndex + 3)%4]}; + + //NOTE: fan indices on the hull are (0,1,2)(0,2,3). So if the 3rd vertex of the hull is an intermediate point it works + // as a test vertex for both triangles. Otherwise, the test vertices on the fan are 1 and 5. + int outsideTest0 = 1; + int outsideTest1 = 1; + + if( fanIndices[2] == 1 + ||fanIndices[2] == 2) + { + outsideTest0 = outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[2]]); + } + else + { + DEBUG_ASSERT(fanIndices[1] == 1 || fanIndices[1] == 2); + DEBUG_ASSERT(fanIndices[5] == 1 || fanIndices[5] == 2); + + outsideTest0 = mg_cubic_outside_test(testCoords[fanIndices[1]]); + outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[5]]); + } + + //NOTE: push triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + for(int i=0; i<3; i++) + { + mg_push_vertex_cubic(canvas, p[fanIndices[i]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i]]), outsideTest0}); + } + for(int i=0; i<3; i++) + { + mg_push_vertex_cubic(canvas, p[fanIndices[i+3]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i+3]]), outsideTest1}); + } + + for(int i=0; i<6; i++) + { + indices[i] = baseIndex + i; + } + } +} + +//NOTE(martin): global path fill + +void mg_render_fill(mg_canvas_data* canvas, mg_path_elt* elements, mg_path_descriptor* path) +{ + u32 eltCount = path->count; + vec2 startPoint = path->startPoint; + vec2 endPoint = path->startPoint; + vec2 currentPoint = path->startPoint; + + for(int eltIndex=0; eltIndexp[0], elt->p[1], elt->p[2]}; + + switch(elt->type) + { + case MG_PATH_MOVE: + { + startPoint = elt->p[0]; + endPoint = elt->p[0]; + currentPoint = endPoint; + continue; + } break; + + case MG_PATH_LINE: + { + endPoint = controlPoints[1]; + } break; + + case MG_PATH_QUADRATIC: + { + mg_render_fill_quadratic(canvas, controlPoints); + endPoint = controlPoints[2]; + + } break; + + case MG_PATH_CUBIC: + { + mg_render_fill_cubic(canvas, controlPoints); + endPoint = controlPoints[3]; + } break; + } + + //NOTE(martin): now fill interior triangle + u32 baseIndex = mg_vertices_base_index(canvas); + int* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex(canvas, startPoint); + mg_push_vertex(canvas, currentPoint); + mg_push_vertex(canvas, endPoint); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + + currentPoint = endPoint; + } +} + +//----------------------------------------------------------------------------------------------------------- +// Path Stroking +//----------------------------------------------------------------------------------------------------------- + +void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], mg_attributes* attributes) +{ + //NOTE(martin): get normals multiplied by halfWidth + f32 halfW = attributes->width/2; + + vec2 n0 = {p[0].y - p[1].y, + p[1].x - p[0].x}; + f32 norm0 = sqrt(n0.x*n0.x + n0.y*n0.y); + n0.x *= halfW/norm0; + n0.y *= halfW/norm0; + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, (vec2){p[0].x + n0.x, p[0].y + n0.y}); + mg_push_vertex(canvas, (vec2){p[1].x + n0.x, p[1].y + n0.y}); + mg_push_vertex(canvas, (vec2){p[1].x - n0.x, p[1].y - n0.y}); + mg_push_vertex(canvas, (vec2){p[0].x - n0.x, p[0].y - n0.y}); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +bool mg_intersect_hull_legs(vec2 p0, vec2 p1, vec2 p2, vec2 p3, vec2* intersection) +{ + /*NOTE: check intersection of lines (p0-p1) and (p2-p3) + + P = p0 + u(p1-p0) + P = p2 + w(p3-p2) + */ + bool found = false; + + f32 den = (p0.x - p1.x)*(p2.y - p3.y) - (p0.y - p1.y)*(p2.x - p3.x); + if(fabs(den) > 0.0001) + { + f32 u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/den; + f32 w = ((p0.x - p2.x)*(p0.y - p1.y) - (p0.y - p2.y)*(p0.x - p1.x))/den; + + intersection->x = p0.x + u*(p1.x - p0.x); + intersection->y = p0.y + u*(p1.y - p0.y); + found = true; + } + return(found); +} + +bool mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) +{ + //NOTE: we should have no more than two coincident points here. This means the leg between + // those two points can't be offset, but we can set a double point at the start of first leg, + // end of first leg, or we can join the first and last leg to create a missing middle one + + vec2 legs[3][2] = {0}; + bool valid[3] = {0}; + + for(int i=0; i= 1e-6) + { + n = vec2_mul(offset/norm, n); + legs[i][0] = vec2_add(p[i], n); + legs[i][1] = vec2_add(p[i+1], n); + valid[i] = true; + } + } + + //NOTE: now we find intersections + + // first point is either the start of the first or second leg + if(valid[0]) + { + result[0] = legs[0][0]; + } + else + { + ASSERT(valid[1]); + result[0] = legs[1][0]; + } + + for(int i=1; isplitCount++; + + //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm + // the q_n are the points along the hull's segments at parameter t + // s is the split point. + + f32 oneMt = 1-t; + + vec2 q0 = {oneMt*p[0].x + t*p[1].x, + oneMt*p[0].y + t*p[1].y}; + + vec2 q1 = {oneMt*p[1].x + t*p[2].x, + oneMt*p[1].y + t*p[2].y}; + + vec2 s = {oneMt*q0.x + t*q1.x, + oneMt*q0.y + t*q1.y}; + + outLeft[0] = p[0]; + outLeft[1] = q0; + outLeft[2] = s; + + outRight[0] = s; + outRight[1] = q1; + outRight[2] = p[2]; +} + + +void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[3], mg_attributes* attributes) +{ + //NOTE: check for degenerate line case + const f32 equalEps = 1e-3; + if(vec2_close(p[0], p[1], equalEps)) + { + mg_render_stroke_line(canvas, p+1, attributes); + return; + } + else if(vec2_close(p[1], p[2], equalEps)) + { + mg_render_stroke_line(canvas, p, attributes); + return; + } + + #define CHECK_SAMPLE_COUNT 5 + f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6}; + + vec2 positiveOffsetHull[3]; + vec2 negativeOffsetHull[3]; + + if( !mg_offset_hull(3, p, positiveOffsetHull, 0.5 * attributes->width) + ||!mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width)) + { + //NOTE: offsetting the hull failed, split the curve + vec2 splitLeft[3]; + vec2 splitRight[3]; + mg_quadratic_split(p, 0.5, splitLeft, splitRight); + mg_render_stroke_quadratic(canvas, splitLeft, attributes); + mg_render_stroke_quadratic(canvas, splitRight, attributes); + } + else + { + //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance + // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: + // + // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 + // + // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + + //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot + f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); + f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); + f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); + + f32 maxOvershoot = 0; + f32 maxOvershootParameter = 0; + + for(int i=0; i maxOvershoot) + { + maxOvershoot = overshoot; + maxOvershootParameter = t; + } + } + + if(maxOvershoot > 0) + { + vec2 splitLeft[3]; + vec2 splitRight[3]; + mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_quadratic(canvas, splitLeft, attributes); + mg_render_stroke_quadratic(canvas, splitRight, attributes); + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + + mg_next_shape(canvas, attributes); + + mg_render_fill_quadratic(canvas, positiveOffsetHull); + mg_render_fill_quadratic(canvas, negativeOffsetHull); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, positiveOffsetHull[0]); + mg_push_vertex(canvas, positiveOffsetHull[2]); + mg_push_vertex(canvas, negativeOffsetHull[2]); + mg_push_vertex(canvas, negativeOffsetHull[0]); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + } + #undef CHECK_SAMPLE_COUNT +} + +vec2 mg_cubic_get_point(vec2 p[4], f32 t) +{ + vec2 r; + + f32 oneMt = 1-t; + f32 oneMt2 = Square(oneMt); + f32 oneMt3 = oneMt2*oneMt; + f32 t2 = Square(t); + f32 t3 = t2*t; + + r.x = oneMt3*p[0].x + 3*oneMt2*t*p[1].x + 3*oneMt*t2*p[2].x + t3*p[3].x; + r.y = oneMt3*p[0].y + 3*oneMt2*t*p[1].y + 3*oneMt*t2*p[2].y + t3*p[3].y; + + return(r); +} + +void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4]) +{ + //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm + // the q_n are the points along the hull's segments at parameter t + // the r_n are the points along the (q_n, q_n+1) segments at parameter t + // s is the split point. + + vec2 q0 = {(1-t)*p[0].x + t*p[1].x, + (1-t)*p[0].y + t*p[1].y}; + + vec2 q1 = {(1-t)*p[1].x + t*p[2].x, + (1-t)*p[1].y + t*p[2].y}; + + vec2 q2 = {(1-t)*p[2].x + t*p[3].x, + (1-t)*p[2].y + t*p[3].y}; + + vec2 r0 = {(1-t)*q0.x + t*q1.x, + (1-t)*q0.y + t*q1.y}; + + vec2 r1 = {(1-t)*q1.x + t*q2.x, + (1-t)*q1.y + t*q2.y}; + + vec2 s = {(1-t)*r0.x + t*r1.x, + (1-t)*r0.y + t*r1.y};; + + outLeft[0] = p[0]; + outLeft[1] = q0; + outLeft[2] = r0; + outLeft[3] = s; + + outRight[0] = s; + outRight[1] = r1; + outRight[2] = q2; + outRight[3] = p[3]; +} + +void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes) +{ + //NOTE: check degenerate line cases + f32 equalEps = 1e-3; + + if( (vec2_close(p[0], p[1], equalEps) && vec2_close(p[2], p[3], equalEps)) + ||(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[2], equalEps)) + ||(vec2_close(p[1], p[2], equalEps) && vec2_close(p[2], p[3], equalEps))) + { + vec2 line[2] = {p[0], p[3]}; + mg_render_stroke_line(canvas, line, attributes); + return; + } + else if(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[3], equalEps)) + { + vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[2]))}; + mg_render_stroke_line(canvas, line, attributes); + return; + } + else if(vec2_close(p[0], p[2], equalEps) && vec2_close(p[2], p[3], equalEps)) + { + vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[1]))}; + mg_render_stroke_line(canvas, line, attributes); + return; + } + + #define CHECK_SAMPLE_COUNT 5 + f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6}; + + vec2 positiveOffsetHull[4]; + vec2 negativeOffsetHull[4]; + + if( !mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width) + || !mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width)) + { + vec2 splitLeft[4]; + vec2 splitRight[4]; + mg_cubic_split(p, 0.5, splitLeft, splitRight); + mg_render_stroke_cubic(canvas, splitLeft, attributes); + mg_render_stroke_cubic(canvas, splitRight, attributes); + return; + } + + //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance + // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: + // + // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 + // + // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + + //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot + f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); + f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); + f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); + + f32 maxOvershoot = 0; + f32 maxOvershootParameter = 0; + + for(int i=0; i maxOvershoot) + { + maxOvershoot = overshoot; + maxOvershootParameter = t; + } + } + + if(maxOvershoot > 0) + { + vec2 splitLeft[4]; + vec2 splitRight[4]; + mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_cubic(canvas, splitLeft, attributes); + mg_render_stroke_cubic(canvas, splitRight, attributes); + + //TODO: render joint between the split curves + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + mg_next_shape(canvas, attributes); + + mg_render_fill_cubic(canvas, positiveOffsetHull); + mg_render_fill_cubic(canvas, negativeOffsetHull); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, positiveOffsetHull[0]); + mg_push_vertex(canvas, positiveOffsetHull[3]); + mg_push_vertex(canvas, negativeOffsetHull[3]); + mg_push_vertex(canvas, negativeOffsetHull[0]); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + #undef CHECK_SAMPLE_COUNT +} + +void mg_stroke_cap(mg_canvas_data* canvas, vec2 p0, vec2 direction, mg_attributes* attributes) +{ + //NOTE(martin): compute the tangent and normal vectors (multiplied by half width) at the cap point + + f32 dn = sqrt(Square(direction.x) + Square(direction.y)); + f32 alpha = 0.5 * attributes->width/dn; + + vec2 n0 = {-alpha*direction.y, + alpha*direction.x}; + + vec2 m0 = {alpha*direction.x, + alpha*direction.y}; + + mg_next_shape(canvas, attributes); + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, (vec2){p0.x + n0.x, p0.y + n0.y}); + mg_push_vertex(canvas, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y}); + mg_push_vertex(canvas, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y}); + mg_push_vertex(canvas, (vec2){p0.x - n0.x, p0.y - n0.y}); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_stroke_joint(mg_canvas_data* canvas, + vec2 p0, + vec2 t0, + vec2 t1, + mg_attributes* attributes) +{ + //NOTE(martin): compute the normals at the joint point + f32 norm_t0 = sqrt(Square(t0.x) + Square(t0.y)); + f32 norm_t1 = sqrt(Square(t1.x) + Square(t1.y)); + + vec2 n0 = {-t0.y, t0.x}; + n0.x /= norm_t0; + n0.y /= norm_t0; + + vec2 n1 = {-t1.y, t1.x}; + n1.x /= norm_t1; + n1.y /= norm_t1; + + //NOTE(martin): the sign of the cross product determines if the normals are facing outwards or inwards the angle. + // we flip them to face outwards if needed + f32 crossZ = n0.x*n1.y - n0.y*n1.x; + if(crossZ > 0) + { + n0.x *= -1; + n0.y *= -1; + n1.x *= -1; + n1.y *= -1; + } + + mg_next_shape(canvas, attributes); + + //NOTE(martin): use the same code as hull offset to find mitter point... + /*NOTE(martin): let vector u = (n0+n1) and vector v = pIntersect - p1 + then v = u * (2*offset / norm(u)^2) + (this can be derived from writing the pythagoras theorems in the triangles of the joint) + */ + f32 halfW = 0.5 * attributes->width; + vec2 u = {n0.x + n1.x, n0.y + n1.y}; + f32 uNormSquare = u.x*u.x + u.y*u.y; + f32 alpha = attributes->width / uNormSquare; + vec2 v = {u.x * alpha, u.y * alpha}; + + f32 excursionSquare = uNormSquare * Square(alpha - attributes->width/4); + + if( attributes->joint == MG_JOINT_MITER + && excursionSquare <= Square(attributes->maxJointExcursion)) + { + vec2 mitterPoint = {p0.x + v.x, p0.y + v.y}; + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, p0); + mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}); + mg_push_vertex(canvas, mitterPoint); + mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + else + { + //NOTE(martin): add a bevel joint + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex(canvas, p0); + mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}); + mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}); + + DEBUG_ASSERT(!isnan(n0.x) && !isnan(n0.y) && !isnan(n1.x) && !isnan(n1.y)); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + } +} + +void mg_render_stroke_element(mg_canvas_data* canvas, + mg_path_elt* element, + mg_attributes* attributes, + vec2 currentPoint, + vec2* startTangent, + vec2* endTangent, + vec2* endPoint) +{ + vec2 controlPoints[4] = {currentPoint, element->p[0], element->p[1], element->p[2]}; + int endPointIndex = 0; + mg_next_shape(canvas, attributes); + + switch(element->type) + { + case MG_PATH_LINE: + mg_render_stroke_line(canvas, controlPoints, attributes); + endPointIndex = 1; + break; + + case MG_PATH_QUADRATIC: + mg_render_stroke_quadratic(canvas, controlPoints, attributes); + endPointIndex = 2; + break; + + case MG_PATH_CUBIC: + mg_render_stroke_cubic(canvas, controlPoints, attributes); + endPointIndex = 3; + break; + + case MG_PATH_MOVE: + ASSERT(0, "should be unreachable"); + break; + } + + //NOTE: ensure tangents are properly computed even in presence of coincident points + //TODO: see if we can do this in a less hacky way + + for(int i=1; i<4; i++) + { + if( controlPoints[i].x != controlPoints[0].x + || controlPoints[i].y != controlPoints[0].y) + { + *startTangent = (vec2){.x = controlPoints[i].x - controlPoints[0].x, + .y = controlPoints[i].y - controlPoints[0].y}; + break; + } + } + *endPoint = controlPoints[endPointIndex]; + + for(int i=endPointIndex-1; i>=0; i++) + { + if( controlPoints[i].x != endPoint->x + || controlPoints[i].y != endPoint->y) + { + *endTangent = (vec2){.x = endPoint->x - controlPoints[i].x, + .y = endPoint->y - controlPoints[i].y}; + break; + } + } + + DEBUG_ASSERT(startTangent->x != 0 || startTangent->y != 0); +} + +u32 mg_render_stroke_subpath(mg_canvas_data* canvas, + mg_path_elt* elements, + mg_path_descriptor* path, + mg_attributes* attributes, + u32 startIndex, + vec2 startPoint) +{ + u32 eltCount = path->count; + DEBUG_ASSERT(startIndex < eltCount); + + vec2 currentPoint = startPoint; + vec2 endPoint = {0, 0}; + vec2 previousEndTangent = {0, 0}; + vec2 firstTangent = {0, 0}; + vec2 startTangent = {0, 0}; + vec2 endTangent = {0, 0}; + + //NOTE(martin): render first element and compute first tangent + mg_render_stroke_element(canvas, elements + startIndex, attributes, currentPoint, &startTangent, &endTangent, &endPoint); + + firstTangent = startTangent; + previousEndTangent = endTangent; + currentPoint = endPoint; + + //NOTE(martin): render subsequent elements along with their joints + u32 eltIndex = startIndex + 1; + for(; + eltIndexjoint != MG_JOINT_NONE) + { + mg_stroke_joint(canvas, currentPoint, previousEndTangent, startTangent, attributes); + } + previousEndTangent = endTangent; + currentPoint = endPoint; + } + u32 subPathEltCount = eltIndex - (startIndex+1); + + //NOTE(martin): draw end cap / joint. We ensure there's at least two segments to draw a closing joint + if( subPathEltCount > 1 + && startPoint.x == endPoint.x + && startPoint.y == endPoint.y) + { + if(attributes->joint != MG_JOINT_NONE) + { + //NOTE(martin): add a closing joint if the path is closed + mg_stroke_joint(canvas, endPoint, endTangent, firstTangent, attributes); + } + } + else if(attributes->cap == MG_CAP_SQUARE) + { + //NOTE(martin): add start and end cap + mg_stroke_cap(canvas, startPoint, (vec2){-startTangent.x, -startTangent.y}, attributes); + mg_stroke_cap(canvas, endPoint, startTangent, attributes); + } + + return(eltIndex); +} + + +void mg_render_stroke(mg_canvas_data* canvas, + mg_path_elt* elements, + mg_path_descriptor* path, + mg_attributes* attributes) +{ + u32 eltCount = path->count; + DEBUG_ASSERT(eltCount); + + vec2 startPoint = path->startPoint; + u32 startIndex = 0; + + while(startIndex < eltCount) + { + //NOTE(martin): eliminate leading moves + while(startIndex < eltCount && elements[startIndex].type == MG_PATH_MOVE) + { + startPoint = elements[startIndex].p[0]; + startIndex++; + } + + if(startIndex < eltCount) + { + startIndex = mg_render_stroke_subpath(canvas, elements, path, attributes, startIndex, startPoint); + } + } +} + +mg_canvas mg_canvas_prepare(mg_canvas canvas) +{ + mg_canvas old = __mgCurrentCanvasHandle; + + __mgCurrentCanvasHandle = canvas; + __mgCurrentCanvas = mg_canvas_data_from_handle(canvas); + + if(__mgCurrentCanvas) + { + mg_surface_prepare(__mgCurrentCanvas->surface); + } + return(old); +} + +vec2 mg_canvas_size(void) +{ + vec2 res = {0}; + if(__mgCurrentCanvas) + { + mp_rect frame = mg_surface_get_frame(__mgCurrentCanvas->surface); + res = (vec2){frame.w, frame.h}; + } + return(res); +} + +void mg_draw_batch(mg_canvas_data* canvas, mg_image_data* image) +{ + mg_finalize_shape(canvas); + + if(canvas->backend && canvas->backend->drawBatch && canvas->indexCount) + { + canvas->backend->drawBatch(canvas->backend, image, canvas->nextShapeIndex, canvas->vertexCount, canvas->indexCount); + } + mg_reset_shape_index(canvas); + + canvas->vertexCount = 0; + canvas->indexCount = 0; +} + +void mg_flush_commands(int primitiveCount, mg_primitive* primitives, mg_path_elt* pathElements) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + + + if(canvas->backend && canvas->backend->render) + { + int eltCount = canvas->path.startIndex + canvas->path.count; + canvas->backend->render(canvas->backend, canvas->clearColor, primitiveCount, primitives, eltCount, pathElements); + return; + } + + u32 nextIndex = 0; + + mg_reset_shape_index(canvas); + + canvas->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + canvas->image = mg_image_nil(); + + canvas->backend->begin(canvas->backend, canvas->clearColor); + + //DEBUG + canvas->splitCount = 0; + + for(int i=0; i= primitiveCount) + { + log_error("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex); + break; + } + mg_primitive* primitive = &(primitives[nextIndex]); + nextIndex++; + + if(i && primitive->attributes.image.h != canvas->image.h) + { + mg_image_data* imageData = mg_image_data_from_handle(canvas->image); + mg_draw_batch(canvas, imageData); + canvas->image = primitive->attributes.image; + } + + switch(primitive->cmd) + { + case MG_CMD_FILL: + { + mg_next_shape(canvas, &primitive->attributes); + mg_render_fill(canvas, + pathElements + primitive->path.startIndex, + &primitive->path); + } break; + + case MG_CMD_STROKE: + { + mg_render_stroke(canvas, + pathElements + primitive->path.startIndex, + &primitive->path, + &primitive->attributes); + } break; + + case MG_CMD_JUMP: + { + if(primitive->jump == ~0) + { + //NOTE(martin): normal end of stream marker + goto exit_command_loop; + } + else if(primitive->jump >= primitiveCount) + { + log_error("invalid jump location '%i' in graphics command buffer\n", primitive->jump); + goto exit_command_loop; + } + else + { + nextIndex = primitive->jump; + } + } break; + } + } + exit_command_loop: ; + + printf("path elements: %i, splitCount = %i\n", canvas->path.startIndex + canvas->path.count, canvas->splitCount); + + + mg_image_data* imageData = mg_image_data_from_handle(canvas->image); + mg_draw_batch(canvas, imageData); + + canvas->backend->end(canvas->backend); + + //NOTE(martin): clear buffers + canvas->vertexCount = 0; + canvas->indexCount = 0; +} diff --git a/src/ui.c b/src/ui.c index a7399b4..9c27438 100644 --- a/src/ui.c +++ b/src/ui.c @@ -2293,7 +2293,7 @@ typedef struct ui_edit_command } ui_edit_command; -#if PLATFORM_WIN64 +#if PLATFORM_WINDOWS #define OS_COPY_PASTE_MOD MP_KEYMOD_CTRL #elif PLATFORM_MACOS #define OS_COPY_PASTE_MOD MP_KEYMOD_CMD