[mtl canvas] testing the metal canvas rendering the ghostscript tiger, and acknowledging it's painfully slow
This commit is contained in:
parent
1d36088302
commit
92f4909d63
2
build.sh
2
build.sh
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DEBUG_FLAGS="-g -O1 -DDEBUG -DLOG_COMPILE_DEBUG"
|
DEBUG_FLAGS="-g -DDEBUG -DLOG_COMPILE_DEBUG"
|
||||||
#DEBUG_FLAGS="-O3"
|
#DEBUG_FLAGS="-O3"
|
||||||
|
|
||||||
#--------------------------------------------------------------
|
#--------------------------------------------------------------
|
||||||
|
|
|
@ -165,7 +165,7 @@ int main()
|
||||||
mg_circle_fill(x, y, 200);
|
mg_circle_fill(x, y, 200);
|
||||||
|
|
||||||
// smile
|
// smile
|
||||||
f32 frown = frameTime > 0.033 ? 100 : 0;
|
f32 frown = frameTime > 0.033 ? -100 : 0;
|
||||||
|
|
||||||
mg_set_color_rgba(0, 0, 0, 1);
|
mg_set_color_rgba(0, 0, 0, 1);
|
||||||
mg_set_width(20);
|
mg_set_width(20);
|
||||||
|
|
|
@ -106,7 +106,7 @@ int main()
|
||||||
//NOTE: create surface, canvas and font
|
//NOTE: create surface, canvas and font
|
||||||
|
|
||||||
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT);
|
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT);
|
||||||
mg_surface_swap_interval(surface, 1);
|
mg_surface_swap_interval(surface, 0);
|
||||||
|
|
||||||
mg_canvas canvas = mg_canvas_create(surface);
|
mg_canvas canvas = mg_canvas_create(surface);
|
||||||
|
|
||||||
|
@ -286,9 +286,10 @@ int main()
|
||||||
mg_fill();
|
mg_fill();
|
||||||
|
|
||||||
|
|
||||||
f64 startFlushTime = mp_get_time(MP_CLOCK_MONOTONIC);
|
f64 startFlushTime = mp_get_time(MP_CLOCK_MONOTONIC);
|
||||||
|
|
||||||
mg_surface_prepare(surface);
|
mg_surface_prepare(surface);
|
||||||
|
|
||||||
mg_flush();
|
mg_flush();
|
||||||
|
|
||||||
f64 startPresentTime = mp_get_time(MP_CLOCK_MONOTONIC);
|
f64 startPresentTime = mp_get_time(MP_CLOCK_MONOTONIC);
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
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.dll.lib /out:../../bin/example_canvas.exe
|
|
@ -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"
|
||||||
|
FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG"
|
||||||
|
|
||||||
|
clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/example_tiger main.c
|
|
@ -0,0 +1,154 @@
|
||||||
|
/************************************************************//**
|
||||||
|
*
|
||||||
|
* @file: main.cpp
|
||||||
|
* @author: Martin Fouilleul
|
||||||
|
* @date: 30/07/2022
|
||||||
|
* @revision:
|
||||||
|
*
|
||||||
|
*****************************************************************/
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<string.h>
|
||||||
|
#include<errno.h>
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
|
||||||
|
#include<math.h>
|
||||||
|
|
||||||
|
#include"milepost.h"
|
||||||
|
|
||||||
|
#define LOG_SUBSYSTEM "Main"
|
||||||
|
|
||||||
|
#include"tiger.c"
|
||||||
|
|
||||||
|
mg_font create_font()
|
||||||
|
{
|
||||||
|
//NOTE(martin): create font
|
||||||
|
str8 fontPath = mp_app_get_resource_path(mem_scratch(), "../resources/OpenSansLatinSubset.ttf");
|
||||||
|
char* fontPathCString = str8_to_cstring(mem_scratch(), fontPath);
|
||||||
|
|
||||||
|
FILE* fontFile = fopen(fontPathCString, "r");
|
||||||
|
if(!fontFile)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Could not load font file '%s': %s\n", fontPathCString, strerror(errno));
|
||||||
|
return(mg_font_nil());
|
||||||
|
}
|
||||||
|
unsigned char* fontData = 0;
|
||||||
|
fseek(fontFile, 0, SEEK_END);
|
||||||
|
u32 fontDataSize = ftell(fontFile);
|
||||||
|
rewind(fontFile);
|
||||||
|
fontData = (unsigned char*)malloc(fontDataSize);
|
||||||
|
fread(fontData, 1, fontDataSize, fontFile);
|
||||||
|
fclose(fontFile);
|
||||||
|
|
||||||
|
unicode_range ranges[5] = {UNICODE_RANGE_BASIC_LATIN,
|
||||||
|
UNICODE_RANGE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
|
||||||
|
UNICODE_RANGE_LATIN_EXTENDED_A,
|
||||||
|
UNICODE_RANGE_LATIN_EXTENDED_B,
|
||||||
|
UNICODE_RANGE_SPECIALS};
|
||||||
|
|
||||||
|
mg_font font = mg_font_create_from_memory(fontDataSize, fontData, 5, ranges);
|
||||||
|
free(fontData);
|
||||||
|
|
||||||
|
return(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
LogLevel(LOG_LEVEL_WARNING);
|
||||||
|
|
||||||
|
mp_init();
|
||||||
|
mp_clock_init(); //TODO put that in mp_init()?
|
||||||
|
|
||||||
|
mp_rect windowRect = {.x = 100, .y = 100, .w = 810, .h = 610};
|
||||||
|
mp_window window = mp_window_create(windowRect, "test", 0);
|
||||||
|
|
||||||
|
mp_rect contentRect = mp_window_get_content_rect(window);
|
||||||
|
|
||||||
|
//NOTE: create surface
|
||||||
|
mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_DEFAULT);
|
||||||
|
mg_surface_swap_interval(surface, 0);
|
||||||
|
|
||||||
|
//TODO: create canvas
|
||||||
|
mg_canvas canvas = mg_canvas_create(surface);
|
||||||
|
|
||||||
|
if(mg_canvas_is_nil(canvas))
|
||||||
|
{
|
||||||
|
printf("Error: couldn't create canvas\n");
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_font font = create_font();
|
||||||
|
|
||||||
|
// start app
|
||||||
|
mp_window_bring_to_front(window);
|
||||||
|
mp_window_focus(window);
|
||||||
|
|
||||||
|
f64 frameTime = 0;
|
||||||
|
|
||||||
|
while(!mp_should_quit())
|
||||||
|
{
|
||||||
|
f64 startTime = mp_get_time(MP_CLOCK_MONOTONIC);
|
||||||
|
|
||||||
|
mp_pump_events(0);
|
||||||
|
mp_event event = {0};
|
||||||
|
while(mp_next_event(&event))
|
||||||
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case MP_EVENT_WINDOW_CLOSE:
|
||||||
|
{
|
||||||
|
mp_request_quit();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case MP_EVENT_KEYBOARD_KEY:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_surface_prepare(surface);
|
||||||
|
|
||||||
|
mg_set_color_rgba(1, 0, 1, 1);
|
||||||
|
mg_clear();
|
||||||
|
|
||||||
|
mg_matrix_push((mg_mat2x3){1, 0, 300,
|
||||||
|
0, 1, 200});
|
||||||
|
|
||||||
|
draw_tiger();
|
||||||
|
|
||||||
|
mg_matrix_pop();
|
||||||
|
// text
|
||||||
|
mg_set_color_rgba(0, 0, 1, 1);
|
||||||
|
mg_set_font(font);
|
||||||
|
mg_set_font_size(12);
|
||||||
|
mg_move_to(50, 600-50);
|
||||||
|
|
||||||
|
str8 text = str8_pushf(mem_scratch(),
|
||||||
|
"Milepost vector graphics test program (frame time = %fs, fps = %f)...",
|
||||||
|
frameTime,
|
||||||
|
1./frameTime);
|
||||||
|
mg_text_outlines(text);
|
||||||
|
mg_fill();
|
||||||
|
|
||||||
|
printf("Milepost vector graphics test program (frame time = %fs, fps = %f)...\n",
|
||||||
|
frameTime,
|
||||||
|
1./frameTime);
|
||||||
|
|
||||||
|
mg_flush();
|
||||||
|
mg_surface_present(surface);
|
||||||
|
|
||||||
|
mem_arena_clear(mem_scratch());
|
||||||
|
frameTime = mp_get_time(MP_CLOCK_MONOTONIC) - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_font_destroy(font);
|
||||||
|
mg_canvas_destroy(canvas);
|
||||||
|
mg_surface_destroy(surface);
|
||||||
|
mp_window_destroy(window);
|
||||||
|
|
||||||
|
mp_terminate();
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
import xml.etree.ElementTree as et
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def expect_char(s, i, c):
|
||||||
|
if i >= len(s) or c != s[i]:
|
||||||
|
print("error: expected character " + c + " at index " + str(i))
|
||||||
|
exit()
|
||||||
|
return(i+1)
|
||||||
|
|
||||||
|
def accept_char(s, i, c):
|
||||||
|
if i < len(s) and c == s[i]:
|
||||||
|
i += 1
|
||||||
|
return(i)
|
||||||
|
|
||||||
|
def consume_number(s, i):
|
||||||
|
sign = 1
|
||||||
|
if s[i] == '+':
|
||||||
|
i += 1
|
||||||
|
if s[i] == '-':
|
||||||
|
sign = -1
|
||||||
|
i += 1
|
||||||
|
res = 0
|
||||||
|
decimalPos = 0.1
|
||||||
|
while i < len(s) and s[i].isdigit() :
|
||||||
|
res *= 10
|
||||||
|
res += int(s[i])
|
||||||
|
i += 1
|
||||||
|
if i < len(s) and s[i] == '.':
|
||||||
|
i += 1
|
||||||
|
while i < len(s) and s[i].isdigit() :
|
||||||
|
res += decimalPos * int(s[i])
|
||||||
|
decimalPos *= 0.1
|
||||||
|
i += 1
|
||||||
|
return(res*sign, i)
|
||||||
|
|
||||||
|
def consume_point(s, i):
|
||||||
|
(x, i) = consume_number(s, i)
|
||||||
|
i = accept_char(s, i, ',')
|
||||||
|
(y, i) = consume_number(s, i)
|
||||||
|
return(x, y, i)
|
||||||
|
|
||||||
|
def f2s(x):
|
||||||
|
return f'{x:.3f}'
|
||||||
|
|
||||||
|
class svgContext:
|
||||||
|
sp = (0, 0)
|
||||||
|
cp = (0, 0)
|
||||||
|
rp = (0, 0)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.cp = self.sp = self.rp = (0,0)
|
||||||
|
|
||||||
|
def move_to(self, rel, x, y):
|
||||||
|
if rel:
|
||||||
|
x += self.cp[0]
|
||||||
|
y += self.cp[1]
|
||||||
|
print("\tmg_move_to(" + f2s(x) + ", " + f2s(y) + ");")
|
||||||
|
self.sp = (x, y)
|
||||||
|
self.cp = (x, y)
|
||||||
|
self.rp = self.cp
|
||||||
|
|
||||||
|
def curve_to(self, rel, x1, y1, x2, y2, x3, y3):
|
||||||
|
if rel:
|
||||||
|
x1 += self.cp[0]
|
||||||
|
y1 += self.cp[1]
|
||||||
|
x2 += self.cp[0]
|
||||||
|
y2 += self.cp[1]
|
||||||
|
x3 += self.cp[0]
|
||||||
|
y3 += self.cp[1]
|
||||||
|
print("\tmg_cubic_to(" + f2s(x1) + ", " + f2s(y1) + ", " + f2s(x2) + ", " + f2s(y2) + ", " + f2s(x3) + ", " + f2s(y3) + ");")
|
||||||
|
self.rp = (x2, y2)
|
||||||
|
self.cp = (x3, y3)
|
||||||
|
|
||||||
|
def smooth_curve_to(self, rel, x2, y2, x3, y3):
|
||||||
|
if rel:
|
||||||
|
x2 += self.cp[0]
|
||||||
|
y2 += self.cp[1]
|
||||||
|
x3 += self.cp[0]
|
||||||
|
y3 += self.cp[1]
|
||||||
|
x1 = 2*self.cp[0] - self.rp[0]
|
||||||
|
y1 = 2*self.cp[1] - self.rp[1]
|
||||||
|
print("\tmg_cubic_to(" + f2s(x1) + ", " + f2s(y1) + ", " + f2s(x2) + ", " + f2s(y2) + ", " + f2s(x3) + ", " + f2s(y3) + ");")
|
||||||
|
self.rp = (x2, y2)
|
||||||
|
self.cp = (x3, y3)
|
||||||
|
|
||||||
|
def line_to(self, rel, x1, y1):
|
||||||
|
if rel:
|
||||||
|
x1 += self.cp[0]
|
||||||
|
y1 += self.cp[1]
|
||||||
|
print("\tmg_line_to(" + f2s(x1) + ", " + f2s(y1) + ");")
|
||||||
|
self.cp = (x1, y1)
|
||||||
|
self.rp = self.cp
|
||||||
|
|
||||||
|
def vertical_to(self, rel, y1):
|
||||||
|
if rel:
|
||||||
|
y1 += self.cp[1]
|
||||||
|
x1 = self.cp[0]
|
||||||
|
print("\tmg_line_to(" + f2s(x1) + ", " + f2s(y1) + ");")
|
||||||
|
self.cp = (x1, y1)
|
||||||
|
self.rp = self.cp
|
||||||
|
|
||||||
|
def close_path(self):
|
||||||
|
print("\tmg_close_path();");
|
||||||
|
self.cp = self.rp = self.sp
|
||||||
|
|
||||||
|
def print_path(path, ctx):
|
||||||
|
# print("path " + path.get('id') + ":")
|
||||||
|
d = path.get('d')
|
||||||
|
index = 0
|
||||||
|
c = d[index]
|
||||||
|
|
||||||
|
while index < len(d):
|
||||||
|
c = d[index]
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
rel = c.islower()
|
||||||
|
c = c.lower()
|
||||||
|
|
||||||
|
if c == 'm':
|
||||||
|
(x, y, index) = consume_point(d, index)
|
||||||
|
ctx.move_to(rel, x, y)
|
||||||
|
|
||||||
|
while index < len(d) and (d[index] == ',' or d[index] == '+' or d[index] == '-'):
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x, y, index) = consume_point(d, index)
|
||||||
|
ctx.move_to(rel, x, y)
|
||||||
|
|
||||||
|
elif c == 'l':
|
||||||
|
(x1, y1, index) = consume_point(d, index)
|
||||||
|
ctx.line_to(rel, x1, y1)
|
||||||
|
|
||||||
|
while index < len(d) and (d[index] == ',' or d[index] == '+' or d[index] == '-'):
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x1, y1, index) = consume_point(d, index)
|
||||||
|
ctx.line_to(rel, x1, y1)
|
||||||
|
|
||||||
|
elif c == 'v':
|
||||||
|
(y1, index) = consume_number(d, index)
|
||||||
|
ctx.vertical_to(rel, y1)
|
||||||
|
|
||||||
|
elif c == 'c':
|
||||||
|
(x1, y1, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x2, y2, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x3, y3, index) = consume_point(d, index)
|
||||||
|
ctx.curve_to(rel, x1, y1, x2, y2, x3, y3)
|
||||||
|
|
||||||
|
while index < len(d) and (d[index] == ',' or d[index] == '+' or d[index] == '-'):
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x1, y1, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x2, y2, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x3, y3, index) = consume_point(d, index)
|
||||||
|
ctx.curve_to(rel, x1, y1, x2, y2, x3, y3)
|
||||||
|
|
||||||
|
elif c == 's':
|
||||||
|
(x2, y2, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x3, y3, index) = consume_point(d, index)
|
||||||
|
ctx.smooth_curve_to(rel, x2, y2, x3, y3)
|
||||||
|
|
||||||
|
while index < len(d) and (d[index] == ',' or d[index] == '+' or d[index] == '-'):
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x2, y2, index) = consume_point(d, index)
|
||||||
|
index = accept_char(d, index, ',')
|
||||||
|
(x3, y3, index) = consume_point(d, index)
|
||||||
|
ctx.smooth_curve_to(rel, x2, y2, x3, y3)
|
||||||
|
|
||||||
|
|
||||||
|
elif c == 'z':
|
||||||
|
ctx.close_path()
|
||||||
|
index += 1
|
||||||
|
else:
|
||||||
|
print('error: unrecognized command')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_color(s):
|
||||||
|
s = s.lstrip('#')
|
||||||
|
if len(s) == 3:
|
||||||
|
return (int(s[0]+s[0], 16)/255., int(s[1]+s[1], 16)/255., int(s[2]+s[2], 16)/255.)
|
||||||
|
elif len(s) == 6:
|
||||||
|
return (int(s[0:2], 16)/255., int(s[2:4], 16)/255., int(s[4:6], 16)/255.)
|
||||||
|
else:
|
||||||
|
print("error: unrecognized color: " + s)
|
||||||
|
exit()
|
||||||
|
|
||||||
|
tree = et.parse('./Ghostscript_Tiger.svg')
|
||||||
|
ctx = svgContext()
|
||||||
|
|
||||||
|
print("void draw_tiger()")
|
||||||
|
print("{")
|
||||||
|
|
||||||
|
for g in tree.iter('{http://www.w3.org/2000/svg}g'):
|
||||||
|
|
||||||
|
for path in g.findall('{http://www.w3.org/2000/svg}path'):
|
||||||
|
ctx.reset()
|
||||||
|
print_path(path, ctx)
|
||||||
|
|
||||||
|
fill = g.get('fill')
|
||||||
|
stroke = g.get('stroke')
|
||||||
|
stroke_width = g.get('stroke-width')
|
||||||
|
|
||||||
|
if fill != None and fill != "none":
|
||||||
|
(r, g, b) = parse_color(fill)
|
||||||
|
print("\tmg_set_color_rgba(" + f2s(r) + ", " + f2s(g) + ", " + f2s(b) + ", 1);")
|
||||||
|
print("\tmg_fill();")
|
||||||
|
|
||||||
|
if stroke_width != None:
|
||||||
|
print("\tmg_set_width(" + stroke_width + ");");
|
||||||
|
|
||||||
|
if stroke != None and stroke != "none":
|
||||||
|
(r, g, b) = parse_color(stroke)
|
||||||
|
if fill != None:
|
||||||
|
ctx.reset()
|
||||||
|
print_path(path, ctx)
|
||||||
|
print("\tmg_set_color_rgba(" + f2s(r) + ", " + f2s(g) + ", " + f2s(b) + ", 1);")
|
||||||
|
print("\tmg_stroke();")
|
||||||
|
|
||||||
|
if (stroke == None or stroke == 'none') and (fill == None or fill == ''):
|
||||||
|
print("error, group " + g.get("id") + " has no command")
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("}")
|
File diff suppressed because it is too large
Load Diff
711
src/graphics.c
711
src/graphics.c
|
@ -720,6 +720,7 @@ void mg_push_command(mg_canvas_data* canvas, mg_primitive primitive)
|
||||||
{
|
{
|
||||||
//NOTE(martin): push primitive and updates current stream, eventually patching a pending jump.
|
//NOTE(martin): push primitive and updates current stream, eventually patching a pending jump.
|
||||||
ASSERT(canvas->primitiveCount < MG_MAX_PRIMITIVE_COUNT);
|
ASSERT(canvas->primitiveCount < MG_MAX_PRIMITIVE_COUNT);
|
||||||
|
|
||||||
canvas->primitives[canvas->primitiveCount] = primitive;
|
canvas->primitives[canvas->primitiveCount] = primitive;
|
||||||
canvas->primitives[canvas->primitiveCount].attributes = canvas->attributes;
|
canvas->primitives[canvas->primitiveCount].attributes = canvas->attributes;
|
||||||
canvas->primitives[canvas->primitiveCount].attributes.transform = mg_matrix_stack_top(canvas);
|
canvas->primitives[canvas->primitiveCount].attributes.transform = mg_matrix_stack_top(canvas);
|
||||||
|
@ -736,9 +737,11 @@ void mg_new_path(mg_canvas_data* canvas)
|
||||||
|
|
||||||
void mg_path_push_elements(mg_canvas_data* canvas, u32 count, mg_path_elt* elements)
|
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);
|
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));
|
memcpy(canvas->pathElements + canvas->path.startIndex + canvas->path.count, elements, count*sizeof(mg_path_elt));
|
||||||
canvas->path.count += count;
|
canvas->path.count += count;
|
||||||
|
|
||||||
|
ASSERT(canvas->path.count < MG_MAX_PATH_ELEMENT_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_path_push_element(mg_canvas_data* canvas, mg_path_elt elt)
|
void mg_path_push_element(mg_canvas_data* canvas, mg_path_elt elt)
|
||||||
|
@ -840,8 +843,8 @@ void mg_push_vertex_cubic(mg_canvas_data* canvas, vec2 pos, vec4 cubic)
|
||||||
vec2 screenPos = mg_mat2x3_mul(canvas->transform, pos);
|
vec2 screenPos = mg_mat2x3_mul(canvas->transform, pos);
|
||||||
|
|
||||||
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
||||||
DEBUG_ASSERT(canvas->vertexCount < layout->maxVertexCount);
|
ASSERT(canvas->vertexCount < layout->maxVertexCount);
|
||||||
DEBUG_ASSERT(canvas->nextShapeIndex > 0);
|
ASSERT(canvas->nextShapeIndex > 0);
|
||||||
|
|
||||||
int shapeIndex = maximum(0, canvas->nextShapeIndex-1);
|
int shapeIndex = maximum(0, canvas->nextShapeIndex-1);
|
||||||
u32 index = canvas->vertexCount;
|
u32 index = canvas->vertexCount;
|
||||||
|
@ -925,6 +928,12 @@ void mg_split_and_fill_cubic(mg_canvas_data* canvas, vec2 p[4], f32 tSplit)
|
||||||
mg_render_fill_cubic(canvas, subPointsHigh);
|
mg_render_fill_cubic(canvas, subPointsHigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mg_cubic_outside_test(vec4 c)
|
||||||
|
{
|
||||||
|
int res = (c.x*c.x*c.x - c.y*c.z < 0) ? -1 : 1;
|
||||||
|
return(res);
|
||||||
|
}
|
||||||
|
|
||||||
void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4])
|
void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4])
|
||||||
{
|
{
|
||||||
LOG_DEBUG("graphics render fill cubic\n");
|
LOG_DEBUG("graphics render fill cubic\n");
|
||||||
|
@ -953,7 +962,7 @@ void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4])
|
||||||
f32 c3y = 3.0*p[1].y - 3.0*p[2].y + p[3].y - p[0].y;
|
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
|
//TODO(martin): we shouldn't need scaling here since now we're doing our shader math in fixed point?
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
c1x /= 10;
|
c1x /= 10;
|
||||||
c1y /= 10;
|
c1y /= 10;
|
||||||
|
@ -1226,143 +1235,174 @@ void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4])
|
||||||
|
|
||||||
} while(currentPointIndex != leftMostPointIndex && i<4);
|
} while(currentPointIndex != leftMostPointIndex && i<4);
|
||||||
|
|
||||||
|
//NOTE(martin): triangulation and inside/outside tests. In the shader, the outside is defined by s*(k^3 - lm) > 0
|
||||||
|
// ie the 4th coordinate s flips the inside/outside test.
|
||||||
|
// We affect s such that the covered are is between the curve and the line joining p0 and p3.
|
||||||
|
|
||||||
//TODO: quick fix, maybe later cull degenerate hulls beforehand
|
//TODO: quick fix, maybe later cull degenerate hulls beforehand
|
||||||
if(convexHullCount <= 2)
|
if(convexHullCount <= 2)
|
||||||
{
|
{
|
||||||
//NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing.
|
//NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if(convexHullCount == 3)
|
||||||
//NOTE(martin): rearrange convex hull to put p0 first
|
|
||||||
int startIndex = -1;
|
|
||||||
int orderedHullIndices[4];
|
|
||||||
for(int i=0; i<convexHullCount; i++)
|
|
||||||
{
|
{
|
||||||
if(convexHullIndices[i] == 0)
|
/*NOTE(martin):
|
||||||
|
We have 3 case here:
|
||||||
|
1) Endpoints are coincidents. We push on triangle, and test an intermediate point for orientation.
|
||||||
|
2) The point not on the hull is an endpoint. We push two triangle (p0, p3, p1) and (p0, p3, p2). We test the intermediate
|
||||||
|
points to know if we must flip the orientation of the curve.
|
||||||
|
3) The point not on the hull is an intermediate point: we emit one triangle. We test the intermediate point on the hull
|
||||||
|
to know if we must flip the orientation of the curve.
|
||||||
|
*/
|
||||||
|
if( p[0].x == p[3].x
|
||||||
|
&& p[0].y == p[3].y)
|
||||||
{
|
{
|
||||||
startIndex = i;
|
//NOTE: case 1: endpoints are coincidents
|
||||||
|
int outsideTest = mg_cubic_outside_test(testCoords[1]);
|
||||||
|
|
||||||
|
//NOTE: push triangle
|
||||||
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
||||||
|
i32* indices = mg_reserve_indices(canvas, 3);
|
||||||
|
|
||||||
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest});
|
||||||
|
mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest});
|
||||||
|
mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest});
|
||||||
|
|
||||||
|
for(int i=0; i<3; i++)
|
||||||
|
{
|
||||||
|
indices[i] = baseIndex + i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(startIndex >= 0)
|
else
|
||||||
{
|
{
|
||||||
orderedHullIndices[i-startIndex] = convexHullIndices[i];
|
//NOTE: find point not on the hull
|
||||||
|
int insidePointIndex = -1;
|
||||||
|
{
|
||||||
|
bool present[4] = {0};
|
||||||
|
for(int i=0; i<3; i++)
|
||||||
|
{
|
||||||
|
present[convexHullIndices[i]] = true;
|
||||||
|
}
|
||||||
|
for(int i=0; i<4; i++)
|
||||||
|
{
|
||||||
|
if(!present[i])
|
||||||
|
{
|
||||||
|
insidePointIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG_ASSERT(insidePointIndex >= 0 && insidePointIndex < 4);
|
||||||
|
|
||||||
|
if(insidePointIndex == 0 || insidePointIndex == 3)
|
||||||
|
{
|
||||||
|
//NOTE: case 2: the point inside the hull is an endpoint
|
||||||
|
|
||||||
|
int outsideTest0 = mg_cubic_outside_test(testCoords[1]);
|
||||||
|
int outsideTest1 = mg_cubic_outside_test(testCoords[2]);
|
||||||
|
|
||||||
|
//NOTE: push triangles
|
||||||
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
||||||
|
i32* indices = mg_reserve_indices(canvas, 6);
|
||||||
|
|
||||||
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest0});
|
||||||
|
mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest0});
|
||||||
|
mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest0});
|
||||||
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest1});
|
||||||
|
mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest1});
|
||||||
|
mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest1});
|
||||||
|
|
||||||
|
for(int i=0; i<6; i++)
|
||||||
|
{
|
||||||
|
indices[i] = baseIndex + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int testIndex = (insidePointIndex == 1) ? 2 : 1;
|
||||||
|
int outsideTest = mg_cubic_outside_test(testCoords[testIndex]);
|
||||||
|
|
||||||
|
//NOTE: push triangle
|
||||||
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
||||||
|
i32* indices = mg_reserve_indices(canvas, 3);
|
||||||
|
|
||||||
|
for(int i=0; i<3; i++)
|
||||||
|
{
|
||||||
|
mg_push_vertex_cubic(canvas,
|
||||||
|
p[convexHullIndices[i]],
|
||||||
|
(vec4){vec4_expand_xyz(testCoords[convexHullIndices[i]]),
|
||||||
|
outsideTest});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0; i<3; i++)
|
||||||
|
{
|
||||||
|
indices[i] = baseIndex + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(int i=0; i<startIndex; i++)
|
|
||||||
{
|
|
||||||
orderedHullIndices[convexHullCount-startIndex+i] = convexHullIndices[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
//NOTE(martin): inside/outside tests for the two triangles. In the shader, the outside is defined by s*(k^3 - lm) > 0
|
|
||||||
// ie the 4th coordinate s flips the inside/outside test.
|
|
||||||
// We affect s such that 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++)
|
|
||||||
{
|
|
||||||
vec4 cubic = testCoords[orderedHullIndices[i]];
|
|
||||||
cubic.w = outsideTest;
|
|
||||||
mg_push_vertex_cubic(canvas, p[orderedHullIndices[i]], cubic);
|
|
||||||
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_cubic(canvas,
|
|
||||||
p[orderedHullIndices[0]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest1});
|
|
||||||
|
|
||||||
mg_push_vertex_cubic(canvas,
|
|
||||||
p[orderedHullIndices[1]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[1]]), outsideTest1});
|
|
||||||
|
|
||||||
mg_push_vertex_cubic(canvas,
|
|
||||||
p[orderedHullIndices[2]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest1});
|
|
||||||
|
|
||||||
mg_push_vertex_cubic(canvas,
|
|
||||||
p[orderedHullIndices[0]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest2});
|
|
||||||
|
|
||||||
mg_push_vertex_cubic(canvas,
|
|
||||||
p[orderedHullIndices[2]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest2});
|
|
||||||
|
|
||||||
mg_push_vertex_cubic(canvas,
|
|
||||||
p[orderedHullIndices[3]],
|
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[3]]), outsideTest2});
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
//NOTE(martin): if p1 and p2 are on the same side of (p0,p3), the outside test is the same for both triangles
|
DEBUG_ASSERT(convexHullCount == 4);
|
||||||
int outsideTest = 1;
|
/*NOTE(martin):
|
||||||
if(Cube(testCoords[1].x)-testCoords[1].y*testCoords[1].z < 0)
|
We build a fan from the hull, starting from an endpoint. For each triangle, we test the vertex that is an intermediate
|
||||||
|
control point for orientation
|
||||||
|
*/
|
||||||
|
int endPointIndex = -1;
|
||||||
|
for(int i=0; i<4; i++)
|
||||||
{
|
{
|
||||||
outsideTest = -1;
|
if(convexHullIndices[i] == 0 || convexHullIndices[i] == 3)
|
||||||
|
{
|
||||||
|
endPointIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(endPointIndex >= 0);
|
||||||
|
|
||||||
|
int fanIndices[6] = {convexHullIndices[endPointIndex],
|
||||||
|
convexHullIndices[(endPointIndex + 1)%4],
|
||||||
|
convexHullIndices[(endPointIndex + 2)%4],
|
||||||
|
convexHullIndices[endPointIndex],
|
||||||
|
convexHullIndices[(endPointIndex + 2)%4],
|
||||||
|
convexHullIndices[(endPointIndex + 3)%4]};
|
||||||
|
|
||||||
|
//NOTE: fan indices on the hull are (0,1,2)(0,2,3). So if the 3rd vertex of the hull is an intermediate point it works
|
||||||
|
// as a test vertex for both triangles. Otherwise, the test vertices on the fan are 1 and 5.
|
||||||
|
int outsideTest0 = 1;
|
||||||
|
int outsideTest1 = 1;
|
||||||
|
|
||||||
|
if( fanIndices[2] == 1
|
||||||
|
||fanIndices[2] == 2)
|
||||||
|
{
|
||||||
|
outsideTest0 = outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[2]]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(fanIndices[1] == 1 || fanIndices[1] == 2);
|
||||||
|
DEBUG_ASSERT(fanIndices[5] == 1 || fanIndices[5] == 2);
|
||||||
|
|
||||||
|
outsideTest0 = mg_cubic_outside_test(testCoords[fanIndices[1]]);
|
||||||
|
outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[5]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//NOTE: push triangles
|
||||||
u32 baseIndex = mg_vertices_base_index(canvas);
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
||||||
i32* indices = mg_reserve_indices(canvas, 6);
|
i32* indices = mg_reserve_indices(canvas, 6);
|
||||||
|
|
||||||
for(int i=0; i<4; i++)
|
for(int i=0; i<3; i++)
|
||||||
{
|
{
|
||||||
mg_push_vertex_cubic(canvas,
|
mg_push_vertex_cubic(canvas, p[fanIndices[i]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i]]), outsideTest0});
|
||||||
p[orderedHullIndices[i]],
|
}
|
||||||
(vec4){vec4_expand_xyz(testCoords[orderedHullIndices[i]]), outsideTest});
|
for(int i=0; i<3; i++)
|
||||||
|
{
|
||||||
|
mg_push_vertex_cubic(canvas, p[fanIndices[i+3]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i+3]]), outsideTest1});
|
||||||
}
|
}
|
||||||
|
|
||||||
indices[0] = baseIndex + 0;
|
for(int i=0; i<6; i++)
|
||||||
indices[1] = baseIndex + 1;
|
{
|
||||||
indices[2] = baseIndex + 2;
|
indices[i] = baseIndex + i;
|
||||||
indices[3] = baseIndex + 0;
|
}
|
||||||
indices[4] = baseIndex + 2;
|
|
||||||
indices[5] = baseIndex + 3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1457,79 +1497,122 @@ void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], mg_attributes* att
|
||||||
indices[5] = baseIndex + 3;
|
indices[5] = baseIndex + 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_offset_hull(int count, vec2* p, vec2* result, f32 offset)
|
//TODO put these elsewhere
|
||||||
|
bool vec2_equal(vec2 v0, vec2 v1)
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
return(v0.x == v1.x && v0.y == v1.y);
|
||||||
//WARN: quick fix for coincident middle control points
|
}
|
||||||
if(count == 4 && (fabs(p[1].x - p[2].x) < 0.01) && (fabs(p[1].y - p[2].y) < 0.01))
|
|
||||||
|
bool vec2_close(vec2 p0, vec2 p1, f32 tolerance)
|
||||||
|
{
|
||||||
|
f32 norm2 = (p1.x - p0.x)*(p1.x - p0.x) + (p1.y - p0.y)*(p1.y - p0.y);
|
||||||
|
return(fabs(norm2) < tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 vec2_mul(f32 f, vec2 v)
|
||||||
|
{
|
||||||
|
return((vec2){f*v.x, f*v.y});
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 vec2_add(vec2 v0, vec2 v1)
|
||||||
|
{
|
||||||
|
return((vec2){v0.x + v1.x, v0.y + v1.y});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mg_intersect_hull_legs(vec2 p0, vec2 p1, vec2 p2, vec2 p3, vec2* intersection)
|
||||||
|
{
|
||||||
|
/*NOTE: check intersection of lines (p0-p1) and (p2-p3)
|
||||||
|
|
||||||
|
P = p0 + u(p1-p0)
|
||||||
|
P = p2 + w(p3-p2)
|
||||||
|
*/
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
f32 den = (p0.x - p1.x)*(p2.y - p3.y) - (p0.y - p1.y)*(p2.x - p3.x);
|
||||||
|
if(fabs(den) > 0.0001)
|
||||||
{
|
{
|
||||||
vec2 hull3[3] = {p[0], p[1], p[3]};
|
f32 u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/den;
|
||||||
vec2 result3[3];
|
f32 w = ((p0.x - p2.x)*(p0.y - p1.y) - (p0.y - p2.y)*(p0.x - p1.x))/den;
|
||||||
mg_offset_hull(3, hull3, result3, offset);
|
|
||||||
result[0] = result3[0];
|
intersection->x = p0.x + u*(p1.x - p0.x);
|
||||||
result[1] = result3[1];
|
intersection->y = p0.y + u*(p1.y - p0.y);
|
||||||
result[2] = result3[1];
|
found = true;
|
||||||
result[3] = result3[2];
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
/////////////////////////////////////////////////////////////////////////////////////:
|
return(found);
|
||||||
|
}
|
||||||
|
|
||||||
//TODO(martin): review edge cases (coincident points ? colinear points ? control points pointing outward end point?)
|
bool mg_offset_hull(int count, vec2* p, vec2* result, f32 offset)
|
||||||
//NOTE(martin): first offset control point is just the offset of first control point
|
{
|
||||||
vec2 n = {p[0].y - p[1].y,
|
//NOTE: we should have no more than two coincident points here. This means the leg between
|
||||||
p[1].x - p[0].x};
|
// those two points can't be offset, but we can set a double point at the start of first leg,
|
||||||
f32 norm = sqrt(n.x*n.x + n.y*n.y);
|
// end of first leg, or we can join the first and last leg to create a missing middle one
|
||||||
n.x *= offset/norm;
|
|
||||||
n.y *= offset/norm;
|
|
||||||
|
|
||||||
result[0].x = p[0].x + n.x;
|
vec2 legs[3][2] = {0};
|
||||||
result[0].y = p[0].y + n.y;
|
bool valid[3] = {0};
|
||||||
|
|
||||||
|
for(int i=0; i<count-1; i++)
|
||||||
|
{
|
||||||
|
vec2 n = {p[i].y - p[i+1].y,
|
||||||
|
p[i+1].x - p[i].x};
|
||||||
|
|
||||||
|
f32 norm = sqrt(n.x*n.x + n.y*n.y);
|
||||||
|
if(norm >= 1e-6)
|
||||||
|
{
|
||||||
|
n = vec2_mul(offset/norm, n);
|
||||||
|
legs[i][0] = vec2_add(p[i], n);
|
||||||
|
legs[i][1] = vec2_add(p[i+1], n);
|
||||||
|
valid[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE: now we find intersections
|
||||||
|
|
||||||
|
// first point is either the start of the first or second leg
|
||||||
|
if(valid[0])
|
||||||
|
{
|
||||||
|
result[0] = legs[0][0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ASSERT(valid[1]);
|
||||||
|
result[0] = legs[1][0];
|
||||||
|
}
|
||||||
|
|
||||||
//NOTE(martin): subsequent offset control points are the intersection of offset control lines
|
|
||||||
for(int i=1; i<count-1; i++)
|
for(int i=1; i<count-1; i++)
|
||||||
{
|
{
|
||||||
vec2 p0 = p[i-1];
|
//NOTE: we're computing the control point i, at the end of leg (i-1)
|
||||||
vec2 p1 = p[i];
|
|
||||||
vec2 p2 = p[i+1];
|
|
||||||
|
|
||||||
//NOTE(martin): get normals
|
if(!valid[i-1])
|
||||||
vec2 n0 = {p0.y - p1.y,
|
{
|
||||||
p1.x - p0.x};
|
ASSERT(valid[i]);
|
||||||
f32 norm0 = sqrt(n0.x*n0.x + n0.y*n0.y);
|
result[i] = legs[i][0];
|
||||||
n0.x /= norm0;
|
}
|
||||||
n0.y /= norm0;
|
else if(!valid[i])
|
||||||
|
{
|
||||||
vec2 n1 = {p1.y - p2.y,
|
ASSERT(valid[i-1]);
|
||||||
p2.x - p1.x};
|
result[i] = legs[i-1][0];
|
||||||
f32 norm1 = sqrt(n1.x*n1.x + n1.y*n1.y);
|
}
|
||||||
n1.x /= norm1;
|
else
|
||||||
n1.y /= norm1;
|
{
|
||||||
|
if(!mg_intersect_hull_legs(legs[i-1][0], legs[i-1][1], legs[i][0], legs[i][1], &result[i]))
|
||||||
/*NOTE(martin): let vector u = (n0+n1) and vector v = pIntersect - p1
|
{
|
||||||
then v = u * (2*offset / norm(u)^2)
|
// legs don't intersect.
|
||||||
(this can be derived from writing the pythagoras theorems in the triangles of the joint)
|
return(false);
|
||||||
*/
|
}
|
||||||
vec2 u = {n0.x + n1.x, n0.y + n1.y};
|
}
|
||||||
f32 uNormSquare = u.x*u.x + u.y*u.y;
|
|
||||||
f32 alpha = 2*offset / uNormSquare;
|
|
||||||
vec2 v = {u.x * alpha, u.y * alpha};
|
|
||||||
|
|
||||||
result[i].x = p1.x + v.x;
|
|
||||||
result[i].y = p1.y + v.y;
|
|
||||||
|
|
||||||
ASSERT(!isnan(result[i].x));
|
|
||||||
ASSERT(!isnan(result[i].y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//NOTE(martin): last offset control point is just the offset of last control point
|
if(valid[count-2])
|
||||||
n = (vec2){p[count-2].y - p[count-1].y,
|
{
|
||||||
p[count-1].x - p[count-2].x};
|
result[count-1] = legs[count-2][1];
|
||||||
norm = sqrt(n.x*n.x + n.y*n.y);
|
}
|
||||||
n.x *= offset/norm;
|
else
|
||||||
n.y *= offset/norm;
|
{
|
||||||
|
ASSERT(valid[count-3]);
|
||||||
|
result[count-1] = legs[count-3][1];
|
||||||
|
}
|
||||||
|
|
||||||
result[count-1].x = p[count-1].x + n.x;
|
return(true);
|
||||||
result[count-1].y = p[count-1].y + n.y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 mg_quadratic_get_point(vec2 p[3], f32 t)
|
vec2 mg_quadratic_get_point(vec2 p[3], f32 t)
|
||||||
|
@ -1573,87 +1656,110 @@ void mg_quadratic_split(vec2 p[3], f32 t, vec2 outLeft[3], vec2 outRight[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes)
|
void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[3], mg_attributes* attributes)
|
||||||
{
|
{
|
||||||
|
//NOTE: check for degenerate line case
|
||||||
|
const f32 equalEps = 1e-3;
|
||||||
|
if(vec2_close(p[0], p[1], equalEps))
|
||||||
|
{
|
||||||
|
mg_render_stroke_line(canvas, p+1, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(vec2_close(p[1], p[2], equalEps))
|
||||||
|
{
|
||||||
|
mg_render_stroke_line(canvas, p, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#define CHECK_SAMPLE_COUNT 5
|
#define CHECK_SAMPLE_COUNT 5
|
||||||
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
||||||
|
|
||||||
vec2 positiveOffsetHull[3];
|
vec2 positiveOffsetHull[3];
|
||||||
vec2 negativeOffsetHull[3];
|
vec2 negativeOffsetHull[3];
|
||||||
|
|
||||||
mg_offset_hull(3, p, positiveOffsetHull, 0.5 * attributes->width);
|
if( !mg_offset_hull(3, p, positiveOffsetHull, 0.5 * attributes->width)
|
||||||
mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width);
|
||!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
|
|
||||||
|
|
||||||
//TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot
|
|
||||||
f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width);
|
|
||||||
f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance);
|
|
||||||
f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance);
|
|
||||||
|
|
||||||
f32 maxOvershoot = 0;
|
|
||||||
f32 maxOvershootParameter = 0;
|
|
||||||
|
|
||||||
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
|
|
||||||
{
|
|
||||||
f32 t = checkSamples[i];
|
|
||||||
|
|
||||||
vec2 c = mg_quadratic_get_point(p, t);
|
|
||||||
vec2 cp = mg_quadratic_get_point(positiveOffsetHull, t);
|
|
||||||
vec2 cn = mg_quadratic_get_point(negativeOffsetHull, t);
|
|
||||||
|
|
||||||
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
|
|
||||||
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
|
|
||||||
|
|
||||||
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
|
|
||||||
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
|
|
||||||
|
|
||||||
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
|
|
||||||
|
|
||||||
if(overshoot > maxOvershoot)
|
|
||||||
{
|
|
||||||
maxOvershoot = overshoot;
|
|
||||||
maxOvershootParameter = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(maxOvershoot > 0)
|
|
||||||
{
|
{
|
||||||
|
//NOTE: offsetting the hull failed, split the curve
|
||||||
vec2 splitLeft[3];
|
vec2 splitLeft[3];
|
||||||
vec2 splitRight[3];
|
vec2 splitRight[3];
|
||||||
mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight);
|
mg_quadratic_split(p, 0.5, splitLeft, splitRight);
|
||||||
mg_render_stroke_quadratic(canvas, splitLeft, attributes);
|
mg_render_stroke_quadratic(canvas, splitLeft, attributes);
|
||||||
mg_render_stroke_quadratic(canvas, splitRight, attributes);
|
mg_render_stroke_quadratic(canvas, splitRight, attributes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//NOTE(martin): push the actual fill commands for the offset contour
|
//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
|
||||||
|
|
||||||
mg_next_shape(canvas, attributes);
|
//TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot
|
||||||
|
f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width);
|
||||||
|
f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance);
|
||||||
|
f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance);
|
||||||
|
|
||||||
mg_render_fill_quadratic(canvas, positiveOffsetHull);
|
f32 maxOvershoot = 0;
|
||||||
mg_render_fill_quadratic(canvas, negativeOffsetHull);
|
f32 maxOvershootParameter = 0;
|
||||||
|
|
||||||
//NOTE(martin): add base triangles
|
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
|
||||||
u32 baseIndex = mg_vertices_base_index(canvas);
|
{
|
||||||
i32* indices = mg_reserve_indices(canvas, 6);
|
f32 t = checkSamples[i];
|
||||||
|
|
||||||
mg_push_vertex(canvas, positiveOffsetHull[0]);
|
vec2 c = mg_quadratic_get_point(p, t);
|
||||||
mg_push_vertex(canvas, positiveOffsetHull[2]);
|
vec2 cp = mg_quadratic_get_point(positiveOffsetHull, t);
|
||||||
mg_push_vertex(canvas, negativeOffsetHull[2]);
|
vec2 cn = mg_quadratic_get_point(negativeOffsetHull, t);
|
||||||
mg_push_vertex(canvas, negativeOffsetHull[0]);
|
|
||||||
|
|
||||||
indices[0] = baseIndex + 0;
|
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
|
||||||
indices[1] = baseIndex + 1;
|
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
|
||||||
indices[2] = baseIndex + 2;
|
|
||||||
indices[3] = baseIndex + 0;
|
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
|
||||||
indices[4] = baseIndex + 2;
|
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
|
||||||
indices[5] = baseIndex + 3;
|
|
||||||
|
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
|
||||||
|
|
||||||
|
if(overshoot > maxOvershoot)
|
||||||
|
{
|
||||||
|
maxOvershoot = overshoot;
|
||||||
|
maxOvershootParameter = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(maxOvershoot > 0)
|
||||||
|
{
|
||||||
|
vec2 splitLeft[3];
|
||||||
|
vec2 splitRight[3];
|
||||||
|
mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight);
|
||||||
|
mg_render_stroke_quadratic(canvas, splitLeft, attributes);
|
||||||
|
mg_render_stroke_quadratic(canvas, splitRight, attributes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//NOTE(martin): push the actual fill commands for the offset contour
|
||||||
|
|
||||||
|
mg_next_shape(canvas, attributes);
|
||||||
|
|
||||||
|
mg_render_fill_quadratic(canvas, positiveOffsetHull);
|
||||||
|
mg_render_fill_quadratic(canvas, negativeOffsetHull);
|
||||||
|
|
||||||
|
//NOTE(martin): add base triangles
|
||||||
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
||||||
|
i32* indices = mg_reserve_indices(canvas, 6);
|
||||||
|
|
||||||
|
mg_push_vertex(canvas, positiveOffsetHull[0]);
|
||||||
|
mg_push_vertex(canvas, positiveOffsetHull[2]);
|
||||||
|
mg_push_vertex(canvas, negativeOffsetHull[2]);
|
||||||
|
mg_push_vertex(canvas, negativeOffsetHull[0]);
|
||||||
|
|
||||||
|
indices[0] = baseIndex + 0;
|
||||||
|
indices[1] = baseIndex + 1;
|
||||||
|
indices[2] = baseIndex + 2;
|
||||||
|
indices[3] = baseIndex + 0;
|
||||||
|
indices[4] = baseIndex + 2;
|
||||||
|
indices[5] = baseIndex + 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#undef CHECK_SAMPLE_COUNT
|
#undef CHECK_SAMPLE_COUNT
|
||||||
}
|
}
|
||||||
|
@ -1681,25 +1787,23 @@ void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4])
|
||||||
// the r_n are the points along the (q_n, q_n+1) 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.
|
// s is the split point.
|
||||||
|
|
||||||
f32 oneMt = 1-t;
|
vec2 q0 = {(1-t)*p[0].x + t*p[1].x,
|
||||||
|
(1-t)*p[0].y + t*p[1].y};
|
||||||
|
|
||||||
vec2 q0 = {oneMt*p[0].x + t*p[1].x,
|
vec2 q1 = {(1-t)*p[1].x + t*p[2].x,
|
||||||
oneMt*p[0].y + t*p[1].y};
|
(1-t)*p[1].y + t*p[2].y};
|
||||||
|
|
||||||
vec2 q1 = {oneMt*p[1].x + t*p[2].x,
|
vec2 q2 = {(1-t)*p[2].x + t*p[3].x,
|
||||||
oneMt*p[1].y + t*p[2].y};
|
(1-t)*p[2].y + t*p[3].y};
|
||||||
|
|
||||||
vec2 q2 = {oneMt*p[2].x + t*p[3].x,
|
vec2 r0 = {(1-t)*q0.x + t*q1.x,
|
||||||
oneMt*p[2].y + t*p[3].y};
|
(1-t)*q0.y + t*q1.y};
|
||||||
|
|
||||||
vec2 r0 = {oneMt*q0.x + t*q1.x,
|
vec2 r1 = {(1-t)*q1.x + t*q2.x,
|
||||||
oneMt*q0.y + t*q1.y};
|
(1-t)*q1.y + t*q2.y};
|
||||||
|
|
||||||
vec2 r1 = {oneMt*q1.x + t*q2.x,
|
vec2 s = {(1-t)*r0.x + t*r1.x,
|
||||||
oneMt*q1.y + t*q2.y};
|
(1-t)*r0.y + t*r1.y};;
|
||||||
|
|
||||||
vec2 s = {oneMt*r0.x + t*r1.x,
|
|
||||||
oneMt*r0.y + t*r1.y};;
|
|
||||||
|
|
||||||
outLeft[0] = p[0];
|
outLeft[0] = p[0];
|
||||||
outLeft[1] = q0;
|
outLeft[1] = q0;
|
||||||
|
@ -1714,14 +1818,46 @@ void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4])
|
||||||
|
|
||||||
void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes)
|
void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes)
|
||||||
{
|
{
|
||||||
|
//NOTE: check degenerate line cases
|
||||||
|
f32 equalEps = 1e-3;
|
||||||
|
|
||||||
|
if( (vec2_close(p[0], p[1], equalEps) && vec2_close(p[2], p[3], equalEps))
|
||||||
|
||(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[2], equalEps))
|
||||||
|
||(vec2_close(p[1], p[2], equalEps) && vec2_close(p[2], p[3], equalEps)))
|
||||||
|
{
|
||||||
|
vec2 line[2] = {p[0], p[3]};
|
||||||
|
mg_render_stroke_line(canvas, line, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[3], equalEps))
|
||||||
|
{
|
||||||
|
vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[2]))};
|
||||||
|
mg_render_stroke_line(canvas, line, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(vec2_close(p[0], p[2], equalEps) && vec2_close(p[2], p[3], equalEps))
|
||||||
|
{
|
||||||
|
vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[1]))};
|
||||||
|
mg_render_stroke_line(canvas, line, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#define CHECK_SAMPLE_COUNT 5
|
#define CHECK_SAMPLE_COUNT 5
|
||||||
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
||||||
|
|
||||||
vec2 positiveOffsetHull[4];
|
vec2 positiveOffsetHull[4];
|
||||||
vec2 negativeOffsetHull[4];
|
vec2 negativeOffsetHull[4];
|
||||||
|
|
||||||
mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width);
|
if( !mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width)
|
||||||
mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width);
|
|| !mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width))
|
||||||
|
{
|
||||||
|
vec2 splitLeft[4];
|
||||||
|
vec2 splitRight[4];
|
||||||
|
mg_cubic_split(p, 0.5, splitLeft, splitRight);
|
||||||
|
mg_render_stroke_cubic(canvas, splitLeft, attributes);
|
||||||
|
mg_render_stroke_cubic(canvas, splitRight, attributes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance
|
//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:
|
// thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this:
|
||||||
|
@ -1945,14 +2081,33 @@ void mg_render_stroke_element(mg_canvas_data* canvas,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
*startTangent = (vec2){.x = controlPoints[1].x - controlPoints[0].x,
|
//NOTE: ensure tangents are properly computed even in presence of coincident points
|
||||||
.y = controlPoints[1].y - controlPoints[0].y};
|
//TODO: see if we can do this in a less hacky way
|
||||||
|
|
||||||
*endTangent = (vec2){controlPoints[endPointIndex].x - controlPoints[endPointIndex-1].x,
|
|
||||||
controlPoints[endPointIndex].y - controlPoints[endPointIndex-1].y};
|
|
||||||
|
|
||||||
|
for(int i=1; i<4; i++)
|
||||||
|
{
|
||||||
|
if( controlPoints[i].x != controlPoints[0].x
|
||||||
|
|| controlPoints[i].y != controlPoints[0].y)
|
||||||
|
{
|
||||||
|
*startTangent = (vec2){.x = controlPoints[i].x - controlPoints[0].x,
|
||||||
|
.y = controlPoints[i].y - controlPoints[0].y};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
*endPoint = controlPoints[endPointIndex];
|
*endPoint = controlPoints[endPointIndex];
|
||||||
|
|
||||||
|
for(int i=endPointIndex-1; i>=0; i++)
|
||||||
|
{
|
||||||
|
if( controlPoints[i].x != endPoint->x
|
||||||
|
|| controlPoints[i].y != endPoint->y)
|
||||||
|
{
|
||||||
|
*endTangent = (vec2){.x = endPoint->x - controlPoints[i].x,
|
||||||
|
.y = endPoint->y - controlPoints[i].y};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_ASSERT(startTangent->x != 0 || startTangent->y != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 mg_render_stroke_subpath(mg_canvas_data* canvas,
|
u32 mg_render_stroke_subpath(mg_canvas_data* canvas,
|
||||||
|
@ -2744,10 +2899,7 @@ mg_canvas mg_canvas_create(mg_surface surface)
|
||||||
canvas->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX};
|
canvas->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX};
|
||||||
|
|
||||||
canvasHandle = mg_canvas_handle_alloc(canvas);
|
canvasHandle = mg_canvas_handle_alloc(canvas);
|
||||||
mg_canvas_set_current(canvasHandle);
|
mg_canvas_prepare(canvasHandle);
|
||||||
|
|
||||||
//TODO: move that in mg_canvas_set_current() if needed?
|
|
||||||
mg_surface_prepare(surface);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return(canvasHandle);
|
return(canvasHandle);
|
||||||
|
@ -2773,13 +2925,17 @@ void mg_canvas_destroy(mg_canvas handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mg_canvas mg_canvas_set_current(mg_canvas canvas)
|
mg_canvas mg_canvas_prepare(mg_canvas canvas)
|
||||||
{
|
{
|
||||||
mg_canvas old = __mgCurrentCanvasHandle;
|
mg_canvas old = __mgCurrentCanvasHandle;
|
||||||
|
|
||||||
__mgCurrentCanvasHandle = canvas;
|
__mgCurrentCanvasHandle = canvas;
|
||||||
__mgCurrentCanvas = mg_canvas_data_from_handle(canvas);
|
__mgCurrentCanvas = mg_canvas_data_from_handle(canvas);
|
||||||
|
|
||||||
|
if(__mgCurrentCanvas)
|
||||||
|
{
|
||||||
|
mg_surface_prepare(__mgCurrentCanvas->surface);
|
||||||
|
}
|
||||||
return(old);
|
return(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2998,16 +3154,27 @@ void mg_flush_commands(int primitiveCount, mg_primitive* primitives, mg_path_elt
|
||||||
void mg_flush()
|
void mg_flush()
|
||||||
{
|
{
|
||||||
mg_canvas_data* canvas = __mgCurrentCanvas;
|
mg_canvas_data* canvas = __mgCurrentCanvas;
|
||||||
if(!canvas)
|
if(canvas)
|
||||||
{
|
{
|
||||||
return;
|
mg_flush_commands(canvas->primitiveCount, canvas->primitives, canvas->pathElements);
|
||||||
|
canvas->primitiveCount = 0;
|
||||||
|
canvas->path.startIndex = 0;
|
||||||
|
canvas->path.count = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mg_flush_commands(canvas->primitiveCount, canvas->primitives, canvas->pathElements);
|
void mg_present()
|
||||||
|
{
|
||||||
|
mg_canvas_data* canvas = __mgCurrentCanvas;
|
||||||
|
if(canvas)
|
||||||
|
{
|
||||||
|
mg_flush_commands(canvas->primitiveCount, canvas->primitives, canvas->pathElements);
|
||||||
|
canvas->primitiveCount = 0;
|
||||||
|
canvas->path.startIndex = 0;
|
||||||
|
canvas->path.count = 0;
|
||||||
|
|
||||||
canvas->primitiveCount = 0;
|
mg_surface_present(canvas->surface);
|
||||||
canvas->path.startIndex = 0;
|
}
|
||||||
canvas->path.count = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -174,9 +174,10 @@ MP_API bool mg_canvas_is_nil(mg_canvas canvas);
|
||||||
|
|
||||||
MP_API mg_canvas mg_canvas_create(mg_surface surface);
|
MP_API mg_canvas mg_canvas_create(mg_surface surface);
|
||||||
MP_API void mg_canvas_destroy(mg_canvas canvas);
|
MP_API void mg_canvas_destroy(mg_canvas canvas);
|
||||||
MP_API mg_canvas mg_canvas_set_current(mg_canvas canvas);
|
|
||||||
|
|
||||||
|
MP_API mg_canvas mg_canvas_prepare(mg_canvas canvas);
|
||||||
MP_API void mg_flush(void);
|
MP_API void mg_flush(void);
|
||||||
|
MP_API void mg_present(void);
|
||||||
|
|
||||||
MP_API vec2 mg_canvas_size(void);
|
MP_API vec2 mg_canvas_size(void);
|
||||||
//------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
static const int MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20;
|
static const int MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH = 4<<20;
|
||||||
|
|
||||||
static const int MG_MTL_MAX_BUFFERS_IN_FLIGHT = 3;
|
static const int MG_MTL_MAX_BUFFER_AVAILABLE = 3;
|
||||||
|
|
||||||
typedef struct mg_mtl_canvas_backend
|
typedef struct mg_mtl_canvas_backend
|
||||||
{
|
{
|
||||||
|
@ -49,9 +49,9 @@ typedef struct mg_mtl_canvas_backend
|
||||||
id<MTLTexture> backbuffer;
|
id<MTLTexture> backbuffer;
|
||||||
id<MTLTexture> outTexture;
|
id<MTLTexture> outTexture;
|
||||||
|
|
||||||
id<MTLBuffer> shapeBuffer[MG_MTL_MAX_BUFFERS_IN_FLIGHT];
|
id<MTLBuffer> shapeBuffer[MG_MTL_MAX_BUFFER_AVAILABLE];
|
||||||
id<MTLBuffer> vertexBuffer[MG_MTL_MAX_BUFFERS_IN_FLIGHT];
|
id<MTLBuffer> vertexBuffer[MG_MTL_MAX_BUFFER_AVAILABLE];
|
||||||
id<MTLBuffer> indexBuffer[MG_MTL_MAX_BUFFERS_IN_FLIGHT];
|
id<MTLBuffer> indexBuffer[MG_MTL_MAX_BUFFER_AVAILABLE];
|
||||||
id<MTLBuffer> tileCounters;
|
id<MTLBuffer> tileCounters;
|
||||||
id<MTLBuffer> tileArrayBuffer;
|
id<MTLBuffer> tileArrayBuffer;
|
||||||
id<MTLBuffer> triangleArray;
|
id<MTLBuffer> triangleArray;
|
||||||
|
@ -82,9 +82,12 @@ void mg_mtl_canvas_update_vertex_layout(mg_mtl_canvas_backend* backend)
|
||||||
char* shapeBase = (char*)[backend->shapeBuffer[backend->bufferIndex] contents] + backend->shapeBufferOffset;
|
char* shapeBase = (char*)[backend->shapeBuffer[backend->bufferIndex] contents] + backend->shapeBufferOffset;
|
||||||
char* indexBase = (char*)[backend->indexBuffer[backend->bufferIndex] contents] + backend->indexBufferOffset;
|
char* indexBase = (char*)[backend->indexBuffer[backend->bufferIndex] contents] + backend->indexBufferOffset;
|
||||||
|
|
||||||
|
//TODO: add maxShapeCount
|
||||||
|
|
||||||
backend->interface.vertexLayout = (mg_vertex_layout){
|
backend->interface.vertexLayout = (mg_vertex_layout){
|
||||||
.maxVertexCount = MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH,
|
.maxVertexCount = MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH - backend->vertexBufferOffset/sizeof(mg_vertex),
|
||||||
.maxIndexCount = MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH,
|
.maxIndexCount = MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH - backend->indexBufferOffset/sizeof(int),
|
||||||
|
|
||||||
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
|
.cubicBuffer = vertexBase + offsetof(mg_vertex, cubic),
|
||||||
.cubicStride = sizeof(mg_vertex),
|
.cubicStride = sizeof(mg_vertex),
|
||||||
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
|
.posBuffer = vertexBase + offsetof(mg_vertex, pos),
|
||||||
|
@ -117,11 +120,10 @@ void mg_mtl_canvas_begin(mg_canvas_backend* interface, mg_color clearColor)
|
||||||
backend->indexBufferOffset = 0;
|
backend->indexBufferOffset = 0;
|
||||||
backend->shapeBufferOffset = 0;
|
backend->shapeBufferOffset = 0;
|
||||||
|
|
||||||
dispatch_semaphore_wait(backend->bufferSemaphore, DISPATCH_TIME_FOREVER);
|
|
||||||
backend->bufferIndex = (backend->bufferIndex + 1) % MG_MTL_MAX_BUFFERS_IN_FLIGHT;
|
|
||||||
|
|
||||||
mg_mtl_canvas_update_vertex_layout(backend);
|
mg_mtl_canvas_update_vertex_layout(backend);
|
||||||
|
|
||||||
|
mg_mtl_surface_acquire_command_buffer(surface);
|
||||||
|
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
MTLClearColor mtlClearColor = MTLClearColorMake(clearColor.r,
|
MTLClearColor mtlClearColor = MTLClearColorMake(clearColor.r,
|
||||||
|
@ -150,7 +152,7 @@ void mg_mtl_canvas_end(mg_canvas_backend* interface)
|
||||||
{
|
{
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
mg_mtl_surface_acquire_drawable_and_command_buffer(surface);
|
mg_mtl_surface_acquire_drawable(surface);
|
||||||
if(surface->drawable != nil)
|
if(surface->drawable != nil)
|
||||||
{
|
{
|
||||||
f32 scale = surface->mtlLayer.contentsScale;
|
f32 scale = surface->mtlLayer.contentsScale;
|
||||||
|
@ -175,13 +177,15 @@ void mg_mtl_canvas_end(mg_canvas_backend* interface)
|
||||||
vertexStart: 0
|
vertexStart: 0
|
||||||
vertexCount: 3 ];
|
vertexCount: 3 ];
|
||||||
[renderEncoder endEncoding];
|
[renderEncoder endEncoding];
|
||||||
|
|
||||||
[surface->commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer)
|
|
||||||
{
|
|
||||||
dispatch_semaphore_signal(backend->bufferSemaphore);
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
[surface->commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer)
|
||||||
|
{
|
||||||
|
dispatch_semaphore_signal(backend->bufferSemaphore);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
dispatch_semaphore_wait(backend->bufferSemaphore, DISPATCH_TIME_FOREVER);
|
||||||
|
backend->bufferIndex = (backend->bufferIndex + 1) % MG_MTL_MAX_BUFFER_AVAILABLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,7 +362,7 @@ void mg_mtl_canvas_destroy(mg_canvas_backend* interface)
|
||||||
{
|
{
|
||||||
[backend->outTexture release];
|
[backend->outTexture release];
|
||||||
|
|
||||||
for(int i=0; i < MG_MTL_MAX_BUFFERS_IN_FLIGHT; i++)
|
for(int i=0; i < MG_MTL_MAX_BUFFER_AVAILABLE; i++)
|
||||||
{
|
{
|
||||||
[backend->vertexBuffer[i] release];
|
[backend->vertexBuffer[i] release];
|
||||||
[backend->indexBuffer[i] release];
|
[backend->indexBuffer[i] release];
|
||||||
|
@ -481,13 +485,13 @@ mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface)
|
||||||
//NOTE(martin): create buffers
|
//NOTE(martin): create buffers
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
|
|
||||||
backend->bufferSemaphore = dispatch_semaphore_create(MG_MTL_MAX_BUFFERS_IN_FLIGHT);
|
backend->bufferSemaphore = dispatch_semaphore_create(MG_MTL_MAX_BUFFER_AVAILABLE);
|
||||||
backend->bufferIndex = 0;
|
backend->bufferIndex = 0;
|
||||||
|
|
||||||
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
|
MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined
|
||||||
| MTLResourceStorageModeShared;
|
| MTLResourceStorageModeShared;
|
||||||
|
|
||||||
for(int i=0; i<MG_MTL_MAX_BUFFERS_IN_FLIGHT; i++)
|
for(int i=0; i<MG_MTL_MAX_BUFFER_AVAILABLE; i++)
|
||||||
{
|
{
|
||||||
backend->indexBuffer[i] = [metalSurface->device newBufferWithLength: MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int)
|
backend->indexBuffer[i] = [metalSurface->device newBufferWithLength: MG_MTL_CANVAS_DEFAULT_BUFFER_LENGTH*sizeof(int)
|
||||||
options: bufferOptions];
|
options: bufferOptions];
|
||||||
|
|
|
@ -53,7 +53,16 @@ void mg_mtl_surface_destroy(mg_surface_data* interface)
|
||||||
//NOTE: we don't use mp_layer_cleanup here, because the CAMetalLayer is taken care off by the surface itself
|
//NOTE: we don't use mp_layer_cleanup here, because the CAMetalLayer is taken care off by the surface itself
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_mtl_surface_acquire_drawable_and_command_buffer(mg_mtl_surface* surface)
|
void mg_mtl_surface_acquire_command_buffer(mg_mtl_surface* surface)
|
||||||
|
{
|
||||||
|
if(surface->commandBuffer == nil)
|
||||||
|
{
|
||||||
|
surface->commandBuffer = [surface->commandQueue commandBuffer];
|
||||||
|
[surface->commandBuffer retain];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mg_mtl_surface_acquire_drawable(mg_mtl_surface* surface)
|
||||||
{@autoreleasepool{
|
{@autoreleasepool{
|
||||||
/*WARN(martin):
|
/*WARN(martin):
|
||||||
//TODO: we should stop trying to render if we detect that the app is in the background
|
//TODO: we should stop trying to render if we detect that the app is in the background
|
||||||
|
@ -72,17 +81,12 @@ void mg_mtl_surface_acquire_drawable_and_command_buffer(mg_mtl_surface* surface)
|
||||||
[surface->drawable retain];
|
[surface->drawable retain];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(surface->commandBuffer == nil)
|
|
||||||
{
|
|
||||||
surface->commandBuffer = [surface->commandQueue commandBuffer];
|
|
||||||
[surface->commandBuffer retain];
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
void mg_mtl_surface_prepare(mg_surface_data* interface)
|
void mg_mtl_surface_prepare(mg_surface_data* interface)
|
||||||
{
|
{
|
||||||
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
|
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
|
||||||
mg_mtl_surface_acquire_drawable_and_command_buffer(surface);
|
mg_mtl_surface_acquire_command_buffer(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_mtl_surface_present(mg_surface_data* interface)
|
void mg_mtl_surface_present(mg_surface_data* interface)
|
||||||
|
@ -90,18 +94,18 @@ void mg_mtl_surface_present(mg_surface_data* interface)
|
||||||
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
|
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
if(surface->drawable != nil)
|
if(surface->commandBuffer != nil)
|
||||||
{
|
{
|
||||||
[surface->commandBuffer presentDrawable: surface->drawable];
|
if(surface->drawable != nil)
|
||||||
[surface->drawable release];
|
{
|
||||||
surface->drawable = nil;
|
[surface->commandBuffer presentDrawable: surface->drawable];
|
||||||
|
[surface->drawable release];
|
||||||
|
surface->drawable = nil;
|
||||||
|
}
|
||||||
|
[surface->commandBuffer commit];
|
||||||
|
[surface->commandBuffer release];
|
||||||
|
surface->commandBuffer = nil;
|
||||||
}
|
}
|
||||||
[surface->commandBuffer commit];
|
|
||||||
// [surface->commandBuffer waitUntilCompleted];
|
|
||||||
//TODO: do we really need this?
|
|
||||||
|
|
||||||
[surface->commandBuffer release];
|
|
||||||
surface->commandBuffer = nil;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +132,10 @@ void mg_mtl_surface_set_frame(mg_surface_data* interface, mp_rect frame)
|
||||||
//TODO fix that according to real scaling, depending on the monitor settings
|
//TODO fix that according to real scaling, depending on the monitor settings
|
||||||
static const f32 MG_MTL_SURFACE_CONTENTS_SCALING = 2;
|
static const f32 MG_MTL_SURFACE_CONTENTS_SCALING = 2;
|
||||||
|
|
||||||
|
//NOTE: max frames in flight (n frames being queued on the GPU while we're working on the n+1 frame).
|
||||||
|
// for triple buffering, there's 2 frames being queued on the GPU while we're working on the 3rd frame
|
||||||
|
static const int MG_MTL_MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
mg_surface_data* mg_mtl_surface_create_for_window(mp_window window)
|
mg_surface_data* mg_mtl_surface_create_for_window(mp_window window)
|
||||||
{
|
{
|
||||||
mg_mtl_surface* surface = 0;
|
mg_mtl_surface* surface = 0;
|
||||||
|
@ -214,6 +222,7 @@ void* mg_mtl_surface_drawable(mg_surface surface)
|
||||||
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
|
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
|
||||||
{
|
{
|
||||||
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
|
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
|
||||||
|
mg_mtl_surface_acquire_drawable(mtlSurface);
|
||||||
return(mtlSurface->drawable);
|
return(mtlSurface->drawable);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -228,6 +237,7 @@ void* mg_mtl_surface_command_buffer(mg_surface surface)
|
||||||
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
|
if(surfaceData && surfaceData->backend == MG_BACKEND_METAL)
|
||||||
{
|
{
|
||||||
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
|
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
|
||||||
|
mg_mtl_surface_acquire_command_buffer(mtlSurface);
|
||||||
return(mtlSurface->commandBuffer);
|
return(mtlSurface->commandBuffer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue