diff --git a/build.bat b/build.bat index ecaf302..04a06a4 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,8 @@ if not exist bin mkdir bin +call python scripts\embed_text.py src\gles_canvas_shaders\gles_canvas_fragment.glsl src\gles_canvas_shaders\gles_canvas_vertex.glsl --output src\gles_canvas_shaders.h + set INCLUDES=/I src /I src/util /I src/platform /I ext /I ext/angle_headers -cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% /c /Fo:bin/milepost.obj src/milepost.c +cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GLES /std:c11 %INCLUDES% /c /Fo:bin/milepost.obj src/milepost.c lib bin/milepost.obj /OUT:bin/milepost.lib diff --git a/examples/canvas/build.bat b/examples/canvas/build.bat new file mode 100644 index 0000000..bf1053a --- /dev/null +++ b/examples/canvas/build.bat @@ -0,0 +1,3 @@ + +set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers +cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GLES /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_canvas.exe diff --git a/examples/canvas/main.c b/examples/canvas/main.c index 6d6fcbb..532c364 100644 --- a/examples/canvas/main.c +++ b/examples/canvas/main.c @@ -1,144 +1,153 @@ -/************************************************************//** -* -* @file: main.cpp -* @author: Martin Fouilleul -* @date: 30/07/2022 -* @revision: -* -*****************************************************************/ -#include -#include - -#define _USE_MATH_DEFINES //NOTE: necessary for MSVC -#include - -#include"milepost.h" -#include"metal_surface.h" - -#define LOG_SUBSYSTEM "Main" - -int main() -{ - LogLevel(LOG_LEVEL_DEBUG); - - mp_init(); - - mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600}; - mp_window window = mp_window_create(rect, "test", 0); - - //NOTE: create surface - mg_surface surface = mg_metal_surface_create_for_window(window); - - //TODO: create canvas - mg_canvas canvas = mg_canvas_create(surface); - - // start app - mp_window_bring_to_front(window); - mp_window_focus(window); - - while(!mp_should_quit()) - { - mp_pump_events(0); - mp_event event = {0}; - while(mp_next_event(&event)) - { - switch(event.type) - { - case MP_EVENT_WINDOW_CLOSE: - { - mp_request_quit(); - } break; - - case MP_EVENT_WINDOW_RESIZE: - { - printf("resized, rect = {%f, %f, %f, %f}\n", - event.frame.rect.x, - event.frame.rect.y, - event.frame.rect.w, - event.frame.rect.h); - } break; - - case MP_EVENT_WINDOW_MOVE: - { - printf("moved, rect = {%f, %f, %f, %f}\n", - event.frame.rect.x, - event.frame.rect.y, - event.frame.rect.w, - event.frame.rect.h); - } break; - - case MP_EVENT_MOUSE_MOVE: - { - printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n", - event.move.x, - event.move.y, - event.move.deltaX, - event.move.deltaY); - } break; - - case MP_EVENT_MOUSE_WHEEL: - { - printf("mouse wheel, delta = {%f, %f}\n", - event.move.deltaX, - event.move.deltaY); - } break; - - case MP_EVENT_MOUSE_ENTER: - { - printf("mouse enter\n"); - } break; - - case MP_EVENT_MOUSE_LEAVE: - { - printf("mouse leave\n"); - } break; - - case MP_EVENT_MOUSE_BUTTON: - { - printf("mouse button %i: %i\n", - event.key.code, - event.key.action == MP_KEY_PRESS ? 1 : 0); - } break; - - case MP_EVENT_KEYBOARD_KEY: - { - printf("key %i: %s\n", - event.key.code, - event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat")); - } break; - - case MP_EVENT_KEYBOARD_CHAR: - { - printf("entered char %s\n", event.character.sequence); - } break; - - default: - break; - } - } - - mg_surface_prepare(surface); - mg_set_color_rgba(1, 0, 1, 1); - mg_clear(); - - mg_set_color_rgba(1, 1, 0, 1); - mg_circle_fill(400, 300, 200); - - mg_set_color_rgba(0, 0, 0, 1); - mg_set_width(20); - - mg_move_to(300, 200); - mg_cubic_to(350, 150, 450, 150, 500, 200); - mg_stroke(); - - mg_ellipse_fill(330, 350, 30, 50); - mg_ellipse_fill(470, 350, 30, 50); - - mg_flush(); - mg_surface_present(surface); - } - - mp_terminate(); - - return(0); -} +/************************************************************//** +* +* @file: main.cpp +* @author: Martin Fouilleul +* @date: 30/07/2022 +* @revision: +* +*****************************************************************/ +#include +#include + +#define _USE_MATH_DEFINES //NOTE: necessary for MSVC +#include + +#include"milepost.h" + +#define LOG_SUBSYSTEM "Main" + +int main() +{ + LogLevel(LOG_LEVEL_DEBUG); + + mp_init(); + + mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600}; + mp_window window = mp_window_create(rect, "test", 0); + + //NOTE: create surface +#if defined(OS_MACOS) + mg_surface surface = mg_metal_surface_create_for_window(window); +#elif defined(OS_WIN64) + mg_surface surface = mg_gles_surface_create_for_window(window); +#else + #error "unsupported OS" +#endif + + //TODO: create canvas + mg_canvas canvas = mg_canvas_create(surface); + + // start app + mp_window_bring_to_front(window); + mp_window_focus(window); + + while(!mp_should_quit()) + { + mp_pump_events(0); + mp_event event = {0}; + while(mp_next_event(&event)) + { + switch(event.type) + { + case MP_EVENT_WINDOW_CLOSE: + { + mp_request_quit(); + } break; + + case MP_EVENT_WINDOW_RESIZE: + { + printf("resized, rect = {%f, %f, %f, %f}\n", + event.frame.rect.x, + event.frame.rect.y, + event.frame.rect.w, + event.frame.rect.h); + } break; + + case MP_EVENT_WINDOW_MOVE: + { + printf("moved, rect = {%f, %f, %f, %f}\n", + event.frame.rect.x, + event.frame.rect.y, + event.frame.rect.w, + event.frame.rect.h); + } break; + + case MP_EVENT_MOUSE_MOVE: + { + printf("mouse moved, pos = {%f, %f}, delta = {%f, %f}\n", + event.move.x, + event.move.y, + event.move.deltaX, + event.move.deltaY); + } break; + + case MP_EVENT_MOUSE_WHEEL: + { + printf("mouse wheel, delta = {%f, %f}\n", + event.move.deltaX, + event.move.deltaY); + } break; + + case MP_EVENT_MOUSE_ENTER: + { + printf("mouse enter\n"); + } break; + + case MP_EVENT_MOUSE_LEAVE: + { + printf("mouse leave\n"); + } break; + + case MP_EVENT_MOUSE_BUTTON: + { + printf("mouse button %i: %i\n", + event.key.code, + event.key.action == MP_KEY_PRESS ? 1 : 0); + } break; + + case MP_EVENT_KEYBOARD_KEY: + { + printf("key %i: %s\n", + event.key.code, + event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat")); + } break; + + case MP_EVENT_KEYBOARD_CHAR: + { + printf("entered char %s\n", event.character.sequence); + } break; + + default: + break; + } + } + + mg_surface_prepare(surface); + // background + mg_set_color_rgba(1, 0, 1, 1); + mg_clear(); + + // head + mg_set_color_rgba(1, 1, 0, 1); + mg_circle_fill(400, 300, 200); + + // smile + mg_set_color_rgba(1, 0, 0, 1); + + mg_set_width(20); + mg_move_to(300, 200); + mg_cubic_to(350, 150, 450, 150, 500, 200); + mg_stroke(); + + // eyes + mg_ellipse_fill(330, 350, 30, 50); + mg_ellipse_fill(470, 350, 30, 50); + + mg_flush(); + mg_surface_present(surface); + } + + mp_terminate(); + + return(0); +} diff --git a/examples/triangleGLES/build.bat b/examples/triangleGLES/build.bat index 704d78c..a108856 100644 --- a/examples/triangleGLES/build.bat +++ b/examples/triangleGLES/build.bat @@ -1,2 +1,2 @@ set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers -cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:./ libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:test.exe +cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_gles_triangle.exe diff --git a/examples/win_canvas/build.bat b/examples/win_canvas/build.bat new file mode 100644 index 0000000..bf1053a --- /dev/null +++ b/examples/win_canvas/build.bat @@ -0,0 +1,3 @@ + +set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers +cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GLES /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib /LIBPATH:../../bin libEGL.dll.lib libGLESv2.dll.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_canvas.exe diff --git a/examples/win_canvas/build.sh b/examples/win_canvas/build.sh new file mode 100644 index 0000000..de7d701 --- /dev/null +++ b/examples/win_canvas/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BINDIR=../../bin +RESDIR=../../resources +SRCDIR=../../src + +INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app" +LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore" +FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG -Wl,-dead_strip" + +clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_canvas main.c diff --git a/examples/win_canvas/main.c b/examples/win_canvas/main.c new file mode 100644 index 0000000..75cfbad --- /dev/null +++ b/examples/win_canvas/main.c @@ -0,0 +1,136 @@ +/************************************************************//** +* +* @file: main.cpp +* @author: Martin Fouilleul +* @date: 30/07/2022 +* @revision: +* +*****************************************************************/ +#include +#include + +#define _USE_MATH_DEFINES //NOTE: necessary for MSVC +#include + +#include"milepost.h" + +#define LOG_SUBSYSTEM "Main" + +int main() +{ + LogLevel(LOG_LEVEL_DEBUG); + + mp_init(); + + mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600}; + mp_window window = mp_window_create(rect, "test", 0); + + //NOTE: create surface +#if defined(OS_MACOS) + mg_surface surface = mg_metal_surface_create_for_window(window); +#elif defined(OS_WIN64) + mg_surface surface = mg_gles_surface_create_for_window(window); +#else + #error "unsupported OS" +#endif + + //TODO: create canvas + mg_canvas canvas = mg_canvas_create(surface); + + // start app + mp_window_bring_to_front(window); + mp_window_focus(window); + + f32 dx = 17.000029, dy = 0; + + while(!mp_should_quit()) + { + mp_pump_events(0); + mp_event event = {0}; + while(mp_next_event(&event)) + { + switch(event.type) + { + case MP_EVENT_WINDOW_CLOSE: + { + mp_request_quit(); + } break; + + case MP_EVENT_WINDOW_RESIZE: + { + printf("resized, rect = {%f, %f, %f, %f}\n", + event.frame.rect.x, + event.frame.rect.y, + event.frame.rect.w, + event.frame.rect.h); + } break; + + case MP_EVENT_KEYBOARD_KEY: + { + printf("key %i: %s\n", + event.key.code, + event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat")); + if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT) + { + if(event.key.code == MP_KEY_LEFT) + { + printf("left\n"); + dx-=0.1; + } + else if(event.key.code == MP_KEY_RIGHT) + { + printf("right\n"); + dx+=0.1; + } + else if(event.key.code == MP_KEY_UP) + { + printf("up\n"); + dy+=0.1; + } + else if(event.key.code == MP_KEY_DOWN) + { + printf("down\n"); + dy-=0.1; + } + } + } break; + + default: + break; + } + } + + mg_surface_prepare(surface); + + printf("dx = %f, dy = %f\n", dx, dy); + + // background + mg_set_color_rgba(1, 0, 1, 1); + mg_clear(); +/* + // head + mg_set_color_rgba(1, 1, 0, 1); + mg_circle_fill(dx+400, dy+300, 200); + + // smile + mg_set_color_rgba(0, 0, 0, 1); + + mg_set_width(20); + mg_move_to(dx+300, dy+200); + mg_cubic_to(dx+350, dy+150, dx+450, dy+150, dx+500, dy+200); + mg_stroke(); + + // eyes + mg_ellipse_fill(dx+330, dy+350, 30, 50); + mg_ellipse_fill(dx+470, dy+350, 30, 50); +*/ + mg_rectangle_fill((int)(dx + 200), 200, (int)(dy+300), (int)(dy+300)); + + mg_flush(); + mg_surface_present(surface); + } + + mp_terminate(); + + return(0); +} diff --git a/src/gles_canvas.c b/src/gles_canvas.c index fb0a0b7..1edbbc2 100644 --- a/src/gles_canvas.c +++ b/src/gles_canvas.c @@ -38,41 +38,19 @@ mg_gles_surface* mg_gles_canvas_get_surface(mg_gles_canvas_backend* canvas) return(res); } -void mg_gles_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor) +//NOTE: debugger +typedef struct debug_vertex { - mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface; - mg_gles_surface* surface = mg_gles_canvas_get_surface(backend); - if(!surface) - { - return; - } - - //WARN: dummy test code - - indexCount = 3; - *(vec2*)(interface->vertexLayout.posBuffer) = (vec2){400, 300}; - *(vec2*)(interface->vertexLayout.posBuffer + interface->vertexLayout.posStride) = (vec2){450, 300}; - *(vec2*)(interface->vertexLayout.posBuffer + 2*interface->vertexLayout.posStride) = (vec2){400, 350}; - - for(int i=0; i<3; i++) - { - *(vec4*)(interface->vertexLayout.cubicBuffer + i*interface->vertexLayout.cubicStride) = (vec4){1, 1, 1, 1}; - *(vec2*)(interface->vertexLayout.uvBuffer + i*interface->vertexLayout.uvStride) = (vec2){0, 0}; - *(vec4*)(interface->vertexLayout.colorBuffer + i*interface->vertexLayout.colorStride) = (vec4){1, 0, 0, 1}; - *(vec4*)(interface->vertexLayout.clipBuffer + i*interface->vertexLayout.clipStride) = (vec4){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - *(u32*)(interface->vertexLayout.zIndexBuffer + i*interface->vertexLayout.zIndexStride) = 1; - *(u32*)(interface->vertexLayout.indexBuffer + i*interface->vertexLayout.indexStride) = i; - } - - // end dummy test code - - glUseProgram(backend->program); - glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer); - glUniform1i(0, indexCount); - glDrawArrays(GL_TRIANGLES, 0, 6); -} + vec2 pos; + u8 align0[8]; + vec4 cubic; + vec2 uv; + u8 align1[8]; + vec4 color; + vec4 clip; + int zIndex; + u8 align2[12]; +} debug_vertex; #define LayoutNext(prevName, prevType, nextType) \ AlignUpOnPow2(_cat3_(LAYOUT_, prevName, _OFFSET)+_cat3_(LAYOUT_, prevType, _SIZE), _cat3_(LAYOUT_, nextType, _ALIGN)) @@ -104,17 +82,9 @@ enum { void mg_gles_canvas_update_vertex_layout(mg_gles_canvas_backend* backend) { - if(backend->vertexMapping) - { - glUnmapBuffer(backend->vertexBuffer); - } glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->vertexBuffer); backend->vertexMapping = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, MG_GLES_CANVAS_VERTEX_BUFFER_SIZE, GL_MAP_WRITE_BIT); - if(backend->indexMapping) - { - free(backend->indexMapping); - } glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer); backend->indexMapping = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, MG_GLES_CANVAS_INDEX_BUFFER_SIZE, GL_MAP_WRITE_BIT); @@ -137,6 +107,35 @@ void mg_gles_canvas_update_vertex_layout(mg_gles_canvas_backend* backend) .indexStride = LAYOUT_INT_SIZE}; } +void mg_gles_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor) +{ + mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface; + mg_gles_surface* surface = mg_gles_canvas_get_surface(backend); + if(!surface) + { + return; + } + +/*NOTE: if we want debug_vertex while debugging, the following ensures the struct def doesn't get stripped away + debug_vertex vertex; + printf("foo %p\n", &vertex); +//*/ + glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->vertexBuffer); + glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer); + glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + + + glUseProgram(backend->program); + glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->indexBuffer); + glUniform1i(0, indexCount); + glDrawArrays(GL_TRIANGLES, 0, 6); + + mg_gles_canvas_update_vertex_layout(backend); +} + void mg_gles_canvas_destroy(mg_canvas_backend* interface) { mg_gles_canvas_backend* backend = (mg_gles_canvas_backend*)interface; diff --git a/src/gles_canvas_shaders.h b/src/gles_canvas_shaders.h index f2bb014..93af0f0 100644 --- a/src/gles_canvas_shaders.h +++ b/src/gles_canvas_shaders.h @@ -2,7 +2,7 @@ * * file: gles_canvas_shaders.h * note: string literals auto-generated by embed_text.py -* date: 31/012023 +* date: 01/022023 * **********************************************************************/ #ifndef __GLES_CANVAS_SHADERS_H__ @@ -30,15 +30,112 @@ const char* gles_canvas_fragment = "} vertexBuffer ;\n" "\n" "layout(binding = 1) buffer indexBufferSSBO {\n" -" vec2 elements[];\n" +" uint elements[];\n" "} indexBuffer ;\n" "\n" "layout(location = 0) uniform int indexCount;\n" "layout(location = 0) out vec4 fragColor;\n" "\n" +"bool is_top_left(vec2 a, vec2 b)\n" +"{\n" +" return( (a.y == b.y && b.x < a.x)\n" +" ||(b.y < a.y));\n" +"}\n" +"\n" +"float orient2d(vec2 a, vec2 b, vec2 c)\n" +"{\n" +" //////////////////////////////////////////////////////////////////////////////////////////\n" +" //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues\n" +" // arising when a, b, and c are close. But it degrades when a, c, and c\n" +" // are big. The proper solution is to change the expression to avoid\n" +" // precision loss but I'm too busy/lazy to do it now.\n" +" //////////////////////////////////////////////////////////////////////////////////////////\n" +" a *= 10.;\n" +" b *= 10.;\n" +" c *= 10.;\n" +" return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));\n" +"}\n" +"\n" "void main()\n" "{\n" -" fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" +" vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0);\n" +" vec4 currentColor = vec4(0., 0., 0., 1.0);\n" +"\n" +" vec2 samplePoint = gl_FragCoord.xy;\n" +"\n" +" int currentZIndex = -1;\n" +" int flipCount = 0;\n" +"\n" +"\n" +" for(int i=0; i= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0)\n" +" {\n" +" //TODO check cubic\n" +" vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);\n" +"\n" +" float eps = 0.0001;\n" +" if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)\n" +" {\n" +" if(zIndex == currentZIndex)\n" +" {\n" +" flipCount++;\n" +" }\n" +" else\n" +" {\n" +" if((flipCount & 0x01) != 0)\n" +" {\n" +" pixelColor = currentColor;\n" +" }\n" +" currentColor = pixelColor*(1.-color.a) + color.a*color;\n" +" currentZIndex = zIndex;\n" +" flipCount = 1;\n" +" }\n" +" }\n" +" }\n" +" }\n" +" if((flipCount & 0x01) != 0)\n" +" {\n" +" pixelColor = currentColor;\n" +" }\n" +"\n" +" fragColor = pixelColor;\n" "}\n"; //NOTE: string imported from src\gles_canvas_shaders\gles_canvas_vertex.glsl diff --git a/src/gles_canvas_shaders/gles_canvas_fragment.glsl b/src/gles_canvas_shaders/gles_canvas_fragment.glsl index 53c8ba6..dfaf319 100644 --- a/src/gles_canvas_shaders/gles_canvas_fragment.glsl +++ b/src/gles_canvas_shaders/gles_canvas_fragment.glsl @@ -17,13 +17,110 @@ layout(binding = 0) buffer vertexBufferSSBO { } vertexBuffer ; layout(binding = 1) buffer indexBufferSSBO { - vec2 elements[]; + uint elements[]; } indexBuffer ; layout(location = 0) uniform int indexCount; layout(location = 0) out vec4 fragColor; +bool is_top_left(vec2 a, vec2 b) +{ + return( (a.y == b.y && b.x < a.x) + ||(b.y < a.y)); +} + +float orient2d(vec2 a, vec2 b, vec2 c) +{ + ////////////////////////////////////////////////////////////////////////////////////////// + //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues + // arising when a, b, and c are close. But it degrades when a, c, and c + // are big. The proper solution is to change the expression to avoid + // precision loss but I'm too busy/lazy to do it now. + ////////////////////////////////////////////////////////////////////////////////////////// + a *= 10.; + b *= 10.; + c *= 10.; + return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x)); +} + void main() { - fragColor = vec4(0.0, 1.0, 0.0, 1.0); + vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0); + vec4 currentColor = vec4(0., 0., 0., 1.0); + + vec2 samplePoint = gl_FragCoord.xy; + + int currentZIndex = -1; + int flipCount = 0; + + + for(int i=0; i= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0) + { + //TODO check cubic + vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2); + + float eps = 0.0001; + if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps) + { + if(zIndex == currentZIndex) + { + flipCount++; + } + else + { + if((flipCount & 0x01) != 0) + { + pixelColor = currentColor; + } + currentColor = pixelColor*(1.-color.a) + color.a*color; + currentZIndex = zIndex; + flipCount = 1; + } + } + } + } + if((flipCount & 0x01) != 0) + { + pixelColor = currentColor; + } + + fragColor = pixelColor; } diff --git a/src/graphics.c b/src/graphics.c index 350f0be..ee22343 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -1,3736 +1,3747 @@ -/************************************************************//** -* -* @file: graphics.c -* @author: Martin Fouilleul -* @date: 23/01/2023 -* @revision: -* -*****************************************************************/ - -#define _USE_MATH_DEFINES //NOTE: necessary for MSVC -#include - -#define STB_TRUETYPE_IMPLEMENTATION -#include"stb_truetype.h" - -#define STB_IMAGE_IMPLEMENTATION -#include"stb_image.h" - -#include"debug_log.h" -#include"graphics_internal.h" - -#define LOG_SUBSYSTEM "Graphics" - -//------------------------------------------------------------------------ -// graphics handles structs -//------------------------------------------------------------------------ -typedef struct mg_resource_slot -{ - list_elt freeListElt; - u32 generation; - union - { - mg_surface_data* surface; - mg_image_data* image; - mg_canvas_data* canvas; - mg_font_data* font; - //... - }; - -} mg_resource_slot; - -enum -{ - MG_MAX_RESOURCE_SLOTS = 128, - //... -}; - -typedef struct mg_resource_pool -{ - mg_resource_slot slots[MG_MAX_RESOURCE_SLOTS]; - list_info freeList; - u32 nextIndex; - -} mg_resource_pool; - -typedef struct mg_data -{ - bool init; - - mg_resource_pool surfaces; - mg_resource_pool canvases; - mg_resource_pool fonts; - //... - -} mg_data; - -static mg_data __mgData = {0}; - -void mg_init() -{ - if(!__mgData.init) - { - __mgData.init = true; - //... - } -} - -//------------------------------------------------------------------------ -// handle pools procedures -//------------------------------------------------------------------------ - -mg_resource_slot* mg_resource_slot_alloc(mg_resource_pool* pool) -{ - if(!__mgData.init) - { - //TODO: see if we require an init at all, and if we want to make it explicit to the user - mg_init(); - } - - mg_resource_slot* slot = ListPopEntry(&pool->freeList, mg_resource_slot, freeListElt); - if(!slot && pool->nextIndex < MG_MAX_RESOURCE_SLOTS) - { - slot = &pool->slots[pool->nextIndex]; - slot->generation = 1; - pool->nextIndex++; - } - return(slot); -} - -void mg_resource_slot_recycle(mg_resource_pool* pool, mg_resource_slot* slot) -{ - DEBUG_ASSERT(slot >= pool->slots && slot < pool->slots + MG_MAX_RESOURCE_SLOTS); - #ifdef DEBUG - if(slot->generation == UINT32_MAX) - { - LOG_ERROR("surface slot generation wrap around\n"); - } - #endif - slot->generation++; - ListPush(&pool->freeList, &slot->freeListElt); -} - -mg_resource_slot* mg_resource_slot_from_handle(mg_resource_pool* pool, u64 h) -{ - u32 index = h>>32; - u32 generation = h & 0xffffffff; - if(index >= MG_MAX_RESOURCE_SLOTS) - { - return(0); - } - mg_resource_slot* slot = &pool->slots[index]; - if(slot->generation != generation) - { - return(0); - } - else - { - return(slot); - } -} - -u64 mg_resource_handle_from_slot(mg_resource_pool* pool, mg_resource_slot* slot) -{ - DEBUG_ASSERT( (slot - pool->slots) >= 0 - && (slot - pool->slots) < MG_MAX_RESOURCE_SLOTS); - - u64 h = ((u64)(slot - pool->slots))<<32 - |((u64)(slot->generation)); - return(h); -} - -void mg_resource_handle_recycle(mg_resource_pool* pool, u64 h) -{ - mg_resource_slot* slot = mg_resource_slot_from_handle(pool, h); - if(slot) - { - mg_resource_slot_recycle(pool, slot); - } -} - -//------------------------------------------------------------------------ -// surface handles -//------------------------------------------------------------------------ - -mg_surface mg_surface_nil() { return((mg_surface){.h = 0}); } -bool mg_surface_is_nil(mg_surface surface) { return(surface.h == 0); } - -mg_surface mg_surface_alloc_handle(mg_surface_data* surface) -{ - mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.surfaces); - if(!slot) - { - LOG_ERROR("no more surface slots\n"); - return(mg_surface_nil()); - } - slot->surface = surface; - u64 h = mg_resource_handle_from_slot(&__mgData.surfaces, slot); - mg_surface handle = {h}; - return(handle); -} - -mg_surface_data* mg_surface_data_from_handle(mg_surface surface) -{ - mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.surfaces, surface.h); - mg_surface_data* data = 0; - if(slot) - { - data = slot->surface; - } - return(data); -} - -//------------------------------------------------------------------------ -// canvas handles -//------------------------------------------------------------------------ -mg_canvas mg_canvas_nil() { return((mg_canvas){.h = 0}); } -bool mg_canvas_is_nil(mg_canvas canvas) { return(canvas.h == 0); } - -mg_canvas mg_canvas_alloc_handle(mg_canvas_data* canvas) -{ - mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.canvases); - if(!slot) - { - LOG_ERROR("no more canvas slots\n"); - return(mg_canvas_nil()); - } - slot->canvas = canvas; - u64 h = mg_resource_handle_from_slot(&__mgData.canvases, slot); - mg_canvas handle = {h}; - return(handle); -} - -mg_canvas_data* mg_canvas_data_from_handle(mg_canvas canvas) -{ - mg_canvas_data* data = 0; - mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.canvases, canvas.h); - if(slot) - { - data = slot->canvas; - } - return(data); -} - -//------------------------------------------------------------------------ -// image handles -//------------------------------------------------------------------------ - -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_handle_from_ptr(mg_canvas_data* canvas, mg_image_data* imageData) -{ - DEBUG_ASSERT( (imageData - canvas->images) >= 0 - && (imageData - canvas->images) < MG_IMAGE_MAX_COUNT); - - u64 h = ((u64)(imageData - canvas->images))<<32 - |((u64)(imageData->generation)); - return((mg_image){.h = h}); -} - -void mg_image_data_recycle(mg_canvas_data* canvas, mg_image_data* image) -{ - image->generation++; - ListPush(&canvas->imageFreeList, &image->listElt); -} - -mg_image_data* mg_image_ptr_from_handle(mg_canvas_data* context, mg_image handle) -{ - u32 index = handle.h>>32; - u32 generation = handle.h & 0xffffffff; - - if(index >= MG_IMAGE_MAX_COUNT) - { - return(0); - } - mg_image_data* image = &context->images[index]; - if(image->generation != generation) - { - return(0); - } - else - { - return(image); - } -} - -//------------------------------------------------------------------------ -// font handles -//------------------------------------------------------------------------ - -mg_font mg_font_nil() { return((mg_font){.h = 0}); } -bool mg_font_is_nil(mg_font font) { return(font.h == 0); } - -mg_font mg_font_alloc_handle(mg_font_data* font) -{ - mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.fonts); - if(!slot) - { - LOG_ERROR("no more font slots\n"); - return(mg_font_nil()); - } - slot->font = font; - u64 h = mg_resource_handle_from_slot(&__mgData.fonts, slot); - mg_font handle = {h}; - return(handle); -} - -mg_font_data* mg_font_data_from_handle(mg_font font) -{ - mg_font_data* data = 0; - mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.fonts, font.h); - if(slot) - { - data = slot->font; - } - return(data); -} - -//--------------------------------------------------------------- -// surface API -//--------------------------------------------------------------- - -void mg_surface_destroy(mg_surface handle) -{ - DEBUG_ASSERT(__mgData.init); - mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.surfaces, handle.h); - if(slot) - { - slot->surface->destroy(slot->surface); - mg_resource_slot_recycle(&__mgData.surfaces, slot); - } -} - -void mg_surface_prepare(mg_surface surface) -{ - DEBUG_ASSERT(__mgData.init); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - surfaceData->prepare(surfaceData); - } -} - -void mg_surface_present(mg_surface surface) -{ - DEBUG_ASSERT(__mgData.init); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - surfaceData->present(surfaceData); - } -} - -void mg_surface_set_frame(mg_surface surface, mp_rect frame) -{ - DEBUG_ASSERT(__mgData.init); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - surfaceData->setFrame(surfaceData, frame); - } -} - -mp_rect mg_surface_get_frame(mg_surface surface) -{ - DEBUG_ASSERT(__mgData.init); - mp_rect res = {0}; - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - res = surfaceData->getFrame(surfaceData); - } - return(res); -} - -void mg_surface_set_hidden(mg_surface surface, bool hidden) -{ - DEBUG_ASSERT(__mgData.init); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - surfaceData->setHidden(surfaceData, hidden); - } -} - -bool mg_surface_get_hidden(mg_surface surface) -{ - DEBUG_ASSERT(__mgData.init); - bool res = false; - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - res = surfaceData->getHidden(surfaceData); - } - return(res); -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics canvas internal -//------------------------------------------------------------------------------------------ - -mp_thread_local mg_canvas_data* __mgCurrentCanvas = 0; -mp_thread_local mg_canvas __mgCurrentCanvasHandle = {0}; - -//TODO: move elsewhere? -mg_mat2x3 mg_mat2x3_mul_m(mg_mat2x3 lhs, mg_mat2x3 rhs) -{ - mg_mat2x3 res; - res.m[0] = lhs.m[0]*rhs.m[0] + lhs.m[1]*rhs.m[3]; - res.m[1] = lhs.m[0]*rhs.m[1] + lhs.m[1]*rhs.m[4]; - res.m[2] = lhs.m[0]*rhs.m[2] + lhs.m[1]*rhs.m[5] + lhs.m[2]; - res.m[3] = lhs.m[3]*rhs.m[0] + lhs.m[4]*rhs.m[3]; - res.m[4] = lhs.m[3]*rhs.m[1] + lhs.m[4]*rhs.m[4]; - res.m[5] = lhs.m[3]*rhs.m[2] + lhs.m[4]*rhs.m[5] + lhs.m[5]; - - return(res); -} - -vec2 mg_mat2x3_mul(mg_mat2x3 m, vec2 p) -{ - f32 x = p.x*m.m[0] + p.y*m.m[1] + m.m[2]; - f32 y = p.x*m.m[3] + p.y*m.m[4] + m.m[5]; - return((vec2){x, y}); -} - - -void mg_push_command(mg_canvas_data* canvas, mg_primitive primitive) -{ - //NOTE(martin): push primitive and updates current stream, eventually patching a pending jump. - ASSERT(canvas->primitiveCount < MG_MAX_PRIMITIVE_COUNT); - canvas->primitives[canvas->primitiveCount] = primitive; - canvas->primitiveCount++; -} - -void mg_new_path(mg_canvas_data* canvas) -{ - canvas->path.startIndex += canvas->path.count; - canvas->path.count = 0; - canvas->subPathStartPoint = canvas->subPathLastPoint; - canvas->path.startPoint = canvas->subPathStartPoint; -} - -void mg_path_push_elements(mg_canvas_data* canvas, u32 count, mg_path_elt* elements) -{ - ASSERT(canvas->path.count + canvas->path.startIndex + count <= MG_MAX_PATH_ELEMENT_COUNT); - memcpy(canvas->pathElements + canvas->path.startIndex + canvas->path.count, elements, count*sizeof(mg_path_elt)); - canvas->path.count += count; -} - -void mg_path_push_element(mg_canvas_data* canvas, mg_path_elt elt) -{ - mg_path_push_elements(canvas, 1, &elt); -} - -void mg_reset_z_index(mg_canvas_data* canvas) -{ - canvas->nextZIndex = 1; -} - -u32 mg_get_next_z_index(mg_canvas_data* canvas) -{ - return(canvas->nextZIndex++); -} - -/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// -u32 mg_vertices_base_index(mg_canvas_data* canvas) -{ - return(canvas->vertexCount); -} - -void mg_push_textured_vertex(mg_canvas_data* canvas, vec2 pos, vec4 cubic, vec2 uv, mg_color color, u64 zIndex) -{ - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - - DEBUG_ASSERT(canvas->vertexCount < layout->maxVertexCount); - - u32 offset = canvas->vertexCount; - - *(vec2*)(((char*)layout->posBuffer) + offset*layout->posStride) = pos; - *(vec4*)(((char*)layout->cubicBuffer) + offset*layout->cubicStride) = cubic; - *(vec2*)(((char*)layout->uvBuffer) + offset*layout->uvStride) = uv; - *(mg_color*)(((char*)layout->colorBuffer) + offset*layout->colorStride) = color; - *(u32*)(((char*)layout->zIndexBuffer) + offset*layout->zIndexStride) = zIndex; - *(mp_rect*)(((char*)layout->clipsBuffer) + offset*layout->clipsStride) = canvas->clip; - - canvas->vertexCount++; -} - -void mg_push_vertex(mg_canvas_data* canvas, vec2 pos, vec4 cubic, mg_color color, u64 zIndex) -{ - mg_push_textured_vertex(canvas, pos, cubic, (vec2){0, 0}, color, zIndex); -} - - -/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// - -int* mg_reserve_indices(mg_canvas_data* canvas, u32 indexCount) -{ - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - - ASSERT(canvas->indexCount + indexCount < layout->maxIndexCount); - int* base = ((int*)layout->indexBuffer) + canvas->indexCount; - canvas->indexCount += indexCount; - return(base); -} - -//----------------------------------------------------------------------------------------------------------- -// Path Filling -//----------------------------------------------------------------------------------------------------------- -//NOTE(martin): forward declarations -void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_color color); - -//NOTE(martin): quadratics filling - -void mg_render_fill_quadratic(mg_canvas_data* canvas, vec2 p[3], u32 zIndex, mg_color color) -{ - u32 baseIndex = mg_vertices_base_index(canvas); - - i32* indices = mg_reserve_indices(canvas, 3); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[0].x, p[0].y}), - (vec4){0, 0, 0, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[1].x, p[1].y}), - (vec4){0.5, 0, 0.5, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[2].x, p[2].y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - 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, u32 zIndex, mg_color color) -{ - 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, - mg_mat2x3_mul(canvas->transform, (vec2){p[0].x, p[0].y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){split.x, split.y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[3].x, p[3].y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - - mg_render_fill_cubic(canvas, subPointsLow, zIndex, color); - mg_render_fill_cubic(canvas, subPointsHigh, zIndex, color); - - return; -} - -void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_color color) -{ - LOG_DEBUG("graphics render fill cubic\n"); - - 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 should do the tex coords computations in f64 and scale them to avoid f32 precision/range glitches in shader - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - 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 - LOG_DEBUG("quadratic curve\n"); - - //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, zIndex, color); - 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). - LOG_DEBUG("%s\n", (discrFactor2 > 0 && d1 != 0) ? "serpentine curve" : "cusp with inflection at infinity"); - - //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 texture 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 - LOG_DEBUG("loop curve\n"); - - 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) - { - LOG_DEBUG("split curve at first double point\n"); - mg_split_and_fill_cubic(canvas, p, td/sd, zIndex, color); - return; - } - if(se != 0 && te/se < 0.99 && te/se > 0.01) - { - LOG_DEBUG("split curve at second double point\n"); - mg_split_and_fill_cubic(canvas, p, te/se, zIndex, color); - 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 texture 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 - LOG_DEBUG("cusp at infinity curve\n"); - - 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 texture 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 - LOG_DEBUG("line or point curve (ignored)\n"); - return; - } - else - { - //TODO(martin): handle error ? put some epsilon slack on the conditions ? - LOG_DEBUG("none of the above...\n"); - 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); - - //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; - } - - //NOTE(martin): rearrange convex hull to put p0 first - int startIndex = -1; - int orderedHullIndices[4]; - for(int i=0; i= 0) - { - orderedHullIndices[i-startIndex] = convexHullIndices[i]; - } - } - for(int i=0; i 0 - // ie the 4th coordinate s flips the inside/outside test. - // We affect s such that control points p1 and p2 are always outside the covered area. - - if(convexHullCount <= 3) - { - //NOTE(martin): the convex hull is a triangle - - //NOTE(martin): when we degenerate from 4 control points to a 3 points convex hull, this means one of the - // control points p1 or p2 could be inside the covered area. We want to compute the test for the - // control point which belongs to the convex hull, as we know it should be outside the covered area. - // - // Since there are 3 points in the hull and p0 is the first, and p3 belongs to the hull, this means we - // must select the point of the convex hull which is neither the first, nor p3. - - int testPointIndex = orderedHullIndices[1] == 3 ? orderedHullIndices[2] : orderedHullIndices[1]; - int outsideTest = 1; - if(Cube(testCoords[testPointIndex].x)-testCoords[testPointIndex].y*testCoords[testPointIndex].z < 0) - { - outsideTest = -1; - } - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 3); - - for(int i=0; i<3; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[i]]); - vec4 cubic = testCoords[orderedHullIndices[i]]; - cubic.w = outsideTest; - mg_push_vertex(canvas, pos, cubic, color, zIndex); - - indices[i] = baseIndex + i; - } - } - else if(orderedHullIndices[2] == 3) - { - //NOTE(martin): p1 and p2 are not on the same side of (p0,p3). The outside test can be different for - // the two triangles - int outsideTest1 = 1; - int outsideTest2 = 1; - - int testIndex = orderedHullIndices[1]; - if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) - { - outsideTest1 = -1; - } - - testIndex = orderedHullIndices[3]; - if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) - { - outsideTest2 = -1; - } - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[0]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[1]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[1]]), outsideTest1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[2]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[0]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest2}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[2]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest2}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[3]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[3]]), outsideTest2}, - color, - zIndex); - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 3; - indices[4] = baseIndex + 4; - indices[5] = baseIndex + 5; - } - else - { - //NOTE(martin): if p1 and p2 are on the same side of (p0,p3), the outside test is the same for both triangles - int outsideTest = 1; - if(Cube(testCoords[1].x)-testCoords[1].y*testCoords[1].z < 0) - { - outsideTest = -1; - } - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - for(int i=0; i<4; i++) - { - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[i]]), - (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[i]]), outsideTest}, - color, - zIndex); - } - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - } -} - -//NOTE(martin): global path fill - -void mg_render_fill(mg_canvas_data* canvas, mg_path_elt* elements, mg_path_descriptor* path, u32 zIndex, mg_color color) -{ - 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, zIndex, color); - endPoint = controlPoints[2]; - - } break; - - case MG_PATH_CUBIC: - { - mg_render_fill_cubic(canvas, controlPoints, zIndex, color); - endPoint = controlPoints[3]; - } break; - } - - //NOTE(martin): now fill interior triangle - u32 baseIndex = mg_vertices_base_index(canvas); - int* indices = mg_reserve_indices(canvas, 3); - - vec2 pos[3]; - pos[0] = mg_mat2x3_mul(canvas->transform, startPoint); - pos[1] = mg_mat2x3_mul(canvas->transform, currentPoint); - pos[2] = mg_mat2x3_mul(canvas->transform, endPoint); - - vec4 cubic = {1, 1, 1, 1}; - - for(int i=0; i<3; i++) - { - mg_push_vertex(canvas, pos[i], cubic, color, zIndex); - indices[i] = baseIndex + i; - } - - currentPoint = endPoint; - } -} - -//----------------------------------------------------------------------------------------------------------- -// Path Stroking -//----------------------------------------------------------------------------------------------------------- - -void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], u32 zIndex, 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; - - - mg_color color = attributes->color; - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[0].x + n0.x, p[0].y + n0.y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[1].x + n0.x, p[1].y + n0.y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[1].x - n0.x, p[1].y - n0.y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p[0].x - n0.x, p[0].y - n0.y}), - (vec4){1, 1, 1, 1}, - color, - zIndex); - - indices[0] = baseIndex; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -void mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) -{ -////////////////////////////////////////////////////////////////////////////////////// -//WARN: quick fix for coincident middle control points - if(count == 4 && (p[1].x - p[2].x < 0.01) && (p[1].y - p[2].y < 0.01)) - { - vec2 hull3[3] = {p[0], p[1], p[3]}; - vec2 result3[3]; - mg_offset_hull(3, hull3, result3, offset); - result[0] = result3[0]; - result[1] = result3[1]; - result[2] = result3[1]; - result[3] = result3[2]; - return; - } -/////////////////////////////////////////////////////////////////////////////////////: - - //TODO(martin): review edge cases (coincident points ? colinear points ? control points pointing outward end point?) - //NOTE(martin): first offset control point is just the offset of first control point - vec2 n = {p[0].y - p[1].y, - p[1].x - p[0].x}; - f32 norm = sqrt(n.x*n.x + n.y*n.y); - n.x *= offset/norm; - n.y *= offset/norm; - - result[0].x = p[0].x + n.x; - result[0].y = p[0].y + n.y; - - //NOTE(martin): subsequent offset control points are the intersection of offset control lines - for(int i=1; iwidth); - mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width); - - //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 - - 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) - { - //TODO(martin): split at maxErrorParameter and recurse - vec2 splitLeft[3]; - vec2 splitRight[3]; - mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); - mg_render_stroke_quadratic(canvas, splitLeft, zIndex, attributes); - mg_render_stroke_quadratic(canvas, splitRight, zIndex, attributes); - } - else - { - //NOTE(martin): push the actual fill commands for the offset contour - - u32 zIndex = mg_get_next_z_index(canvas); - - mg_render_fill_quadratic(canvas, positiveOffsetHull, zIndex, attributes->color); - mg_render_fill_quadratic(canvas, negativeOffsetHull, zIndex, attributes->color); - - //NOTE(martin): add base triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, positiveOffsetHull[0]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, positiveOffsetHull[2]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, negativeOffsetHull[2]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, negativeOffsetHull[0]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - 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. - - 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 q2 = {oneMt*p[2].x + t*p[3].x, - oneMt*p[2].y + t*p[3].y}; - - vec2 r0 = {oneMt*q0.x + t*q1.x, - oneMt*q0.y + t*q1.y}; - - vec2 r1 = {oneMt*q1.x + t*q2.x, - oneMt*q1.y + t*q2.y}; - - vec2 s = {oneMt*r0.x + t*r1.x, - oneMt*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], u32 zIndex, mg_attributes* attributes) -{ - #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]; - - mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width); - mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width); - - //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 - - 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) - { - //TODO(martin): split at maxErrorParameter and recurse - vec2 splitLeft[4]; - vec2 splitRight[4]; - mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); - mg_render_stroke_cubic(canvas, splitLeft, zIndex, attributes); - mg_render_stroke_cubic(canvas, splitRight, zIndex, attributes); - } - else - { - //NOTE(martin): push the actual fill commands for the offset contour - - u32 zIndex = mg_get_next_z_index(canvas); - - mg_render_fill_cubic(canvas, positiveOffsetHull, zIndex, attributes->color); - mg_render_fill_cubic(canvas, negativeOffsetHull, zIndex, attributes->color); - - //NOTE(martin): add base triangles - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, positiveOffsetHull[0]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, positiveOffsetHull[3]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, negativeOffsetHull[3]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, negativeOffsetHull[0]), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - 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}; - - u32 zIndex = mg_get_next_z_index(canvas); - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x, p0.y + n0.y}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x - n0.x, p0.y - n0.y}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - 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; - } - - u32 zIndex = mg_get_next_z_index(canvas); - - //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, - mg_mat2x3_mul(canvas->transform, p0), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, mitterPoint), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - 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, - mg_mat2x3_mul(canvas->transform, p0), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - mg_push_vertex(canvas, - mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), - (vec4){1, 1, 1, 1}, - attributes->color, - zIndex); - - 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; - u32 zIndex = mg_get_next_z_index(canvas); - - switch(element->type) - { - case MG_PATH_LINE: - mg_render_stroke_line(canvas, controlPoints, zIndex, attributes); - endPointIndex = 1; - break; - - case MG_PATH_QUADRATIC: - mg_render_stroke_quadratic(canvas, controlPoints, zIndex, attributes); - endPointIndex = 2; - break; - - case MG_PATH_CUBIC: - mg_render_stroke_cubic(canvas, controlPoints, zIndex, attributes); - endPointIndex = 3; - break; - - case MG_PATH_MOVE: - ASSERT(0, "should be unreachable"); - break; - } - - *startTangent = (vec2){.x = controlPoints[1].x - controlPoints[0].x, - .y = controlPoints[1].y - controlPoints[0].y}; - - *endTangent = (vec2){controlPoints[endPointIndex].x - controlPoints[endPointIndex-1].x, - controlPoints[endPointIndex].y - controlPoints[endPointIndex-1].y}; - - *endPoint = controlPoints[endPointIndex]; - -} - -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); - } - } -} - -//----------------------------------------------------------------------------------------------------------- -// Fast shapes primitives -//----------------------------------------------------------------------------------------------------------- - -void mg_render_rectangle_fill(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) -{ - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - u32 zIndex = mg_get_next_z_index(canvas); - - vec2 points[4] = {{rect.x, rect.y}, - {rect.x + rect.w, rect.y}, - {rect.x + rect.w, rect.y + rect.h}, - {rect.x, rect.y + rect.h}}; - - vec4 cubic = {1, 1, 1, 1}; - for(int i=0; i<4; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); - mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); - } - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -void mg_render_rectangle_stroke(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) -{ - //NOTE(martin): stroke a rectangle by fill two scaled rectangles with the same zIndex. - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 12); - - u32 zIndex = mg_get_next_z_index(canvas); - - //NOTE(martin): limit stroke width to the minimum dimension of the rectangle - f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); - f32 halfW = width/2; - - vec2 outerPoints[4] = {{rect.x - halfW, rect.y - halfW}, - {rect.x + rect.w + halfW, rect.y - halfW}, - {rect.x + rect.w + halfW, rect.y + rect.h + halfW}, - {rect.x - halfW, rect.y + rect.h + halfW}}; - - vec2 innerPoints[4] = {{rect.x + halfW, rect.y + halfW}, - {rect.x + rect.w - halfW, rect.y + halfW}, - {rect.x + rect.w - halfW, rect.y + rect.h - halfW}, - {rect.x + halfW, rect.y + rect.h - halfW}}; - - vec4 cubic = {1, 1, 1, 1}; - for(int i=0; i<4; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, outerPoints[i]); - mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); - } - - for(int i=0; i<4; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, innerPoints[i]); - mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); - } - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - indices[6] = baseIndex + 4; - indices[7] = baseIndex + 5; - indices[8] = baseIndex + 6; - indices[9] = baseIndex + 4; - indices[10] = baseIndex + 6; - indices[11] = baseIndex + 7; -} - -void mg_render_fill_arc_corner(mg_canvas_data* canvas, f32 x, f32 y, f32 rx, f32 ry, u32 zIndex, mg_color color) -{ - //NOTE(martin): draw a precomputed arc corner, using a bezier approximation - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - static const vec4 cubics[4] = {{-3.76797, -9.76362, 5.47912, -1}, - {-4.19896, -9.45223, 7.534, -1}, - {-4.19896, -7.534, 9.45223, -1}, - {-3.76797, -5.47912, 9.76362, -1}}; - - f32 cx = rx*4*(sqrt(2)-1)/3; - f32 cy = ry*4*(sqrt(2)-1)/3; - - vec2 points[4] = {{x, y + ry}, - {x, y + ry - cy}, - {x + rx - cx, y}, - {x + rx, y}}; - - for(int i=0; i<4; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); - mg_push_vertex(canvas, pos, cubics[i], color, zIndex); - } - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -void mg_render_rounded_rectangle_fill_with_z_index(mg_canvas_data* canvas, - mg_rounded_rect rect, - mg_attributes* attributes, - u32 zIndex) -{ - //NOTE(martin): draw a rounded rectangle by drawing a normal rectangle and 4 corners, - // approximating an arc by a precomputed bezier curve - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 18); - - //NOTE(martin): inner cutted corner rectangle - vec2 points[8] = {{rect.x + rect.r, rect.y}, - {rect.x + rect.w - rect.r, rect.y}, - {rect.x + rect.w, rect.y + rect.r}, - {rect.x + rect.w, rect.y + rect.h - rect.r}, - {rect.x + rect.w - rect.r, rect.y + rect.h}, - {rect.x + rect.r, rect.y + rect.h}, - {rect.x, rect.y + rect.h - rect.r}, - {rect.x, rect.y + rect.r}}; - - vec4 cubic = {1, 1, 1, 1}; - - for(int i=0; i<8; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); - mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); - } - - static const i32 fanIndices[18] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7 }; // inner fan - for(int i=0; i<18; i++) - { - indices[i] = fanIndices[i] + baseIndex; - } - - mg_render_fill_arc_corner(canvas, rect.x, rect.y, rect.r, rect.r, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y, -rect.r, rect.r, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y + rect.h, -rect.r, -rect.r, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x, rect.y + rect.h, rect.r, -rect.r, zIndex, attributes->color); -} - - -void mg_render_rounded_rectangle_fill(mg_canvas_data* canvas, - mg_rounded_rect rect, - mg_attributes* attributes) -{ - u32 zIndex = mg_get_next_z_index(canvas); - mg_render_rounded_rectangle_fill_with_z_index(canvas, rect, attributes, zIndex); -} - -void mg_render_rounded_rectangle_stroke(mg_canvas_data* canvas, - mg_rounded_rect rect, - mg_attributes* attributes) -{ - //NOTE(martin): stroke rounded rectangle by filling two scaled rounded rectangles with the same zIndex - f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); - f32 halfW = width/2; - - mg_rounded_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width, rect.r - halfW}; - mg_rounded_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width, rect.r + halfW}; - - u32 zIndex = mg_get_next_z_index(canvas); - mg_render_rounded_rectangle_fill_with_z_index(canvas, outer, attributes, zIndex); - mg_render_rounded_rectangle_fill_with_z_index(canvas, inner, attributes, zIndex); -} - -void mg_render_ellipse_fill_with_z_index(mg_canvas_data* canvas, - mp_rect rect, - mg_attributes* attributes, - u32 zIndex) -{ - //NOTE(martin): draw a filled ellipse by drawing a diamond and 4 corners, - // approximating an arc by a precomputed bezier curve - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - f32 rx = rect.w/2; - f32 ry = rect.h/2; - - //NOTE(martin): inner diamond - vec2 points[4] = {{rect.x, rect.y + ry}, - {rect.x + rx, rect.y}, - {rect.x + rect.w, rect.y + ry}, - {rect.x + rx, rect.y + rect.h}}; - - vec4 cubic = {1, 1, 1, 1}; - - for(int i=0; i<4; i++) - { - vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); - mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); - } - - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; - - mg_render_fill_arc_corner(canvas, rect.x, rect.y, rx, ry, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y, -rx, ry, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y + rect.h, -rx, -ry, zIndex, attributes->color); - mg_render_fill_arc_corner(canvas, rect.x, rect.y + rect.h, rx, -ry, zIndex, attributes->color); -} - -void mg_render_ellipse_fill(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) -{ - u32 zIndex = mg_get_next_z_index(canvas); - mg_render_ellipse_fill_with_z_index(canvas, rect, attributes, zIndex); -} - -void mg_render_ellipse_stroke(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) -{ - //NOTE(martin): stroke by filling two scaled ellipsis with the same zIndex - f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); - f32 halfW = width/2; - - mp_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width}; - mp_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width}; - - u32 zIndex = mg_get_next_z_index(canvas); - mg_render_ellipse_fill_with_z_index(canvas, outer, attributes, zIndex); - mg_render_ellipse_fill_with_z_index(canvas, inner, attributes, zIndex); -} - -void mg_render_image(mg_canvas_data* canvas, mg_image image, mp_rect rect) -{ - mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); - if(!imageData) - { - return; - } - - u32 baseIndex = mg_vertices_base_index(canvas); - i32* indices = mg_reserve_indices(canvas, 6); - - u32 zIndex = mg_get_next_z_index(canvas); - - vec2 points[4] = {{rect.x, rect.y}, - {rect.x + rect.w, rect.y}, - {rect.x + rect.w, rect.y + rect.h}, - {rect.x, rect.y + rect.h}}; - - vec2 uv[4] = {{imageData->rect.x + 0.5, imageData->rect.y + 0.5}, - {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + 0.5}, - {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + imageData->rect.h - 0.5}, - {imageData->rect.x + 0.5, imageData->rect.y + imageData->rect.h - 0.5}}; - - vec4 cubic = {1, 1, 1, 1}; - mg_color color = {1, 1, 1, 1}; - - for(int i=0; i<4; i++) - { - vec2 transformedUV = {uv[i].x / MG_ATLAS_SIZE, uv[i].y / MG_ATLAS_SIZE}; - - vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); - mg_push_textured_vertex(canvas, pos, cubic, transformedUV, color, zIndex); - } - indices[0] = baseIndex + 0; - indices[1] = baseIndex + 1; - indices[2] = baseIndex + 2; - indices[3] = baseIndex + 0; - indices[4] = baseIndex + 2; - indices[5] = baseIndex + 3; -} - -void mg_render_rounded_image(mg_canvas_data* canvas, mg_image image, mg_rounded_rect rect, mg_attributes* attributes) -{ - mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); - if(!imageData) - { - return; - } - - //////////////////////////////////////////////////////////////////////////////// - //TODO: this does not work for rotated rectangles - //////////////////////////////////////////////////////////////////////////////// - vec2 uvMin = {(imageData->rect.x + 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + 0.5) / MG_ATLAS_SIZE}; - vec2 uvMax = {(imageData->rect.x + imageData->rect.w - 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + imageData->rect.h - 0.5) / MG_ATLAS_SIZE}; - mp_rect uvRect = {uvMin.x, uvMin.y, uvMax.x - uvMin.x, uvMax.y - uvMin.y}; - - vec2 pMin = mg_mat2x3_mul(canvas->transform, (vec2){rect.x, rect.y}); - vec2 pMax = mg_mat2x3_mul(canvas->transform, (vec2){rect.x + rect.w, rect.y + rect.h}); - mp_rect pRect = {pMin.x, pMin.y, pMax.x - pMin.x, pMax.y - pMin.y}; - - u32 startIndex = mg_vertices_base_index(canvas); - - mg_vertex_layout* layout = &canvas->backend->vertexLayout; - - attributes->color = (mg_color){1, 1, 1, 1}; - mg_render_rounded_rectangle_fill(canvas, rect, attributes); - - u32 indexCount = mg_vertices_base_index(canvas) - startIndex; - - for(int i=0; iposBuffer) + index*layout->posStride); - vec2* uv = (vec2*)(((char*)layout->uvBuffer) + index*layout->uvStride); - - vec2 coordInBoundingSpace = {(pos->x - pRect.x)/pRect.w, - (pos->y - pRect.y)/pRect.h}; - - vec2 mappedUV = {uvRect.x + coordInBoundingSpace.x * uvRect.w, - uvRect.y + coordInBoundingSpace.y * uvRect.h}; - - *uv = mappedUV; - } - -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): fonts -//------------------------------------------------------------------------------------------ - -mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges) -{ - mg_font_data* fontData = malloc_type(mg_font_data); - mg_font font = mg_font_alloc_handle(fontData); - - if(mg_font_is_nil(font)) - { - free(fontData); - return(font); - } - - stbtt_fontinfo stbttFontInfo; - stbtt_InitFont(&stbttFontInfo, buffer, 0); - - //NOTE(martin): load font metrics data - fontData->unitsPerEm = 1./stbtt_ScaleForMappingEmToPixels(&stbttFontInfo, 1); - - int ascent, descent, lineGap, x0, x1, y0, y1; - stbtt_GetFontVMetrics(&stbttFontInfo, &ascent, &descent, &lineGap); - stbtt_GetFontBoundingBox(&stbttFontInfo, &x0, &y0, &x1, &y1); - - fontData->extents.ascent = ascent; - fontData->extents.descent = -descent; - fontData->extents.leading = lineGap; - fontData->extents.width = x1 - x0; - - stbtt_GetCodepointBox(&stbttFontInfo, 'x', &x0, &y0, &x1, &y1); - fontData->extents.xHeight = y1 - y0; - - stbtt_GetCodepointBox(&stbttFontInfo, 'M', &x0, &y0, &x1, &y1); - fontData->extents.capHeight = y1 - y0; - - //NOTE(martin): load codepoint ranges - fontData->rangeCount = rangeCount; - fontData->glyphMap = malloc_array(mg_glyph_map_entry, rangeCount); - fontData->glyphCount = 0; - - for(int i=0; iglyphMap[i].range = ranges[i]; - fontData->glyphMap[i].firstGlyphIndex = fontData->glyphCount + 1; - fontData->glyphCount += ranges[i].count; - } - - fontData->glyphs = malloc_array(mg_glyph_data, fontData->glyphCount); - - //NOTE(martin): first do a count of outlines - int outlineCount = 0; - for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; - u32 firstGlyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex; - u32 endGlyphIndex = firstGlyphIndex + fontData->glyphMap[rangeIndex].range.count; - - for(int glyphIndex = firstGlyphIndex; - glyphIndex < endGlyphIndex; glyphIndex++) - { - int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); - if(stbttGlyphIndex == 0) - { - //NOTE(martin): the codepoint is not found in the font - codePoint++; - continue; - } - //NOTE(martin): load glyph outlines - stbtt_vertex* vertices = 0; - outlineCount += stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); - stbtt_FreeShape(&stbttFontInfo, vertices); - codePoint++; - } - } - //NOTE(martin): allocate outlines - fontData->outlines = malloc_array(mg_path_elt, outlineCount); - fontData->outlineCount = 0; - - //NOTE(martin): load metrics and outlines - for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; - u32 firstGlyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex; - u32 endGlyphIndex = firstGlyphIndex + fontData->glyphMap[rangeIndex].range.count; - - for(int glyphIndex = firstGlyphIndex; - glyphIndex < endGlyphIndex; glyphIndex++) - { - mg_glyph_data* glyph = &(fontData->glyphs[glyphIndex-1]); - - int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); - if(stbttGlyphIndex == 0) - { - //NOTE(martin): the codepoint is not found in the font, we zero the glyph info - memset(glyph, 0, sizeof(*glyph)); - codePoint++; - continue; - } - - glyph->exists = true; - glyph->codePoint = codePoint; - - //NOTE(martin): load glyph metric - int xAdvance, xBearing, x0, y0, x1, y1; - stbtt_GetGlyphHMetrics(&stbttFontInfo, stbttGlyphIndex, &xAdvance, &xBearing); - stbtt_GetGlyphBox(&stbttFontInfo, stbttGlyphIndex, &x0, &y0, &x1, &y1); - - glyph->extents.xAdvance = (f32)xAdvance; - glyph->extents.yAdvance = 0; - glyph->extents.xBearing = (f32)xBearing; - glyph->extents.yBearing = y0; - - glyph->extents.width = x1 - x0; - glyph->extents.height = y1 - y0; - - //NOTE(martin): load glyph outlines - - stbtt_vertex* vertices = 0; - int vertexCount = stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); - - glyph->pathDescriptor = (mg_path_descriptor){.startIndex = fontData->outlineCount, - .count = vertexCount, - .startPoint = {0, 0}}; - - mg_path_elt* elements = fontData->outlines + fontData->outlineCount; - fontData->outlineCount += vertexCount; - vec2 currentPos = {0, 0}; - - for(int vertIndex = 0; vertIndex < vertexCount; vertIndex++) - { - f32 x = vertices[vertIndex].x; - f32 y = vertices[vertIndex].y; - f32 cx = vertices[vertIndex].cx; - f32 cy = vertices[vertIndex].cy; - f32 cx1 = vertices[vertIndex].cx1; - f32 cy1 = vertices[vertIndex].cy1; - - switch(vertices[vertIndex].type) - { - case STBTT_vmove: - elements[vertIndex].type = MG_PATH_MOVE; - elements[vertIndex].p[0] = (vec2){x, y}; - break; - - case STBTT_vline: - elements[vertIndex].type = MG_PATH_LINE; - elements[vertIndex].p[0] = (vec2){x, y}; - break; - - case STBTT_vcurve: - { - elements[vertIndex].type = MG_PATH_QUADRATIC; - elements[vertIndex].p[0] = (vec2){cx, cy}; - elements[vertIndex].p[1] = (vec2){x, y}; - } break; - - case STBTT_vcubic: - elements[vertIndex].type = MG_PATH_CUBIC; - elements[vertIndex].p[0] = (vec2){cx, cy}; - elements[vertIndex].p[1] = (vec2){cx1, cy1}; - elements[vertIndex].p[2] = (vec2){x, y}; - break; - } - currentPos = (vec2){x, y}; - } - stbtt_FreeShape(&stbttFontInfo, vertices); - codePoint++; - } - } - return(font); -} - -void mg_font_destroy(mg_font fontHandle) -{ - mg_font_data* fontData = mg_font_data_from_handle(fontHandle); - if(!fontData) - { - return; - } - #ifdef DEBUG - if(fontData->generation == UINT32_MAX) - { - LOG_ERROR("font info generation wrap around\n"); - } - #endif - fontData->generation++; - - free(fontData->glyphMap); - free(fontData->glyphs); - free(fontData->outlines); - - free(fontData); - mg_resource_handle_recycle(&__mgData.fonts, fontHandle.h); -} - -str32 mg_font_get_glyph_indices_from_font_data(mg_font_data* fontData, str32 codePoints, str32 backing) -{ - u64 count = minimum(codePoints.len, backing.len); - - for(int i = 0; irangeCount; rangeIndex++) - { - if(codePoints.ptr[i] >= fontData->glyphMap[rangeIndex].range.firstCodePoint - && codePoints.ptr[i] < (fontData->glyphMap[rangeIndex].range.firstCodePoint + fontData->glyphMap[rangeIndex].range.count)) - { - u32 rangeOffset = codePoints.ptr[i] - fontData->glyphMap[rangeIndex].range.firstCodePoint; - glyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex + rangeOffset; - break; - } - } - if(glyphIndex && !fontData->glyphs[glyphIndex].exists) - { - backing.ptr[i] = 0; - } - backing.ptr[i] = glyphIndex; - } - str32 res = {.len = count, .ptr = backing.ptr}; - return(res); -} - -u32 mg_font_get_glyph_index_from_font_data(mg_font_data* fontData, utf32 codePoint) -{ - u32 glyphIndex = 0; - str32 codePoints = {1, &codePoint}; - str32 backing = {1, &glyphIndex}; - mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing); - return(glyphIndex); -} - -str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return((str32){0}); - } - return(mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing)); -} - -str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints) -{ - u32* buffer = mem_arena_alloc_array(arena, u32, codePoints.len); - str32 backing = {codePoints.len, buffer}; - return(mg_font_get_glyph_indices(font, codePoints, backing)); -} - -u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint) -{ - u32 glyphIndex = 0; - str32 codePoints = {1, &codePoint}; - str32 backing = {1, &glyphIndex}; - mg_font_get_glyph_indices(font, codePoints, backing); - return(glyphIndex); -} - -mg_glyph_data* mg_font_get_glyph_data(mg_font_data* fontData, u32 glyphIndex) -{ - DEBUG_ASSERT(glyphIndex); - DEBUG_ASSERT(glyphIndex < fontData->glyphCount); - return(&(fontData->glyphs[glyphIndex-1])); -} - -mg_font_extents mg_font_get_extents(mg_font font) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return((mg_font_extents){0}); - } - return(fontData->extents); -} - -mg_font_extents mg_font_get_scaled_extents(mg_font font, f32 emSize) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return((mg_font_extents){0}); - } - f32 scale = emSize/fontData->unitsPerEm; - mg_font_extents extents = fontData->extents; - - extents.ascent *= scale; - extents.descent *= scale; - extents.leading *= scale; - extents.xHeight *= scale; - extents.capHeight *= scale; - extents.width *= scale; - - return(extents); -} - - -f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return(0); - } - return(emSize/fontData->unitsPerEm); -} - -void mg_font_get_glyph_extents_from_font_data(mg_font_data* fontData, - str32 glyphIndices, - mg_text_extents* outExtents) -{ - for(int i=0; i= fontData->glyphCount) - { - continue; - } - mg_glyph_data* glyph = mg_font_get_glyph_data(fontData, glyphIndices.ptr[i]); - outExtents[i] = glyph->extents; - } -} - -int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return(-1); - } - mg_font_get_glyph_extents_from_font_data(fontData, glyphIndices, outExtents); - return(0); -} - -int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents) -{ - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return(-1); - } - u32 glyphIndex = 0; - str32 codePoints = {1, &codePoint}; - str32 backing = {1, &glyphIndex}; - str32 glyphs = mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing); - mg_font_get_glyph_extents_from_font_data(fontData, glyphs, outExtents); - return(0); -} - -mp_rect mg_text_bounding_box_utf32(mg_font font, f32 fontSize, str32 codePoints) -{ - if(!codePoints.len || !codePoints.ptr) - { - return((mp_rect){0}); - } - - mg_font_data* fontData = mg_font_data_from_handle(font); - if(!fontData) - { - return((mp_rect){0}); - } - - mem_arena* scratch = mem_scratch(); - str32 glyphIndices = mg_font_push_glyph_indices(font, scratch, codePoints); - - //NOTE(martin): find width of missing character - //TODO(martin): should cache that at font creation... - mg_text_extents missingGlyphExtents; - u32 missingGlyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 0xfffd); - - if(missingGlyphIndex) - { - mg_font_get_glyph_extents_from_font_data(fontData, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); - } - else - { - //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width - // to render an empty rectangle. Otherwise just render with the max font width - f32 boxWidth = fontData->extents.width * 0.8; - f32 xBearing = fontData->extents.width * 0.1; - f32 xAdvance = fontData->extents.width; - - missingGlyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 'x'); - if(missingGlyphIndex) - { - mg_font_get_glyph_extents_from_font_data(fontData, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); - } - else - { - missingGlyphExtents.xBearing = fontData->extents.width * 0.1; - missingGlyphExtents.yBearing = 0; - missingGlyphExtents.width = fontData->extents.width * 0.8; - missingGlyphExtents.xAdvance = fontData->extents.width; - missingGlyphExtents.yAdvance = 0; - } - } - - //NOTE(martin): accumulate text extents - f32 width = 0; - f32 x = 0; - f32 y = 0; - f32 lineHeight = fontData->extents.descent + fontData->extents.ascent; - - for(int i=0; i= fontData->glyphCount) - { - extents = missingGlyphExtents; - } - else - { - glyph = mg_font_get_glyph_data(fontData, glyphIndices.ptr[i]); - extents = glyph->extents; - } - x += extents.xAdvance; - y += extents.yAdvance; - - if(glyph && glyph->codePoint == '\n') - { - width = maximum(width, x); - x = 0; - y += lineHeight + fontData->extents.leading; - } - } - width = maximum(width, x); - - f32 fontScale = mg_font_get_scale_for_em_pixels(font, fontSize); - mp_rect rect = {0, -fontData->extents.ascent * fontScale, width * fontScale, (y + lineHeight) * fontScale }; - return(rect); -} - -mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text) -{ - if(!text.len || !text.ptr) - { - return((mp_rect){0}); - } - - mem_arena* scratch = mem_scratch(); - str32 codePoints = utf8_push_to_codepoints(scratch, text); - return(mg_text_bounding_box_utf32(font, fontSize, codePoints)); -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics canvas API -//------------------------------------------------------------------------------------------ - -#ifdef MG_IMPLEMENTS_BACKEND_METAL - mg_canvas_backend* mg_metal_canvas_create(mg_surface surface); -#endif - -mg_canvas mg_canvas_create(mg_surface surface) -{ - mg_canvas canvas = mg_canvas_nil(); - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData) - { - mg_canvas_backend* backend = 0; - switch(surfaceData->backend) - { - #ifdef MG_IMPLEMENTS_BACKEND_METAL - case MG_BACKEND_METAL: - backend = mg_metal_canvas_create(surface); - break; - #endif - - /* - case MG_BACKEND_OPENGL: - canvasData = mg_opengl_canvas_create(surface); - break; - */ - default: - break; - } - - if(backend) - { - mg_canvas_data* canvasData = malloc_type(mg_canvas_data); - memset(canvasData, 0, sizeof(mg_canvas_data)); - - canvasData->backend = backend; - - canvasData->primitiveCount = 0; - canvasData->path.startIndex = 0; - canvasData->path.count = 0; - canvasData->nextZIndex = 1; - canvasData->attributes.color = (mg_color){0, 0, 0, 1}; - canvasData->attributes.tolerance = 1; - canvasData->attributes.width = 10; - canvasData->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - - canvasData->transform = (mg_mat2x3){{1, 0, 0, - 0, 1, 0}}; - - - //TODO: review this //////////////////////////////////////////////////////////////////////// - canvas = mg_canvas_alloc_handle(canvasData); - mg_canvas_set_current(canvas); - - //NOTE: create a blank image - //WARN: this requires setting the current context before - u8 bytes[4] = {255, 255, 255, 255}; - canvasData->blankImage = mg_image_create_from_rgba8(1, 1, bytes); - //////////////////////////////////////////////////////////////////////////////////////////////////// - } - } - return(canvas); -} - -void mg_canvas_destroy(mg_canvas handle) -{ - mg_canvas_data* canvas = mg_canvas_data_from_handle(handle); - if(canvas) - { - if(__mgCurrentCanvas == canvas) - { - __mgCurrentCanvas = 0; - __mgCurrentCanvasHandle = mg_canvas_nil(); - } - - if(canvas->backend && canvas->backend->destroy) - { - canvas->backend->destroy(canvas->backend); - } - free(canvas); - mg_resource_handle_recycle(&__mgData.canvases, handle.h); - } -} - -mg_canvas mg_canvas_set_current(mg_canvas canvas) -{ - mg_canvas old = __mgCurrentCanvasHandle; - - __mgCurrentCanvasHandle = canvas; - __mgCurrentCanvas = mg_canvas_data_from_handle(canvas); - - return(old); -} -//////////////////////////////////////////////////////////// - -mg_mat2x3 mg_matrix_stack_top(mg_canvas_data* canvas) -{ - if(canvas->matrixStackSize == 0) - { - return((mg_mat2x3){1, 0, 0, - 0, 1, 0}); - } - else - { - return(canvas->matrixStack[canvas->matrixStackSize-1]); - } -} - -void mg_matrix_stack_push(mg_canvas_data* canvas, mg_mat2x3 transform) -{ - if(canvas->matrixStackSize >= MG_MATRIX_STACK_MAX_DEPTH) - { - LOG_ERROR("matrix stack overflow\n"); - } - else - { - canvas->matrixStack[canvas->matrixStackSize] = transform; - canvas->matrixStackSize++; - canvas->transform = transform; - } -} - -void mg_matrix_stack_pop(mg_canvas_data* canvas) -{ - if(canvas->matrixStackSize == 0) - { - LOG_ERROR("matrix stack underflow\n"); - } - else - { - canvas->matrixStackSize--; - canvas->transform = mg_matrix_stack_top(canvas); - } -} - - -mp_rect mg_clip_stack_top(mg_canvas_data* canvas) -{ - if(canvas->clipStackSize == 0) - { - return((mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}); - } - else - { - return(canvas->clipStack[canvas->clipStackSize-1]); - } -} - -void mg_clip_stack_push(mg_canvas_data* canvas, mp_rect clip) -{ - if(canvas->clipStackSize >= MG_CLIP_STACK_MAX_DEPTH) - { - LOG_ERROR("clip stack overflow\n"); - } - else - { - canvas->clipStack[canvas->clipStackSize] = clip; - canvas->clipStackSize++; - canvas->clip = clip; - } -} - -void mg_clip_stack_pop(mg_canvas_data* canvas) -{ - if(canvas->clipStackSize == 0) - { - LOG_ERROR("clip stack underflow\n"); - } - else - { - canvas->clipStackSize--; - canvas->clip = mg_clip_stack_top(canvas); - } -} - -void mg_do_clip_push(mg_canvas_data* canvas, mp_rect clip) -{ - //NOTE(martin): transform clip - vec2 p0 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x, clip.y}); - vec2 p1 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x + clip.w, clip.y}); - vec2 p2 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x + clip.w, clip.y + clip.h}); - vec2 p3 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x, clip.y + clip.h}); - - f32 x0 = minimum(p0.x, minimum(p1.x, minimum(p2.x, p3.x))); - f32 y0 = minimum(p0.y, minimum(p1.y, minimum(p2.y, p3.y))); - f32 x1 = maximum(p0.x, maximum(p1.x, maximum(p2.x, p3.x))); - f32 y1 = maximum(p0.y, maximum(p1.y, maximum(p2.y, p3.y))); - - mp_rect current = mg_clip_stack_top(canvas); - - //NOTE(martin): intersect with current clip - x0 = maximum(current.x, x0); - y0 = maximum(current.y, y0); - x1 = minimum(current.x + current.w, x1); - y1 = minimum(current.y + current.h, y1); - - mp_rect r = {x0, y0, maximum(0, x1-x0), maximum(0, y1-y0)}; - mg_clip_stack_push(canvas, r); -} - -void mg_flush() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - - mg_color clearColor = {0, 0, 0, 1}; - - u32 count = canvas->primitiveCount; - - u32 nextIndex = 0; - - mg_reset_z_index(canvas); - canvas->transform = (mg_mat2x3){1, 0, 0, - 0, 1, 0}; - canvas->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; - - for(int i=0; i= count) - { - LOG_ERROR("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex); - break; - } - mg_primitive* primitive = &(canvas->primitives[nextIndex]); - nextIndex++; - - switch(primitive->cmd) - { - case MG_CMD_CLEAR: - { - //NOTE(martin): clear buffers - canvas->vertexCount = 0; - canvas->indexCount = 0; - - clearColor = primitive->attributes.color; - } break; - - case MG_CMD_FILL: - { - u32 zIndex = mg_get_next_z_index(canvas); - mg_render_fill(canvas, - canvas->pathElements + primitive->path.startIndex, - &primitive->path, - zIndex, - primitive->attributes.color); - } break; - - case MG_CMD_STROKE: - { - mg_render_stroke(canvas, - canvas->pathElements + primitive->path.startIndex, - &primitive->path, - &primitive->attributes); - } break; - - - case MG_CMD_RECT_FILL: - mg_render_rectangle_fill(canvas, primitive->rect, &primitive->attributes); - break; - - case MG_CMD_RECT_STROKE: - mg_render_rectangle_stroke(canvas, primitive->rect, &primitive->attributes); - break; - - case MG_CMD_ROUND_RECT_FILL: - mg_render_rounded_rectangle_fill(canvas, primitive->roundedRect, &primitive->attributes); - break; - - case MG_CMD_ROUND_RECT_STROKE: - mg_render_rounded_rectangle_stroke(canvas, primitive->roundedRect, &primitive->attributes); - break; - - case MG_CMD_ELLIPSE_FILL: - mg_render_ellipse_fill(canvas, primitive->rect, &primitive->attributes); - break; - - case MG_CMD_ELLIPSE_STROKE: - mg_render_ellipse_stroke(canvas, primitive->rect, &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 >= count) - { - LOG_ERROR("invalid jump location '%i' in graphics command buffer\n", primitive->jump); - goto exit_command_loop; - } - else - { - nextIndex = primitive->jump; - } - } break; - - case MG_CMD_MATRIX_PUSH: - { - mg_mat2x3 transform = mg_matrix_stack_top(canvas); - mg_matrix_stack_push(canvas, mg_mat2x3_mul_m(transform, primitive->matrix)); - } break; - - case MG_CMD_MATRIX_POP: - { - mg_matrix_stack_pop(canvas); - } break; - - case MG_CMD_CLIP_PUSH: - { - //TODO(martin): use only aligned rect and avoid this - mp_rect r = {primitive->rect.x, primitive->rect.y, primitive->rect.w, primitive->rect.h}; - mg_do_clip_push(canvas, r); - } break; - - case MG_CMD_CLIP_POP: - { - mg_clip_stack_pop(canvas); - } break; - - case MG_CMD_IMAGE_DRAW: - { - mg_render_image(canvas, primitive->attributes.image, primitive->rect); - } break; - - case MG_CMD_ROUNDED_IMAGE_DRAW: - { - mg_render_rounded_image(canvas, primitive->attributes.image, primitive->roundedRect, &primitive->attributes); - } break; - - } - } - exit_command_loop: ; - - if(canvas->backend && canvas->backend->drawBuffers) - { - canvas->backend->drawBuffers(canvas->backend, canvas->vertexCount, canvas->indexCount, clearColor); - } - - //NOTE(martin): clear buffers - canvas->primitiveCount = 0; - canvas->vertexCount = 0; - canvas->indexCount = 0; - - canvas->path.startIndex = 0; - canvas->path.count = 0; - - canvas->primitiveCount = 0; - canvas->frameCounter++; -} -//////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------ -//NOTE(martin): transform, viewport and clipping -//------------------------------------------------------------------------------------------ - -void mg_matrix_push(mg_mat2x3 matrix) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_MATRIX_PUSH, .matrix = matrix}); -} - -void mg_matrix_pop() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_MATRIX_POP}); -} - -void mg_clip_push(f32 x, f32 y, f32 w, f32 h) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLIP_PUSH, - .rect = (mp_rect){x, y, w, h}}); -} - -void mg_clip_pop() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLIP_POP}); -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): graphics attributes setting/getting -//------------------------------------------------------------------------------------------ -void mg_set_color(mg_color color) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.color = color; -} - -void mg_set_color_rgba(f32 r, f32 g, f32 b, f32 a) -{ - mg_set_color((mg_color){r, g, b, a}); -} - -void mg_set_width(f32 width) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.width = width; -} - -void mg_set_tolerance(f32 tolerance) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.tolerance = tolerance; -} - -void mg_set_joint(mg_joint_type joint) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.joint = joint; -} - -void mg_set_max_joint_excursion(f32 maxJointExcursion) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.maxJointExcursion = maxJointExcursion; -} - -void mg_set_cap(mg_cap_type cap) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.cap = cap; -} - -void mg_set_font(mg_font font) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.font = font; -} - -void mg_set_font_size(f32 fontSize) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->attributes.fontSize = fontSize; -} - -void mg_set_text_flip(bool flip) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - canvas->textFlip = flip; -} - -mg_color mg_get_color() -{ - mg_color color = {0}; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - color = canvas->attributes.color; - } - return(color); -} - -f32 mg_get_width() -{ - f32 width = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - width = canvas->attributes.width; - } - return(width); -} - -f32 mg_get_tolerance() -{ - f32 tolerance = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - tolerance = canvas->attributes.tolerance; - } - return(tolerance); -} - -mg_joint_type mg_get_joint() -{ - mg_joint_type joint = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - joint = canvas->attributes.joint; - } - return(joint); -} - -f32 mg_get_max_joint_excursion() -{ - f32 maxJointExcursion = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - maxJointExcursion = canvas->attributes.maxJointExcursion; - } - return(maxJointExcursion); -} - -mg_cap_type mg_get_cap() -{ - mg_cap_type cap = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - cap = canvas->attributes.cap; - } - return(cap); -} - -mg_font mg_get_font() -{ - mg_font font = mg_font_nil(); - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - font = canvas->attributes.font; - } - return(font); -} - -f32 mg_get_font_size() -{ - f32 fontSize = 0; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - fontSize = canvas->attributes.fontSize; - } - return(fontSize); -} - -bool mg_get_text_flip() -{ - bool flip = false; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - flip = canvas->textFlip; - } - return(flip); -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): path construction -//------------------------------------------------------------------------------------------ -vec2 mg_get_position() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return((vec2){0, 0}); - } - return(canvas->subPathLastPoint); -} - -void mg_move_to(f32 x, f32 y) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_MOVE, .p[0] = {x, y}})); - canvas->subPathStartPoint = (vec2){x, y}; - canvas->subPathLastPoint = (vec2){x, y}; -} - -void mg_line_to(f32 x, f32 y) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_LINE, .p[0] = {x, y}})); - canvas->subPathLastPoint = (vec2){x, y}; -} - -void mg_quadratic_to(f32 x1, f32 y1, f32 x2, f32 y2) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_QUADRATIC, .p = {{x1, y1}, {x2, y2}}})); - canvas->subPathLastPoint = (vec2){x2, y2}; -} - -void mg_cubic_to(f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_CUBIC, .p = {{x1, y1}, {x2, y2}, {x3, y3}}})); - canvas->subPathLastPoint = (vec2){x3, y3}; -} - -void mg_close_path() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - if( canvas->subPathStartPoint.x != canvas->subPathLastPoint.x - || canvas->subPathStartPoint.y != canvas->subPathLastPoint.y) - { - mg_line_to(canvas->subPathStartPoint.x, canvas->subPathStartPoint.y); - } - canvas->subPathStartPoint = canvas->subPathLastPoint; -} - -mp_rect mg_glyph_outlines_from_font_data(mg_font_data* fontData, str32 glyphIndices) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - - f32 startX = canvas->subPathLastPoint.x; - f32 startY = canvas->subPathLastPoint.y; - f32 maxWidth = 0; - - f32 scale = canvas->attributes.fontSize/fontData->unitsPerEm; - - for(int i=0; isubPathLastPoint.x; - f32 yOffset = canvas->subPathLastPoint.y; - f32 flip = canvas->textFlip ? -1 : 1; - - if(!glyphIndex || glyphIndex >= fontData->glyphCount) - { - LOG_WARNING("code point is not present in font ranges\n"); - //NOTE(martin): try to find the replacement character - glyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 0xfffd); - if(!glyphIndex) - { - //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width - // to render an empty rectangle. Otherwise just render with the max font width - f32 boxWidth = fontData->extents.width * 0.8; - f32 xBearing = fontData->extents.width * 0.1; - f32 xAdvance = fontData->extents.width; - - glyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 'x'); - if(glyphIndex) - { - mg_glyph_data* glyph = &(fontData->glyphs[glyphIndex]); - boxWidth = glyph->extents.width; - xBearing = glyph->extents.xBearing; - xAdvance = glyph->extents.xAdvance; - } - f32 oldStrokeWidth = canvas->attributes.width; - - mg_set_width(boxWidth*0.005); - mg_rectangle_stroke(xOffset + xBearing * scale, - yOffset, - boxWidth * scale * flip, - fontData->extents.capHeight*scale); - - mg_set_width(oldStrokeWidth); - mg_move_to(xOffset + xAdvance * scale, yOffset); - maxWidth = maximum(maxWidth, xOffset + xAdvance*scale - startX); - continue; - } - } - - mg_glyph_data* glyph = mg_font_get_glyph_data(fontData, glyphIndex); - - mg_path_push_elements(canvas, glyph->pathDescriptor.count, fontData->outlines + glyph->pathDescriptor.startIndex); - - mg_path_elt* elements = canvas->pathElements + canvas->path.count + canvas->path.startIndex - glyph->pathDescriptor.count; - for(int eltIndex=0; eltIndexpathDescriptor.count; eltIndex++) - { - for(int pIndex = 0; pIndex < 3; pIndex++) - { - elements[eltIndex].p[pIndex].x = elements[eltIndex].p[pIndex].x * scale + xOffset; - elements[eltIndex].p[pIndex].y = elements[eltIndex].p[pIndex].y * scale * flip + yOffset; - } - } - mg_move_to(xOffset + scale*glyph->extents.xAdvance, yOffset); - - maxWidth = maximum(maxWidth, xOffset + scale*glyph->extents.xAdvance - startX); - } - f32 lineHeight = (fontData->extents.ascent + fontData->extents.descent)*scale; - mp_rect box = {startX, startY, maxWidth, canvas->subPathLastPoint.y - startY + lineHeight }; - return(box); -} - -mp_rect mg_glyph_outlines(str32 glyphIndices) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return((mp_rect){0}); - } - mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); - if(!fontData) - { - return((mp_rect){0}); - } - return(mg_glyph_outlines_from_font_data(fontData, glyphIndices)); -} - -void mg_codepoints_outlines(str32 codePoints) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); - if(!fontData) - { - return; - } - - str32 glyphIndices = mg_font_push_glyph_indices(canvas->attributes.font, mem_scratch(), codePoints); - mg_glyph_outlines_from_font_data(fontData, glyphIndices); -} - -void mg_text_outlines(str8 text) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); - if(!fontData) - { - return; - } - - mem_arena* scratch = mem_scratch(); - str32 codePoints = utf8_push_to_codepoints(scratch, text); - str32 glyphIndices = mg_font_push_glyph_indices(canvas->attributes.font, scratch, codePoints); - - mg_glyph_outlines_from_font_data(fontData, glyphIndices); -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): clear/fill/stroke -//------------------------------------------------------------------------------------------ - -void mg_clear() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLEAR, .attributes = canvas->attributes}); -} - -void mg_fill() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - if(canvas->path.count) - { - mg_push_command(canvas, ((mg_primitive){.cmd = MG_CMD_FILL, .path = canvas->path, .attributes = canvas->attributes})); - mg_new_path(canvas); - } -} - -void mg_stroke() -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - if(canvas->path.count) - { - mg_push_command(canvas, ((mg_primitive){.cmd = MG_CMD_STROKE, .path = canvas->path, .attributes = canvas->attributes})); - mg_new_path(canvas); - } -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): 'fast' shapes primitives -//------------------------------------------------------------------------------------------ -void mg_rectangle_fill(f32 x, f32 y, f32 w, f32 h) -{ -// DEBUG_ASSERT(w>=0 && h>=0); - - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_RECT_FILL, .rect = (mp_rect){x, y, w, h}, .attributes = canvas->attributes})); -} -void mg_rectangle_stroke(f32 x, f32 y, f32 w, f32 h) -{ -// DEBUG_ASSERT(w>=0 && h>=0); - - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_RECT_STROKE, .rect = (mp_rect){x, y, w, h}, .attributes = canvas->attributes})); -} - -void mg_rounded_rectangle_fill(f32 x, f32 y, f32 w, f32 h, f32 r) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_FILL, - .roundedRect = (mg_rounded_rect){x, y, w, h, r}, - .attributes = canvas->attributes})); -} - -void mg_rounded_rectangle_stroke(f32 x, f32 y, f32 w, f32 h, f32 r) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_STROKE, - .roundedRect = (mg_rounded_rect){x, y, w, h, r}, - .attributes = canvas->attributes})); -} - -void mg_circle_fill(f32 x, f32 y, f32 r) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, - .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, - .attributes = canvas->attributes})); -} - -void mg_circle_stroke(f32 x, f32 y, f32 r) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, - .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, - .attributes = canvas->attributes})); -} - -void mg_ellipse_fill(f32 x, f32 y, f32 rx, f32 ry) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, - .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, - .attributes = canvas->attributes})); -} - -void mg_ellipse_stroke(f32 x, f32 y, f32 rx, f32 ry) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_push_command(canvas, - ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, - .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, - .attributes = canvas->attributes})); -} - -void mg_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle) -{ - f32 endAngle = startAngle + arcAngle; - - while(startAngle < endAngle) - { - f32 smallAngle = minimum(endAngle - startAngle, M_PI/4.); - if(smallAngle < 0.001) - { - break; - } - - vec2 v0 = {cos(smallAngle/2), sin(smallAngle/2)}; - vec2 v1 = {(4-v0.x)/3, (1-v0.x)*(3-v0.x)/(3*v0.y)}; - vec2 v2 = {v1.x, -v1.y}; - vec2 v3 = {v0.x, -v0.y}; - - f32 rotAngle = smallAngle/2 + startAngle; - f32 rotCos = cos(rotAngle); - f32 rotSin = sin(rotAngle); - - mg_mat2x3 t = {r*rotCos, -r*rotSin, x, - r*rotSin, r*rotCos, y}; - - v0 = mg_mat2x3_mul(t, v0); - v1 = mg_mat2x3_mul(t, v1); - v2 = mg_mat2x3_mul(t, v2); - v3 = mg_mat2x3_mul(t, v3); - - mg_move_to(v0.x, v0.y); - mg_cubic_to(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y); - - startAngle += smallAngle; - } -} - -//------------------------------------------------------------------------------------------ -//NOTE(martin): images -//------------------------------------------------------------------------------------------ - -mg_image mg_image_create_from_rgba8(u32 width, u32 height, u8* bytes) -{ - mg_image image = mg_image_nil(); - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_image_data* imageData = ListPopEntry(&canvas->imageFreeList, mg_image_data, listElt); - if(!imageData) - { - if(canvas->imageNextIndex < MG_IMAGE_MAX_COUNT) - { - imageData = &canvas->images[canvas->imageNextIndex]; - imageData->generation = 1; - canvas->imageNextIndex++; - } - else - { - LOG_ERROR("image pool full\n"); - } - } - - if(imageData) - { - if(canvas->atlasPos.x + width >= MG_ATLAS_SIZE) - { - canvas->atlasPos.x = 0; - canvas->atlasPos.y += canvas->atlasLineHeight; - } - if(canvas->atlasPos.x + width < MG_ATLAS_SIZE - && canvas->atlasPos.y + height < MG_ATLAS_SIZE) - { - imageData->rect = (mp_rect){canvas->atlasPos.x, - canvas->atlasPos.y, - width, - height}; - - canvas->atlasPos.x += width; - canvas->atlasLineHeight = maximum(canvas->atlasLineHeight, height); - - canvas->backend->atlasUpload(canvas->backend, imageData->rect, bytes); - image = mg_image_handle_from_ptr(canvas, imageData); - } - else - { - mg_image_data_recycle(canvas, imageData); - } - } - } - 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_ptr_from_handle(canvas, image); - if(imageData) - { - //TODO free atlas area - mg_image_data_recycle(canvas, imageData); - } - } -} - -vec2 mg_image_size(mg_image image) -{ - /////////////////////////////////////////////////////////////////////////// - //WARN: this supposes the current canvas is that for which the image was created - /////////////////////////////////////////////////////////////////////////// - - vec2 size = {0}; - mg_canvas_data* canvas = __mgCurrentCanvas; - if(canvas) - { - mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); - if(imageData) - { - size = (vec2){imageData->rect.w, imageData->rect.h}; - } - } - return(size); -} - -void mg_image_draw(mg_image image, mp_rect rect) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_primitive primitive = {.cmd = MG_CMD_IMAGE_DRAW, - .rect = (mp_rect){rect.x, rect.y, rect.w, rect.h}, - .attributes = canvas->attributes}; - primitive.attributes.image = image; - mg_push_command(canvas, primitive); -} - -void mg_rounded_image_draw(mg_image image, mp_rect rect, f32 roundness) -{ - mg_canvas_data* canvas = __mgCurrentCanvas; - if(!canvas) - { - return; - } - mg_primitive primitive = {.cmd = MG_CMD_ROUNDED_IMAGE_DRAW, - .roundedRect = {rect.x, rect.y, rect.w, rect.h, roundness}, - .attributes = canvas->attributes}; - primitive.attributes.image = image; - - mg_push_command(canvas, primitive); -} - - - -#undef LOG_SUBSYSTEM +/************************************************************//** +* +* @file: graphics.c +* @author: Martin Fouilleul +* @date: 23/01/2023 +* @revision: +* +*****************************************************************/ + +#define _USE_MATH_DEFINES //NOTE: necessary for MSVC +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include"stb_truetype.h" + +#define STB_IMAGE_IMPLEMENTATION +#include"stb_image.h" + +#include"debug_log.h" +#include"graphics_internal.h" + +#define LOG_SUBSYSTEM "Graphics" + +//------------------------------------------------------------------------ +// graphics handles structs +//------------------------------------------------------------------------ +typedef struct mg_resource_slot +{ + list_elt freeListElt; + u32 generation; + union + { + mg_surface_data* surface; + mg_image_data* image; + mg_canvas_data* canvas; + mg_font_data* font; + //... + }; + +} mg_resource_slot; + +enum +{ + MG_MAX_RESOURCE_SLOTS = 128, + //... +}; + +typedef struct mg_resource_pool +{ + mg_resource_slot slots[MG_MAX_RESOURCE_SLOTS]; + list_info freeList; + u32 nextIndex; + +} mg_resource_pool; + +typedef struct mg_data +{ + bool init; + + mg_resource_pool surfaces; + mg_resource_pool canvases; + mg_resource_pool fonts; + //... + +} mg_data; + +static mg_data __mgData = {0}; + +void mg_init() +{ + if(!__mgData.init) + { + __mgData.init = true; + //... + } +} + +//------------------------------------------------------------------------ +// handle pools procedures +//------------------------------------------------------------------------ + +mg_resource_slot* mg_resource_slot_alloc(mg_resource_pool* pool) +{ + if(!__mgData.init) + { + //TODO: see if we require an init at all, and if we want to make it explicit to the user + mg_init(); + } + + mg_resource_slot* slot = ListPopEntry(&pool->freeList, mg_resource_slot, freeListElt); + if(!slot && pool->nextIndex < MG_MAX_RESOURCE_SLOTS) + { + slot = &pool->slots[pool->nextIndex]; + slot->generation = 1; + pool->nextIndex++; + } + return(slot); +} + +void mg_resource_slot_recycle(mg_resource_pool* pool, mg_resource_slot* slot) +{ + DEBUG_ASSERT(slot >= pool->slots && slot < pool->slots + MG_MAX_RESOURCE_SLOTS); + #ifdef DEBUG + if(slot->generation == UINT32_MAX) + { + LOG_ERROR("surface slot generation wrap around\n"); + } + #endif + slot->generation++; + ListPush(&pool->freeList, &slot->freeListElt); +} + +mg_resource_slot* mg_resource_slot_from_handle(mg_resource_pool* pool, u64 h) +{ + u32 index = h>>32; + u32 generation = h & 0xffffffff; + if(index >= MG_MAX_RESOURCE_SLOTS) + { + return(0); + } + mg_resource_slot* slot = &pool->slots[index]; + if(slot->generation != generation) + { + return(0); + } + else + { + return(slot); + } +} + +u64 mg_resource_handle_from_slot(mg_resource_pool* pool, mg_resource_slot* slot) +{ + DEBUG_ASSERT( (slot - pool->slots) >= 0 + && (slot - pool->slots) < MG_MAX_RESOURCE_SLOTS); + + u64 h = ((u64)(slot - pool->slots))<<32 + |((u64)(slot->generation)); + return(h); +} + +void mg_resource_handle_recycle(mg_resource_pool* pool, u64 h) +{ + mg_resource_slot* slot = mg_resource_slot_from_handle(pool, h); + if(slot) + { + mg_resource_slot_recycle(pool, slot); + } +} + +//------------------------------------------------------------------------ +// surface handles +//------------------------------------------------------------------------ + +mg_surface mg_surface_nil() { return((mg_surface){.h = 0}); } +bool mg_surface_is_nil(mg_surface surface) { return(surface.h == 0); } + +mg_surface mg_surface_alloc_handle(mg_surface_data* surface) +{ + mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.surfaces); + if(!slot) + { + LOG_ERROR("no more surface slots\n"); + return(mg_surface_nil()); + } + slot->surface = surface; + u64 h = mg_resource_handle_from_slot(&__mgData.surfaces, slot); + mg_surface handle = {h}; + return(handle); +} + +mg_surface_data* mg_surface_data_from_handle(mg_surface surface) +{ + mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.surfaces, surface.h); + mg_surface_data* data = 0; + if(slot) + { + data = slot->surface; + } + return(data); +} + +//------------------------------------------------------------------------ +// canvas handles +//------------------------------------------------------------------------ +mg_canvas mg_canvas_nil() { return((mg_canvas){.h = 0}); } +bool mg_canvas_is_nil(mg_canvas canvas) { return(canvas.h == 0); } + +mg_canvas mg_canvas_alloc_handle(mg_canvas_data* canvas) +{ + mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.canvases); + if(!slot) + { + LOG_ERROR("no more canvas slots\n"); + return(mg_canvas_nil()); + } + slot->canvas = canvas; + u64 h = mg_resource_handle_from_slot(&__mgData.canvases, slot); + mg_canvas handle = {h}; + return(handle); +} + +mg_canvas_data* mg_canvas_data_from_handle(mg_canvas canvas) +{ + mg_canvas_data* data = 0; + mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.canvases, canvas.h); + if(slot) + { + data = slot->canvas; + } + return(data); +} + +//------------------------------------------------------------------------ +// image handles +//------------------------------------------------------------------------ + +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_handle_from_ptr(mg_canvas_data* canvas, mg_image_data* imageData) +{ + DEBUG_ASSERT( (imageData - canvas->images) >= 0 + && (imageData - canvas->images) < MG_IMAGE_MAX_COUNT); + + u64 h = ((u64)(imageData - canvas->images))<<32 + |((u64)(imageData->generation)); + return((mg_image){.h = h}); +} + +void mg_image_data_recycle(mg_canvas_data* canvas, mg_image_data* image) +{ + image->generation++; + ListPush(&canvas->imageFreeList, &image->listElt); +} + +mg_image_data* mg_image_ptr_from_handle(mg_canvas_data* context, mg_image handle) +{ + u32 index = handle.h>>32; + u32 generation = handle.h & 0xffffffff; + + if(index >= MG_IMAGE_MAX_COUNT) + { + return(0); + } + mg_image_data* image = &context->images[index]; + if(image->generation != generation) + { + return(0); + } + else + { + return(image); + } +} + +//------------------------------------------------------------------------ +// font handles +//------------------------------------------------------------------------ + +mg_font mg_font_nil() { return((mg_font){.h = 0}); } +bool mg_font_is_nil(mg_font font) { return(font.h == 0); } + +mg_font mg_font_alloc_handle(mg_font_data* font) +{ + mg_resource_slot* slot = mg_resource_slot_alloc(&__mgData.fonts); + if(!slot) + { + LOG_ERROR("no more font slots\n"); + return(mg_font_nil()); + } + slot->font = font; + u64 h = mg_resource_handle_from_slot(&__mgData.fonts, slot); + mg_font handle = {h}; + return(handle); +} + +mg_font_data* mg_font_data_from_handle(mg_font font) +{ + mg_font_data* data = 0; + mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.fonts, font.h); + if(slot) + { + data = slot->font; + } + return(data); +} + +//--------------------------------------------------------------- +// surface API +//--------------------------------------------------------------- + +void mg_surface_destroy(mg_surface handle) +{ + DEBUG_ASSERT(__mgData.init); + mg_resource_slot* slot = mg_resource_slot_from_handle(&__mgData.surfaces, handle.h); + if(slot) + { + slot->surface->destroy(slot->surface); + mg_resource_slot_recycle(&__mgData.surfaces, slot); + } +} + +void mg_surface_prepare(mg_surface surface) +{ + DEBUG_ASSERT(__mgData.init); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + surfaceData->prepare(surfaceData); + } +} + +void mg_surface_present(mg_surface surface) +{ + DEBUG_ASSERT(__mgData.init); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + surfaceData->present(surfaceData); + } +} + +void mg_surface_set_frame(mg_surface surface, mp_rect frame) +{ + DEBUG_ASSERT(__mgData.init); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + surfaceData->setFrame(surfaceData, frame); + } +} + +mp_rect mg_surface_get_frame(mg_surface surface) +{ + DEBUG_ASSERT(__mgData.init); + mp_rect res = {0}; + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + res = surfaceData->getFrame(surfaceData); + } + return(res); +} + +void mg_surface_set_hidden(mg_surface surface, bool hidden) +{ + DEBUG_ASSERT(__mgData.init); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + surfaceData->setHidden(surfaceData, hidden); + } +} + +bool mg_surface_get_hidden(mg_surface surface) +{ + DEBUG_ASSERT(__mgData.init); + bool res = false; + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + res = surfaceData->getHidden(surfaceData); + } + return(res); +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics canvas internal +//------------------------------------------------------------------------------------------ + +mp_thread_local mg_canvas_data* __mgCurrentCanvas = 0; +mp_thread_local mg_canvas __mgCurrentCanvasHandle = {0}; + +//TODO: move elsewhere? +mg_mat2x3 mg_mat2x3_mul_m(mg_mat2x3 lhs, mg_mat2x3 rhs) +{ + mg_mat2x3 res; + res.m[0] = lhs.m[0]*rhs.m[0] + lhs.m[1]*rhs.m[3]; + res.m[1] = lhs.m[0]*rhs.m[1] + lhs.m[1]*rhs.m[4]; + res.m[2] = lhs.m[0]*rhs.m[2] + lhs.m[1]*rhs.m[5] + lhs.m[2]; + res.m[3] = lhs.m[3]*rhs.m[0] + lhs.m[4]*rhs.m[3]; + res.m[4] = lhs.m[3]*rhs.m[1] + lhs.m[4]*rhs.m[4]; + res.m[5] = lhs.m[3]*rhs.m[2] + lhs.m[4]*rhs.m[5] + lhs.m[5]; + + return(res); +} + +vec2 mg_mat2x3_mul(mg_mat2x3 m, vec2 p) +{ + f32 x = p.x*m.m[0] + p.y*m.m[1] + m.m[2]; + f32 y = p.x*m.m[3] + p.y*m.m[4] + m.m[5]; + return((vec2){x, y}); +} + + +void mg_push_command(mg_canvas_data* canvas, mg_primitive primitive) +{ + //NOTE(martin): push primitive and updates current stream, eventually patching a pending jump. + ASSERT(canvas->primitiveCount < MG_MAX_PRIMITIVE_COUNT); + canvas->primitives[canvas->primitiveCount] = primitive; + canvas->primitiveCount++; +} + +void mg_new_path(mg_canvas_data* canvas) +{ + canvas->path.startIndex += canvas->path.count; + canvas->path.count = 0; + canvas->subPathStartPoint = canvas->subPathLastPoint; + canvas->path.startPoint = canvas->subPathStartPoint; +} + +void mg_path_push_elements(mg_canvas_data* canvas, u32 count, mg_path_elt* elements) +{ + ASSERT(canvas->path.count + canvas->path.startIndex + count <= MG_MAX_PATH_ELEMENT_COUNT); + memcpy(canvas->pathElements + canvas->path.startIndex + canvas->path.count, elements, count*sizeof(mg_path_elt)); + canvas->path.count += count; +} + +void mg_path_push_element(mg_canvas_data* canvas, mg_path_elt elt) +{ + mg_path_push_elements(canvas, 1, &elt); +} + +void mg_reset_z_index(mg_canvas_data* canvas) +{ + canvas->nextZIndex = 1; +} + +u32 mg_get_next_z_index(mg_canvas_data* canvas) +{ + return(canvas->nextZIndex++); +} + +/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// +u32 mg_vertices_base_index(mg_canvas_data* canvas) +{ + return(canvas->vertexCount); +} + +void mg_push_textured_vertex(mg_canvas_data* canvas, vec2 pos, vec4 cubic, vec2 uv, mg_color color, u64 zIndex) +{ + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + + DEBUG_ASSERT(canvas->vertexCount < layout->maxVertexCount); + + u32 offset = canvas->vertexCount; + + *(vec2*)(((char*)layout->posBuffer) + offset*layout->posStride) = pos; + *(vec4*)(((char*)layout->cubicBuffer) + offset*layout->cubicStride) = cubic; + *(vec2*)(((char*)layout->uvBuffer) + offset*layout->uvStride) = uv; + *(mg_color*)(((char*)layout->colorBuffer) + offset*layout->colorStride) = color; + *(u32*)(((char*)layout->zIndexBuffer) + offset*layout->zIndexStride) = zIndex; + *(mp_rect*)(((char*)layout->clipBuffer) + offset*layout->clipStride) = canvas->clip; + + canvas->vertexCount++; +} + +void mg_push_vertex(mg_canvas_data* canvas, vec2 pos, vec4 cubic, mg_color color, u64 zIndex) +{ + mg_push_textured_vertex(canvas, pos, cubic, (vec2){0, 0}, color, zIndex); +} + + +/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// + +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); +} + +//----------------------------------------------------------------------------------------------------------- +// Path Filling +//----------------------------------------------------------------------------------------------------------- +//NOTE(martin): forward declarations +void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_color color); + +//NOTE(martin): quadratics filling + +void mg_render_fill_quadratic(mg_canvas_data* canvas, vec2 p[3], u32 zIndex, mg_color color) +{ + u32 baseIndex = mg_vertices_base_index(canvas); + + i32* indices = mg_reserve_indices(canvas, 3); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[0].x, p[0].y}), + (vec4){0, 0, 0, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[1].x, p[1].y}), + (vec4){0.5, 0, 0.5, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[2].x, p[2].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + 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, u32 zIndex, mg_color color) +{ + 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, + mg_mat2x3_mul(canvas->transform, (vec2){p[0].x, p[0].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){split.x, split.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[3].x, p[3].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + + mg_render_fill_cubic(canvas, subPointsLow, zIndex, color); + mg_render_fill_cubic(canvas, subPointsHigh, zIndex, color); + + return; +} + +void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_color color) +{ + LOG_DEBUG("graphics render fill cubic\n"); + + 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 should do the tex coords computations in f64 and scale them to avoid f32 precision/range glitches in shader + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + 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 + LOG_DEBUG("quadratic curve\n"); + + //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, zIndex, color); + 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). + LOG_DEBUG("%s\n", (discrFactor2 > 0 && d1 != 0) ? "serpentine curve" : "cusp with inflection at infinity"); + + //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 texture 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 + LOG_DEBUG("loop curve\n"); + + 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) + { + LOG_DEBUG("split curve at first double point\n"); + mg_split_and_fill_cubic(canvas, p, td/sd, zIndex, color); + return; + } + if(se != 0 && te/se < 0.99 && te/se > 0.01) + { + LOG_DEBUG("split curve at second double point\n"); + mg_split_and_fill_cubic(canvas, p, te/se, zIndex, color); + 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 texture 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 + LOG_DEBUG("cusp at infinity curve\n"); + + 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 texture 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 + LOG_DEBUG("line or point curve (ignored)\n"); + return; + } + else + { + //TODO(martin): handle error ? put some epsilon slack on the conditions ? + LOG_DEBUG("none of the above...\n"); + 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); + + //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; + } + + //NOTE(martin): rearrange convex hull to put p0 first + int startIndex = -1; + int orderedHullIndices[4]; + for(int i=0; i= 0) + { + orderedHullIndices[i-startIndex] = convexHullIndices[i]; + } + } + for(int i=0; i 0 + // ie the 4th coordinate s flips the inside/outside test. + // We affect s such that control points p1 and p2 are always outside the covered area. + + if(convexHullCount <= 3) + { + //NOTE(martin): the convex hull is a triangle + + //NOTE(martin): when we degenerate from 4 control points to a 3 points convex hull, this means one of the + // control points p1 or p2 could be inside the covered area. We want to compute the test for the + // control point which belongs to the convex hull, as we know it should be outside the covered area. + // + // Since there are 3 points in the hull and p0 is the first, and p3 belongs to the hull, this means we + // must select the point of the convex hull which is neither the first, nor p3. + + int testPointIndex = orderedHullIndices[1] == 3 ? orderedHullIndices[2] : orderedHullIndices[1]; + int outsideTest = 1; + if(Cube(testCoords[testPointIndex].x)-testCoords[testPointIndex].y*testCoords[testPointIndex].z < 0) + { + outsideTest = -1; + } + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 3); + + for(int i=0; i<3; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[i]]); + vec4 cubic = testCoords[orderedHullIndices[i]]; + cubic.w = outsideTest; + mg_push_vertex(canvas, pos, cubic, color, zIndex); + + indices[i] = baseIndex + i; + } + } + else if(orderedHullIndices[2] == 3) + { + //NOTE(martin): p1 and p2 are not on the same side of (p0,p3). The outside test can be different for + // the two triangles + int outsideTest1 = 1; + int outsideTest2 = 1; + + int testIndex = orderedHullIndices[1]; + if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) + { + outsideTest1 = -1; + } + + testIndex = orderedHullIndices[3]; + if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) + { + outsideTest2 = -1; + } + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[0]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[1]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[1]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[2]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[0]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest2}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[2]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest2}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[3]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[3]]), outsideTest2}, + color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 3; + indices[4] = baseIndex + 4; + indices[5] = baseIndex + 5; + } + else + { + //NOTE(martin): if p1 and p2 are on the same side of (p0,p3), the outside test is the same for both triangles + int outsideTest = 1; + if(Cube(testCoords[1].x)-testCoords[1].y*testCoords[1].z < 0) + { + outsideTest = -1; + } + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + for(int i=0; i<4; i++) + { + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, p[orderedHullIndices[i]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[i]]), outsideTest}, + color, + zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } +} + +//NOTE(martin): global path fill + +void mg_render_fill(mg_canvas_data* canvas, mg_path_elt* elements, mg_path_descriptor* path, u32 zIndex, mg_color color) +{ + 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, zIndex, color); + endPoint = controlPoints[2]; + + } break; + + case MG_PATH_CUBIC: + { + mg_render_fill_cubic(canvas, controlPoints, zIndex, color); + endPoint = controlPoints[3]; + } break; + } + + //NOTE(martin): now fill interior triangle + u32 baseIndex = mg_vertices_base_index(canvas); + int* indices = mg_reserve_indices(canvas, 3); + + vec2 pos[3]; + pos[0] = mg_mat2x3_mul(canvas->transform, startPoint); + pos[1] = mg_mat2x3_mul(canvas->transform, currentPoint); + pos[2] = mg_mat2x3_mul(canvas->transform, endPoint); + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<3; i++) + { + mg_push_vertex(canvas, pos[i], cubic, color, zIndex); + indices[i] = baseIndex + i; + } + + currentPoint = endPoint; + } +} + +//----------------------------------------------------------------------------------------------------------- +// Path Stroking +//----------------------------------------------------------------------------------------------------------- + +void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], u32 zIndex, 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; + + + mg_color color = attributes->color; + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[0].x + n0.x, p[0].y + n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[1].x + n0.x, p[1].y + n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[1].x - n0.x, p[1].y - n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p[0].x - n0.x, p[0].y - n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) +{ +////////////////////////////////////////////////////////////////////////////////////// +//WARN: quick fix for coincident middle control points + if(count == 4 && (p[1].x - p[2].x < 0.01) && (p[1].y - p[2].y < 0.01)) + { + vec2 hull3[3] = {p[0], p[1], p[3]}; + vec2 result3[3]; + mg_offset_hull(3, hull3, result3, offset); + result[0] = result3[0]; + result[1] = result3[1]; + result[2] = result3[1]; + result[3] = result3[2]; + return; + } +/////////////////////////////////////////////////////////////////////////////////////: + + //TODO(martin): review edge cases (coincident points ? colinear points ? control points pointing outward end point?) + //NOTE(martin): first offset control point is just the offset of first control point + vec2 n = {p[0].y - p[1].y, + p[1].x - p[0].x}; + f32 norm = sqrt(n.x*n.x + n.y*n.y); + n.x *= offset/norm; + n.y *= offset/norm; + + result[0].x = p[0].x + n.x; + result[0].y = p[0].y + n.y; + + //NOTE(martin): subsequent offset control points are the intersection of offset control lines + for(int i=1; iwidth); + mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width); + + //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 + + 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) + { + //TODO(martin): split at maxErrorParameter and recurse + vec2 splitLeft[3]; + vec2 splitRight[3]; + mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_quadratic(canvas, splitLeft, zIndex, attributes); + mg_render_stroke_quadratic(canvas, splitRight, zIndex, attributes); + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + + u32 zIndex = mg_get_next_z_index(canvas); + + mg_render_fill_quadratic(canvas, positiveOffsetHull, zIndex, attributes->color); + mg_render_fill_quadratic(canvas, negativeOffsetHull, zIndex, attributes->color); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, positiveOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, positiveOffsetHull[2]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, negativeOffsetHull[2]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, negativeOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + 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. + + 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 q2 = {oneMt*p[2].x + t*p[3].x, + oneMt*p[2].y + t*p[3].y}; + + vec2 r0 = {oneMt*q0.x + t*q1.x, + oneMt*q0.y + t*q1.y}; + + vec2 r1 = {oneMt*q1.x + t*q2.x, + oneMt*q1.y + t*q2.y}; + + vec2 s = {oneMt*r0.x + t*r1.x, + oneMt*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], u32 zIndex, mg_attributes* attributes) +{ + #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]; + + mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width); + mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width); + + //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 + + 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) + { + //TODO(martin): split at maxErrorParameter and recurse + vec2 splitLeft[4]; + vec2 splitRight[4]; + mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_cubic(canvas, splitLeft, zIndex, attributes); + mg_render_stroke_cubic(canvas, splitRight, zIndex, attributes); + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + + u32 zIndex = mg_get_next_z_index(canvas); + + mg_render_fill_cubic(canvas, positiveOffsetHull, zIndex, attributes->color); + mg_render_fill_cubic(canvas, negativeOffsetHull, zIndex, attributes->color); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, positiveOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, positiveOffsetHull[3]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, negativeOffsetHull[3]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, negativeOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + 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}; + + u32 zIndex = mg_get_next_z_index(canvas); + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x, p0.y + n0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x - n0.x, p0.y - n0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + 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; + } + + u32 zIndex = mg_get_next_z_index(canvas); + + //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, + mg_mat2x3_mul(canvas->transform, p0), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, mitterPoint), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + 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, + mg_mat2x3_mul(canvas->transform, p0), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(canvas, + mg_mat2x3_mul(canvas->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + 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; + u32 zIndex = mg_get_next_z_index(canvas); + + switch(element->type) + { + case MG_PATH_LINE: + mg_render_stroke_line(canvas, controlPoints, zIndex, attributes); + endPointIndex = 1; + break; + + case MG_PATH_QUADRATIC: + mg_render_stroke_quadratic(canvas, controlPoints, zIndex, attributes); + endPointIndex = 2; + break; + + case MG_PATH_CUBIC: + mg_render_stroke_cubic(canvas, controlPoints, zIndex, attributes); + endPointIndex = 3; + break; + + case MG_PATH_MOVE: + ASSERT(0, "should be unreachable"); + break; + } + + *startTangent = (vec2){.x = controlPoints[1].x - controlPoints[0].x, + .y = controlPoints[1].y - controlPoints[0].y}; + + *endTangent = (vec2){controlPoints[endPointIndex].x - controlPoints[endPointIndex-1].x, + controlPoints[endPointIndex].y - controlPoints[endPointIndex-1].y}; + + *endPoint = controlPoints[endPointIndex]; + +} + +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); + } + } +} + +//----------------------------------------------------------------------------------------------------------- +// Fast shapes primitives +//----------------------------------------------------------------------------------------------------------- + +void mg_render_rectangle_fill(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) +{ + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + u32 zIndex = mg_get_next_z_index(canvas); + + vec2 points[4] = {{rect.x, rect.y}, + {rect.x + rect.w, rect.y}, + {rect.x + rect.w, rect.y + rect.h}, + {rect.x, rect.y + rect.h}}; + + vec4 cubic = {1, 1, 1, 1}; + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); + mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rectangle_stroke(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) +{ + //NOTE(martin): stroke a rectangle by fill two scaled rectangles with the same zIndex. + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 12); + + u32 zIndex = mg_get_next_z_index(canvas); + + //NOTE(martin): limit stroke width to the minimum dimension of the rectangle + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + vec2 outerPoints[4] = {{rect.x - halfW, rect.y - halfW}, + {rect.x + rect.w + halfW, rect.y - halfW}, + {rect.x + rect.w + halfW, rect.y + rect.h + halfW}, + {rect.x - halfW, rect.y + rect.h + halfW}}; + + vec2 innerPoints[4] = {{rect.x + halfW, rect.y + halfW}, + {rect.x + rect.w - halfW, rect.y + halfW}, + {rect.x + rect.w - halfW, rect.y + rect.h - halfW}, + {rect.x + halfW, rect.y + rect.h - halfW}}; + + vec4 cubic = {1, 1, 1, 1}; + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, outerPoints[i]); + mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); + } + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, innerPoints[i]); + mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + indices[6] = baseIndex + 4; + indices[7] = baseIndex + 5; + indices[8] = baseIndex + 6; + indices[9] = baseIndex + 4; + indices[10] = baseIndex + 6; + indices[11] = baseIndex + 7; +} + +void mg_render_fill_arc_corner(mg_canvas_data* canvas, f32 x, f32 y, f32 rx, f32 ry, u32 zIndex, mg_color color) +{ + //NOTE(martin): draw a precomputed arc corner, using a bezier approximation + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + static const vec4 cubics[4] = {{-3.76797, -9.76362, 5.47912, -1}, + {-4.19896, -9.45223, 7.534, -1}, + {-4.19896, -7.534, 9.45223, -1}, + {-3.76797, -5.47912, 9.76362, -1}}; + + f32 cx = rx*4*(sqrt(2)-1)/3; + f32 cy = ry*4*(sqrt(2)-1)/3; + + vec2 points[4] = {{x, y + ry}, + {x, y + ry - cy}, + {x + rx - cx, y}, + {x + rx, y}}; + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); + mg_push_vertex(canvas, pos, cubics[i], color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rounded_rectangle_fill_with_z_index(mg_canvas_data* canvas, + mg_rounded_rect rect, + mg_attributes* attributes, + u32 zIndex) +{ + //NOTE(martin): draw a rounded rectangle by drawing a normal rectangle and 4 corners, + // approximating an arc by a precomputed bezier curve + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 18); + + //NOTE(martin): inner cutted corner rectangle + vec2 points[8] = {{rect.x + rect.r, rect.y}, + {rect.x + rect.w - rect.r, rect.y}, + {rect.x + rect.w, rect.y + rect.r}, + {rect.x + rect.w, rect.y + rect.h - rect.r}, + {rect.x + rect.w - rect.r, rect.y + rect.h}, + {rect.x + rect.r, rect.y + rect.h}, + {rect.x, rect.y + rect.h - rect.r}, + {rect.x, rect.y + rect.r}}; + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<8; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); + mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); + } + + static const i32 fanIndices[18] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7 }; // inner fan + for(int i=0; i<18; i++) + { + indices[i] = fanIndices[i] + baseIndex; + } + + mg_render_fill_arc_corner(canvas, rect.x, rect.y, rect.r, rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y, -rect.r, rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y + rect.h, -rect.r, -rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x, rect.y + rect.h, rect.r, -rect.r, zIndex, attributes->color); +} + + +void mg_render_rounded_rectangle_fill(mg_canvas_data* canvas, + mg_rounded_rect rect, + mg_attributes* attributes) +{ + u32 zIndex = mg_get_next_z_index(canvas); + mg_render_rounded_rectangle_fill_with_z_index(canvas, rect, attributes, zIndex); +} + +void mg_render_rounded_rectangle_stroke(mg_canvas_data* canvas, + mg_rounded_rect rect, + mg_attributes* attributes) +{ + //NOTE(martin): stroke rounded rectangle by filling two scaled rounded rectangles with the same zIndex + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + mg_rounded_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width, rect.r - halfW}; + mg_rounded_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width, rect.r + halfW}; + + u32 zIndex = mg_get_next_z_index(canvas); + mg_render_rounded_rectangle_fill_with_z_index(canvas, outer, attributes, zIndex); + mg_render_rounded_rectangle_fill_with_z_index(canvas, inner, attributes, zIndex); +} + +void mg_render_ellipse_fill_with_z_index(mg_canvas_data* canvas, + mp_rect rect, + mg_attributes* attributes, + u32 zIndex) +{ + //NOTE(martin): draw a filled ellipse by drawing a diamond and 4 corners, + // approximating an arc by a precomputed bezier curve + f32 rx = rect.w/2; + f32 ry = rect.h/2; + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + //NOTE(martin): inner diamond + vec2 points[4] = {{rect.x, rect.y + ry}, + {rect.x + rx, rect.y}, + {rect.x + rect.w, rect.y + ry}, + {rect.x + rx, rect.y + rect.h}}; + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); + mg_push_vertex(canvas, pos, cubic, attributes->color, zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + + mg_render_fill_arc_corner(canvas, rect.x, rect.y, rx, ry, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y, -rx, ry, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x + rect.w, rect.y + rect.h, -rx, -ry, zIndex, attributes->color); + mg_render_fill_arc_corner(canvas, rect.x, rect.y + rect.h, rx, -ry, zIndex, attributes->color); +} + +void mg_render_ellipse_fill(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) +{ + u32 zIndex = mg_get_next_z_index(canvas); + mg_render_ellipse_fill_with_z_index(canvas, rect, attributes, zIndex); +} + +void mg_render_ellipse_stroke(mg_canvas_data* canvas, mp_rect rect, mg_attributes* attributes) +{ + //NOTE(martin): stroke by filling two scaled ellipsis with the same zIndex + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + mp_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width}; + mp_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width}; + + u32 zIndex = mg_get_next_z_index(canvas); + mg_render_ellipse_fill_with_z_index(canvas, outer, attributes, zIndex); + mg_render_ellipse_fill_with_z_index(canvas, inner, attributes, zIndex); +} + +void mg_render_image(mg_canvas_data* canvas, mg_image image, mp_rect rect) +{ + mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); + if(!imageData) + { + return; + } + + u32 baseIndex = mg_vertices_base_index(canvas); + i32* indices = mg_reserve_indices(canvas, 6); + + u32 zIndex = mg_get_next_z_index(canvas); + + vec2 points[4] = {{rect.x, rect.y}, + {rect.x + rect.w, rect.y}, + {rect.x + rect.w, rect.y + rect.h}, + {rect.x, rect.y + rect.h}}; + + vec2 uv[4] = {{imageData->rect.x + 0.5, imageData->rect.y + 0.5}, + {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + 0.5}, + {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + imageData->rect.h - 0.5}, + {imageData->rect.x + 0.5, imageData->rect.y + imageData->rect.h - 0.5}}; + + vec4 cubic = {1, 1, 1, 1}; + mg_color color = {1, 1, 1, 1}; + + for(int i=0; i<4; i++) + { + vec2 transformedUV = {uv[i].x / MG_ATLAS_SIZE, uv[i].y / MG_ATLAS_SIZE}; + + vec2 pos = mg_mat2x3_mul(canvas->transform, points[i]); + mg_push_textured_vertex(canvas, pos, cubic, transformedUV, color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rounded_image(mg_canvas_data* canvas, mg_image image, mg_rounded_rect rect, mg_attributes* attributes) +{ + mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); + if(!imageData) + { + return; + } + + //////////////////////////////////////////////////////////////////////////////// + //TODO: this does not work for rotated rectangles + //////////////////////////////////////////////////////////////////////////////// + vec2 uvMin = {(imageData->rect.x + 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + 0.5) / MG_ATLAS_SIZE}; + vec2 uvMax = {(imageData->rect.x + imageData->rect.w - 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + imageData->rect.h - 0.5) / MG_ATLAS_SIZE}; + mp_rect uvRect = {uvMin.x, uvMin.y, uvMax.x - uvMin.x, uvMax.y - uvMin.y}; + + vec2 pMin = mg_mat2x3_mul(canvas->transform, (vec2){rect.x, rect.y}); + vec2 pMax = mg_mat2x3_mul(canvas->transform, (vec2){rect.x + rect.w, rect.y + rect.h}); + mp_rect pRect = {pMin.x, pMin.y, pMax.x - pMin.x, pMax.y - pMin.y}; + + u32 startIndex = mg_vertices_base_index(canvas); + + mg_vertex_layout* layout = &canvas->backend->vertexLayout; + + attributes->color = (mg_color){1, 1, 1, 1}; + mg_render_rounded_rectangle_fill(canvas, rect, attributes); + + u32 indexCount = mg_vertices_base_index(canvas) - startIndex; + + for(int i=0; iposBuffer) + index*layout->posStride); + vec2* uv = (vec2*)(((char*)layout->uvBuffer) + index*layout->uvStride); + + vec2 coordInBoundingSpace = {(pos->x - pRect.x)/pRect.w, + (pos->y - pRect.y)/pRect.h}; + + vec2 mappedUV = {uvRect.x + coordInBoundingSpace.x * uvRect.w, + uvRect.y + coordInBoundingSpace.y * uvRect.h}; + + *uv = mappedUV; + } + +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): fonts +//------------------------------------------------------------------------------------------ + +mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges) +{ + mg_font_data* fontData = malloc_type(mg_font_data); + mg_font font = mg_font_alloc_handle(fontData); + + if(mg_font_is_nil(font)) + { + free(fontData); + return(font); + } + + stbtt_fontinfo stbttFontInfo; + stbtt_InitFont(&stbttFontInfo, buffer, 0); + + //NOTE(martin): load font metrics data + fontData->unitsPerEm = 1./stbtt_ScaleForMappingEmToPixels(&stbttFontInfo, 1); + + int ascent, descent, lineGap, x0, x1, y0, y1; + stbtt_GetFontVMetrics(&stbttFontInfo, &ascent, &descent, &lineGap); + stbtt_GetFontBoundingBox(&stbttFontInfo, &x0, &y0, &x1, &y1); + + fontData->extents.ascent = ascent; + fontData->extents.descent = -descent; + fontData->extents.leading = lineGap; + fontData->extents.width = x1 - x0; + + stbtt_GetCodepointBox(&stbttFontInfo, 'x', &x0, &y0, &x1, &y1); + fontData->extents.xHeight = y1 - y0; + + stbtt_GetCodepointBox(&stbttFontInfo, 'M', &x0, &y0, &x1, &y1); + fontData->extents.capHeight = y1 - y0; + + //NOTE(martin): load codepoint ranges + fontData->rangeCount = rangeCount; + fontData->glyphMap = malloc_array(mg_glyph_map_entry, rangeCount); + fontData->glyphCount = 0; + + for(int i=0; iglyphMap[i].range = ranges[i]; + fontData->glyphMap[i].firstGlyphIndex = fontData->glyphCount + 1; + fontData->glyphCount += ranges[i].count; + } + + fontData->glyphs = malloc_array(mg_glyph_data, fontData->glyphCount); + + //NOTE(martin): first do a count of outlines + int outlineCount = 0; + for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; + u32 firstGlyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex; + u32 endGlyphIndex = firstGlyphIndex + fontData->glyphMap[rangeIndex].range.count; + + for(int glyphIndex = firstGlyphIndex; + glyphIndex < endGlyphIndex; glyphIndex++) + { + int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); + if(stbttGlyphIndex == 0) + { + //NOTE(martin): the codepoint is not found in the font + codePoint++; + continue; + } + //NOTE(martin): load glyph outlines + stbtt_vertex* vertices = 0; + outlineCount += stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); + stbtt_FreeShape(&stbttFontInfo, vertices); + codePoint++; + } + } + //NOTE(martin): allocate outlines + fontData->outlines = malloc_array(mg_path_elt, outlineCount); + fontData->outlineCount = 0; + + //NOTE(martin): load metrics and outlines + for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; + u32 firstGlyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex; + u32 endGlyphIndex = firstGlyphIndex + fontData->glyphMap[rangeIndex].range.count; + + for(int glyphIndex = firstGlyphIndex; + glyphIndex < endGlyphIndex; glyphIndex++) + { + mg_glyph_data* glyph = &(fontData->glyphs[glyphIndex-1]); + + int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); + if(stbttGlyphIndex == 0) + { + //NOTE(martin): the codepoint is not found in the font, we zero the glyph info + memset(glyph, 0, sizeof(*glyph)); + codePoint++; + continue; + } + + glyph->exists = true; + glyph->codePoint = codePoint; + + //NOTE(martin): load glyph metric + int xAdvance, xBearing, x0, y0, x1, y1; + stbtt_GetGlyphHMetrics(&stbttFontInfo, stbttGlyphIndex, &xAdvance, &xBearing); + stbtt_GetGlyphBox(&stbttFontInfo, stbttGlyphIndex, &x0, &y0, &x1, &y1); + + glyph->extents.xAdvance = (f32)xAdvance; + glyph->extents.yAdvance = 0; + glyph->extents.xBearing = (f32)xBearing; + glyph->extents.yBearing = y0; + + glyph->extents.width = x1 - x0; + glyph->extents.height = y1 - y0; + + //NOTE(martin): load glyph outlines + + stbtt_vertex* vertices = 0; + int vertexCount = stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); + + glyph->pathDescriptor = (mg_path_descriptor){.startIndex = fontData->outlineCount, + .count = vertexCount, + .startPoint = {0, 0}}; + + mg_path_elt* elements = fontData->outlines + fontData->outlineCount; + fontData->outlineCount += vertexCount; + vec2 currentPos = {0, 0}; + + for(int vertIndex = 0; vertIndex < vertexCount; vertIndex++) + { + f32 x = vertices[vertIndex].x; + f32 y = vertices[vertIndex].y; + f32 cx = vertices[vertIndex].cx; + f32 cy = vertices[vertIndex].cy; + f32 cx1 = vertices[vertIndex].cx1; + f32 cy1 = vertices[vertIndex].cy1; + + switch(vertices[vertIndex].type) + { + case STBTT_vmove: + elements[vertIndex].type = MG_PATH_MOVE; + elements[vertIndex].p[0] = (vec2){x, y}; + break; + + case STBTT_vline: + elements[vertIndex].type = MG_PATH_LINE; + elements[vertIndex].p[0] = (vec2){x, y}; + break; + + case STBTT_vcurve: + { + elements[vertIndex].type = MG_PATH_QUADRATIC; + elements[vertIndex].p[0] = (vec2){cx, cy}; + elements[vertIndex].p[1] = (vec2){x, y}; + } break; + + case STBTT_vcubic: + elements[vertIndex].type = MG_PATH_CUBIC; + elements[vertIndex].p[0] = (vec2){cx, cy}; + elements[vertIndex].p[1] = (vec2){cx1, cy1}; + elements[vertIndex].p[2] = (vec2){x, y}; + break; + } + currentPos = (vec2){x, y}; + } + stbtt_FreeShape(&stbttFontInfo, vertices); + codePoint++; + } + } + return(font); +} + +void mg_font_destroy(mg_font fontHandle) +{ + mg_font_data* fontData = mg_font_data_from_handle(fontHandle); + if(!fontData) + { + return; + } + #ifdef DEBUG + if(fontData->generation == UINT32_MAX) + { + LOG_ERROR("font info generation wrap around\n"); + } + #endif + fontData->generation++; + + free(fontData->glyphMap); + free(fontData->glyphs); + free(fontData->outlines); + + free(fontData); + mg_resource_handle_recycle(&__mgData.fonts, fontHandle.h); +} + +str32 mg_font_get_glyph_indices_from_font_data(mg_font_data* fontData, str32 codePoints, str32 backing) +{ + u64 count = minimum(codePoints.len, backing.len); + + for(int i = 0; irangeCount; rangeIndex++) + { + if(codePoints.ptr[i] >= fontData->glyphMap[rangeIndex].range.firstCodePoint + && codePoints.ptr[i] < (fontData->glyphMap[rangeIndex].range.firstCodePoint + fontData->glyphMap[rangeIndex].range.count)) + { + u32 rangeOffset = codePoints.ptr[i] - fontData->glyphMap[rangeIndex].range.firstCodePoint; + glyphIndex = fontData->glyphMap[rangeIndex].firstGlyphIndex + rangeOffset; + break; + } + } + if(glyphIndex && !fontData->glyphs[glyphIndex].exists) + { + backing.ptr[i] = 0; + } + backing.ptr[i] = glyphIndex; + } + str32 res = {.len = count, .ptr = backing.ptr}; + return(res); +} + +u32 mg_font_get_glyph_index_from_font_data(mg_font_data* fontData, utf32 codePoint) +{ + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing); + return(glyphIndex); +} + +str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return((str32){0}); + } + return(mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing)); +} + +str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints) +{ + u32* buffer = mem_arena_alloc_array(arena, u32, codePoints.len); + str32 backing = {codePoints.len, buffer}; + return(mg_font_get_glyph_indices(font, codePoints, backing)); +} + +u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint) +{ + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + mg_font_get_glyph_indices(font, codePoints, backing); + return(glyphIndex); +} + +mg_glyph_data* mg_font_get_glyph_data(mg_font_data* fontData, u32 glyphIndex) +{ + DEBUG_ASSERT(glyphIndex); + DEBUG_ASSERT(glyphIndex < fontData->glyphCount); + return(&(fontData->glyphs[glyphIndex-1])); +} + +mg_font_extents mg_font_get_extents(mg_font font) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return((mg_font_extents){0}); + } + return(fontData->extents); +} + +mg_font_extents mg_font_get_scaled_extents(mg_font font, f32 emSize) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return((mg_font_extents){0}); + } + f32 scale = emSize/fontData->unitsPerEm; + mg_font_extents extents = fontData->extents; + + extents.ascent *= scale; + extents.descent *= scale; + extents.leading *= scale; + extents.xHeight *= scale; + extents.capHeight *= scale; + extents.width *= scale; + + return(extents); +} + + +f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return(0); + } + return(emSize/fontData->unitsPerEm); +} + +void mg_font_get_glyph_extents_from_font_data(mg_font_data* fontData, + str32 glyphIndices, + mg_text_extents* outExtents) +{ + for(int i=0; i= fontData->glyphCount) + { + continue; + } + mg_glyph_data* glyph = mg_font_get_glyph_data(fontData, glyphIndices.ptr[i]); + outExtents[i] = glyph->extents; + } +} + +int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return(-1); + } + mg_font_get_glyph_extents_from_font_data(fontData, glyphIndices, outExtents); + return(0); +} + +int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents) +{ + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return(-1); + } + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + str32 glyphs = mg_font_get_glyph_indices_from_font_data(fontData, codePoints, backing); + mg_font_get_glyph_extents_from_font_data(fontData, glyphs, outExtents); + return(0); +} + +mp_rect mg_text_bounding_box_utf32(mg_font font, f32 fontSize, str32 codePoints) +{ + if(!codePoints.len || !codePoints.ptr) + { + return((mp_rect){0}); + } + + mg_font_data* fontData = mg_font_data_from_handle(font); + if(!fontData) + { + return((mp_rect){0}); + } + + mem_arena* scratch = mem_scratch(); + str32 glyphIndices = mg_font_push_glyph_indices(font, scratch, codePoints); + + //NOTE(martin): find width of missing character + //TODO(martin): should cache that at font creation... + mg_text_extents missingGlyphExtents; + u32 missingGlyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 0xfffd); + + if(missingGlyphIndex) + { + mg_font_get_glyph_extents_from_font_data(fontData, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); + } + else + { + //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width + // to render an empty rectangle. Otherwise just render with the max font width + f32 boxWidth = fontData->extents.width * 0.8; + f32 xBearing = fontData->extents.width * 0.1; + f32 xAdvance = fontData->extents.width; + + missingGlyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 'x'); + if(missingGlyphIndex) + { + mg_font_get_glyph_extents_from_font_data(fontData, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); + } + else + { + missingGlyphExtents.xBearing = fontData->extents.width * 0.1; + missingGlyphExtents.yBearing = 0; + missingGlyphExtents.width = fontData->extents.width * 0.8; + missingGlyphExtents.xAdvance = fontData->extents.width; + missingGlyphExtents.yAdvance = 0; + } + } + + //NOTE(martin): accumulate text extents + f32 width = 0; + f32 x = 0; + f32 y = 0; + f32 lineHeight = fontData->extents.descent + fontData->extents.ascent; + + for(int i=0; i= fontData->glyphCount) + { + extents = missingGlyphExtents; + } + else + { + glyph = mg_font_get_glyph_data(fontData, glyphIndices.ptr[i]); + extents = glyph->extents; + } + x += extents.xAdvance; + y += extents.yAdvance; + + if(glyph && glyph->codePoint == '\n') + { + width = maximum(width, x); + x = 0; + y += lineHeight + fontData->extents.leading; + } + } + width = maximum(width, x); + + f32 fontScale = mg_font_get_scale_for_em_pixels(font, fontSize); + mp_rect rect = {0, -fontData->extents.ascent * fontScale, width * fontScale, (y + lineHeight) * fontScale }; + return(rect); +} + +mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text) +{ + if(!text.len || !text.ptr) + { + return((mp_rect){0}); + } + + mem_arena* scratch = mem_scratch(); + str32 codePoints = utf8_push_to_codepoints(scratch, text); + return(mg_text_bounding_box_utf32(font, fontSize, codePoints)); +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics canvas API +//------------------------------------------------------------------------------------------ + +#ifdef MG_IMPLEMENTS_BACKEND_METAL + mg_canvas_backend* mg_metal_canvas_create(mg_surface surface); +#endif + +#ifdef MG_IMPLEMENTS_BACKEND_GLES + mg_canvas_backend* mg_gles_canvas_create(mg_surface surface); +#endif + +mg_canvas mg_canvas_create(mg_surface surface) +{ + mg_canvas canvas = mg_canvas_nil(); + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData) + { + mg_canvas_backend* backend = 0; + switch(surfaceData->backend) + { + #ifdef MG_IMPLEMENTS_BACKEND_METAL + case MG_BACKEND_METAL: + backend = mg_metal_canvas_create(surface); + break; + #endif + + #ifdef MG_IMPLEMENTS_BACKEND_GLES + case MG_BACKEND_GLES: + backend = mg_gles_canvas_create(surface); + break; + #endif + + /* + case MG_BACKEND_OPENGL: + canvasData = mg_opengl_canvas_create(surface); + break; + */ + default: + break; + } + + if(backend) + { + mg_canvas_data* canvasData = malloc_type(mg_canvas_data); + memset(canvasData, 0, sizeof(mg_canvas_data)); + + canvasData->backend = backend; + + canvasData->primitiveCount = 0; + canvasData->path.startIndex = 0; + canvasData->path.count = 0; + canvasData->nextZIndex = 1; + canvasData->attributes.color = (mg_color){0, 0, 0, 1}; + canvasData->attributes.tolerance = 1; + canvasData->attributes.width = 10; + canvasData->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + + canvasData->transform = (mg_mat2x3){{1, 0, 0, + 0, 1, 0}}; + + + //TODO: review this //////////////////////////////////////////////////////////////////////// + canvas = mg_canvas_alloc_handle(canvasData); + mg_canvas_set_current(canvas); + + //NOTE: create a blank image + //WARN: this requires setting the current context before + u8 bytes[4] = {255, 255, 255, 255}; + canvasData->blankImage = mg_image_create_from_rgba8(1, 1, bytes); + //////////////////////////////////////////////////////////////////////////////////////////////////// + } + } + return(canvas); +} + +void mg_canvas_destroy(mg_canvas handle) +{ + mg_canvas_data* canvas = mg_canvas_data_from_handle(handle); + if(canvas) + { + if(__mgCurrentCanvas == canvas) + { + __mgCurrentCanvas = 0; + __mgCurrentCanvasHandle = mg_canvas_nil(); + } + + if(canvas->backend && canvas->backend->destroy) + { + canvas->backend->destroy(canvas->backend); + } + free(canvas); + mg_resource_handle_recycle(&__mgData.canvases, handle.h); + } +} + +mg_canvas mg_canvas_set_current(mg_canvas canvas) +{ + mg_canvas old = __mgCurrentCanvasHandle; + + __mgCurrentCanvasHandle = canvas; + __mgCurrentCanvas = mg_canvas_data_from_handle(canvas); + + return(old); +} +//////////////////////////////////////////////////////////// + +mg_mat2x3 mg_matrix_stack_top(mg_canvas_data* canvas) +{ + if(canvas->matrixStackSize == 0) + { + return((mg_mat2x3){1, 0, 0, + 0, 1, 0}); + } + else + { + return(canvas->matrixStack[canvas->matrixStackSize-1]); + } +} + +void mg_matrix_stack_push(mg_canvas_data* canvas, mg_mat2x3 transform) +{ + if(canvas->matrixStackSize >= MG_MATRIX_STACK_MAX_DEPTH) + { + LOG_ERROR("matrix stack overflow\n"); + } + else + { + canvas->matrixStack[canvas->matrixStackSize] = transform; + canvas->matrixStackSize++; + canvas->transform = transform; + } +} + +void mg_matrix_stack_pop(mg_canvas_data* canvas) +{ + if(canvas->matrixStackSize == 0) + { + LOG_ERROR("matrix stack underflow\n"); + } + else + { + canvas->matrixStackSize--; + canvas->transform = mg_matrix_stack_top(canvas); + } +} + + +mp_rect mg_clip_stack_top(mg_canvas_data* canvas) +{ + if(canvas->clipStackSize == 0) + { + return((mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}); + } + else + { + return(canvas->clipStack[canvas->clipStackSize-1]); + } +} + +void mg_clip_stack_push(mg_canvas_data* canvas, mp_rect clip) +{ + if(canvas->clipStackSize >= MG_CLIP_STACK_MAX_DEPTH) + { + LOG_ERROR("clip stack overflow\n"); + } + else + { + canvas->clipStack[canvas->clipStackSize] = clip; + canvas->clipStackSize++; + canvas->clip = clip; + } +} + +void mg_clip_stack_pop(mg_canvas_data* canvas) +{ + if(canvas->clipStackSize == 0) + { + LOG_ERROR("clip stack underflow\n"); + } + else + { + canvas->clipStackSize--; + canvas->clip = mg_clip_stack_top(canvas); + } +} + +void mg_do_clip_push(mg_canvas_data* canvas, mp_rect clip) +{ + //NOTE(martin): transform clip + vec2 p0 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x, clip.y}); + vec2 p1 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x + clip.w, clip.y}); + vec2 p2 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x + clip.w, clip.y + clip.h}); + vec2 p3 = mg_mat2x3_mul(canvas->transform, (vec2){clip.x, clip.y + clip.h}); + + f32 x0 = minimum(p0.x, minimum(p1.x, minimum(p2.x, p3.x))); + f32 y0 = minimum(p0.y, minimum(p1.y, minimum(p2.y, p3.y))); + f32 x1 = maximum(p0.x, maximum(p1.x, maximum(p2.x, p3.x))); + f32 y1 = maximum(p0.y, maximum(p1.y, maximum(p2.y, p3.y))); + + mp_rect current = mg_clip_stack_top(canvas); + + //NOTE(martin): intersect with current clip + x0 = maximum(current.x, x0); + y0 = maximum(current.y, y0); + x1 = minimum(current.x + current.w, x1); + y1 = minimum(current.y + current.h, y1); + + mp_rect r = {x0, y0, maximum(0, x1-x0), maximum(0, y1-y0)}; + mg_clip_stack_push(canvas, r); +} + +void mg_flush() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + + mg_color clearColor = {0, 0, 0, 1}; + + u32 count = canvas->primitiveCount; + + u32 nextIndex = 0; + + mg_reset_z_index(canvas); + canvas->transform = (mg_mat2x3){1, 0, 0, + 0, 1, 0}; + canvas->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + + for(int i=0; i= count) + { + LOG_ERROR("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex); + break; + } + mg_primitive* primitive = &(canvas->primitives[nextIndex]); + nextIndex++; + + switch(primitive->cmd) + { + case MG_CMD_CLEAR: + { + //NOTE(martin): clear buffers + canvas->vertexCount = 0; + canvas->indexCount = 0; + + clearColor = primitive->attributes.color; + } break; + + case MG_CMD_FILL: + { + u32 zIndex = mg_get_next_z_index(canvas); + mg_render_fill(canvas, + canvas->pathElements + primitive->path.startIndex, + &primitive->path, + zIndex, + primitive->attributes.color); + } break; + + case MG_CMD_STROKE: + { + mg_render_stroke(canvas, + canvas->pathElements + primitive->path.startIndex, + &primitive->path, + &primitive->attributes); + } break; + + + case MG_CMD_RECT_FILL: + mg_render_rectangle_fill(canvas, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_RECT_STROKE: + mg_render_rectangle_stroke(canvas, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_ROUND_RECT_FILL: + mg_render_rounded_rectangle_fill(canvas, primitive->roundedRect, &primitive->attributes); + break; + + case MG_CMD_ROUND_RECT_STROKE: + mg_render_rounded_rectangle_stroke(canvas, primitive->roundedRect, &primitive->attributes); + break; + + case MG_CMD_ELLIPSE_FILL: + mg_render_ellipse_fill(canvas, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_ELLIPSE_STROKE: + mg_render_ellipse_stroke(canvas, primitive->rect, &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 >= count) + { + LOG_ERROR("invalid jump location '%i' in graphics command buffer\n", primitive->jump); + goto exit_command_loop; + } + else + { + nextIndex = primitive->jump; + } + } break; + + case MG_CMD_MATRIX_PUSH: + { + mg_mat2x3 transform = mg_matrix_stack_top(canvas); + mg_matrix_stack_push(canvas, mg_mat2x3_mul_m(transform, primitive->matrix)); + } break; + + case MG_CMD_MATRIX_POP: + { + mg_matrix_stack_pop(canvas); + } break; + + case MG_CMD_CLIP_PUSH: + { + //TODO(martin): use only aligned rect and avoid this + mp_rect r = {primitive->rect.x, primitive->rect.y, primitive->rect.w, primitive->rect.h}; + mg_do_clip_push(canvas, r); + } break; + + case MG_CMD_CLIP_POP: + { + mg_clip_stack_pop(canvas); + } break; + + case MG_CMD_IMAGE_DRAW: + { + mg_render_image(canvas, primitive->attributes.image, primitive->rect); + } break; + + case MG_CMD_ROUNDED_IMAGE_DRAW: + { + mg_render_rounded_image(canvas, primitive->attributes.image, primitive->roundedRect, &primitive->attributes); + } break; + + } + } + exit_command_loop: ; + + if(canvas->backend && canvas->backend->drawBuffers) + { + canvas->backend->drawBuffers(canvas->backend, canvas->vertexCount, canvas->indexCount, clearColor); + } + + //NOTE(martin): clear buffers + canvas->primitiveCount = 0; + canvas->vertexCount = 0; + canvas->indexCount = 0; + + canvas->path.startIndex = 0; + canvas->path.count = 0; + + canvas->primitiveCount = 0; + canvas->frameCounter++; +} +//////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------ +//NOTE(martin): transform, viewport and clipping +//------------------------------------------------------------------------------------------ + +void mg_matrix_push(mg_mat2x3 matrix) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_MATRIX_PUSH, .matrix = matrix}); +} + +void mg_matrix_pop() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_MATRIX_POP}); +} + +void mg_clip_push(f32 x, f32 y, f32 w, f32 h) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLIP_PUSH, + .rect = (mp_rect){x, y, w, h}}); +} + +void mg_clip_pop() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLIP_POP}); +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics attributes setting/getting +//------------------------------------------------------------------------------------------ +void mg_set_color(mg_color color) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.color = color; +} + +void mg_set_color_rgba(f32 r, f32 g, f32 b, f32 a) +{ + mg_set_color((mg_color){r, g, b, a}); +} + +void mg_set_width(f32 width) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.width = width; +} + +void mg_set_tolerance(f32 tolerance) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.tolerance = tolerance; +} + +void mg_set_joint(mg_joint_type joint) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.joint = joint; +} + +void mg_set_max_joint_excursion(f32 maxJointExcursion) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.maxJointExcursion = maxJointExcursion; +} + +void mg_set_cap(mg_cap_type cap) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.cap = cap; +} + +void mg_set_font(mg_font font) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.font = font; +} + +void mg_set_font_size(f32 fontSize) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->attributes.fontSize = fontSize; +} + +void mg_set_text_flip(bool flip) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + canvas->textFlip = flip; +} + +mg_color mg_get_color() +{ + mg_color color = {0}; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + color = canvas->attributes.color; + } + return(color); +} + +f32 mg_get_width() +{ + f32 width = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + width = canvas->attributes.width; + } + return(width); +} + +f32 mg_get_tolerance() +{ + f32 tolerance = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + tolerance = canvas->attributes.tolerance; + } + return(tolerance); +} + +mg_joint_type mg_get_joint() +{ + mg_joint_type joint = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + joint = canvas->attributes.joint; + } + return(joint); +} + +f32 mg_get_max_joint_excursion() +{ + f32 maxJointExcursion = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + maxJointExcursion = canvas->attributes.maxJointExcursion; + } + return(maxJointExcursion); +} + +mg_cap_type mg_get_cap() +{ + mg_cap_type cap = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + cap = canvas->attributes.cap; + } + return(cap); +} + +mg_font mg_get_font() +{ + mg_font font = mg_font_nil(); + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + font = canvas->attributes.font; + } + return(font); +} + +f32 mg_get_font_size() +{ + f32 fontSize = 0; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + fontSize = canvas->attributes.fontSize; + } + return(fontSize); +} + +bool mg_get_text_flip() +{ + bool flip = false; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + flip = canvas->textFlip; + } + return(flip); +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): path construction +//------------------------------------------------------------------------------------------ +vec2 mg_get_position() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return((vec2){0, 0}); + } + return(canvas->subPathLastPoint); +} + +void mg_move_to(f32 x, f32 y) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_MOVE, .p[0] = {x, y}})); + canvas->subPathStartPoint = (vec2){x, y}; + canvas->subPathLastPoint = (vec2){x, y}; +} + +void mg_line_to(f32 x, f32 y) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_LINE, .p[0] = {x, y}})); + canvas->subPathLastPoint = (vec2){x, y}; +} + +void mg_quadratic_to(f32 x1, f32 y1, f32 x2, f32 y2) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_QUADRATIC, .p = {{x1, y1}, {x2, y2}}})); + canvas->subPathLastPoint = (vec2){x2, y2}; +} + +void mg_cubic_to(f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_path_push_element(canvas, ((mg_path_elt){.type = MG_PATH_CUBIC, .p = {{x1, y1}, {x2, y2}, {x3, y3}}})); + canvas->subPathLastPoint = (vec2){x3, y3}; +} + +void mg_close_path() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + if( canvas->subPathStartPoint.x != canvas->subPathLastPoint.x + || canvas->subPathStartPoint.y != canvas->subPathLastPoint.y) + { + mg_line_to(canvas->subPathStartPoint.x, canvas->subPathStartPoint.y); + } + canvas->subPathStartPoint = canvas->subPathLastPoint; +} + +mp_rect mg_glyph_outlines_from_font_data(mg_font_data* fontData, str32 glyphIndices) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + + f32 startX = canvas->subPathLastPoint.x; + f32 startY = canvas->subPathLastPoint.y; + f32 maxWidth = 0; + + f32 scale = canvas->attributes.fontSize/fontData->unitsPerEm; + + for(int i=0; isubPathLastPoint.x; + f32 yOffset = canvas->subPathLastPoint.y; + f32 flip = canvas->textFlip ? -1 : 1; + + if(!glyphIndex || glyphIndex >= fontData->glyphCount) + { + LOG_WARNING("code point is not present in font ranges\n"); + //NOTE(martin): try to find the replacement character + glyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 0xfffd); + if(!glyphIndex) + { + //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width + // to render an empty rectangle. Otherwise just render with the max font width + f32 boxWidth = fontData->extents.width * 0.8; + f32 xBearing = fontData->extents.width * 0.1; + f32 xAdvance = fontData->extents.width; + + glyphIndex = mg_font_get_glyph_index_from_font_data(fontData, 'x'); + if(glyphIndex) + { + mg_glyph_data* glyph = &(fontData->glyphs[glyphIndex]); + boxWidth = glyph->extents.width; + xBearing = glyph->extents.xBearing; + xAdvance = glyph->extents.xAdvance; + } + f32 oldStrokeWidth = canvas->attributes.width; + + mg_set_width(boxWidth*0.005); + mg_rectangle_stroke(xOffset + xBearing * scale, + yOffset, + boxWidth * scale * flip, + fontData->extents.capHeight*scale); + + mg_set_width(oldStrokeWidth); + mg_move_to(xOffset + xAdvance * scale, yOffset); + maxWidth = maximum(maxWidth, xOffset + xAdvance*scale - startX); + continue; + } + } + + mg_glyph_data* glyph = mg_font_get_glyph_data(fontData, glyphIndex); + + mg_path_push_elements(canvas, glyph->pathDescriptor.count, fontData->outlines + glyph->pathDescriptor.startIndex); + + mg_path_elt* elements = canvas->pathElements + canvas->path.count + canvas->path.startIndex - glyph->pathDescriptor.count; + for(int eltIndex=0; eltIndexpathDescriptor.count; eltIndex++) + { + for(int pIndex = 0; pIndex < 3; pIndex++) + { + elements[eltIndex].p[pIndex].x = elements[eltIndex].p[pIndex].x * scale + xOffset; + elements[eltIndex].p[pIndex].y = elements[eltIndex].p[pIndex].y * scale * flip + yOffset; + } + } + mg_move_to(xOffset + scale*glyph->extents.xAdvance, yOffset); + + maxWidth = maximum(maxWidth, xOffset + scale*glyph->extents.xAdvance - startX); + } + f32 lineHeight = (fontData->extents.ascent + fontData->extents.descent)*scale; + mp_rect box = {startX, startY, maxWidth, canvas->subPathLastPoint.y - startY + lineHeight }; + return(box); +} + +mp_rect mg_glyph_outlines(str32 glyphIndices) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return((mp_rect){0}); + } + mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); + if(!fontData) + { + return((mp_rect){0}); + } + return(mg_glyph_outlines_from_font_data(fontData, glyphIndices)); +} + +void mg_codepoints_outlines(str32 codePoints) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); + if(!fontData) + { + return; + } + + str32 glyphIndices = mg_font_push_glyph_indices(canvas->attributes.font, mem_scratch(), codePoints); + mg_glyph_outlines_from_font_data(fontData, glyphIndices); +} + +void mg_text_outlines(str8 text) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_font_data* fontData = mg_font_data_from_handle(canvas->attributes.font); + if(!fontData) + { + return; + } + + mem_arena* scratch = mem_scratch(); + str32 codePoints = utf8_push_to_codepoints(scratch, text); + str32 glyphIndices = mg_font_push_glyph_indices(canvas->attributes.font, scratch, codePoints); + + mg_glyph_outlines_from_font_data(fontData, glyphIndices); +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): clear/fill/stroke +//------------------------------------------------------------------------------------------ + +void mg_clear() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, (mg_primitive){.cmd = MG_CMD_CLEAR, .attributes = canvas->attributes}); +} + +void mg_fill() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + if(canvas->path.count) + { + mg_push_command(canvas, ((mg_primitive){.cmd = MG_CMD_FILL, .path = canvas->path, .attributes = canvas->attributes})); + mg_new_path(canvas); + } +} + +void mg_stroke() +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + if(canvas->path.count) + { + mg_push_command(canvas, ((mg_primitive){.cmd = MG_CMD_STROKE, .path = canvas->path, .attributes = canvas->attributes})); + mg_new_path(canvas); + } +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): 'fast' shapes primitives +//------------------------------------------------------------------------------------------ +void mg_rectangle_fill(f32 x, f32 y, f32 w, f32 h) +{ +// DEBUG_ASSERT(w>=0 && h>=0); + + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_RECT_FILL, .rect = (mp_rect){x, y, w, h}, .attributes = canvas->attributes})); +} +void mg_rectangle_stroke(f32 x, f32 y, f32 w, f32 h) +{ +// DEBUG_ASSERT(w>=0 && h>=0); + + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_RECT_STROKE, .rect = (mp_rect){x, y, w, h}, .attributes = canvas->attributes})); +} + +void mg_rounded_rectangle_fill(f32 x, f32 y, f32 w, f32 h, f32 r) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_FILL, + .roundedRect = (mg_rounded_rect){x, y, w, h, r}, + .attributes = canvas->attributes})); +} + +void mg_rounded_rectangle_stroke(f32 x, f32 y, f32 w, f32 h, f32 r) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_STROKE, + .roundedRect = (mg_rounded_rect){x, y, w, h, r}, + .attributes = canvas->attributes})); +} + +void mg_circle_fill(f32 x, f32 y, f32 r) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, + .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, + .attributes = canvas->attributes})); +} + +void mg_circle_stroke(f32 x, f32 y, f32 r) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, + .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, + .attributes = canvas->attributes})); +} + +void mg_ellipse_fill(f32 x, f32 y, f32 rx, f32 ry) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, + .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, + .attributes = canvas->attributes})); +} + +void mg_ellipse_stroke(f32 x, f32 y, f32 rx, f32 ry) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_push_command(canvas, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, + .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, + .attributes = canvas->attributes})); +} + +void mg_arc(f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle) +{ + f32 endAngle = startAngle + arcAngle; + + while(startAngle < endAngle) + { + f32 smallAngle = minimum(endAngle - startAngle, M_PI/4.); + if(smallAngle < 0.001) + { + break; + } + + vec2 v0 = {cos(smallAngle/2), sin(smallAngle/2)}; + vec2 v1 = {(4-v0.x)/3, (1-v0.x)*(3-v0.x)/(3*v0.y)}; + vec2 v2 = {v1.x, -v1.y}; + vec2 v3 = {v0.x, -v0.y}; + + f32 rotAngle = smallAngle/2 + startAngle; + f32 rotCos = cos(rotAngle); + f32 rotSin = sin(rotAngle); + + mg_mat2x3 t = {r*rotCos, -r*rotSin, x, + r*rotSin, r*rotCos, y}; + + v0 = mg_mat2x3_mul(t, v0); + v1 = mg_mat2x3_mul(t, v1); + v2 = mg_mat2x3_mul(t, v2); + v3 = mg_mat2x3_mul(t, v3); + + mg_move_to(v0.x, v0.y); + mg_cubic_to(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y); + + startAngle += smallAngle; + } +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): images +//------------------------------------------------------------------------------------------ + +mg_image mg_image_create_from_rgba8(u32 width, u32 height, u8* bytes) +{ + mg_image image = mg_image_nil(); + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + mg_image_data* imageData = ListPopEntry(&canvas->imageFreeList, mg_image_data, listElt); + if(!imageData) + { + if(canvas->imageNextIndex < MG_IMAGE_MAX_COUNT) + { + imageData = &canvas->images[canvas->imageNextIndex]; + imageData->generation = 1; + canvas->imageNextIndex++; + } + else + { + LOG_ERROR("image pool full\n"); + } + } + + if(imageData) + { + if(canvas->atlasPos.x + width >= MG_ATLAS_SIZE) + { + canvas->atlasPos.x = 0; + canvas->atlasPos.y += canvas->atlasLineHeight; + } + if(canvas->atlasPos.x + width < MG_ATLAS_SIZE + && canvas->atlasPos.y + height < MG_ATLAS_SIZE) + { + imageData->rect = (mp_rect){canvas->atlasPos.x, + canvas->atlasPos.y, + width, + height}; + + canvas->atlasPos.x += width; + canvas->atlasLineHeight = maximum(canvas->atlasLineHeight, height); + + canvas->backend->atlasUpload(canvas->backend, imageData->rect, bytes); + image = mg_image_handle_from_ptr(canvas, imageData); + } + else + { + mg_image_data_recycle(canvas, imageData); + } + } + } + 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_ptr_from_handle(canvas, image); + if(imageData) + { + //TODO free atlas area + mg_image_data_recycle(canvas, imageData); + } + } +} + +vec2 mg_image_size(mg_image image) +{ + /////////////////////////////////////////////////////////////////////////// + //WARN: this supposes the current canvas is that for which the image was created + /////////////////////////////////////////////////////////////////////////// + + vec2 size = {0}; + mg_canvas_data* canvas = __mgCurrentCanvas; + if(canvas) + { + mg_image_data* imageData = mg_image_ptr_from_handle(canvas, image); + if(imageData) + { + size = (vec2){imageData->rect.w, imageData->rect.h}; + } + } + return(size); +} + +void mg_image_draw(mg_image image, mp_rect rect) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_primitive primitive = {.cmd = MG_CMD_IMAGE_DRAW, + .rect = (mp_rect){rect.x, rect.y, rect.w, rect.h}, + .attributes = canvas->attributes}; + primitive.attributes.image = image; + mg_push_command(canvas, primitive); +} + +void mg_rounded_image_draw(mg_image image, mp_rect rect, f32 roundness) +{ + mg_canvas_data* canvas = __mgCurrentCanvas; + if(!canvas) + { + return; + } + mg_primitive primitive = {.cmd = MG_CMD_ROUNDED_IMAGE_DRAW, + .roundedRect = {rect.x, rect.y, rect.w, rect.h, roundness}, + .attributes = canvas->attributes}; + primitive.attributes.image = image; + + mg_push_command(canvas, primitive); +} + + + +#undef LOG_SUBSYSTEM diff --git a/src/graphics_internal.h b/src/graphics_internal.h index 471fcef..007e46f 100644 --- a/src/graphics_internal.h +++ b/src/graphics_internal.h @@ -1,290 +1,290 @@ -/************************************************************//** -* -* @file: graphics_internal.h -* @author: Martin Fouilleul -* @date: 23/01/2023 -* @revision: -* -*****************************************************************/ -#ifndef __GRAPHICS_INTERNAL_H_ -#define __GRAPHICS_INTERNAL_H_ - -#include"graphics.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { MG_BACKEND_DUMMY, - MG_BACKEND_METAL, - MG_BACKEND_GL, - MG_BACKEND_GLES, - //... - } mg_backend_id; - -typedef struct mg_surface_data mg_surface_data; - -typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface); -typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface); -typedef void (*mg_surface_present_proc)(mg_surface_data* surface); -typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface); -typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame); -typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface); -typedef void (*mg_surface_set_hidden_proc)(mg_surface_data* surface, bool hidden); - -typedef struct mg_surface_data -{ - mg_backend_id backend; - - mg_surface_destroy_proc destroy; - mg_surface_prepare_proc prepare; - mg_surface_present_proc present; - mg_surface_get_frame_proc getFrame; - mg_surface_set_frame_proc setFrame; - mg_surface_get_hidden_proc getHidden; - mg_surface_set_hidden_proc setHidden; - -} mg_surface_data; - -mg_surface mg_surface_alloc_handle(mg_surface_data* surface); -mg_surface_data* mg_surface_data_from_handle(mg_surface handle); - -//--------------------------------------------------------------- -// graphics structs -//--------------------------------------------------------------- -typedef enum { MG_PATH_MOVE, - MG_PATH_LINE, - MG_PATH_QUADRATIC, - MG_PATH_CUBIC } mg_path_elt_type; - -typedef struct mg_path_elt -{ - mg_path_elt_type type; - vec2 p[3]; - -} mg_path_elt; - -typedef struct mg_path_descriptor -{ - u32 startIndex; - u32 count; - vec2 startPoint; - -} mg_path_descriptor; - -typedef struct mg_attributes -{ - f32 width; - f32 tolerance; - mg_color color; - mg_joint_type joint; - f32 maxJointExcursion; - mg_cap_type cap; - - mg_font font; - f32 fontSize; - - mg_image image; - - mp_rect clip; - -} mg_attributes; - -typedef struct mg_rounded_rect -{ - f32 x; - f32 y; - f32 w; - f32 h; - f32 r; -} mg_rounded_rect; - -typedef enum { MG_CMD_CLEAR = 0, - MG_CMD_FILL, - MG_CMD_STROKE, - MG_CMD_RECT_FILL, - MG_CMD_RECT_STROKE, - MG_CMD_ROUND_RECT_FILL, - MG_CMD_ROUND_RECT_STROKE, - MG_CMD_ELLIPSE_FILL, - MG_CMD_ELLIPSE_STROKE, - MG_CMD_JUMP, - MG_CMD_MATRIX_PUSH, - MG_CMD_MATRIX_POP, - MG_CMD_CLIP_PUSH, - MG_CMD_CLIP_POP, - MG_CMD_IMAGE_DRAW, - MG_CMD_ROUNDED_IMAGE_DRAW, - } mg_primitive_cmd; - -typedef struct mg_primitive -{ - mg_primitive_cmd cmd; - mg_attributes attributes; - - union - { - mg_path_descriptor path; - mp_rect rect; - mg_rounded_rect roundedRect; - utf32 codePoint; - u32 jump; - mg_mat2x3 matrix; - }; - -} mg_primitive; - -typedef struct mg_glyph_map_entry -{ - unicode_range range; - u32 firstGlyphIndex; - -} mg_glyph_map_entry; - -typedef struct mg_glyph_data -{ - bool exists; - utf32 codePoint; - mg_path_descriptor pathDescriptor; - mg_text_extents extents; - //... - -} mg_glyph_data; - -enum -{ - MG_STREAM_MAX_COUNT = 128, - MG_IMAGE_MAX_COUNT = 128 -}; - -typedef struct mg_image_data -{ - list_elt listElt; - u32 generation; - - mp_rect rect; - -} mg_image_data; - -enum -{ - MG_MATRIX_STACK_MAX_DEPTH = 64, - MG_CLIP_STACK_MAX_DEPTH = 64, - MG_MAX_PATH_ELEMENT_COUNT = 2<<20, - MG_MAX_PRIMITIVE_COUNT = 8<<10 -}; - -typedef struct mg_font_data -{ - list_elt freeListElt; - u32 generation; - - u32 rangeCount; - u32 glyphCount; - u32 outlineCount; - mg_glyph_map_entry* glyphMap; - mg_glyph_data* glyphs; - mg_path_elt* outlines; - - f32 unitsPerEm; - mg_font_extents extents; - -} mg_font_data; - -typedef struct mg_vertex_layout -{ - u32 maxVertexCount; - u32 maxIndexCount; - - void* posBuffer; - u32 posStride; - - void* cubicBuffer; - u32 cubicStride; - - void* uvBuffer; - u32 uvStride; - - void* colorBuffer; - u32 colorStride; - - void* zIndexBuffer; - u32 zIndexStride; - - void* clipsBuffer; - u32 clipsStride; - - void* indexBuffer; - u32 indexStride; - -} 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_draw_buffers_proc)(mg_canvas_backend* backend, u32 vertexCount, u32 indexCount, mg_color clearColor); -typedef void (*mg_canvas_backend_atlas_upload_proc)(mg_canvas_backend* backend, mp_rect rect, u8* bytes); - -typedef struct mg_canvas_backend -{ - mg_vertex_layout vertexLayout; - - mg_canvas_backend_destroy_proc destroy; - mg_canvas_backend_draw_buffers_proc drawBuffers; - mg_canvas_backend_atlas_upload_proc atlasUpload; - -} mg_canvas_backend; - -typedef struct mg_canvas_data -{ - list_elt freeListElt; - u32 generation; - - u64 frameCounter; - - mg_mat2x3 transform; - mp_rect clip; - mg_attributes attributes; - bool textFlip; - - mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT]; - mg_path_descriptor path; - vec2 subPathStartPoint; - vec2 subPathLastPoint; - - mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH]; - u32 matrixStackSize; - - mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH]; - u32 clipStackSize; - - u32 nextZIndex; - u32 primitiveCount; - mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT]; - - u32 vertexCount; - u32 indexCount; - - mg_image_data images[MG_IMAGE_MAX_COUNT]; - u32 imageNextIndex; - list_info imageFreeList; - - vec2 atlasPos; - u32 atlasLineHeight; - mg_image blankImage; - - mg_canvas_backend* backend; - -} mg_canvas_data; - -enum -{ - MG_ATLAS_SIZE = 8192, -}; - - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif //__GRAPHICS_INTERNAL_H_ +/************************************************************//** +* +* @file: graphics_internal.h +* @author: Martin Fouilleul +* @date: 23/01/2023 +* @revision: +* +*****************************************************************/ +#ifndef __GRAPHICS_INTERNAL_H_ +#define __GRAPHICS_INTERNAL_H_ + +#include"graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { MG_BACKEND_DUMMY, + MG_BACKEND_METAL, + MG_BACKEND_GL, + MG_BACKEND_GLES, + //... + } mg_backend_id; + +typedef struct mg_surface_data mg_surface_data; + +typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface); +typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface); +typedef void (*mg_surface_present_proc)(mg_surface_data* surface); +typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface); +typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame); +typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface); +typedef void (*mg_surface_set_hidden_proc)(mg_surface_data* surface, bool hidden); + +typedef struct mg_surface_data +{ + mg_backend_id backend; + + mg_surface_destroy_proc destroy; + mg_surface_prepare_proc prepare; + mg_surface_present_proc present; + mg_surface_get_frame_proc getFrame; + mg_surface_set_frame_proc setFrame; + mg_surface_get_hidden_proc getHidden; + mg_surface_set_hidden_proc setHidden; + +} mg_surface_data; + +mg_surface mg_surface_alloc_handle(mg_surface_data* surface); +mg_surface_data* mg_surface_data_from_handle(mg_surface handle); + +//--------------------------------------------------------------- +// graphics structs +//--------------------------------------------------------------- +typedef enum { MG_PATH_MOVE, + MG_PATH_LINE, + MG_PATH_QUADRATIC, + MG_PATH_CUBIC } mg_path_elt_type; + +typedef struct mg_path_elt +{ + mg_path_elt_type type; + vec2 p[3]; + +} mg_path_elt; + +typedef struct mg_path_descriptor +{ + u32 startIndex; + u32 count; + vec2 startPoint; + +} mg_path_descriptor; + +typedef struct mg_attributes +{ + f32 width; + f32 tolerance; + mg_color color; + mg_joint_type joint; + f32 maxJointExcursion; + mg_cap_type cap; + + mg_font font; + f32 fontSize; + + mg_image image; + + mp_rect clip; + +} mg_attributes; + +typedef struct mg_rounded_rect +{ + f32 x; + f32 y; + f32 w; + f32 h; + f32 r; +} mg_rounded_rect; + +typedef enum { MG_CMD_CLEAR = 0, + MG_CMD_FILL, + MG_CMD_STROKE, + MG_CMD_RECT_FILL, + MG_CMD_RECT_STROKE, + MG_CMD_ROUND_RECT_FILL, + MG_CMD_ROUND_RECT_STROKE, + MG_CMD_ELLIPSE_FILL, + MG_CMD_ELLIPSE_STROKE, + MG_CMD_JUMP, + MG_CMD_MATRIX_PUSH, + MG_CMD_MATRIX_POP, + MG_CMD_CLIP_PUSH, + MG_CMD_CLIP_POP, + MG_CMD_IMAGE_DRAW, + MG_CMD_ROUNDED_IMAGE_DRAW, + } mg_primitive_cmd; + +typedef struct mg_primitive +{ + mg_primitive_cmd cmd; + mg_attributes attributes; + + union + { + mg_path_descriptor path; + mp_rect rect; + mg_rounded_rect roundedRect; + utf32 codePoint; + u32 jump; + mg_mat2x3 matrix; + }; + +} mg_primitive; + +typedef struct mg_glyph_map_entry +{ + unicode_range range; + u32 firstGlyphIndex; + +} mg_glyph_map_entry; + +typedef struct mg_glyph_data +{ + bool exists; + utf32 codePoint; + mg_path_descriptor pathDescriptor; + mg_text_extents extents; + //... + +} mg_glyph_data; + +enum +{ + MG_STREAM_MAX_COUNT = 128, + MG_IMAGE_MAX_COUNT = 128 +}; + +typedef struct mg_image_data +{ + list_elt listElt; + u32 generation; + + mp_rect rect; + +} mg_image_data; + +enum +{ + MG_MATRIX_STACK_MAX_DEPTH = 64, + MG_CLIP_STACK_MAX_DEPTH = 64, + MG_MAX_PATH_ELEMENT_COUNT = 2<<20, + MG_MAX_PRIMITIVE_COUNT = 8<<10 +}; + +typedef struct mg_font_data +{ + list_elt freeListElt; + u32 generation; + + u32 rangeCount; + u32 glyphCount; + u32 outlineCount; + mg_glyph_map_entry* glyphMap; + mg_glyph_data* glyphs; + mg_path_elt* outlines; + + f32 unitsPerEm; + mg_font_extents extents; + +} mg_font_data; + +typedef struct mg_vertex_layout +{ + u32 maxVertexCount; + u32 maxIndexCount; + + char* posBuffer; + u32 posStride; + + char* cubicBuffer; + u32 cubicStride; + + char* uvBuffer; + u32 uvStride; + + char* colorBuffer; + u32 colorStride; + + char* zIndexBuffer; + u32 zIndexStride; + + char* clipBuffer; + u32 clipStride; + + char* indexBuffer; + u32 indexStride; + +} 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_draw_buffers_proc)(mg_canvas_backend* backend, u32 vertexCount, u32 indexCount, mg_color clearColor); +typedef void (*mg_canvas_backend_atlas_upload_proc)(mg_canvas_backend* backend, mp_rect rect, u8* bytes); + +typedef struct mg_canvas_backend +{ + mg_vertex_layout vertexLayout; + + mg_canvas_backend_destroy_proc destroy; + mg_canvas_backend_draw_buffers_proc drawBuffers; + mg_canvas_backend_atlas_upload_proc atlasUpload; + +} mg_canvas_backend; + +typedef struct mg_canvas_data +{ + list_elt freeListElt; + u32 generation; + + u64 frameCounter; + + mg_mat2x3 transform; + mp_rect clip; + mg_attributes attributes; + bool textFlip; + + mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT]; + mg_path_descriptor path; + vec2 subPathStartPoint; + vec2 subPathLastPoint; + + mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH]; + u32 matrixStackSize; + + mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH]; + u32 clipStackSize; + + u32 nextZIndex; + u32 primitiveCount; + mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT]; + + u32 vertexCount; + u32 indexCount; + + mg_image_data images[MG_IMAGE_MAX_COUNT]; + u32 imageNextIndex; + list_info imageFreeList; + + vec2 atlasPos; + u32 atlasLineHeight; + mg_image blankImage; + + mg_canvas_backend* backend; + +} mg_canvas_data; + +enum +{ + MG_ATLAS_SIZE = 8192, +}; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__GRAPHICS_INTERNAL_H_ diff --git a/src/metal_canvas.m b/src/metal_canvas.m index 4a163a4..faee470 100644 --- a/src/metal_canvas.m +++ b/src/metal_canvas.m @@ -1,442 +1,442 @@ -/************************************************************//** -* -* @file: metal_canvas.m -* @author: Martin Fouilleul -* @date: 12/07/2020 -* @revision: 24/01/2023 -* -*****************************************************************/ -#import -#import -#include - -#include"graphics_internal.h" -#include"macro_helpers.h" -#include"osx_app.h" - -#include"metal_shader.h" - -#define LOG_SUBSYSTEM "Graphics" - -static const int MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20; - -typedef struct mg_metal_canvas_backend -{ - mg_canvas_backend interface; - mg_surface surface; - - // permanent metal resources - id tilingPipeline; - id sortingPipeline; - id boxingPipeline; - id computePipeline; - id renderPipeline; - - mp_rect viewPort; - - // textures and buffers - id outTexture; - id atlasTexture; - id vertexBuffer; - id indexBuffer; - id tileCounters; - id tilesArray; - id triangleArray; - id boxArray; - -} mg_metal_canvas_backend; - -mg_metal_surface* mg_metal_canvas_get_surface(mg_metal_canvas_backend* canvas) -{ - mg_metal_surface* res = 0; - mg_surface_data* data = mg_surface_data_from_handle(canvas->surface); - if(data && data->backend == MG_BACKEND_METAL) - { - res = (mg_metal_surface*)data; - } - return(res); -} - -void mg_metal_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor) -{ - mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; - mg_metal_surface* surface = mg_metal_canvas_get_surface(backend); - if(!surface) - { - return; - } - - @autoreleasepool - { - if(surface->commandBuffer == nil || surface->commandBuffer == nil) - { - mg_metal_surface_acquire_drawable_and_command_buffer(surface); - } - - ASSERT(indexCount * sizeof(i32) < [backend->indexBuffer length]); - - f32 scale = surface->metalLayer.contentsScale; - vector_uint2 viewportSize = {backend->viewPort.w * scale, backend->viewPort.h * scale}; - - //----------------------------------------------------------- - //NOTE(martin): encode the clear counter - //----------------------------------------------------------- - id blitEncoder = [surface->commandBuffer blitCommandEncoder]; - [blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; - [blitEncoder endEncoding]; - - //----------------------------------------------------------- - //NOTE(martin): encode the boxing pass - //----------------------------------------------------------- - id boxEncoder = [surface->commandBuffer computeCommandEncoder]; - [boxEncoder setComputePipelineState: backend->boxingPipeline]; - [boxEncoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0]; - [boxEncoder setBuffer: backend->indexBuffer offset:0 atIndex: 1]; - [boxEncoder setBuffer: backend->triangleArray offset:0 atIndex: 2]; - [boxEncoder setBuffer: backend->boxArray offset:0 atIndex: 3]; - [boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4]; - - MTLSize boxGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); - MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1); - - [boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; - [boxEncoder endEncoding]; - - //----------------------------------------------------------- - //NOTE(martin): encode the tiling pass - //----------------------------------------------------------- - - id tileEncoder = [surface->commandBuffer computeCommandEncoder]; - [tileEncoder setComputePipelineState: backend->tilingPipeline]; - [tileEncoder setBuffer: backend->boxArray offset:0 atIndex: 0]; - [tileEncoder setBuffer: backend->tileCounters offset:0 atIndex: 1]; - [tileEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; - [tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; - - [tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; - [tileEncoder endEncoding]; - - //----------------------------------------------------------- - //NOTE(martin): encode the sorting pass - //----------------------------------------------------------- - - id sortEncoder = [surface->commandBuffer computeCommandEncoder]; - [sortEncoder setComputePipelineState: backend->sortingPipeline]; - [sortEncoder setBuffer: backend->tileCounters offset:0 atIndex: 0]; - [sortEncoder setBuffer: backend->triangleArray offset:0 atIndex: 1]; - [sortEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; - [sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; - - u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; - u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; - - MTLSize sortGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); - MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1); - - [sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize]; - [sortEncoder endEncoding]; - - //----------------------------------------------------------- - //NOTE(martin): create compute encoder and encode commands - //----------------------------------------------------------- - vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a}; - - id encoder = [surface->commandBuffer computeCommandEncoder]; - [encoder setComputePipelineState:backend->computePipeline]; - [encoder setTexture: backend->outTexture atIndex: 0]; - [encoder setTexture: backend->atlasTexture atIndex: 1]; - [encoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0]; - [encoder setBuffer: backend->tileCounters offset:0 atIndex: 1]; - [encoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; - [encoder setBuffer: backend->triangleArray offset:0 atIndex: 3]; - [encoder setBuffer: backend->boxArray offset:0 atIndex: 4]; - [encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5]; - - //TODO: check that we don't exceed maxTotalThreadsPerThreadgroup - DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= backend->computePipeline.maxTotalThreadsPerThreadgroup); - MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1); - MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1); - - [encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize]; - [encoder endEncoding]; - - //----------------------------------------------------------- - //NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer - //----------------------------------------------------------- - - MTLViewport viewport = {backend->viewPort.x * scale, - backend->viewPort.y * scale, - backend->viewPort.w * scale, - backend->viewPort.h * scale, - 0, - 1}; - - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture; - renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; - - id renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - [renderEncoder setViewport: viewport]; - [renderEncoder setRenderPipelineState: backend->renderPipeline]; - [renderEncoder setFragmentTexture: backend->outTexture atIndex: 0]; - [renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle - vertexStart: 0 - vertexCount: 3 ]; - [renderEncoder endEncoding]; - } -} - -/* -void mg_metal_canvas_viewport(mg_canvas_backend* interface, mp_rect viewPort) -{ - mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; - mg_metal_surface* surface = mg_metal_canvas_get_surface(backend); - if(!surface) - { - return; - } - - backend->viewPort = viewPort; - - @autoreleasepool - { - f32 scale = surface->metalLayer.contentsScale; - CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale}; - - [backend->outTexture release]; - - MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModePrivate; - texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; - texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; - texDesc.width = drawableSize.width; - texDesc.height = drawableSize.height; - - backend->outTexture = [surface->device newTextureWithDescriptor:texDesc]; - } -} -*/ - -void mg_metal_canvas_update_vertex_layout(mg_metal_canvas_backend* backend) -{ - char* vertexBase = (char*)[backend->vertexBuffer contents]; - - backend->interface.vertexLayout = (mg_vertex_layout){ - .maxVertexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH, - .maxIndexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH, - .posBuffer = vertexBase + offsetof(mg_vertex, pos), - .posStride = sizeof(mg_vertex), - .cubicBuffer = vertexBase + offsetof(mg_vertex, cubic), - .cubicStride = sizeof(mg_vertex), - .uvBuffer = vertexBase + offsetof(mg_vertex, uv), - .uvStride = sizeof(mg_vertex), - .colorBuffer = vertexBase + offsetof(mg_vertex, color), - .colorStride = sizeof(mg_vertex), - .zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex), - .zIndexStride = sizeof(mg_vertex), - .clipsBuffer = vertexBase + offsetof(mg_vertex, clip), - .clipsStride = sizeof(mg_vertex), - .indexBuffer = [backend->indexBuffer contents], - .indexStride = sizeof(int)}; -} - -void mg_metal_canvas_destroy(mg_canvas_backend* interface) -{ - mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; - - @autoreleasepool - { - [backend->outTexture release]; - [backend->atlasTexture release]; - [backend->vertexBuffer release]; - [backend->indexBuffer release]; - [backend->tilesArray release]; - [backend->triangleArray release]; - [backend->boxArray release]; - [backend->computePipeline release]; - } -} - -void mg_metal_canvas_atlas_upload(mg_canvas_backend* interface, mp_rect rect, u8* bytes) -{@autoreleasepool{ - mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; - - MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h); - [backend->atlasTexture replaceRegion:region - mipmapLevel:0 - withBytes:(void*)bytes - bytesPerRow: 4 * rect.w]; -}} - -mg_canvas_backend* mg_metal_canvas_create(mg_surface surface) -{ - mg_metal_canvas_backend* backend = 0; - - mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); - if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) - { - mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; - - backend = malloc_type(mg_metal_canvas_backend); - backend->surface = surface; - - //NOTE(martin): setup interface functions - backend->interface.destroy = mg_metal_canvas_destroy; - backend->interface.drawBuffers = mg_metal_canvas_draw_buffers; - backend->interface.atlasUpload = mg_metal_canvas_atlas_upload; - - mp_rect frame = mg_surface_get_frame(surface); - backend->viewPort = (mp_rect){0, 0, frame.w, frame.h}; - - @autoreleasepool - { - f32 scale = metalSurface->metalLayer.contentsScale; - CGSize drawableSize = (CGSize){.width = backend->viewPort.w * scale, .height = backend->viewPort.h * scale}; - - //----------------------------------------------------------- - //NOTE(martin): create our output texture - //----------------------------------------------------------- - MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModePrivate; - texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; - texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; - texDesc.width = drawableSize.width; - texDesc.height = drawableSize.height; - - backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc]; - //TODO(martin): retain ? - - //----------------------------------------------------------- - //NOTE(martin): create our atlas texture - //----------------------------------------------------------- - texDesc.textureType = MTLTextureType2D; - texDesc.storageMode = MTLStorageModeManaged; - texDesc.usage = MTLTextureUsageShaderRead; - texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm; - texDesc.width = MG_ATLAS_SIZE; - texDesc.height = MG_ATLAS_SIZE; - - backend->atlasTexture = [metalSurface->device newTextureWithDescriptor:texDesc]; - - //----------------------------------------------------------- - //NOTE(martin): create buffers for vertex and index - //----------------------------------------------------------- - - MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined - | MTLResourceStorageModeShared; - - backend->indexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int) - options: bufferOptions]; - - backend->vertexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex) - options: bufferOptions]; - - backend->tilesArray = [metalSurface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES - options: MTLResourceStorageModePrivate]; - - backend->triangleArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data) - options: MTLResourceStorageModePrivate]; - - backend->boxArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4) - options: MTLResourceStorageModePrivate]; - - //TODO(martin): retain ? - //----------------------------------------------------------- - //NOTE(martin): create and initialize tile counters - //----------------------------------------------------------- - backend->tileCounters = [metalSurface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint) - options: MTLResourceStorageModePrivate]; - id commandBuffer = [metalSurface->commandQueue commandBuffer]; - id blitEncoder = [commandBuffer blitCommandEncoder]; - [blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; - [blitEncoder endEncoding]; - [commandBuffer commit]; - - //----------------------------------------------------------- - //NOTE(martin): load the library - //----------------------------------------------------------- - - //TODO(martin): filepath magic to find metallib path when not in the working directory - str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.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 tilingFunction = [library newFunctionWithName:@"TileKernel"]; - id sortingFunction = [library newFunctionWithName:@"SortKernel"]; - id boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"]; - id computeFunction = [library newFunctionWithName:@"RenderKernel"]; - id vertexFunction = [library newFunctionWithName:@"VertexShader"]; - id fragmentFunction = [library newFunctionWithName:@"FragmentShader"]; - - //----------------------------------------------------------- - //NOTE(martin): setup our data layout and pipeline state - //----------------------------------------------------------- - NSError* error = NULL; - backend->computePipeline = [metalSurface->device newComputePipelineStateWithFunction: computeFunction - error:&error]; - ASSERT(backend->computePipeline); - - MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; - tilingPipelineDesc.computeFunction = tilingFunction; - // tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; - - backend->tilingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc - options: MTLPipelineOptionNone - reflection: nil - error: &error]; - - MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; - sortingPipelineDesc.computeFunction = sortingFunction; - // sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; - - backend->sortingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc - options: MTLPipelineOptionNone - reflection: nil - error: &error]; - - MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; - boxingPipelineDesc.computeFunction = boxingFunction; - // boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; - - backend->boxingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc - options: MTLPipelineOptionNone - reflection: nil - error: &error]; - //----------------------------------------------------------- - //NOTE(martin): setup our render pipeline state - //----------------------------------------------------------- - // create and initialize the pipeline state descriptor - MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; - pipelineStateDescriptor.label = @"My simple pipeline"; - pipelineStateDescriptor.vertexFunction = vertexFunction; - pipelineStateDescriptor.fragmentFunction = fragmentFunction; - pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->metalLayer.pixelFormat; - - // create render pipeline - backend->renderPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err]; - if(err != nil) - { - const char* errStr = [[err localizedDescription] UTF8String]; - const char* descStr = [[err localizedFailureReason] UTF8String]; - const char* recovStr = [[err localizedRecoverySuggestion] UTF8String]; - LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr); - return(0); - } - } - - mg_metal_canvas_update_vertex_layout(backend); - } - - return((mg_canvas_backend*)backend); -} - - -#undef LOG_SUBSYSTEM +/************************************************************//** +* +* @file: metal_canvas.m +* @author: Martin Fouilleul +* @date: 12/07/2020 +* @revision: 24/01/2023 +* +*****************************************************************/ +#import +#import +#include + +#include"graphics_internal.h" +#include"macro_helpers.h" +#include"osx_app.h" + +#include"metal_shader.h" + +#define LOG_SUBSYSTEM "Graphics" + +static const int MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20; + +typedef struct mg_metal_canvas_backend +{ + mg_canvas_backend interface; + mg_surface surface; + + // permanent metal resources + id tilingPipeline; + id sortingPipeline; + id boxingPipeline; + id computePipeline; + id renderPipeline; + + mp_rect viewPort; + + // textures and buffers + id outTexture; + id atlasTexture; + id vertexBuffer; + id indexBuffer; + id tileCounters; + id tilesArray; + id triangleArray; + id boxArray; + +} mg_metal_canvas_backend; + +mg_metal_surface* mg_metal_canvas_get_surface(mg_metal_canvas_backend* canvas) +{ + mg_metal_surface* res = 0; + mg_surface_data* data = mg_surface_data_from_handle(canvas->surface); + if(data && data->backend == MG_BACKEND_METAL) + { + res = (mg_metal_surface*)data; + } + return(res); +} + +void mg_metal_canvas_draw_buffers(mg_canvas_backend* interface, u32 vertexCount, u32 indexCount, mg_color clearColor) +{ + mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; + mg_metal_surface* surface = mg_metal_canvas_get_surface(backend); + if(!surface) + { + return; + } + + @autoreleasepool + { + if(surface->commandBuffer == nil || surface->commandBuffer == nil) + { + mg_metal_surface_acquire_drawable_and_command_buffer(surface); + } + + ASSERT(indexCount * sizeof(i32) < [backend->indexBuffer length]); + + f32 scale = surface->metalLayer.contentsScale; + vector_uint2 viewportSize = {backend->viewPort.w * scale, backend->viewPort.h * scale}; + + //----------------------------------------------------------- + //NOTE(martin): encode the clear counter + //----------------------------------------------------------- + id blitEncoder = [surface->commandBuffer blitCommandEncoder]; + [blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; + [blitEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the boxing pass + //----------------------------------------------------------- + id boxEncoder = [surface->commandBuffer computeCommandEncoder]; + [boxEncoder setComputePipelineState: backend->boxingPipeline]; + [boxEncoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0]; + [boxEncoder setBuffer: backend->indexBuffer offset:0 atIndex: 1]; + [boxEncoder setBuffer: backend->triangleArray offset:0 atIndex: 2]; + [boxEncoder setBuffer: backend->boxArray offset:0 atIndex: 3]; + [boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4]; + + MTLSize boxGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); + MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1); + + [boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; + [boxEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the tiling pass + //----------------------------------------------------------- + + id tileEncoder = [surface->commandBuffer computeCommandEncoder]; + [tileEncoder setComputePipelineState: backend->tilingPipeline]; + [tileEncoder setBuffer: backend->boxArray offset:0 atIndex: 0]; + [tileEncoder setBuffer: backend->tileCounters offset:0 atIndex: 1]; + [tileEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; + [tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; + + [tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; + [tileEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the sorting pass + //----------------------------------------------------------- + + id sortEncoder = [surface->commandBuffer computeCommandEncoder]; + [sortEncoder setComputePipelineState: backend->sortingPipeline]; + [sortEncoder setBuffer: backend->tileCounters offset:0 atIndex: 0]; + [sortEncoder setBuffer: backend->triangleArray offset:0 atIndex: 1]; + [sortEncoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; + [sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; + + u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; + u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; + + MTLSize sortGroupSize = MTLSizeMake(backend->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); + MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1); + + [sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize]; + [sortEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): create compute encoder and encode commands + //----------------------------------------------------------- + vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a}; + + id encoder = [surface->commandBuffer computeCommandEncoder]; + [encoder setComputePipelineState:backend->computePipeline]; + [encoder setTexture: backend->outTexture atIndex: 0]; + [encoder setTexture: backend->atlasTexture atIndex: 1]; + [encoder setBuffer: backend->vertexBuffer offset:0 atIndex: 0]; + [encoder setBuffer: backend->tileCounters offset:0 atIndex: 1]; + [encoder setBuffer: backend->tilesArray offset:0 atIndex: 2]; + [encoder setBuffer: backend->triangleArray offset:0 atIndex: 3]; + [encoder setBuffer: backend->boxArray offset:0 atIndex: 4]; + [encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5]; + + //TODO: check that we don't exceed maxTotalThreadsPerThreadgroup + DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= backend->computePipeline.maxTotalThreadsPerThreadgroup); + MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1); + MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1); + + [encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize]; + [encoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer + //----------------------------------------------------------- + + MTLViewport viewport = {backend->viewPort.x * scale, + backend->viewPort.y * scale, + backend->viewPort.w * scale, + backend->viewPort.h * scale, + 0, + 1}; + + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + + id renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder setViewport: viewport]; + [renderEncoder setRenderPipelineState: backend->renderPipeline]; + [renderEncoder setFragmentTexture: backend->outTexture atIndex: 0]; + [renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle + vertexStart: 0 + vertexCount: 3 ]; + [renderEncoder endEncoding]; + } +} + +/* +void mg_metal_canvas_viewport(mg_canvas_backend* interface, mp_rect viewPort) +{ + mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; + mg_metal_surface* surface = mg_metal_canvas_get_surface(backend); + if(!surface) + { + return; + } + + backend->viewPort = viewPort; + + @autoreleasepool + { + f32 scale = surface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale}; + + [backend->outTexture release]; + + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; + texDesc.width = drawableSize.width; + texDesc.height = drawableSize.height; + + backend->outTexture = [surface->device newTextureWithDescriptor:texDesc]; + } +} +*/ + +void mg_metal_canvas_update_vertex_layout(mg_metal_canvas_backend* backend) +{ + char* vertexBase = (char*)[backend->vertexBuffer contents]; + + backend->interface.vertexLayout = (mg_vertex_layout){ + .maxVertexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH, + .maxIndexCount = MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH, + .posBuffer = vertexBase + offsetof(mg_vertex, pos), + .posStride = sizeof(mg_vertex), + .cubicBuffer = vertexBase + offsetof(mg_vertex, cubic), + .cubicStride = sizeof(mg_vertex), + .uvBuffer = vertexBase + offsetof(mg_vertex, uv), + .uvStride = sizeof(mg_vertex), + .colorBuffer = vertexBase + offsetof(mg_vertex, color), + .colorStride = sizeof(mg_vertex), + .zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex), + .zIndexStride = sizeof(mg_vertex), + .clipBuffer = vertexBase + offsetof(mg_vertex, clip), + .clipStride = sizeof(mg_vertex), + .indexBuffer = [backend->indexBuffer contents], + .indexStride = sizeof(int)}; +} + +void mg_metal_canvas_destroy(mg_canvas_backend* interface) +{ + mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; + + @autoreleasepool + { + [backend->outTexture release]; + [backend->atlasTexture release]; + [backend->vertexBuffer release]; + [backend->indexBuffer release]; + [backend->tilesArray release]; + [backend->triangleArray release]; + [backend->boxArray release]; + [backend->computePipeline release]; + } +} + +void mg_metal_canvas_atlas_upload(mg_canvas_backend* interface, mp_rect rect, u8* bytes) +{@autoreleasepool{ + mg_metal_canvas_backend* backend = (mg_metal_canvas_backend*)interface; + + MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h); + [backend->atlasTexture replaceRegion:region + mipmapLevel:0 + withBytes:(void*)bytes + bytesPerRow: 4 * rect.w]; +}} + +mg_canvas_backend* mg_metal_canvas_create(mg_surface surface) +{ + mg_metal_canvas_backend* backend = 0; + + mg_surface_data* surfaceData = mg_surface_data_from_handle(surface); + if(surfaceData && surfaceData->backend == MG_BACKEND_METAL) + { + mg_metal_surface* metalSurface = (mg_metal_surface*)surfaceData; + + backend = malloc_type(mg_metal_canvas_backend); + backend->surface = surface; + + //NOTE(martin): setup interface functions + backend->interface.destroy = mg_metal_canvas_destroy; + backend->interface.drawBuffers = mg_metal_canvas_draw_buffers; + backend->interface.atlasUpload = mg_metal_canvas_atlas_upload; + + mp_rect frame = mg_surface_get_frame(surface); + backend->viewPort = (mp_rect){0, 0, frame.w, frame.h}; + + @autoreleasepool + { + f32 scale = metalSurface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = backend->viewPort.w * scale, .height = backend->viewPort.h * scale}; + + //----------------------------------------------------------- + //NOTE(martin): create our output texture + //----------------------------------------------------------- + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; + texDesc.width = drawableSize.width; + texDesc.height = drawableSize.height; + + backend->outTexture = [metalSurface->device newTextureWithDescriptor:texDesc]; + //TODO(martin): retain ? + + //----------------------------------------------------------- + //NOTE(martin): create our atlas texture + //----------------------------------------------------------- + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModeManaged; + texDesc.usage = MTLTextureUsageShaderRead; + texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm; + texDesc.width = MG_ATLAS_SIZE; + texDesc.height = MG_ATLAS_SIZE; + + backend->atlasTexture = [metalSurface->device newTextureWithDescriptor:texDesc]; + + //----------------------------------------------------------- + //NOTE(martin): create buffers for vertex and index + //----------------------------------------------------------- + + MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined + | MTLResourceStorageModeShared; + + backend->indexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int) + options: bufferOptions]; + + backend->vertexBuffer = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex) + options: bufferOptions]; + + backend->tilesArray = [metalSurface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES + options: MTLResourceStorageModePrivate]; + + backend->triangleArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data) + options: MTLResourceStorageModePrivate]; + + backend->boxArray = [metalSurface->device newBufferWithLength: MG_METAL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4) + options: MTLResourceStorageModePrivate]; + + //TODO(martin): retain ? + //----------------------------------------------------------- + //NOTE(martin): create and initialize tile counters + //----------------------------------------------------------- + backend->tileCounters = [metalSurface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint) + options: MTLResourceStorageModePrivate]; + id commandBuffer = [metalSurface->commandQueue commandBuffer]; + id blitEncoder = [commandBuffer blitCommandEncoder]; + [blitEncoder fillBuffer: backend->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; + [blitEncoder endEncoding]; + [commandBuffer commit]; + + //----------------------------------------------------------- + //NOTE(martin): load the library + //----------------------------------------------------------- + + //TODO(martin): filepath magic to find metallib path when not in the working directory + str8 shaderPath = mp_app_get_resource_path(mem_scratch(), "../resources/metal_shader.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 tilingFunction = [library newFunctionWithName:@"TileKernel"]; + id sortingFunction = [library newFunctionWithName:@"SortKernel"]; + id boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"]; + id computeFunction = [library newFunctionWithName:@"RenderKernel"]; + id vertexFunction = [library newFunctionWithName:@"VertexShader"]; + id fragmentFunction = [library newFunctionWithName:@"FragmentShader"]; + + //----------------------------------------------------------- + //NOTE(martin): setup our data layout and pipeline state + //----------------------------------------------------------- + NSError* error = NULL; + backend->computePipeline = [metalSurface->device newComputePipelineStateWithFunction: computeFunction + error:&error]; + ASSERT(backend->computePipeline); + + MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + tilingPipelineDesc.computeFunction = tilingFunction; + // tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + backend->tilingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + + MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + sortingPipelineDesc.computeFunction = sortingFunction; + // sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + backend->sortingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + + MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + boxingPipelineDesc.computeFunction = boxingFunction; + // boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + backend->boxingPipeline = [metalSurface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + //----------------------------------------------------------- + //NOTE(martin): setup our render pipeline state + //----------------------------------------------------------- + // create and initialize the pipeline state descriptor + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"My simple pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = metalSurface->metalLayer.pixelFormat; + + // create render pipeline + backend->renderPipeline = [metalSurface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err]; + if(err != nil) + { + const char* errStr = [[err localizedDescription] UTF8String]; + const char* descStr = [[err localizedFailureReason] UTF8String]; + const char* recovStr = [[err localizedRecoverySuggestion] UTF8String]; + LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr); + return(0); + } + } + + mg_metal_canvas_update_vertex_layout(backend); + } + + return((mg_canvas_backend*)backend); +} + + +#undef LOG_SUBSYSTEM diff --git a/src/metal_shader.metal b/src/metal_shader.metal index d0faf02..8a71d4e 100644 --- a/src/metal_shader.metal +++ b/src/metal_shader.metal @@ -1,349 +1,349 @@ - -#include -#include - -#include"metal_shader.h" - -using namespace metal; - -struct vs_out -{ - float4 pos [[position]]; - float2 uv; -}; - -vertex vs_out VertexShader(ushort vid [[vertex_id]]) -{ - vs_out out; - out.uv = float2((vid << 1) & 2, vid & 2); - out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1); - return(out); -} - -fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d tex [[texture(0)]]) -{ - constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); - return(float4(tex.sample(smp, i.uv).rgb, 1)); -} - - -bool is_top_left(float2 a, float2 b) -{ - return( (a.y == b.y && b.x < a.x) - ||(b.y < a.y)); -} - -kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]], - constant uint* indexBuffer [[buffer(1)]], - device mg_triangle_data* triangleArray [[buffer(2)]], - device float4* boxArray [[buffer(3)]], - constant float* contentsScaling [[buffer(4)]], - uint gid [[thread_position_in_grid]]) -{ - uint triangleIndex = gid; - uint vertexIndex = triangleIndex*3; - - uint i0 = indexBuffer[vertexIndex]; - uint i1 = indexBuffer[vertexIndex+1]; - uint i2 = indexBuffer[vertexIndex+2]; - - float2 p0 = vertexBuffer[i0].pos * contentsScaling[0]; - float2 p1 = vertexBuffer[i1].pos * contentsScaling[0]; - float2 p2 = vertexBuffer[i2].pos * contentsScaling[0]; - - //NOTE(martin): compute triangle bounding box - float2 boxMin = min(min(p0, p1), p2); - float2 boxMax = max(max(p0, p1), p2); - - //NOTE(martin): clip bounding box against clip rect - vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip; - float2 clipMin(clip.x, clip.y); - float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1); - - //NOTE(martin): intersect with current clip - boxMin = max(boxMin, clipMin); - boxMax = min(boxMax, clipMax); - - //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge - float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x; - if(cw < 0) - { - uint tmpIndex = i1; - i1 = i2; - i2 = tmpIndex; - - float2 tmpPoint = p1; - p1 = p2; - p2 = tmpPoint; - } - int bias0 = is_top_left(p1, p2) ? 0 : -1; - int bias1 = is_top_left(p2, p0) ? 0 : -1; - int bias2 = is_top_left(p0, p1) ? 0 : -1; - - //NOTE(martin): fill triangle data - boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y); - - triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex; - triangleArray[triangleIndex].i0 = i0; - triangleArray[triangleIndex].i1 = i1; - triangleArray[triangleIndex].i2 = i2; - triangleArray[triangleIndex].p0 = p0; - triangleArray[triangleIndex].p1 = p1; - triangleArray[triangleIndex].p2 = p2; - triangleArray[triangleIndex].bias0 = bias0; - triangleArray[triangleIndex].bias1 = bias1; - triangleArray[triangleIndex].bias2 = bias2; -} - -kernel void TileKernel(const device float4* boxArray [[buffer(0)]], - device volatile atomic_uint* tileCounters [[buffer(1)]], - device uint* tilesArray [[buffer(2)]], - constant vector_uint2* viewport [[buffer(3)]], - uint gid [[thread_position_in_grid]]) -{ - uint2 tilesMatrixDim = (*viewport - 1) / RENDERER_TILE_SIZE + 1; - uint nTilesX = tilesMatrixDim.x; - uint nTilesY = tilesMatrixDim.y; - - uint triangleIndex = gid; - uint4 box = uint4(floor(boxArray[triangleIndex]))/RENDERER_TILE_SIZE; - uint xMin = max((uint)0, box.x); - uint yMin = max((uint)0, box.y); - uint xMax = min(box.z, nTilesX-1); - uint yMax = min(box.w, nTilesY-1); - - for(uint y = yMin; y <= yMax; y++) - { - for(uint x = xMin ; x <= xMax; x++) - { - uint tileIndex = y*nTilesX + x; - device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; - uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed); - tileBuffer[counter] = triangleIndex; - } - } -} - -kernel void SortKernel(const device uint* tileCounters [[buffer(0)]], - const device mg_triangle_data* triangleArray [[buffer(1)]], - device uint* tilesArray [[buffer(2)]], - constant vector_uint2* viewport [[buffer(3)]], - uint gid [[thread_position_in_grid]]) -{ - uint tileIndex = gid; - device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; - uint tileBufferSize = tileCounters[tileIndex]; - - for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++) - { - uint elt = tileBuffer[eltIndex]; - uint eltZIndex = triangleArray[elt].zIndex; - - int backIndex = eltIndex-1; - for(; backIndex >= 0; backIndex--) - { - uint backElt = tileBuffer[backIndex]; - uint backEltZIndex = triangleArray[backElt].zIndex; - if(eltZIndex >= backEltZIndex) - { - break; - } - else - { - tileBuffer[backIndex+1] = backElt; - } - } - tileBuffer[backIndex+1] = elt; - } -} - -float orient2d(float2 a, float2 b, float2 c) -{ - ////////////////////////////////////////////////////////////////////////////////////////// - //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues - // arising when a, b, and c are close. But it degrades when a, c, and c - // are big. The proper solution is to change the expression to avoid - // precision loss but I'm too busy/lazy to do it now. - ////////////////////////////////////////////////////////////////////////////////////////// - a *= 10; - b *= 10; - c *= 10; - return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x)); -} - -kernel void RenderKernel(texture2d outTexture [[texture(0)]], - texture2d texAtlas [[texture(1)]], - const device mg_vertex* vertexBuffer [[buffer(0)]], - device uint* tileCounters [[buffer(1)]], - const device uint* tilesArray [[buffer(2)]], - const device mg_triangle_data* triangleArray [[buffer(3)]], - const device float4* boxArray [[buffer(4)]], - constant vector_float4* clearColor [[buffer(5)]], - uint2 gid [[thread_position_in_grid]], - uint2 tgid [[threadgroup_position_in_grid]], - uint2 threadsPerThreadgroup [[threads_per_threadgroup]], - uint2 gridSize [[threads_per_grid]]) -{ - //TODO: guard against thread group size not equal to tile size? - const uint2 tilesMatrixDim = (gridSize - 1) / RENDERER_TILE_SIZE + 1; -// const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE; - const uint2 tilePos = gid/RENDERER_TILE_SIZE; - const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x; - const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE; - - const uint tileBufferSize = tileCounters[tileIndex]; - - -//#define RENDERER_DEBUG_TILES -#ifdef RENDERER_DEBUG_TILES - //NOTE(martin): color code debug values and show the tile grid - uint nTileX = tilesMatrixDim.x; - uint nTileY = tilesMatrixDim.y; - - if(tilePos.x == 2 && tilePos.y == 12) - { - outTexture.write(float4(1, 0.5, 1, 1), gid); - return; - } - - if(nTileY != 13 || nTileX != 13) - { - outTexture.write(float4(1, 1, 0, 1), gid); - return; - } - - if(tilePos.x > nTileX || tilePos.y > nTileY) - { - outTexture.write(float4(0, 1, 1, 1), gid); - return; - } - - if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0)) - { - outTexture.write(float4(0, 0, 0, 1), gid); - return; - } - if(tileBufferSize <= 0) - { - outTexture.write(float4(0, 1, 0, 1), gid); - return; - } - else - { - outTexture.write(float4(1, 0, 0, 1), gid); - return; - } -#endif - const float2 sampleOffsets[6] = { float2(5./12, 5./12), - float2(-3./12, 3./12), - float2(1./12, 1./12), - float2(3./12, -1./12), - float2(-5./12, -3./12), - float2(-1./12, -5./12)}; - - int zIndices[6]; - uint flipCounts[6]; - float4 pixelColors[6]; - float4 nextColors[6]; - for(int i=0; i<6; i++) - { - zIndices[i] = -1; - flipCounts[i] = 0; - pixelColors[i] = *clearColor; - nextColors[i] = *clearColor; - } - - for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++) - { - float4 box = boxArray[tileBuffer[tileBufferIndex]]; - const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]]; - - float2 p0 = triangle->p0; - float2 p1 = triangle->p1; - float2 p2 = triangle->p2; - - int bias0 = triangle->bias0; - int bias1 = triangle->bias1; - int bias2 = triangle->bias2; - - const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]); - const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]); - const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]); - - float4 cubic0 = v0->cubic; - float4 cubic1 = v1->cubic; - float4 cubic2 = v2->cubic; - - float2 uv0 = v0->uv; - float2 uv1 = v1->uv; - float2 uv2 = v2->uv; - - int zIndex = v0->zIndex; - float4 color = v0->color; - - for(int i=0; i<6; i++) - { - float2 samplePoint = (float2)gid + sampleOffsets[i]; - - //NOTE(martin): cull if pixel is outside box - if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w) - { - continue; - } - - float w0 = orient2d(p1, p2, samplePoint); - float w1 = orient2d(p2, p0, samplePoint); - float w2 = orient2d(p0, p1, samplePoint); - - if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0) - { - float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2); - float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2); - - constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); - float4 texColor = texAtlas.sample(smp, uv); - - //TODO(martin): this is a quick and dirty fix for solid polygons where we use - // cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to - // flip the sign. - // We should really use another value that always lead to <= 0, but we must - // make sure we never share these vertices with bezier shapes. - // Alternatively, an ugly (but maybe less than this one) solution would be - // to check if uvs are equal on all vertices of the triangle and always render - // those triangles. - float eps = 0.0001; - if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps) - { - if(zIndex == zIndices[i]) - { - flipCounts[i]++; - } - else - { - if(flipCounts[i] & 0x01) - { - pixelColors[i] = nextColors[i]; - } - - float4 nextCol = color*texColor; - nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol; - - zIndices[i] = zIndex; - flipCounts[i] = 1; - } - } - } - } - } - float4 out = float4(0, 0, 0, 0); - for(int i=0; i<6; i++) - { - if(flipCounts[i] & 0x01) - { - pixelColors[i] = nextColors[i]; - } - out += pixelColors[i]; - } - out = float4(out.xyz/6, 1); - outTexture.write(out, gid); -} + +#include +#include + +#include"metal_shader.h" + +using namespace metal; + +struct vs_out +{ + float4 pos [[position]]; + float2 uv; +}; + +vertex vs_out VertexShader(ushort vid [[vertex_id]]) +{ + vs_out out; + out.uv = float2((vid << 1) & 2, vid & 2); + out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1); + return(out); +} + +fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d tex [[texture(0)]]) +{ + constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); + return(float4(tex.sample(smp, i.uv).rgb, 1)); +} + + +bool is_top_left(float2 a, float2 b) +{ + return( (a.y == b.y && b.x < a.x) + ||(b.y < a.y)); +} + +kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]], + constant uint* indexBuffer [[buffer(1)]], + device mg_triangle_data* triangleArray [[buffer(2)]], + device float4* boxArray [[buffer(3)]], + constant float* contentsScaling [[buffer(4)]], + uint gid [[thread_position_in_grid]]) +{ + uint triangleIndex = gid; + uint vertexIndex = triangleIndex*3; + + uint i0 = indexBuffer[vertexIndex]; + uint i1 = indexBuffer[vertexIndex+1]; + uint i2 = indexBuffer[vertexIndex+2]; + + float2 p0 = vertexBuffer[i0].pos * contentsScaling[0]; + float2 p1 = vertexBuffer[i1].pos * contentsScaling[0]; + float2 p2 = vertexBuffer[i2].pos * contentsScaling[0]; + + //NOTE(martin): compute triangle bounding box + float2 boxMin = min(min(p0, p1), p2); + float2 boxMax = max(max(p0, p1), p2); + + //NOTE(martin): clip bounding box against clip rect + vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip; + float2 clipMin(clip.x, clip.y); + float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1); + + //NOTE(martin): intersect with current clip + boxMin = max(boxMin, clipMin); + boxMax = min(boxMax, clipMax); + + //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge + float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x; + if(cw < 0) + { + uint tmpIndex = i1; + i1 = i2; + i2 = tmpIndex; + + float2 tmpPoint = p1; + p1 = p2; + p2 = tmpPoint; + } + int bias0 = is_top_left(p1, p2) ? 0 : -1; + int bias1 = is_top_left(p2, p0) ? 0 : -1; + int bias2 = is_top_left(p0, p1) ? 0 : -1; + + //NOTE(martin): fill triangle data + boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y); + + triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex; + triangleArray[triangleIndex].i0 = i0; + triangleArray[triangleIndex].i1 = i1; + triangleArray[triangleIndex].i2 = i2; + triangleArray[triangleIndex].p0 = p0; + triangleArray[triangleIndex].p1 = p1; + triangleArray[triangleIndex].p2 = p2; + triangleArray[triangleIndex].bias0 = bias0; + triangleArray[triangleIndex].bias1 = bias1; + triangleArray[triangleIndex].bias2 = bias2; +} + +kernel void TileKernel(const device float4* boxArray [[buffer(0)]], + device volatile atomic_uint* tileCounters [[buffer(1)]], + device uint* tilesArray [[buffer(2)]], + constant vector_uint2* viewport [[buffer(3)]], + uint gid [[thread_position_in_grid]]) +{ + uint2 tilesMatrixDim = (*viewport - 1) / RENDERER_TILE_SIZE + 1; + uint nTilesX = tilesMatrixDim.x; + uint nTilesY = tilesMatrixDim.y; + + uint triangleIndex = gid; + uint4 box = uint4(floor(boxArray[triangleIndex]))/RENDERER_TILE_SIZE; + uint xMin = max((uint)0, box.x); + uint yMin = max((uint)0, box.y); + uint xMax = min(box.z, nTilesX-1); + uint yMax = min(box.w, nTilesY-1); + + for(uint y = yMin; y <= yMax; y++) + { + for(uint x = xMin ; x <= xMax; x++) + { + uint tileIndex = y*nTilesX + x; + device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; + uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed); + tileBuffer[counter] = triangleIndex; + } + } +} + +kernel void SortKernel(const device uint* tileCounters [[buffer(0)]], + const device mg_triangle_data* triangleArray [[buffer(1)]], + device uint* tilesArray [[buffer(2)]], + constant vector_uint2* viewport [[buffer(3)]], + uint gid [[thread_position_in_grid]]) +{ + uint tileIndex = gid; + device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; + uint tileBufferSize = tileCounters[tileIndex]; + + for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++) + { + uint elt = tileBuffer[eltIndex]; + uint eltZIndex = triangleArray[elt].zIndex; + + int backIndex = eltIndex-1; + for(; backIndex >= 0; backIndex--) + { + uint backElt = tileBuffer[backIndex]; + uint backEltZIndex = triangleArray[backElt].zIndex; + if(eltZIndex >= backEltZIndex) + { + break; + } + else + { + tileBuffer[backIndex+1] = backElt; + } + } + tileBuffer[backIndex+1] = elt; + } +} + +float orient2d(float2 a, float2 b, float2 c) +{ + ////////////////////////////////////////////////////////////////////////////////////////// + //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues + // arising when a, b, and c are close. But it degrades when a, c, and c + // are big. The proper solution is to change the expression to avoid + // precision loss but I'm too busy/lazy to do it now. + ////////////////////////////////////////////////////////////////////////////////////////// + a *= 10; + b *= 10; + c *= 10; + return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x)); +} + +kernel void RenderKernel(texture2d outTexture [[texture(0)]], + texture2d texAtlas [[texture(1)]], + const device mg_vertex* vertexBuffer [[buffer(0)]], + device uint* tileCounters [[buffer(1)]], + const device uint* tilesArray [[buffer(2)]], + const device mg_triangle_data* triangleArray [[buffer(3)]], + const device float4* boxArray [[buffer(4)]], + constant vector_float4* clearColor [[buffer(5)]], + uint2 gid [[thread_position_in_grid]], + uint2 tgid [[threadgroup_position_in_grid]], + uint2 threadsPerThreadgroup [[threads_per_threadgroup]], + uint2 gridSize [[threads_per_grid]]) +{ + //TODO: guard against thread group size not equal to tile size? + const uint2 tilesMatrixDim = (gridSize - 1) / RENDERER_TILE_SIZE + 1; +// const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE; + const uint2 tilePos = gid/RENDERER_TILE_SIZE; + const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x; + const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE; + + const uint tileBufferSize = tileCounters[tileIndex]; + + +//#define RENDERER_DEBUG_TILES +#ifdef RENDERER_DEBUG_TILES + //NOTE(martin): color code debug values and show the tile grid + uint nTileX = tilesMatrixDim.x; + uint nTileY = tilesMatrixDim.y; + + if(tilePos.x == 2 && tilePos.y == 12) + { + outTexture.write(float4(1, 0.5, 1, 1), gid); + return; + } + + if(nTileY != 13 || nTileX != 13) + { + outTexture.write(float4(1, 1, 0, 1), gid); + return; + } + + if(tilePos.x > nTileX || tilePos.y > nTileY) + { + outTexture.write(float4(0, 1, 1, 1), gid); + return; + } + + if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0)) + { + outTexture.write(float4(0, 0, 0, 1), gid); + return; + } + if(tileBufferSize <= 0) + { + outTexture.write(float4(0, 1, 0, 1), gid); + return; + } + else + { + outTexture.write(float4(1, 0, 0, 1), gid); + return; + } +#endif + const float2 sampleOffsets[6] = { float2(5./12, 5./12), + float2(-3./12, 3./12), + float2(1./12, 1./12), + float2(3./12, -1./12), + float2(-5./12, -3./12), + float2(-1./12, -5./12)}; + + int zIndices[6]; + uint flipCounts[6]; + float4 pixelColors[6]; + float4 nextColors[6]; + for(int i=0; i<6; i++) + { + zIndices[i] = -1; + flipCounts[i] = 0; + pixelColors[i] = *clearColor; + nextColors[i] = *clearColor; + } + + for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++) + { + float4 box = boxArray[tileBuffer[tileBufferIndex]]; + const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]]; + + float2 p0 = triangle->p0; + float2 p1 = triangle->p1; + float2 p2 = triangle->p2; + + int bias0 = triangle->bias0; + int bias1 = triangle->bias1; + int bias2 = triangle->bias2; + + const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]); + const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]); + const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]); + + float4 cubic0 = v0->cubic; + float4 cubic1 = v1->cubic; + float4 cubic2 = v2->cubic; + + float2 uv0 = v0->uv; + float2 uv1 = v1->uv; + float2 uv2 = v2->uv; + + int zIndex = v0->zIndex; + float4 color = v0->color; + + for(int i=0; i<6; i++) + { + float2 samplePoint = (float2)gid + sampleOffsets[i]; + + //NOTE(martin): cull if pixel is outside box + if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w) + { + continue; + } + + float w0 = orient2d(p1, p2, samplePoint); + float w1 = orient2d(p2, p0, samplePoint); + float w2 = orient2d(p0, p1, samplePoint); + + if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0) + { + float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2); + float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2); + + constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); + float4 texColor = texAtlas.sample(smp, uv); + + //TODO(martin): this is a quick and dirty fix for solid polygons where we use + // cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to + // flip the sign. + // We should really use another value that always lead to <= 0, but we must + // make sure we never share these vertices with bezier shapes. + // Alternatively, an ugly (but maybe less than this one) solution would be + // to check if uvs are equal on all vertices of the triangle and always render + // those triangles. + float eps = 0.0001; + if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps) + { + if(zIndex == zIndices[i]) + { + flipCounts[i]++; + } + else + { + if(flipCounts[i] & 0x01) + { + pixelColors[i] = nextColors[i]; + } + + float4 nextCol = color*texColor; + nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol; + + zIndices[i] = zIndex; + flipCounts[i] = 1; + } + } + } + } + } + float4 out = float4(0, 0, 0, 0); + for(int i=0; i<6; i++) + { + if(flipCounts[i] & 0x01) + { + pixelColors[i] = nextColors[i]; + } + out += pixelColors[i]; + } + out = float4(out.xyz/6, 1); + outTexture.write(out, gid); +} diff --git a/src/milepost.c b/src/milepost.c index c6de6bb..287580f 100644 --- a/src/milepost.c +++ b/src/milepost.c @@ -54,6 +54,7 @@ #include"win32_app.c" // #include"win32_gl_surface.c" #include"win32_gles_surface.c" + #include"gles_canvas.c" #elif defined(OS_MACOS) //NOTE: macos application layer is defined in milepost.m #else diff --git a/src/milepost.h b/src/milepost.h index 7243848..ed87c6a 100644 --- a/src/milepost.h +++ b/src/milepost.h @@ -40,8 +40,17 @@ #if defined(OS_WIN64) || defined(OS_WIN32) #define WIN32_GL_LOADER_API // #include"win32_gl_loader.h" + + #if MG_IMPLEMENTS_BACKEND_GLES + #include"win32_gles_surface.h" + #endif #endif +#if MG_IMPLEMENTS_BACKEND_METAL +#include"metal_surface.h" +#endif + + //---------------------------------------------------------------- // graphics/ui layer //---------------------------------------------------------------- diff --git a/src/mp_app.c b/src/mp_app.c index ab116b3..b9f20e8 100644 --- a/src/mp_app.c +++ b/src/mp_app.c @@ -1,349 +1,349 @@ -/************************************************************//** -* -* @file: mp_app_internal.c -* @author: Martin Fouilleul -* @date: 23/12/2022 -* @revision: -* -*****************************************************************/ - -#include"mp_app_internal.h" - -#define LOG_SUBSYSTEM "Application" - -mp_app __mpApp = {0}; - -//--------------------------------------------------------------- -// Window handles -//--------------------------------------------------------------- - -void mp_init_window_handles() -{ - ListInit(&__mpApp.windowFreeList); - for(int i=0; i>32; - u32 generation = handle.h & 0xffffffff; - if(index >= MP_APP_MAX_WINDOWS) - { - return(0); - } - mp_window_data* window = &__mpApp.windowPool[index]; - if(window->generation != generation) - { - return(0); - } - else - { - return(window); - } -} - -mp_window mp_window_handle_from_ptr(mp_window_data* window) -{ - DEBUG_ASSERT( (window - __mpApp.windowPool) >= 0 - && (window - __mpApp.windowPool) < MP_APP_MAX_WINDOWS); - - u64 h = ((u64)(window - __mpApp.windowPool))<<32 - | ((u64)window->generation); - - return((mp_window){h}); -} - -void mp_window_recycle_ptr(mp_window_data* window) -{ - window->generation++; - ListPush(&__mpApp.windowFreeList, &window->freeListElt); -} - -//--------------------------------------------------------------- -// Init -//--------------------------------------------------------------- - -static void mp_init_common() -{ - mp_init_window_handles(); - ringbuffer_init(&__mpApp.eventQueue, 16); -} - -static void mp_terminate_common() -{ - ringbuffer_cleanup(&__mpApp.eventQueue); -} - -//--------------------------------------------------------------- -// Event handling -//--------------------------------------------------------------- - -void mp_queue_event(mp_event* event) -{ - if(ringbuffer_write_available(&__mpApp.eventQueue) < sizeof(mp_event)) - { - LOG_ERROR("event queue full\n"); - } - else - { - u32 written = ringbuffer_write(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event); - DEBUG_ASSERT(written == sizeof(mp_event)); - } -} - -bool mp_next_event(mp_event* event) -{ - //NOTE pop and return event from queue - if(ringbuffer_read_available(&__mpApp.eventQueue) >= sizeof(mp_event)) - { - u64 read = ringbuffer_read(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event); - DEBUG_ASSERT(read == sizeof(mp_event)); - return(true); - } - else - { - return(false); - } -} - -//--------------------------------------------------------------- -// Input state updating -//--------------------------------------------------------------- - -static void mp_update_key_state(mp_key_state* key, bool down) -{ - u64 frameCounter = __mpApp.inputState.frameCounter; - if(key->lastUpdate != frameCounter) - { - key->transitionCounter = 0; - key->clicked = false; - key->doubleClicked = false; - key->lastUpdate = frameCounter; - } - if(key->down == down) - { - key->transitionCounter++; - } - key->down = down; -} - -static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY) -{ - u64 frameCounter = __mpApp.inputState.frameCounter; - mp_mouse_state* mouse = &__mpApp.inputState.mouse; - if(mouse->lastUpdate != frameCounter) - { - mouse->delta = (vec2){0, 0}; - mouse->wheel = (vec2){0, 0}; - mouse->lastUpdate = frameCounter; - } - mouse->pos = (vec2){x, y}; - mouse->delta.x += deltaX; - mouse->delta.y += deltaY; -} - -static void mp_update_mouse_wheel(f32 deltaX, f32 deltaY) -{ - u64 frameCounter = __mpApp.inputState.frameCounter; - mp_mouse_state* mouse = &__mpApp.inputState.mouse; - if(mouse->lastUpdate != frameCounter) - { - mouse->delta = (vec2){0, 0}; - mouse->wheel = (vec2){0, 0}; - mouse->lastUpdate = frameCounter; - } - mouse->wheel.x += deltaX; - mouse->wheel.y += deltaY; -} - -static void mp_update_text(utf32 codepoint) -{ - u64 frameCounter = __mpApp.inputState.frameCounter; - mp_text_state* text = &__mpApp.inputState.text; - - if(text->lastUpdate != frameCounter) - { - text->codePoints.len = 0; - text->lastUpdate = frameCounter; - } - - text->codePoints.ptr = text->backing; - if(text->codePoints.len < MP_INPUT_TEXT_BACKING_SIZE) - { - text->codePoints.ptr[text->codePoints.len] = codepoint; - text->codePoints.len++; - } - else - { - LOG_WARNING("too many input codepoints per frame, dropping input"); - } -} - -//-------------------------------------------------------------------- -// Input state polling -//-------------------------------------------------------------------- - -mp_key_state mp_input_get_key_state(mp_key_code key) -{ - if(key <= MP_KEY_COUNT) - { - return(__mpApp.inputState.keyboard.keys[key]); - } - else - { - return((mp_key_state){0}); - } -} -mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button) -{ - if(button <= MP_MOUSE_BUTTON_COUNT) - { - return(__mpApp.inputState.mouse.buttons[button]); - } - else - { - return((mp_key_state){0}); - } -} - -bool mp_input_check_key_transition(mp_key_state* key, bool pressed) -{ - bool res = ( (key->lastUpdate == __mpApp.inputState.frameCounter) - && key->transitionCounter - &&(key->down == pressed || key->transitionCounter > 1)); - return(res); -} - -bool mp_input_key_down(mp_key_code key) -{ - mp_key_state state = mp_input_get_key_state(key); - return(state.down); -} -bool mp_input_key_pressed(mp_key_code key) -{ - mp_key_state state = mp_input_get_key_state(key); - bool res = mp_input_check_key_transition(&state, true); - return(res); -} - -bool mp_input_key_released(mp_key_code key) -{ - mp_key_state state = mp_input_get_key_state(key); - bool res = mp_input_check_key_transition(&state, false); - return(res); -} - -bool mp_input_mouse_down(mp_mouse_button button) -{ - mp_key_state state = mp_input_get_mouse_button_state(button); - return(state.down); -} - -bool mp_input_mouse_pressed(mp_mouse_button button) -{ - mp_key_state state = mp_input_get_mouse_button_state(button); - bool res = mp_input_check_key_transition(&state, true); - return(res); -} - -bool mp_input_mouse_released(mp_mouse_button button) -{ - mp_key_state state = mp_input_get_mouse_button_state(button); - bool res = mp_input_check_key_transition(&state, false); - return(res); -} - -bool mp_input_mouse_clicked(mp_mouse_button button) -{ - mp_key_state state = mp_input_get_mouse_button_state(button); - return(state.clicked); -} - -bool mp_input_mouse_double_clicked(mp_mouse_button button) -{ - mp_key_state state = mp_input_get_mouse_button_state(button); - if(state.lastUpdate == __mpApp.inputState.frameCounter) - { - return(state.doubleClicked); - } - else - { - return(false); - } -} - -mp_key_mods mp_input_key_mods() -{ - return(__mpApp.inputState.keyboard.mods); -} - -vec2 mp_input_mouse_position() -{ - return(__mpApp.inputState.mouse.pos); -} - -vec2 mp_input_mouse_delta() -{ - if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter) - { - return(__mpApp.inputState.mouse.delta); - } - else - { - return((vec2){0, 0}); - } -} - -vec2 mp_input_mouse_wheel() -{ - if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter) - { - return(__mpApp.inputState.mouse.wheel); - } - else - { - return((vec2){0, 0}); - } -} - -str32 mp_input_text_utf32(mem_arena* arena) -{ - str32 res = {0}; - if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter) - { - res = str32_push_copy(arena, __mpApp.inputState.text.codePoints); - } - return(res); -} - -str8 mp_input_text_utf8(mem_arena* arena) -{ - str8 res = {0}; - if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter) - { - res = utf8_push_from_codepoints(arena, __mpApp.inputState.text.codePoints); - } - return(res); -} - - -#undef LOG_SUBSYSTEM +/************************************************************//** +* +* @file: mp_app_internal.c +* @author: Martin Fouilleul +* @date: 23/12/2022 +* @revision: +* +*****************************************************************/ + +#include"mp_app_internal.h" + +#define LOG_SUBSYSTEM "Application" + +mp_app __mpApp = {0}; + +//--------------------------------------------------------------- +// Window handles +//--------------------------------------------------------------- + +void mp_init_window_handles() +{ + ListInit(&__mpApp.windowFreeList); + for(int i=0; i>32; + u32 generation = handle.h & 0xffffffff; + if(index >= MP_APP_MAX_WINDOWS) + { + return(0); + } + mp_window_data* window = &__mpApp.windowPool[index]; + if(window->generation != generation) + { + return(0); + } + else + { + return(window); + } +} + +mp_window mp_window_handle_from_ptr(mp_window_data* window) +{ + DEBUG_ASSERT( (window - __mpApp.windowPool) >= 0 + && (window - __mpApp.windowPool) < MP_APP_MAX_WINDOWS); + + u64 h = ((u64)(window - __mpApp.windowPool))<<32 + | ((u64)window->generation); + + return((mp_window){h}); +} + +void mp_window_recycle_ptr(mp_window_data* window) +{ + window->generation++; + ListPush(&__mpApp.windowFreeList, &window->freeListElt); +} + +//--------------------------------------------------------------- +// Init +//--------------------------------------------------------------- + +static void mp_init_common() +{ + mp_init_window_handles(); + ringbuffer_init(&__mpApp.eventQueue, 16); +} + +static void mp_terminate_common() +{ + ringbuffer_cleanup(&__mpApp.eventQueue); +} + +//--------------------------------------------------------------- +// Event handling +//--------------------------------------------------------------- + +void mp_queue_event(mp_event* event) +{ + if(ringbuffer_write_available(&__mpApp.eventQueue) < sizeof(mp_event)) + { + LOG_ERROR("event queue full\n"); + } + else + { + u32 written = ringbuffer_write(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event); + DEBUG_ASSERT(written == sizeof(mp_event)); + } +} + +bool mp_next_event(mp_event* event) +{ + //NOTE pop and return event from queue + if(ringbuffer_read_available(&__mpApp.eventQueue) >= sizeof(mp_event)) + { + u64 read = ringbuffer_read(&__mpApp.eventQueue, sizeof(mp_event), (u8*)event); + DEBUG_ASSERT(read == sizeof(mp_event)); + return(true); + } + else + { + return(false); + } +} + +//--------------------------------------------------------------- +// Input state updating +//--------------------------------------------------------------- + +static void mp_update_key_state(mp_key_state* key, bool down) +{ + u64 frameCounter = __mpApp.inputState.frameCounter; + if(key->lastUpdate != frameCounter) + { + key->transitionCounter = 0; + key->clicked = false; + key->doubleClicked = false; + key->lastUpdate = frameCounter; + } + if(key->down == down) + { + key->transitionCounter++; + } + key->down = down; +} + +static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY) +{ + u64 frameCounter = __mpApp.inputState.frameCounter; + mp_mouse_state* mouse = &__mpApp.inputState.mouse; + if(mouse->lastUpdate != frameCounter) + { + mouse->delta = (vec2){0, 0}; + mouse->wheel = (vec2){0, 0}; + mouse->lastUpdate = frameCounter; + } + mouse->pos = (vec2){x, y}; + mouse->delta.x += deltaX; + mouse->delta.y += deltaY; +} + +static void mp_update_mouse_wheel(f32 deltaX, f32 deltaY) +{ + u64 frameCounter = __mpApp.inputState.frameCounter; + mp_mouse_state* mouse = &__mpApp.inputState.mouse; + if(mouse->lastUpdate != frameCounter) + { + mouse->delta = (vec2){0, 0}; + mouse->wheel = (vec2){0, 0}; + mouse->lastUpdate = frameCounter; + } + mouse->wheel.x += deltaX; + mouse->wheel.y += deltaY; +} + +static void mp_update_text(utf32 codepoint) +{ + u64 frameCounter = __mpApp.inputState.frameCounter; + mp_text_state* text = &__mpApp.inputState.text; + + if(text->lastUpdate != frameCounter) + { + text->codePoints.len = 0; + text->lastUpdate = frameCounter; + } + + text->codePoints.ptr = text->backing; + if(text->codePoints.len < MP_INPUT_TEXT_BACKING_SIZE) + { + text->codePoints.ptr[text->codePoints.len] = codepoint; + text->codePoints.len++; + } + else + { + LOG_WARNING("too many input codepoints per frame, dropping input"); + } +} + +//-------------------------------------------------------------------- +// Input state polling +//-------------------------------------------------------------------- + +mp_key_state mp_input_get_key_state(mp_key_code key) +{ + if(key <= MP_KEY_COUNT) + { + return(__mpApp.inputState.keyboard.keys[key]); + } + else + { + return((mp_key_state){0}); + } +} +mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button) +{ + if(button <= MP_MOUSE_BUTTON_COUNT) + { + return(__mpApp.inputState.mouse.buttons[button]); + } + else + { + return((mp_key_state){0}); + } +} + +bool mp_input_check_key_transition(mp_key_state* key, bool pressed) +{ + bool res = ( (key->lastUpdate == __mpApp.inputState.frameCounter) + && key->transitionCounter + &&(key->down == pressed || key->transitionCounter > 1)); + return(res); +} + +bool mp_input_key_down(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + return(state.down); +} +bool mp_input_key_pressed(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + bool res = mp_input_check_key_transition(&state, true); + return(res); +} + +bool mp_input_key_released(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + bool res = mp_input_check_key_transition(&state, false); + return(res); +} + +bool mp_input_mouse_down(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + return(state.down); +} + +bool mp_input_mouse_pressed(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + bool res = mp_input_check_key_transition(&state, true); + return(res); +} + +bool mp_input_mouse_released(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + bool res = mp_input_check_key_transition(&state, false); + return(res); +} + +bool mp_input_mouse_clicked(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + return(state.clicked); +} + +bool mp_input_mouse_double_clicked(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + if(state.lastUpdate == __mpApp.inputState.frameCounter) + { + return(state.doubleClicked); + } + else + { + return(false); + } +} + +mp_key_mods mp_input_key_mods() +{ + return(__mpApp.inputState.keyboard.mods); +} + +vec2 mp_input_mouse_position() +{ + return(__mpApp.inputState.mouse.pos); +} + +vec2 mp_input_mouse_delta() +{ + if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter) + { + return(__mpApp.inputState.mouse.delta); + } + else + { + return((vec2){0, 0}); + } +} + +vec2 mp_input_mouse_wheel() +{ + if(__mpApp.inputState.mouse.lastUpdate == __mpApp.inputState.frameCounter) + { + return(__mpApp.inputState.mouse.wheel); + } + else + { + return((vec2){0, 0}); + } +} + +str32 mp_input_text_utf32(mem_arena* arena) +{ + str32 res = {0}; + if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter) + { + res = str32_push_copy(arena, __mpApp.inputState.text.codePoints); + } + return(res); +} + +str8 mp_input_text_utf8(mem_arena* arena) +{ + str8 res = {0}; + if(__mpApp.inputState.text.lastUpdate == __mpApp.inputState.frameCounter) + { + res = utf8_push_from_codepoints(arena, __mpApp.inputState.text.codePoints); + } + return(res); +} + + +#undef LOG_SUBSYSTEM diff --git a/src/mp_app_internal.h b/src/mp_app_internal.h index b538f53..8a64756 100644 --- a/src/mp_app_internal.h +++ b/src/mp_app_internal.h @@ -1,152 +1,152 @@ -/************************************************************//** -* -* @file: mp_app_internal.h -* @author: Martin Fouilleul -* @date: 23/12/2022 -* @revision: -* -*****************************************************************/ -#ifndef __MP_APP_INTERNAL_H_ -#define __MP_APP_INTERNAL_H_ - -#include"platform.h" -#include"ringbuffer.h" - -#if defined(OS_WIN64) || defined(OS_WIN32) - #include"win32_app.h" -#elif defined(OS_MACOS) - #include"osx_app.h" -#else - #error "platform not supported yet" -#endif - -//--------------------------------------------------------------- -// Input State -//--------------------------------------------------------------- - -typedef struct mp_key_utf8 -{ - u8 labelLen; - char label[8]; -} mp_key_utf8; - -typedef struct mp_key_state -{ - u64 lastUpdate; - u32 transitionCounter; - bool down; - bool clicked; - bool doubleClicked; - -} mp_key_state; - -typedef struct mp_keyboard_state -{ - mp_key_state keys[MP_KEY_COUNT]; - mp_key_mods mods; -} mp_keyboard_state; - -typedef struct mp_mouse_state -{ - u64 lastUpdate; - bool posValid; - vec2 pos; - vec2 delta; - vec2 wheel; - - union - { - mp_key_state buttons[MP_MOUSE_BUTTON_COUNT]; - struct - { - mp_key_state left; - mp_key_state right; - mp_key_state middle; - mp_key_state ext1; - mp_key_state ext2; - }; - }; -} mp_mouse_state; - -enum { MP_INPUT_TEXT_BACKING_SIZE = 64 }; - -typedef struct mp_text_state -{ - u64 lastUpdate; - utf32 backing[MP_INPUT_TEXT_BACKING_SIZE]; - str32 codePoints; -} mp_text_state; - -typedef struct mp_input_state -{ - u64 frameCounter; - mp_keyboard_state keyboard; - mp_mouse_state mouse; - mp_text_state text; -} mp_input_state; - -//--------------------------------------------------------------- -// Window structure -//--------------------------------------------------------------- - -typedef struct mp_frame_stats -{ - f64 start; - f64 workTime; - f64 remainingTime; - f64 targetFramePeriod; -} mp_frame_stats; - -typedef struct mp_window_data -{ - list_elt freeListElt; - u32 generation; - - mp_rect contentRect; - mp_rect frameRect; - mp_window_style style; - - bool shouldClose; //TODO could be in status flags - bool hidden; - bool minimized; - - MP_PLATFORM_WINDOW_DATA -} mp_window_data; - - -//--------------------------------------------------------------- -// Global App State -//--------------------------------------------------------------- - -enum { MP_APP_MAX_WINDOWS = 128 }; - -typedef struct mp_app -{ - bool init; - bool shouldQuit; - bool minimized; - - str8 pendingPathDrop; - mem_arena eventArena; - - ringbuffer eventQueue; - - mp_frame_stats frameStats; - - mp_window_data windowPool[MP_APP_MAX_WINDOWS]; - list_info windowFreeList; - - mp_live_resize_callback liveResizeCallback; - void* liveResizeData; - - mp_input_state inputState; - - mp_key_utf8 keyLabels[256]; - int keyCodes[256]; - int nativeKeys[MP_KEY_COUNT]; - - MP_PLATFORM_APP_DATA -} mp_app; - - -#endif // __MP_APP_INTERNAL_H_ +/************************************************************//** +* +* @file: mp_app_internal.h +* @author: Martin Fouilleul +* @date: 23/12/2022 +* @revision: +* +*****************************************************************/ +#ifndef __MP_APP_INTERNAL_H_ +#define __MP_APP_INTERNAL_H_ + +#include"platform.h" +#include"ringbuffer.h" + +#if defined(OS_WIN64) || defined(OS_WIN32) + #include"win32_app.h" +#elif defined(OS_MACOS) + #include"osx_app.h" +#else + #error "platform not supported yet" +#endif + +//--------------------------------------------------------------- +// Input State +//--------------------------------------------------------------- + +typedef struct mp_key_utf8 +{ + u8 labelLen; + char label[8]; +} mp_key_utf8; + +typedef struct mp_key_state +{ + u64 lastUpdate; + u32 transitionCounter; + bool down; + bool clicked; + bool doubleClicked; + +} mp_key_state; + +typedef struct mp_keyboard_state +{ + mp_key_state keys[MP_KEY_COUNT]; + mp_key_mods mods; +} mp_keyboard_state; + +typedef struct mp_mouse_state +{ + u64 lastUpdate; + bool posValid; + vec2 pos; + vec2 delta; + vec2 wheel; + + union + { + mp_key_state buttons[MP_MOUSE_BUTTON_COUNT]; + struct + { + mp_key_state left; + mp_key_state right; + mp_key_state middle; + mp_key_state ext1; + mp_key_state ext2; + }; + }; +} mp_mouse_state; + +enum { MP_INPUT_TEXT_BACKING_SIZE = 64 }; + +typedef struct mp_text_state +{ + u64 lastUpdate; + utf32 backing[MP_INPUT_TEXT_BACKING_SIZE]; + str32 codePoints; +} mp_text_state; + +typedef struct mp_input_state +{ + u64 frameCounter; + mp_keyboard_state keyboard; + mp_mouse_state mouse; + mp_text_state text; +} mp_input_state; + +//--------------------------------------------------------------- +// Window structure +//--------------------------------------------------------------- + +typedef struct mp_frame_stats +{ + f64 start; + f64 workTime; + f64 remainingTime; + f64 targetFramePeriod; +} mp_frame_stats; + +typedef struct mp_window_data +{ + list_elt freeListElt; + u32 generation; + + mp_rect contentRect; + mp_rect frameRect; + mp_window_style style; + + bool shouldClose; //TODO could be in status flags + bool hidden; + bool minimized; + + MP_PLATFORM_WINDOW_DATA +} mp_window_data; + + +//--------------------------------------------------------------- +// Global App State +//--------------------------------------------------------------- + +enum { MP_APP_MAX_WINDOWS = 128 }; + +typedef struct mp_app +{ + bool init; + bool shouldQuit; + bool minimized; + + str8 pendingPathDrop; + mem_arena eventArena; + + ringbuffer eventQueue; + + mp_frame_stats frameStats; + + mp_window_data windowPool[MP_APP_MAX_WINDOWS]; + list_info windowFreeList; + + mp_live_resize_callback liveResizeCallback; + void* liveResizeData; + + mp_input_state inputState; + + mp_key_utf8 keyLabels[512]; + int keyCodes[512]; + int nativeKeys[MP_KEY_COUNT]; + + MP_PLATFORM_APP_DATA +} mp_app; + + +#endif // __MP_APP_INTERNAL_H_ diff --git a/src/osx_gles_surface.h b/src/osx_gles_surface.h index ec51ddd..842ded2 100644 --- a/src/osx_gles_surface.h +++ b/src/osx_gles_surface.h @@ -1,32 +1,32 @@ -/************************************************************//** -* -* @file: osx_gles_surface.cpp -* @author: Martin Fouilleul -* @date: 18/08/2022 -* @revision: -* -*****************************************************************/ -//#include -//#include -#include - -#include -#define EGL_EGLEXT_PROTOTYPES -#include -#include - -#include"graphics_internal.h" - -typedef struct mg_gles_surface -{ - mg_surface_data interface; - - NSView* view; - CALayer* layer; - EGLDisplay eglDisplay; - EGLConfig eglConfig; - EGLContext eglContext; - EGLSurface eglSurface; - EGLint numConfigs; - -} mg_gles_surface; +/************************************************************//** +* +* @file: osx_gles_surface.cpp +* @author: Martin Fouilleul +* @date: 18/08/2022 +* @revision: +* +*****************************************************************/ +//#include +//#include +#include + +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include + +#include"graphics_internal.h" + +typedef struct mg_gles_surface +{ + mg_surface_data interface; + + NSView* view; + CALayer* layer; + EGLDisplay eglDisplay; + EGLConfig eglConfig; + EGLContext eglContext; + EGLSurface eglSurface; + EGLint numConfigs; + +} mg_gles_surface; diff --git a/src/osx_gles_surface.m b/src/osx_gles_surface.m index c495002..490f23d 100644 --- a/src/osx_gles_surface.m +++ b/src/osx_gles_surface.m @@ -1,184 +1,184 @@ -/************************************************************//** -* -* @file: osx_gles_surface.cpp -* @author: Martin Fouilleul -* @date: 18/08/2022 -* @revision: -* -*****************************************************************/ -//#include -//#include -#include - -#include -#define EGL_EGLEXT_PROTOTYPES -#include -#include - -#include"graphics_internal.h" -#include"osx_gles_surface.h" - - -void mg_gles_surface_destroy(mg_surface_data* interface) -{ - ////////////////////////////////////////////////// - //TODO - ////////////////////////////////////////////////// -} - -void mg_gles_surface_prepare(mg_surface_data* interface) -{ - mg_gles_surface* surface = (mg_gles_surface*)interface; - eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext); -} - -void mg_gles_surface_present(mg_surface_data* interface) -{ - - //TODO: eglSwapBuffers seem to never block in macOS (ie eglSwapInterval doesn't seem to have any effect) - // We need to use a CVDisplayLink to time this if we want surface present to block - - mg_gles_surface* surface = (mg_gles_surface*)interface; - eglSwapBuffers(surface->eglDisplay, surface->eglSurface); -} - -void mg_gles_surface_set_frame(mg_surface_data* interface, mp_rect frame) -{ - mg_gles_surface* surface = (mg_gles_surface*)interface; - f32 scale = surface->layer.contentsScale; - CGRect bounds = (CGRect){{frame.x * scale, frame.y * scale}, {.width = frame.w*scale, .height = frame.h*scale}}; - [surface->layer setBounds: bounds]; -} - -mp_rect mg_gles_surface_get_frame(mg_surface_data* interface) -{ - mg_gles_surface* surface = (mg_gles_surface*)interface; - f32 scale = surface->layer.contentsScale; - CGRect bounds = [surface->layer bounds]; - mp_rect rect = {bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale}; - return(rect); -} - - -void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden) -{ - //TODO: doesn't make sense for an offscreen surface? -} - -bool mg_gles_surface_get_hidden(mg_surface_data* interface) -{ - //TODO: doesn't make sense for an offscreen surface? - return(false); -} - -vec2 mg_gles_surface_get_size(mg_surface_data* interface) -{ - mg_gles_surface* surface = (mg_gles_surface*)interface; - CGRect bounds = [surface->layer bounds]; - f32 scale = surface->layer.contentsScale; - vec2 res = {bounds.size.width/scale, bounds.size.height/scale}; - return(res); -} - -mg_surface mg_gles_surface_create_with_view(u32 width, u32 height, NSView* view) -{ - mg_gles_surface* surface = malloc_type(mg_gles_surface); - memset(surface, 0, sizeof(mg_gles_surface)); - - surface->interface.backend = MG_BACKEND_GLES; - surface->interface.destroy = mg_gles_surface_destroy; - surface->interface.prepare = mg_gles_surface_prepare; - surface->interface.present = mg_gles_surface_present; - surface->interface.getFrame = mg_gles_surface_get_frame; - surface->interface.setFrame = mg_gles_surface_set_frame; - surface->interface.getHidden = mg_gles_surface_get_hidden; - surface->interface.setHidden = mg_gles_surface_set_hidden; - - surface->view = view; - - @autoreleasepool - { - surface->layer = [[CALayer alloc] init]; - [surface->layer retain]; - [surface->layer setBounds:CGRectMake(0, 0, width, height)]; - - if(surface->view) - { - [surface->view setWantsLayer: YES]; - surface->view.layer = surface->layer; - } - } - - - EGLAttrib displayAttribs[] = { - //NOTE: we need to explicitly set EGL_PLATFORM_ANGLE_TYPE_ANGLE to EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, because - // EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE defaults to using CGL, and eglSetSwapInterval is broken for this backend - EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, - EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, - EGL_NONE}; - - surface->eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*)EGL_DEFAULT_DISPLAY, displayAttribs); - eglInitialize(surface->eglDisplay, NULL, NULL); - - EGLint const configAttributes[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 24, - EGL_STENCIL_SIZE, 8, - EGL_SAMPLE_BUFFERS, 0, - EGL_SAMPLES, EGL_DONT_CARE, - EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FIXED_EXT, - EGL_NONE }; - - eglChooseConfig(surface->eglDisplay, configAttributes, &surface->eglConfig, 1, &surface->numConfigs); - - EGLint const surfaceAttributes[] = {EGL_NONE}; - surface->eglSurface = eglCreateWindowSurface(surface->eglDisplay, surface->eglConfig, surface->layer, surfaceAttributes); - - eglBindAPI(EGL_OPENGL_ES_API); - EGLint contextAttributes[] = { - EGL_CONTEXT_MAJOR_VERSION_KHR, 3, - EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS - EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE, - EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE, - EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE, - EGL_NONE}; - - surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes); - eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext); - - eglSwapInterval(surface->eglDisplay, 1); - - mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface); - return(handle); -} - -mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height) -{ - return(mg_gles_surface_create_with_view(width, height, 0)); -} - -mg_surface mg_gles_surface_create_for_window(mp_window window) -{ - mg_surface res = mg_surface_nil(); - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - @autoreleasepool - { - NSRect frame = [[windowData->osx.nsWindow contentView] frame]; - NSView* view = [[NSView alloc] initWithFrame: frame]; - - res = mg_gles_surface_create_with_view(frame.size.width, frame.size.height, view); - - if(!mg_surface_is_nil(res)) - { - [[windowData->osx.nsWindow contentView] addSubview: view]; - } - } - } - return(res); -} +/************************************************************//** +* +* @file: osx_gles_surface.cpp +* @author: Martin Fouilleul +* @date: 18/08/2022 +* @revision: +* +*****************************************************************/ +//#include +//#include +#include + +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include + +#include"graphics_internal.h" +#include"osx_gles_surface.h" + + +void mg_gles_surface_destroy(mg_surface_data* interface) +{ + ////////////////////////////////////////////////// + //TODO + ////////////////////////////////////////////////// +} + +void mg_gles_surface_prepare(mg_surface_data* interface) +{ + mg_gles_surface* surface = (mg_gles_surface*)interface; + eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext); +} + +void mg_gles_surface_present(mg_surface_data* interface) +{ + + //TODO: eglSwapBuffers seem to never block in macOS (ie eglSwapInterval doesn't seem to have any effect) + // We need to use a CVDisplayLink to time this if we want surface present to block + + mg_gles_surface* surface = (mg_gles_surface*)interface; + eglSwapBuffers(surface->eglDisplay, surface->eglSurface); +} + +void mg_gles_surface_set_frame(mg_surface_data* interface, mp_rect frame) +{ + mg_gles_surface* surface = (mg_gles_surface*)interface; + f32 scale = surface->layer.contentsScale; + CGRect bounds = (CGRect){{frame.x * scale, frame.y * scale}, {.width = frame.w*scale, .height = frame.h*scale}}; + [surface->layer setBounds: bounds]; +} + +mp_rect mg_gles_surface_get_frame(mg_surface_data* interface) +{ + mg_gles_surface* surface = (mg_gles_surface*)interface; + f32 scale = surface->layer.contentsScale; + CGRect bounds = [surface->layer bounds]; + mp_rect rect = {bounds.origin.x / scale, bounds.origin.y / scale, bounds.size.width / scale, bounds.size.height / scale}; + return(rect); +} + + +void mg_gles_surface_set_hidden(mg_surface_data* interface, bool hidden) +{ + //TODO: doesn't make sense for an offscreen surface? +} + +bool mg_gles_surface_get_hidden(mg_surface_data* interface) +{ + //TODO: doesn't make sense for an offscreen surface? + return(false); +} + +vec2 mg_gles_surface_get_size(mg_surface_data* interface) +{ + mg_gles_surface* surface = (mg_gles_surface*)interface; + CGRect bounds = [surface->layer bounds]; + f32 scale = surface->layer.contentsScale; + vec2 res = {bounds.size.width/scale, bounds.size.height/scale}; + return(res); +} + +mg_surface mg_gles_surface_create_with_view(u32 width, u32 height, NSView* view) +{ + mg_gles_surface* surface = malloc_type(mg_gles_surface); + memset(surface, 0, sizeof(mg_gles_surface)); + + surface->interface.backend = MG_BACKEND_GLES; + surface->interface.destroy = mg_gles_surface_destroy; + surface->interface.prepare = mg_gles_surface_prepare; + surface->interface.present = mg_gles_surface_present; + surface->interface.getFrame = mg_gles_surface_get_frame; + surface->interface.setFrame = mg_gles_surface_set_frame; + surface->interface.getHidden = mg_gles_surface_get_hidden; + surface->interface.setHidden = mg_gles_surface_set_hidden; + + surface->view = view; + + @autoreleasepool + { + surface->layer = [[CALayer alloc] init]; + [surface->layer retain]; + [surface->layer setBounds:CGRectMake(0, 0, width, height)]; + + if(surface->view) + { + [surface->view setWantsLayer: YES]; + surface->view.layer = surface->layer; + } + } + + + EGLAttrib displayAttribs[] = { + //NOTE: we need to explicitly set EGL_PLATFORM_ANGLE_TYPE_ANGLE to EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, because + // EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE defaults to using CGL, and eglSetSwapInterval is broken for this backend + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, + EGL_NONE}; + + surface->eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*)EGL_DEFAULT_DISPLAY, displayAttribs); + eglInitialize(surface->eglDisplay, NULL, NULL); + + EGLint const configAttributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, 0, + EGL_SAMPLES, EGL_DONT_CARE, + EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FIXED_EXT, + EGL_NONE }; + + eglChooseConfig(surface->eglDisplay, configAttributes, &surface->eglConfig, 1, &surface->numConfigs); + + EGLint const surfaceAttributes[] = {EGL_NONE}; + surface->eglSurface = eglCreateWindowSurface(surface->eglDisplay, surface->eglConfig, surface->layer, surfaceAttributes); + + eglBindAPI(EGL_OPENGL_ES_API); + EGLint contextAttributes[] = { + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS + EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE, + EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE, + EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE, + EGL_NONE}; + + surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes); + eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext); + + eglSwapInterval(surface->eglDisplay, 1); + + mg_surface handle = mg_surface_alloc_handle((mg_surface_data*)surface); + return(handle); +} + +mg_surface mg_gles_surface_create_offscreen(u32 width, u32 height) +{ + return(mg_gles_surface_create_with_view(width, height, 0)); +} + +mg_surface mg_gles_surface_create_for_window(mp_window window) +{ + mg_surface res = mg_surface_nil(); + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + @autoreleasepool + { + NSRect frame = [[windowData->osx.nsWindow contentView] frame]; + NSView* view = [[NSView alloc] initWithFrame: frame]; + + res = mg_gles_surface_create_with_view(frame.size.width, frame.size.height, view); + + if(!mg_surface_is_nil(res)) + { + [[windowData->osx.nsWindow contentView] addSubview: view]; + } + } + } + return(res); +} diff --git a/src/win32_app.c b/src/win32_app.c index 5ae08ab..286ef69 100644 --- a/src/win32_app.c +++ b/src/win32_app.c @@ -1,724 +1,724 @@ -/************************************************************//** -* -* @file: win32_app.c -* @author: Martin Fouilleul -* @date: 16/12/2022 -* @revision: -* -*****************************************************************/ - -#include"mp_app.c" - -#define LOG_SUBSYSTEM "Application" - -void mp_init_keys() -{ - memset(__mpApp.keyCodes, MP_KEY_UNKNOWN, 256*sizeof(int)); - - __mpApp.keyCodes[0x00B] = MP_KEY_0; - __mpApp.keyCodes[0x002] = MP_KEY_1; - __mpApp.keyCodes[0x003] = MP_KEY_2; - __mpApp.keyCodes[0x004] = MP_KEY_3; - __mpApp.keyCodes[0x005] = MP_KEY_4; - __mpApp.keyCodes[0x006] = MP_KEY_5; - __mpApp.keyCodes[0x007] = MP_KEY_6; - __mpApp.keyCodes[0x008] = MP_KEY_7; - __mpApp.keyCodes[0x009] = MP_KEY_8; - __mpApp.keyCodes[0x00A] = MP_KEY_9; - __mpApp.keyCodes[0x01E] = MP_KEY_A; - __mpApp.keyCodes[0x030] = MP_KEY_B; - __mpApp.keyCodes[0x02E] = MP_KEY_C; - __mpApp.keyCodes[0x020] = MP_KEY_D; - __mpApp.keyCodes[0x012] = MP_KEY_E; - __mpApp.keyCodes[0x021] = MP_KEY_F; - __mpApp.keyCodes[0x022] = MP_KEY_G; - __mpApp.keyCodes[0x023] = MP_KEY_H; - __mpApp.keyCodes[0x017] = MP_KEY_I; - __mpApp.keyCodes[0x024] = MP_KEY_J; - __mpApp.keyCodes[0x025] = MP_KEY_K; - __mpApp.keyCodes[0x026] = MP_KEY_L; - __mpApp.keyCodes[0x032] = MP_KEY_M; - __mpApp.keyCodes[0x031] = MP_KEY_N; - __mpApp.keyCodes[0x018] = MP_KEY_O; - __mpApp.keyCodes[0x019] = MP_KEY_P; - __mpApp.keyCodes[0x010] = MP_KEY_Q; - __mpApp.keyCodes[0x013] = MP_KEY_R; - __mpApp.keyCodes[0x01F] = MP_KEY_S; - __mpApp.keyCodes[0x014] = MP_KEY_T; - __mpApp.keyCodes[0x016] = MP_KEY_U; - __mpApp.keyCodes[0x02F] = MP_KEY_V; - __mpApp.keyCodes[0x011] = MP_KEY_W; - __mpApp.keyCodes[0x02D] = MP_KEY_X; - __mpApp.keyCodes[0x015] = MP_KEY_Y; - __mpApp.keyCodes[0x02C] = MP_KEY_Z; - __mpApp.keyCodes[0x028] = MP_KEY_APOSTROPHE; - __mpApp.keyCodes[0x02B] = MP_KEY_BACKSLASH; - __mpApp.keyCodes[0x033] = MP_KEY_COMMA; - __mpApp.keyCodes[0x00D] = MP_KEY_EQUAL; - __mpApp.keyCodes[0x029] = MP_KEY_GRAVE_ACCENT; - __mpApp.keyCodes[0x01A] = MP_KEY_LEFT_BRACKET; - __mpApp.keyCodes[0x00C] = MP_KEY_MINUS; - __mpApp.keyCodes[0x034] = MP_KEY_PERIOD; - __mpApp.keyCodes[0x01B] = MP_KEY_RIGHT_BRACKET; - __mpApp.keyCodes[0x027] = MP_KEY_SEMICOLON; - __mpApp.keyCodes[0x035] = MP_KEY_SLASH; - __mpApp.keyCodes[0x056] = MP_KEY_WORLD_2; - __mpApp.keyCodes[0x00E] = MP_KEY_BACKSPACE; - __mpApp.keyCodes[0x153] = MP_KEY_DELETE; - __mpApp.keyCodes[0x14F] = MP_KEY_END; - __mpApp.keyCodes[0x01C] = MP_KEY_ENTER; - __mpApp.keyCodes[0x001] = MP_KEY_ESCAPE; - __mpApp.keyCodes[0x147] = MP_KEY_HOME; - __mpApp.keyCodes[0x152] = MP_KEY_INSERT; - __mpApp.keyCodes[0x15D] = MP_KEY_MENU; - __mpApp.keyCodes[0x151] = MP_KEY_PAGE_DOWN; - __mpApp.keyCodes[0x149] = MP_KEY_PAGE_UP; - __mpApp.keyCodes[0x045] = MP_KEY_PAUSE; - __mpApp.keyCodes[0x146] = MP_KEY_PAUSE; - __mpApp.keyCodes[0x039] = MP_KEY_SPACE; - __mpApp.keyCodes[0x00F] = MP_KEY_TAB; - __mpApp.keyCodes[0x03A] = MP_KEY_CAPS_LOCK; - __mpApp.keyCodes[0x145] = MP_KEY_NUM_LOCK; - __mpApp.keyCodes[0x046] = MP_KEY_SCROLL_LOCK; - __mpApp.keyCodes[0x03B] = MP_KEY_F1; - __mpApp.keyCodes[0x03C] = MP_KEY_F2; - __mpApp.keyCodes[0x03D] = MP_KEY_F3; - __mpApp.keyCodes[0x03E] = MP_KEY_F4; - __mpApp.keyCodes[0x03F] = MP_KEY_F5; - __mpApp.keyCodes[0x040] = MP_KEY_F6; - __mpApp.keyCodes[0x041] = MP_KEY_F7; - __mpApp.keyCodes[0x042] = MP_KEY_F8; - __mpApp.keyCodes[0x043] = MP_KEY_F9; - __mpApp.keyCodes[0x044] = MP_KEY_F10; - __mpApp.keyCodes[0x057] = MP_KEY_F11; - __mpApp.keyCodes[0x058] = MP_KEY_F12; - __mpApp.keyCodes[0x064] = MP_KEY_F13; - __mpApp.keyCodes[0x065] = MP_KEY_F14; - __mpApp.keyCodes[0x066] = MP_KEY_F15; - __mpApp.keyCodes[0x067] = MP_KEY_F16; - __mpApp.keyCodes[0x068] = MP_KEY_F17; - __mpApp.keyCodes[0x069] = MP_KEY_F18; - __mpApp.keyCodes[0x06A] = MP_KEY_F19; - __mpApp.keyCodes[0x06B] = MP_KEY_F20; - __mpApp.keyCodes[0x06C] = MP_KEY_F21; - __mpApp.keyCodes[0x06D] = MP_KEY_F22; - __mpApp.keyCodes[0x06E] = MP_KEY_F23; - __mpApp.keyCodes[0x076] = MP_KEY_F24; - __mpApp.keyCodes[0x038] = MP_KEY_LEFT_ALT; - __mpApp.keyCodes[0x01D] = MP_KEY_LEFT_CONTROL; - __mpApp.keyCodes[0x02A] = MP_KEY_LEFT_SHIFT; - __mpApp.keyCodes[0x15B] = MP_KEY_LEFT_SUPER; - __mpApp.keyCodes[0x137] = MP_KEY_PRINT_SCREEN; - __mpApp.keyCodes[0x138] = MP_KEY_RIGHT_ALT; - __mpApp.keyCodes[0x11D] = MP_KEY_RIGHT_CONTROL; - __mpApp.keyCodes[0x036] = MP_KEY_RIGHT_SHIFT; - __mpApp.keyCodes[0x15C] = MP_KEY_RIGHT_SUPER; - __mpApp.keyCodes[0x150] = MP_KEY_DOWN; - __mpApp.keyCodes[0x14B] = MP_KEY_LEFT; - __mpApp.keyCodes[0x14D] = MP_KEY_RIGHT; - __mpApp.keyCodes[0x148] = MP_KEY_UP; - __mpApp.keyCodes[0x052] = MP_KEY_KP_0; - __mpApp.keyCodes[0x04F] = MP_KEY_KP_1; - __mpApp.keyCodes[0x050] = MP_KEY_KP_2; - __mpApp.keyCodes[0x051] = MP_KEY_KP_3; - __mpApp.keyCodes[0x04B] = MP_KEY_KP_4; - __mpApp.keyCodes[0x04C] = MP_KEY_KP_5; - __mpApp.keyCodes[0x04D] = MP_KEY_KP_6; - __mpApp.keyCodes[0x047] = MP_KEY_KP_7; - __mpApp.keyCodes[0x048] = MP_KEY_KP_8; - __mpApp.keyCodes[0x049] = MP_KEY_KP_9; - __mpApp.keyCodes[0x04E] = MP_KEY_KP_ADD; - __mpApp.keyCodes[0x053] = MP_KEY_KP_DECIMAL; - __mpApp.keyCodes[0x135] = MP_KEY_KP_DIVIDE; - __mpApp.keyCodes[0x11C] = MP_KEY_KP_ENTER; - __mpApp.keyCodes[0x037] = MP_KEY_KP_MULTIPLY; - __mpApp.keyCodes[0x04A] = MP_KEY_KP_SUBTRACT; - - memset(__mpApp.nativeKeys, 0, sizeof(int)*MP_KEY_COUNT); - for(int nativeKey=0; nativeKey<256; nativeKey++) - { - mp_key_code mpKey = __mpApp.keyCodes[nativeKey]; - if(mpKey) - { - __mpApp.nativeKeys[mpKey] = nativeKey; - } - } -} - -void mp_init() -{ - if(!__mpApp.init) - { - memset(&__mpApp, 0, sizeof(__mpApp)); - - mp_init_common(); - mp_init_keys(); - - __mpApp.win32.savedConsoleCodePage = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - } -} - -void mp_terminate() -{ - if(__mpApp.init) - { - SetConsoleOutputCP(__mpApp.win32.savedConsoleCodePage); - - mp_terminate_common(); - __mpApp = (mp_app){0}; - } -} - -static mp_key_code mp_convert_win32_key(int code) -{ - return(__mpApp.keyCodes[code]); -} - -static mp_key_mods mp_get_mod_keys() -{ - mp_key_mods mods = 0; - if(GetKeyState(VK_SHIFT) & 0x8000) - { - mods |= MP_KEYMOD_SHIFT; - } - if(GetKeyState(VK_CONTROL) & 0x8000) - { - mods |= MP_KEYMOD_CTRL; - } - if(GetKeyState(VK_MENU) & 0x8000) - { - mods |= MP_KEYMOD_ALT; - } - if((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) - { - mods |= MP_KEYMOD_CMD; - } - return(mods); -} - -static void process_mouse_event(mp_window_data* window, mp_key_action action, mp_key_code button) -{ - if(action == MP_KEY_PRESS) - { - if(!__mpApp.win32.mouseCaptureMask) - { - SetCapture(window->win32.hWnd); - } - __mpApp.win32.mouseCaptureMask |= (1<bottom, rect->left, rect->top - rect->bottom, rect->right - rect->left}; - mp_queue_event(&event); - } break; - - case WM_MOVING: - { - RECT* rect = (RECT*)lParam; - - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_WINDOW_MOVE; - event.frame.rect = (mp_rect){rect->bottom, rect->left, rect->top - rect->bottom, rect->right - rect->left}; - mp_queue_event(&event); - } break; - - case WM_SETFOCUS: - { - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_WINDOW_FOCUS; - mp_queue_event(&event); - } break; - - case WM_KILLFOCUS: - { - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_WINDOW_UNFOCUS; - mp_queue_event(&event); - } break; - - case WM_SIZE: - { - bool minimized = (wParam == SIZE_MINIMIZED); - if(minimized != mpWindow->minimized) - { - mpWindow->minimized = minimized; - - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - - if(minimized) - { - event.type = MP_EVENT_WINDOW_HIDE; - } - else if(mpWindow->minimized) - { - event.type = MP_EVENT_WINDOW_SHOW; - } - mp_queue_event(&event); - } - } break; - - case WM_LBUTTONDOWN: - { - process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_LEFT); - } break; - - case WM_RBUTTONDOWN: - { - process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_RIGHT); - } break; - - case WM_MBUTTONDOWN: - { - process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_MIDDLE); - } break; - - case WM_LBUTTONUP: - { - process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_LEFT); - } break; - - case WM_RBUTTONUP: - { - process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_RIGHT); - } break; - - case WM_MBUTTONUP: - { - process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_MIDDLE); - } break; - - case WM_MOUSEMOVE: - { - RECT rect; - GetClientRect(mpWindow->win32.hWnd, &rect); - - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_MOUSE_MOVE; - event.move.x = LOWORD(lParam); - event.move.y = rect.bottom - HIWORD(lParam); - - if(__mpApp.inputState.mouse.posValid) - { - event.move.deltaX = event.move.x - __mpApp.inputState.mouse.pos.x; - event.move.deltaY = event.move.y - __mpApp.inputState.mouse.pos.y; - } - else - { - __mpApp.inputState.mouse.posValid = true; - } - - if(!__mpApp.win32.mouseTracked) - { - __mpApp.win32.mouseTracked = true; - - TRACKMOUSEEVENT track; - memset(&track, 0, sizeof(track)); - track.cbSize = sizeof(track); - track.dwFlags = TME_LEAVE; - track.hwndTrack = mpWindow->win32.hWnd; - TrackMouseEvent(&track); - - mp_event enter = {.window = event.window, - .type = MP_EVENT_MOUSE_ENTER, - .move.x = event.move.x, - .move.y = event.move.y}; - mp_queue_event(&enter); - } - __mpApp.inputState.mouse.pos.x = event.move.x; - __mpApp.inputState.mouse.pos.y = event.move.y; - - mp_queue_event(&event); - } break; - - case WM_MOUSELEAVE: - { - __mpApp.win32.mouseTracked = false; - - if(!__mpApp.win32.mouseCaptureMask) - { - __mpApp.inputState.mouse.posValid = false; - } - - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_MOUSE_LEAVE; - mp_queue_event(&event); - } break; - - case WM_MOUSEWHEEL: - { - process_wheel_event(mpWindow, 0, (float)((i16)HIWORD(wParam))); - } break; - - case WM_MOUSEHWHEEL: - { - process_wheel_event(mpWindow, (float)((i16)HIWORD(wParam)), 0); - } break; - - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - { - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_KEYBOARD_KEY; - event.key.action = (lParam & 0x40000000) ? MP_KEY_REPEAT : MP_KEY_PRESS; - event.key.code = mp_convert_win32_key(HIWORD(lParam) & 0x1ff); - event.key.mods = mp_get_mod_keys(); - mp_queue_event(&event); - } break; - - case WM_KEYUP: - case WM_SYSKEYUP: - { - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_KEYBOARD_KEY; - event.key.action = MP_KEY_RELEASE; - event.key.code = mp_convert_win32_key(HIWORD(lParam) & 0x1ff); - event.key.mods = mp_get_mod_keys(); - mp_queue_event(&event); - } break; - - case WM_CHAR: - { - mp_event event = {0}; - event.window = mp_window_handle_from_ptr(mpWindow); - event.type = MP_EVENT_KEYBOARD_CHAR; - event.character.codepoint = (utf32)wParam; - str8 seq = utf8_encode(event.character.sequence, event.character.codepoint); - event.character.seqLen = seq.len; - mp_queue_event(&event); - } break; - - case WM_DROPFILES: - { - //TODO - } break; - - default: - { - result = DefWindowProc(windowHandle, message, wParam, lParam); - } break; - } - - return(result); -} - -//-------------------------------------------------------------------- -// app management -//-------------------------------------------------------------------- - -bool mp_should_quit() -{ - return(__mpApp.shouldQuit); -} - -void mp_cancel_quit() -{ - __mpApp.shouldQuit = false; -} - -void mp_request_quit() -{ - __mpApp.shouldQuit = true; -} - -void mp_pump_events(f64 timeout) -{ - MSG message; - while(PeekMessage(&message, 0, 0, 0, PM_REMOVE)) - { - TranslateMessage(&message); - DispatchMessage(&message); - } - __mpApp.inputState.frameCounter++; -} - -//-------------------------------------------------------------------- -// window management -//-------------------------------------------------------------------- - -mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style style) -{ - WNDCLASS windowClass = {.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, - .lpfnWndProc = WinProc, - .hInstance = GetModuleHandleW(NULL), - .lpszClassName = "ApplicationWindowClass", - .hCursor = LoadCursor(0, IDC_ARROW)}; - - if(!RegisterClass(&windowClass)) - { - //TODO: error - goto quit; - } - - HWND windowHandle = CreateWindow("ApplicationWindowClass", "Test Window", - WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, - 800, 600, - 0, 0, windowClass.hInstance, 0); - - if(!windowHandle) - { - //TODO: error - goto quit; - } - - UpdateWindow(windowHandle); - - //TODO: return wrapped window - quit:; - mp_window_data* window = mp_window_alloc(); - window->win32.hWnd = windowHandle; - - SetPropW(windowHandle, L"MilePost", window); - - return(mp_window_handle_from_ptr(window)); -} - -void mp_window_destroy(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - DestroyWindow(windowData->win32.hWnd); - //TODO: check when to unregister class - - mp_window_recycle_ptr(windowData); - } -} - -void* mp_window_native_pointer(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - return(windowData->win32.hWnd); - } - else - { - return(0); - } -} - -bool mp_window_should_close(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - return(windowData->shouldClose); - } - else - { - return(false); - } -} - -void mp_window_request_close(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - windowData->shouldClose = true; - PostMessage(windowData->win32.hWnd, WM_CLOSE, 0, 0); - } -} - -void mp_window_cancel_close(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - windowData->shouldClose = false; - } -} - - -bool mp_window_is_hidden(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - return(IsWindowVisible(windowData->win32.hWnd)); - } - else - { - return(false); - } -} - -void mp_window_hide(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - ShowWindow(windowData->win32.hWnd, SW_HIDE); - } -} - -void mp_window_show(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - ShowWindow(windowData->win32.hWnd, SW_NORMAL); - } -} - -bool mp_window_is_minimized(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - return(windowData->minimized); - } - else - { - return(false); - } -} - -void mp_window_minimize(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - ShowWindow(windowData->win32.hWnd, SW_MINIMIZE); - } -} - -void mp_window_maximize(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - ShowWindow(windowData->win32.hWnd, SW_MAXIMIZE); - } -} - -void mp_window_restore(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - ShowWindow(windowData->win32.hWnd, SW_RESTORE); - } -} - -bool mp_window_has_focus(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - return(GetActiveWindow() == windowData->win32.hWnd); - } - else - { - return(false); - } -} - -void mp_window_focus(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - SetFocus(windowData->win32.hWnd); - } -} - -void mp_window_unfocus(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - SetFocus(0); - } -} - -void mp_window_send_to_back(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - SetWindowPos(windowData->win32.hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } -} - -void mp_window_bring_to_front(mp_window window) -{ - mp_window_data* windowData = mp_window_ptr_from_handle(window); - if(windowData) - { - if(!IsWindowVisible(windowData->win32.hWnd)) - { - ShowWindow(windowData->win32.hWnd, SW_NORMAL); - } - SetWindowPos(windowData->win32.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } -} - -#undef LOG_SUBSYSTEM +/************************************************************//** +* +* @file: win32_app.c +* @author: Martin Fouilleul +* @date: 16/12/2022 +* @revision: +* +*****************************************************************/ + +#include"mp_app.c" + +#define LOG_SUBSYSTEM "Application" + +void mp_init_keys() +{ + memset(__mpApp.keyCodes, MP_KEY_UNKNOWN, 256*sizeof(int)); + + __mpApp.keyCodes[0x00B] = MP_KEY_0; + __mpApp.keyCodes[0x002] = MP_KEY_1; + __mpApp.keyCodes[0x003] = MP_KEY_2; + __mpApp.keyCodes[0x004] = MP_KEY_3; + __mpApp.keyCodes[0x005] = MP_KEY_4; + __mpApp.keyCodes[0x006] = MP_KEY_5; + __mpApp.keyCodes[0x007] = MP_KEY_6; + __mpApp.keyCodes[0x008] = MP_KEY_7; + __mpApp.keyCodes[0x009] = MP_KEY_8; + __mpApp.keyCodes[0x00A] = MP_KEY_9; + __mpApp.keyCodes[0x01E] = MP_KEY_A; + __mpApp.keyCodes[0x030] = MP_KEY_B; + __mpApp.keyCodes[0x02E] = MP_KEY_C; + __mpApp.keyCodes[0x020] = MP_KEY_D; + __mpApp.keyCodes[0x012] = MP_KEY_E; + __mpApp.keyCodes[0x021] = MP_KEY_F; + __mpApp.keyCodes[0x022] = MP_KEY_G; + __mpApp.keyCodes[0x023] = MP_KEY_H; + __mpApp.keyCodes[0x017] = MP_KEY_I; + __mpApp.keyCodes[0x024] = MP_KEY_J; + __mpApp.keyCodes[0x025] = MP_KEY_K; + __mpApp.keyCodes[0x026] = MP_KEY_L; + __mpApp.keyCodes[0x032] = MP_KEY_M; + __mpApp.keyCodes[0x031] = MP_KEY_N; + __mpApp.keyCodes[0x018] = MP_KEY_O; + __mpApp.keyCodes[0x019] = MP_KEY_P; + __mpApp.keyCodes[0x010] = MP_KEY_Q; + __mpApp.keyCodes[0x013] = MP_KEY_R; + __mpApp.keyCodes[0x01F] = MP_KEY_S; + __mpApp.keyCodes[0x014] = MP_KEY_T; + __mpApp.keyCodes[0x016] = MP_KEY_U; + __mpApp.keyCodes[0x02F] = MP_KEY_V; + __mpApp.keyCodes[0x011] = MP_KEY_W; + __mpApp.keyCodes[0x02D] = MP_KEY_X; + __mpApp.keyCodes[0x015] = MP_KEY_Y; + __mpApp.keyCodes[0x02C] = MP_KEY_Z; + __mpApp.keyCodes[0x028] = MP_KEY_APOSTROPHE; + __mpApp.keyCodes[0x02B] = MP_KEY_BACKSLASH; + __mpApp.keyCodes[0x033] = MP_KEY_COMMA; + __mpApp.keyCodes[0x00D] = MP_KEY_EQUAL; + __mpApp.keyCodes[0x029] = MP_KEY_GRAVE_ACCENT; + __mpApp.keyCodes[0x01A] = MP_KEY_LEFT_BRACKET; + __mpApp.keyCodes[0x00C] = MP_KEY_MINUS; + __mpApp.keyCodes[0x034] = MP_KEY_PERIOD; + __mpApp.keyCodes[0x01B] = MP_KEY_RIGHT_BRACKET; + __mpApp.keyCodes[0x027] = MP_KEY_SEMICOLON; + __mpApp.keyCodes[0x035] = MP_KEY_SLASH; + __mpApp.keyCodes[0x056] = MP_KEY_WORLD_2; + __mpApp.keyCodes[0x00E] = MP_KEY_BACKSPACE; + __mpApp.keyCodes[0x153] = MP_KEY_DELETE; + __mpApp.keyCodes[0x14F] = MP_KEY_END; + __mpApp.keyCodes[0x01C] = MP_KEY_ENTER; + __mpApp.keyCodes[0x001] = MP_KEY_ESCAPE; + __mpApp.keyCodes[0x147] = MP_KEY_HOME; + __mpApp.keyCodes[0x152] = MP_KEY_INSERT; + __mpApp.keyCodes[0x15D] = MP_KEY_MENU; + __mpApp.keyCodes[0x151] = MP_KEY_PAGE_DOWN; + __mpApp.keyCodes[0x149] = MP_KEY_PAGE_UP; + __mpApp.keyCodes[0x045] = MP_KEY_PAUSE; + __mpApp.keyCodes[0x146] = MP_KEY_PAUSE; + __mpApp.keyCodes[0x039] = MP_KEY_SPACE; + __mpApp.keyCodes[0x00F] = MP_KEY_TAB; + __mpApp.keyCodes[0x03A] = MP_KEY_CAPS_LOCK; + __mpApp.keyCodes[0x145] = MP_KEY_NUM_LOCK; + __mpApp.keyCodes[0x046] = MP_KEY_SCROLL_LOCK; + __mpApp.keyCodes[0x03B] = MP_KEY_F1; + __mpApp.keyCodes[0x03C] = MP_KEY_F2; + __mpApp.keyCodes[0x03D] = MP_KEY_F3; + __mpApp.keyCodes[0x03E] = MP_KEY_F4; + __mpApp.keyCodes[0x03F] = MP_KEY_F5; + __mpApp.keyCodes[0x040] = MP_KEY_F6; + __mpApp.keyCodes[0x041] = MP_KEY_F7; + __mpApp.keyCodes[0x042] = MP_KEY_F8; + __mpApp.keyCodes[0x043] = MP_KEY_F9; + __mpApp.keyCodes[0x044] = MP_KEY_F10; + __mpApp.keyCodes[0x057] = MP_KEY_F11; + __mpApp.keyCodes[0x058] = MP_KEY_F12; + __mpApp.keyCodes[0x064] = MP_KEY_F13; + __mpApp.keyCodes[0x065] = MP_KEY_F14; + __mpApp.keyCodes[0x066] = MP_KEY_F15; + __mpApp.keyCodes[0x067] = MP_KEY_F16; + __mpApp.keyCodes[0x068] = MP_KEY_F17; + __mpApp.keyCodes[0x069] = MP_KEY_F18; + __mpApp.keyCodes[0x06A] = MP_KEY_F19; + __mpApp.keyCodes[0x06B] = MP_KEY_F20; + __mpApp.keyCodes[0x06C] = MP_KEY_F21; + __mpApp.keyCodes[0x06D] = MP_KEY_F22; + __mpApp.keyCodes[0x06E] = MP_KEY_F23; + __mpApp.keyCodes[0x076] = MP_KEY_F24; + __mpApp.keyCodes[0x038] = MP_KEY_LEFT_ALT; + __mpApp.keyCodes[0x01D] = MP_KEY_LEFT_CONTROL; + __mpApp.keyCodes[0x02A] = MP_KEY_LEFT_SHIFT; + __mpApp.keyCodes[0x15B] = MP_KEY_LEFT_SUPER; + __mpApp.keyCodes[0x137] = MP_KEY_PRINT_SCREEN; + __mpApp.keyCodes[0x138] = MP_KEY_RIGHT_ALT; + __mpApp.keyCodes[0x11D] = MP_KEY_RIGHT_CONTROL; + __mpApp.keyCodes[0x036] = MP_KEY_RIGHT_SHIFT; + __mpApp.keyCodes[0x15C] = MP_KEY_RIGHT_SUPER; + __mpApp.keyCodes[0x150] = MP_KEY_DOWN; + __mpApp.keyCodes[0x14B] = MP_KEY_LEFT; + __mpApp.keyCodes[0x14D] = MP_KEY_RIGHT; + __mpApp.keyCodes[0x148] = MP_KEY_UP; + __mpApp.keyCodes[0x052] = MP_KEY_KP_0; + __mpApp.keyCodes[0x04F] = MP_KEY_KP_1; + __mpApp.keyCodes[0x050] = MP_KEY_KP_2; + __mpApp.keyCodes[0x051] = MP_KEY_KP_3; + __mpApp.keyCodes[0x04B] = MP_KEY_KP_4; + __mpApp.keyCodes[0x04C] = MP_KEY_KP_5; + __mpApp.keyCodes[0x04D] = MP_KEY_KP_6; + __mpApp.keyCodes[0x047] = MP_KEY_KP_7; + __mpApp.keyCodes[0x048] = MP_KEY_KP_8; + __mpApp.keyCodes[0x049] = MP_KEY_KP_9; + __mpApp.keyCodes[0x04E] = MP_KEY_KP_ADD; + __mpApp.keyCodes[0x053] = MP_KEY_KP_DECIMAL; + __mpApp.keyCodes[0x135] = MP_KEY_KP_DIVIDE; + __mpApp.keyCodes[0x11C] = MP_KEY_KP_ENTER; + __mpApp.keyCodes[0x037] = MP_KEY_KP_MULTIPLY; + __mpApp.keyCodes[0x04A] = MP_KEY_KP_SUBTRACT; + + memset(__mpApp.nativeKeys, 0, sizeof(int)*MP_KEY_COUNT); + for(int nativeKey=0; nativeKey<256; nativeKey++) + { + mp_key_code mpKey = __mpApp.keyCodes[nativeKey]; + if(mpKey) + { + __mpApp.nativeKeys[mpKey] = nativeKey; + } + } +} + +void mp_init() +{ + if(!__mpApp.init) + { + memset(&__mpApp, 0, sizeof(__mpApp)); + + mp_init_common(); + mp_init_keys(); + + __mpApp.win32.savedConsoleCodePage = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + } +} + +void mp_terminate() +{ + if(__mpApp.init) + { + SetConsoleOutputCP(__mpApp.win32.savedConsoleCodePage); + + mp_terminate_common(); + __mpApp = (mp_app){0}; + } +} + +static mp_key_code mp_convert_win32_key(int code) +{ + return(__mpApp.keyCodes[code]); +} + +static mp_key_mods mp_get_mod_keys() +{ + mp_key_mods mods = 0; + if(GetKeyState(VK_SHIFT) & 0x8000) + { + mods |= MP_KEYMOD_SHIFT; + } + if(GetKeyState(VK_CONTROL) & 0x8000) + { + mods |= MP_KEYMOD_CTRL; + } + if(GetKeyState(VK_MENU) & 0x8000) + { + mods |= MP_KEYMOD_ALT; + } + if((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) + { + mods |= MP_KEYMOD_CMD; + } + return(mods); +} + +static void process_mouse_event(mp_window_data* window, mp_key_action action, mp_key_code button) +{ + if(action == MP_KEY_PRESS) + { + if(!__mpApp.win32.mouseCaptureMask) + { + SetCapture(window->win32.hWnd); + } + __mpApp.win32.mouseCaptureMask |= (1<bottom, rect->left, rect->top - rect->bottom, rect->right - rect->left}; + mp_queue_event(&event); + } break; + + case WM_MOVING: + { + RECT* rect = (RECT*)lParam; + + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_MOVE; + event.frame.rect = (mp_rect){rect->bottom, rect->left, rect->top - rect->bottom, rect->right - rect->left}; + mp_queue_event(&event); + } break; + + case WM_SETFOCUS: + { + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_FOCUS; + mp_queue_event(&event); + } break; + + case WM_KILLFOCUS: + { + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_UNFOCUS; + mp_queue_event(&event); + } break; + + case WM_SIZE: + { + bool minimized = (wParam == SIZE_MINIMIZED); + if(minimized != mpWindow->minimized) + { + mpWindow->minimized = minimized; + + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + + if(minimized) + { + event.type = MP_EVENT_WINDOW_HIDE; + } + else if(mpWindow->minimized) + { + event.type = MP_EVENT_WINDOW_SHOW; + } + mp_queue_event(&event); + } + } break; + + case WM_LBUTTONDOWN: + { + process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_LEFT); + } break; + + case WM_RBUTTONDOWN: + { + process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_RIGHT); + } break; + + case WM_MBUTTONDOWN: + { + process_mouse_event(mpWindow, MP_KEY_PRESS, MP_MOUSE_MIDDLE); + } break; + + case WM_LBUTTONUP: + { + process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_LEFT); + } break; + + case WM_RBUTTONUP: + { + process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_RIGHT); + } break; + + case WM_MBUTTONUP: + { + process_mouse_event(mpWindow, MP_KEY_RELEASE, MP_MOUSE_MIDDLE); + } break; + + case WM_MOUSEMOVE: + { + RECT rect; + GetClientRect(mpWindow->win32.hWnd, &rect); + + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_MOUSE_MOVE; + event.move.x = LOWORD(lParam); + event.move.y = rect.bottom - HIWORD(lParam); + + if(__mpApp.inputState.mouse.posValid) + { + event.move.deltaX = event.move.x - __mpApp.inputState.mouse.pos.x; + event.move.deltaY = event.move.y - __mpApp.inputState.mouse.pos.y; + } + else + { + __mpApp.inputState.mouse.posValid = true; + } + + if(!__mpApp.win32.mouseTracked) + { + __mpApp.win32.mouseTracked = true; + + TRACKMOUSEEVENT track; + memset(&track, 0, sizeof(track)); + track.cbSize = sizeof(track); + track.dwFlags = TME_LEAVE; + track.hwndTrack = mpWindow->win32.hWnd; + TrackMouseEvent(&track); + + mp_event enter = {.window = event.window, + .type = MP_EVENT_MOUSE_ENTER, + .move.x = event.move.x, + .move.y = event.move.y}; + mp_queue_event(&enter); + } + __mpApp.inputState.mouse.pos.x = event.move.x; + __mpApp.inputState.mouse.pos.y = event.move.y; + + mp_queue_event(&event); + } break; + + case WM_MOUSELEAVE: + { + __mpApp.win32.mouseTracked = false; + + if(!__mpApp.win32.mouseCaptureMask) + { + __mpApp.inputState.mouse.posValid = false; + } + + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_MOUSE_LEAVE; + mp_queue_event(&event); + } break; + + case WM_MOUSEWHEEL: + { + process_wheel_event(mpWindow, 0, (float)((i16)HIWORD(wParam))); + } break; + + case WM_MOUSEHWHEEL: + { + process_wheel_event(mpWindow, (float)((i16)HIWORD(wParam)), 0); + } break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_KEYBOARD_KEY; + event.key.action = (lParam & 0x40000000) ? MP_KEY_REPEAT : MP_KEY_PRESS; + event.key.code = mp_convert_win32_key(HIWORD(lParam) & 0x1ff); + event.key.mods = mp_get_mod_keys(); + mp_queue_event(&event); + } break; + + case WM_KEYUP: + case WM_SYSKEYUP: + { + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_KEYBOARD_KEY; + event.key.action = MP_KEY_RELEASE; + event.key.code = mp_convert_win32_key(HIWORD(lParam) & 0x1ff); + event.key.mods = mp_get_mod_keys(); + mp_queue_event(&event); + } break; + + case WM_CHAR: + { + mp_event event = {0}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_KEYBOARD_CHAR; + event.character.codepoint = (utf32)wParam; + str8 seq = utf8_encode(event.character.sequence, event.character.codepoint); + event.character.seqLen = seq.len; + mp_queue_event(&event); + } break; + + case WM_DROPFILES: + { + //TODO + } break; + + default: + { + result = DefWindowProc(windowHandle, message, wParam, lParam); + } break; + } + + return(result); +} + +//-------------------------------------------------------------------- +// app management +//-------------------------------------------------------------------- + +bool mp_should_quit() +{ + return(__mpApp.shouldQuit); +} + +void mp_cancel_quit() +{ + __mpApp.shouldQuit = false; +} + +void mp_request_quit() +{ + __mpApp.shouldQuit = true; +} + +void mp_pump_events(f64 timeout) +{ + MSG message; + while(PeekMessage(&message, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + __mpApp.inputState.frameCounter++; +} + +//-------------------------------------------------------------------- +// window management +//-------------------------------------------------------------------- + +mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style style) +{ + WNDCLASS windowClass = {.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + .lpfnWndProc = WinProc, + .hInstance = GetModuleHandleW(NULL), + .lpszClassName = "ApplicationWindowClass", + .hCursor = LoadCursor(0, IDC_ARROW)}; + + if(!RegisterClass(&windowClass)) + { + //TODO: error + goto quit; + } + + HWND windowHandle = CreateWindow("ApplicationWindowClass", "Test Window", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + 800, 600, + 0, 0, windowClass.hInstance, 0); + + if(!windowHandle) + { + //TODO: error + goto quit; + } + + UpdateWindow(windowHandle); + + //TODO: return wrapped window + quit:; + mp_window_data* window = mp_window_alloc(); + window->win32.hWnd = windowHandle; + + SetPropW(windowHandle, L"MilePost", window); + + return(mp_window_handle_from_ptr(window)); +} + +void mp_window_destroy(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + DestroyWindow(windowData->win32.hWnd); + //TODO: check when to unregister class + + mp_window_recycle_ptr(windowData); + } +} + +void* mp_window_native_pointer(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->win32.hWnd); + } + else + { + return(0); + } +} + +bool mp_window_should_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->shouldClose); + } + else + { + return(false); + } +} + +void mp_window_request_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + windowData->shouldClose = true; + PostMessage(windowData->win32.hWnd, WM_CLOSE, 0, 0); + } +} + +void mp_window_cancel_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + windowData->shouldClose = false; + } +} + + +bool mp_window_is_hidden(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(IsWindowVisible(windowData->win32.hWnd)); + } + else + { + return(false); + } +} + +void mp_window_hide(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + ShowWindow(windowData->win32.hWnd, SW_HIDE); + } +} + +void mp_window_show(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + ShowWindow(windowData->win32.hWnd, SW_NORMAL); + } +} + +bool mp_window_is_minimized(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->minimized); + } + else + { + return(false); + } +} + +void mp_window_minimize(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + ShowWindow(windowData->win32.hWnd, SW_MINIMIZE); + } +} + +void mp_window_maximize(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + ShowWindow(windowData->win32.hWnd, SW_MAXIMIZE); + } +} + +void mp_window_restore(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + ShowWindow(windowData->win32.hWnd, SW_RESTORE); + } +} + +bool mp_window_has_focus(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(GetActiveWindow() == windowData->win32.hWnd); + } + else + { + return(false); + } +} + +void mp_window_focus(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + SetFocus(windowData->win32.hWnd); + } +} + +void mp_window_unfocus(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + SetFocus(0); + } +} + +void mp_window_send_to_back(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + SetWindowPos(windowData->win32.hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } +} + +void mp_window_bring_to_front(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + if(!IsWindowVisible(windowData->win32.hWnd)) + { + ShowWindow(windowData->win32.hWnd, SW_NORMAL); + } + SetWindowPos(windowData->win32.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } +} + +#undef LOG_SUBSYSTEM diff --git a/src/win32_gles_surface.c b/src/win32_gles_surface.c index 9005aa7..8fe4085 100644 --- a/src/win32_gles_surface.c +++ b/src/win32_gles_surface.c @@ -6,7 +6,7 @@ * @revision: * *****************************************************************/ -#include +#include #define EGL_EGLEXT_PROTOTYPES #include #include @@ -108,7 +108,7 @@ mg_surface mg_gles_surface_create_for_window(mp_window window) eglBindAPI(EGL_OPENGL_ES_API); EGLint contextAttributes[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, - EGL_CONTEXT_MINOR_VERSION_KHR, 0, //NOTE: Angle can't create a GLES 3.1 context on macOS + EGL_CONTEXT_MINOR_VERSION_KHR, 1, //NOTE: Angle can't create a GLES 3.1 context on macOS EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE, EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE, EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE, diff --git a/src/win32_gles_surface.h b/src/win32_gles_surface.h index a89dff0..92cfbdd 100644 --- a/src/win32_gles_surface.h +++ b/src/win32_gles_surface.h @@ -9,6 +9,9 @@ #ifndef __WIN32_GLES_SURFACE_H_ #define __WIN32_GLES_SURFACE_H_ -mg_surface mg_gles_surface_create_for_window(mg_window window); +#include"graphics.h" +#include"mp_app.h" + +mg_surface mg_gles_surface_create_for_window(mp_window window); #endif // __WIN32_GLES_SURFACE_H_