mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge pull request #2780 from Noisyfox:bugfix/amd-png
Fix PNG build plate texture not rendering on AMD GPUs
This commit is contained in:
		
						commit
						93f62a47f5
					
				
					 6 changed files with 360 additions and 168 deletions
				
			
		|  | @ -1 +1 @@ | |||
| Upstream source: https://github.com/memononen/nanosvg/tree/c1f6e209c16b18b46aa9f45d7e619acf42c29726 | ||||
| Upstream source: https://github.com/fltk/nanosvg/archive/abcd277ea45e9098bed752cf9c6875b533c0892f.zip | ||||
|  | @ -72,6 +72,7 @@ extern "C" { | |||
| */ | ||||
| 
 | ||||
| enum NSVGpaintType { | ||||
| 	NSVG_PAINT_UNDEF = -1, | ||||
| 	NSVG_PAINT_NONE = 0, | ||||
| 	NSVG_PAINT_COLOR = 1, | ||||
| 	NSVG_PAINT_LINEAR_GRADIENT = 2, | ||||
|  | @ -119,7 +120,7 @@ typedef struct NSVGgradient { | |||
| } NSVGgradient; | ||||
| 
 | ||||
| typedef struct NSVGpaint { | ||||
| 	char type; | ||||
| 	signed char type; | ||||
| 	union { | ||||
| 		unsigned int color; | ||||
| 		NSVGgradient* gradient; | ||||
|  | @ -143,14 +144,17 @@ typedef struct NSVGshape | |||
| 	float opacity;				// Opacity of the shape.
 | ||||
| 	float strokeWidth;			// Stroke width (scaled).
 | ||||
| 	float strokeDashOffset;		// Stroke dash offset (scaled).
 | ||||
| 	float strokeDashArray[8];			// Stroke dash array (scaled).
 | ||||
| 	char strokeDashCount;				// Number of dash values in dash array.
 | ||||
| 	float strokeDashArray[8];	// Stroke dash array (scaled).
 | ||||
| 	char strokeDashCount;		// Number of dash values in dash array.
 | ||||
| 	char strokeLineJoin;		// Stroke join type.
 | ||||
| 	char strokeLineCap;			// Stroke cap type.
 | ||||
| 	float miterLimit;			// Miter limit
 | ||||
| 	char fillRule;				// Fill rule, see NSVGfillRule.
 | ||||
| 	unsigned char flags;		// Logical or of NSVG_FLAGS_* flags
 | ||||
| 	float bounds[4];			// Tight bounding box of the shape [minx,miny,maxx,maxy].
 | ||||
| 	char fillGradient[64];		// Optional 'id' of fill gradient
 | ||||
| 	char strokeGradient[64];	// Optional 'id' of stroke gradient
 | ||||
| 	float xform[6];				// Root transformation for fill/stroke gradient
 | ||||
| 	NSVGpath* paths;			// Linked list of paths in the image.
 | ||||
| 	struct NSVGshape* next;		// Pointer to next shape, or NULL if last element.
 | ||||
| } NSVGshape; | ||||
|  | @ -181,16 +185,13 @@ void nsvgDelete(NSVGimage* image); | |||
| #endif | ||||
| #endif | ||||
| 
 | ||||
| #endif // NANOSVG_H
 | ||||
| 
 | ||||
| #ifdef NANOSVG_IMPLEMENTATION | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string/replace.hpp> | ||||
| 
 | ||||
| #define NSVG_PI (3.14159265358979323846264338327f) | ||||
| #define NSVG_KAPPA90 (0.5522847493f)	// Length proportional to radius of a cubic bezier handle for 90deg arcs.
 | ||||
| 
 | ||||
|  | @ -227,11 +228,6 @@ static int nsvg__isdigit(char c) | |||
| 	return c >= '0' && c <= '9'; | ||||
| } | ||||
| 
 | ||||
| static int nsvg__isnum(char c) | ||||
| { | ||||
| 	return strchr("0123456789+-.eE", c) != 0; | ||||
| } | ||||
| 
 | ||||
| static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } | ||||
| static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } | ||||
| 
 | ||||
|  | @ -402,7 +398,7 @@ typedef struct NSVGgradientData | |||
| { | ||||
| 	char id[64]; | ||||
| 	char ref[64]; | ||||
| 	char type; | ||||
| 	signed char type; | ||||
| 	union { | ||||
| 		NSVGlinearData linear; | ||||
| 		NSVGradialData radial; | ||||
|  | @ -618,7 +614,7 @@ static void nsvg__curveBounds(float* bounds, float* curve) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static NSVGparser* nsvg__createParser() | ||||
| static NSVGparser* nsvg__createParser(void) | ||||
| { | ||||
| 	NSVGparser* p; | ||||
| 	p = (NSVGparser*)malloc(sizeof(NSVGparser)); | ||||
|  | @ -738,9 +734,11 @@ static void nsvg__lineTo(NSVGparser* p, float x, float y) | |||
| 
 | ||||
| static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) | ||||
| { | ||||
| 	nsvg__addPoint(p, cpx1, cpy1); | ||||
| 	nsvg__addPoint(p, cpx2, cpy2); | ||||
| 	nsvg__addPoint(p, x, y); | ||||
| 	if (p->npts > 0) { | ||||
| 		nsvg__addPoint(p, cpx1, cpy1); | ||||
| 		nsvg__addPoint(p, cpx2, cpy2); | ||||
| 		nsvg__addPoint(p, x, y); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static NSVGattrib* nsvg__getAttr(NSVGparser* p) | ||||
|  | @ -810,7 +808,9 @@ static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, | |||
| static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) | ||||
| { | ||||
| 	NSVGgradientData* grad = p->gradients; | ||||
| 	while (grad) { | ||||
| 	if (id == NULL || *id == '\0') | ||||
| 		return NULL; | ||||
| 	while (grad != NULL) { | ||||
| 		if (strcmp(grad->id, id) == 0) | ||||
| 			return grad; | ||||
| 		grad = grad->next; | ||||
|  | @ -818,28 +818,34 @@ static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) | |||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) | ||||
| static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType) | ||||
| { | ||||
| 	NSVGattrib* attr = nsvg__getAttr(p); | ||||
| 	NSVGgradientData* data = NULL; | ||||
| 	NSVGgradientData* ref = NULL; | ||||
| 	NSVGgradientStop* stops = NULL; | ||||
| 	NSVGgradient* grad; | ||||
| 	float ox, oy, sw, sh, sl; | ||||
| 	int nstops = 0; | ||||
| 	int refIter; | ||||
| 
 | ||||
| 	data = nsvg__findGradientData(p, id); | ||||
| 	if (data == NULL) return NULL; | ||||
| 
 | ||||
| 	// TODO: use ref to fill in all unset values too.
 | ||||
| 	ref = data; | ||||
| 	refIter = 0; | ||||
| 	while (ref != NULL) { | ||||
| 		NSVGgradientData* nextRef = NULL; | ||||
| 		if (stops == NULL && ref->stops != NULL) { | ||||
| 			stops = ref->stops; | ||||
| 			nstops = ref->nstops; | ||||
| 			break; | ||||
| 		} | ||||
| 		ref = nsvg__findGradientData(p, ref->ref); | ||||
| 		nextRef = nsvg__findGradientData(p, ref->ref); | ||||
| 		if (nextRef == ref) break; // prevent infite loops on malformed data
 | ||||
| 		ref = nextRef; | ||||
| 		refIter++; | ||||
| 		if (refIter > 32) break; // prevent infite loops on malformed data
 | ||||
| 	} | ||||
| 	if (stops == NULL) return NULL; | ||||
| 
 | ||||
|  | @ -888,7 +894,7 @@ static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const f | |||
| 	} | ||||
| 
 | ||||
| 	nsvg__xformMultiply(grad->xform, data->xform); | ||||
| 	nsvg__xformMultiply(grad->xform, attr->xform); | ||||
| 	nsvg__xformMultiply(grad->xform, xform); | ||||
| 
 | ||||
| 	grad->spread = data->spread; | ||||
| 	memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); | ||||
|  | @ -952,6 +958,9 @@ static void nsvg__addShape(NSVGparser* p) | |||
| 	memset(shape, 0, sizeof(NSVGshape)); | ||||
| 
 | ||||
| 	memcpy(shape->id, attr->id, sizeof shape->id); | ||||
| 	memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient); | ||||
| 	memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient); | ||||
| 	memcpy(shape->xform, attr->xform, sizeof shape->xform); | ||||
| 	scale = nsvg__getAverageScale(attr->xform); | ||||
| 	shape->strokeWidth = attr->strokeWidth * scale; | ||||
| 	shape->strokeDashOffset = attr->strokeDashOffset * scale; | ||||
|  | @ -987,13 +996,7 @@ static void nsvg__addShape(NSVGparser* p) | |||
| 		shape->fill.color = attr->fillColor; | ||||
| 		shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; | ||||
| 	} else if (attr->hasFill == 2) { | ||||
| 		float inv[6], localBounds[4]; | ||||
| 		nsvg__xformInverse(inv, attr->xform); | ||||
| 		nsvg__getLocalBounds(localBounds, shape, inv); | ||||
| 		shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); | ||||
| 		if (shape->fill.gradient == NULL) { | ||||
| 			shape->fill.type = NSVG_PAINT_NONE; | ||||
| 		} | ||||
| 		shape->fill.type = NSVG_PAINT_UNDEF; | ||||
| 	} | ||||
| 
 | ||||
| 	// Set stroke
 | ||||
|  | @ -1004,12 +1007,7 @@ static void nsvg__addShape(NSVGparser* p) | |||
| 		shape->stroke.color = attr->strokeColor; | ||||
| 		shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; | ||||
| 	} else if (attr->hasStroke == 2) { | ||||
| 		float inv[6], localBounds[4]; | ||||
| 		nsvg__xformInverse(inv, attr->xform); | ||||
| 		nsvg__getLocalBounds(localBounds, shape, inv); | ||||
| 		shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); | ||||
| 		if (shape->stroke.gradient == NULL) | ||||
| 			shape->stroke.type = NSVG_PAINT_NONE; | ||||
| 		shape->stroke.type = NSVG_PAINT_UNDEF; | ||||
| 	} | ||||
| 
 | ||||
| 	// Set flags
 | ||||
|  | @ -1042,6 +1040,10 @@ static void nsvg__addPath(NSVGparser* p, char closed) | |||
| 	if (closed) | ||||
| 		nsvg__lineTo(p, p->pts[0], p->pts[1]); | ||||
| 
 | ||||
| 	// Expect 1 + N*3 points (N = number of cubic bezier segments).
 | ||||
| 	if ((p->npts % 3) != 1) | ||||
| 		return; | ||||
| 
 | ||||
| 	path = (NSVGpath*)malloc(sizeof(NSVGpath)); | ||||
| 	if (path == NULL) goto error; | ||||
| 	memset(path, 0, sizeof(NSVGpath)); | ||||
|  | @ -1090,7 +1092,7 @@ static double nsvg__atof(const char* s) | |||
| 	char* cur = (char*)s; | ||||
| 	char* end = NULL; | ||||
| 	double res = 0.0, sign = 1.0; | ||||
| 	long long intPart = 0, fracPart = 0; | ||||
| 	double intPart = 0.0, fracPart = 0.0; | ||||
| 	char hasIntPart = 0, hasFracPart = 0; | ||||
| 
 | ||||
| 	// Parse optional sign
 | ||||
|  | @ -1104,9 +1106,13 @@ static double nsvg__atof(const char* s) | |||
| 	// Parse integer part
 | ||||
| 	if (nsvg__isdigit(*cur)) { | ||||
| 		// Parse digit sequence
 | ||||
| #ifdef _MSC_VER | ||||
| 		intPart = (double)_strtoi64(cur, &end, 10); | ||||
| #else | ||||
| 		intPart = (double)strtoll(cur, &end, 10); | ||||
| #endif | ||||
| 		if (cur != end) { | ||||
| 			res = (double)intPart; | ||||
| 			res = intPart; | ||||
| 			hasIntPart = 1; | ||||
| 			cur = end; | ||||
| 		} | ||||
|  | @ -1117,9 +1123,13 @@ static double nsvg__atof(const char* s) | |||
| 		cur++; // Skip '.'
 | ||||
| 		if (nsvg__isdigit(*cur)) { | ||||
| 			// Parse digit sequence
 | ||||
| 			fracPart = strtoll(cur, &end, 10); | ||||
| #ifdef _MSC_VER | ||||
| 			fracPart = (double)_strtoi64(cur, &end, 10); | ||||
| #else | ||||
| 			fracPart = (double)strtoll(cur, &end, 10); | ||||
| #endif | ||||
| 			if (cur != end) { | ||||
| 				res += (double)fracPart / pow(10.0, (double)(end - cur)); | ||||
| 				res += fracPart / pow(10.0, (double)(end - cur)); | ||||
| 				hasFracPart = 1; | ||||
| 				cur = end; | ||||
| 			} | ||||
|  | @ -1132,11 +1142,11 @@ static double nsvg__atof(const char* s) | |||
| 
 | ||||
| 	// Parse optional exponent
 | ||||
| 	if (*cur == 'e' || *cur == 'E') { | ||||
| 		int expPart = 0; | ||||
| 		double expPart = 0.0; | ||||
| 		cur++; // skip 'E'
 | ||||
| 		expPart = strtol(cur, &end, 10); // Parse digit sequence with sign
 | ||||
| 		expPart = (double)strtol(cur, &end, 10); // Parse digit sequence with sign
 | ||||
| 		if (cur != end) { | ||||
| 			res *= pow(10.0, (double)expPart); | ||||
| 			res *= pow(10.0, expPart); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1170,7 +1180,7 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size) | |||
| 		} | ||||
| 	} | ||||
| 	// exponent
 | ||||
| 	if (*s == 'e' || *s == 'E') { | ||||
| 	if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { | ||||
| 		if (i < last) it[i++] = *s; | ||||
| 		s++; | ||||
| 		if (*s == '-' || *s == '+') { | ||||
|  | @ -1187,6 +1197,19 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size) | |||
| 	return s; | ||||
| } | ||||
| 
 | ||||
| static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it) | ||||
| { | ||||
| 	it[0] = '\0'; | ||||
| 	while (*s && (nsvg__isspace(*s) || *s == ',')) s++; | ||||
| 	if (!*s) return s; | ||||
| 	if (*s == '0' || *s == '1') { | ||||
| 		it[0] = *s++; | ||||
| 		it[1] = '\0'; | ||||
| 		return s; | ||||
| 	} | ||||
| 	return s; | ||||
| } | ||||
| 
 | ||||
| static const char* nsvg__getNextPathItem(const char* s, char* it) | ||||
| { | ||||
| 	it[0] = '\0'; | ||||
|  | @ -1207,35 +1230,66 @@ static const char* nsvg__getNextPathItem(const char* s, char* it) | |||
| 
 | ||||
| static unsigned int nsvg__parseColorHex(const char* str) | ||||
| { | ||||
| 	unsigned int c = 0, r = 0, g = 0, b = 0; | ||||
| 	int n = 0; | ||||
| 	str++; // skip #
 | ||||
| 	// Calculate number of characters.
 | ||||
| 	while(str[n] && !nsvg__isspace(str[n])) | ||||
| 		n++; | ||||
| 	if (n == 6) { | ||||
| 		sscanf(str, "%x", &c); | ||||
| 	} else if (n == 3) { | ||||
| 		sscanf(str, "%x", &c); | ||||
| 		c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); | ||||
| 		c |= c<<4; | ||||
| 	} | ||||
| 	r = (c >> 16) & 0xff; | ||||
| 	g = (c >> 8) & 0xff; | ||||
| 	b = c & 0xff; | ||||
| 	return NSVG_RGB(r,g,b); | ||||
| 	unsigned int r=0, g=0, b=0; | ||||
| 	if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 )		// 2 digit hex
 | ||||
| 		return NSVG_RGB(r, g, b); | ||||
| 	if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 )		// 1 digit hex, e.g. #abc -> 0xccbbaa
 | ||||
| 		return NSVG_RGB(r*17, g*17, b*17);			// same effect as (r<<4|r), (g<<4|g), ..
 | ||||
| 	return NSVG_RGB(128, 128, 128); | ||||
| } | ||||
| 
 | ||||
| // Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters).
 | ||||
| // This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors
 | ||||
| // for backwards compatibility. Note: other image viewers return black instead.
 | ||||
| 
 | ||||
| static unsigned int nsvg__parseColorRGB(const char* str) | ||||
| { | ||||
| 	int r = -1, g = -1, b = -1; | ||||
| 	char s1[32]="", s2[32]=""; | ||||
| 	sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); | ||||
| 	if (strchr(s1, '%')) { | ||||
| 		return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); | ||||
| 	} else { | ||||
| 		return NSVG_RGB(r,g,b); | ||||
| 	int i; | ||||
| 	unsigned int rgbi[3]; | ||||
| 	float rgbf[3]; | ||||
| 	// try decimal integers first
 | ||||
| 	if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) { | ||||
| 		// integers failed, try percent values (float, locale independent)
 | ||||
| 		const char delimiter[3] = {',', ',', ')'}; | ||||
| 		str += 4; // skip "rgb("
 | ||||
| 		for (i = 0; i < 3; i++) { | ||||
| 			while (*str && (nsvg__isspace(*str))) str++; 	// skip leading spaces
 | ||||
| 			if (*str == '+') str++;				// skip '+' (don't allow '-')
 | ||||
| 			if (!*str) break; | ||||
| 			rgbf[i] = nsvg__atof(str); | ||||
| 
 | ||||
| 			// Note 1: it would be great if nsvg__atof() returned how many
 | ||||
| 			// bytes it consumed but it doesn't. We need to skip the number,
 | ||||
| 			// the '%' character, spaces, and the delimiter ',' or ')'.
 | ||||
| 
 | ||||
| 			// Note 2: The following code does not allow values like "33.%",
 | ||||
| 			// i.e. a decimal point w/o fractional part, but this is consistent
 | ||||
| 			// with other image viewers, e.g. firefox, chrome, eog, gimp.
 | ||||
| 
 | ||||
| 			while (*str && nsvg__isdigit(*str)) str++;		// skip integer part
 | ||||
| 			if (*str == '.') { | ||||
| 				str++; | ||||
| 				if (!nsvg__isdigit(*str)) break;		// error: no digit after '.'
 | ||||
| 				while (*str && nsvg__isdigit(*str)) str++;	// skip fractional part
 | ||||
| 			} | ||||
| 			if (*str == '%') str++; else break; | ||||
| 			while (nsvg__isspace(*str)) str++; | ||||
| 			if (*str == delimiter[i]) str++; | ||||
| 			else break; | ||||
| 		} | ||||
| 		if (i == 3) { | ||||
| 			rgbi[0] = roundf(rgbf[0] * 2.55f); | ||||
| 			rgbi[1] = roundf(rgbf[1] * 2.55f); | ||||
| 			rgbi[2] = roundf(rgbf[2] * 2.55f); | ||||
| 		} else { | ||||
| 			rgbi[0] = rgbi[1] = rgbi[2] = 128; | ||||
| 		} | ||||
| 	} | ||||
| 	// clip values as the CSS spec requires
 | ||||
| 	for (i = 0; i < 3; i++) { | ||||
| 		if (rgbi[i] > 255) rgbi[i] = 255; | ||||
| 	} | ||||
| 	return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]); | ||||
| } | ||||
| 
 | ||||
| typedef struct NSVGNamedColor { | ||||
|  | @ -1460,6 +1514,15 @@ static int nsvg__parseUnits(const char* units) | |||
| 	return NSVG_UNITS_USER; | ||||
| } | ||||
| 
 | ||||
| static int nsvg__isCoordinate(const char* s) | ||||
| { | ||||
| 	// optional sign
 | ||||
| 	if (*s == '-' || *s == '+') | ||||
| 		s++; | ||||
| 	// must have at least one digit, or start by a dot
 | ||||
| 	return (nsvg__isdigit(*s) || *s == '.'); | ||||
| } | ||||
| 
 | ||||
| static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) | ||||
| { | ||||
| 	NSVGcoordinate coord = {0, NSVG_UNITS_USER}; | ||||
|  | @ -1599,25 +1662,32 @@ static int nsvg__parseRotate(float* xform, const char* str) | |||
| static void nsvg__parseTransform(float* xform, const char* str) | ||||
| { | ||||
| 	float t[6]; | ||||
| 	int len; | ||||
| 	nsvg__xformIdentity(xform); | ||||
| 	while (*str) | ||||
| 	{ | ||||
| 		if (strncmp(str, "matrix", 6) == 0) | ||||
| 			str += nsvg__parseMatrix(t, str); | ||||
| 			len = nsvg__parseMatrix(t, str); | ||||
| 		else if (strncmp(str, "translate", 9) == 0) | ||||
| 			str += nsvg__parseTranslate(t, str); | ||||
| 			len = nsvg__parseTranslate(t, str); | ||||
| 		else if (strncmp(str, "scale", 5) == 0) | ||||
| 			str += nsvg__parseScale(t, str); | ||||
| 			len = nsvg__parseScale(t, str); | ||||
| 		else if (strncmp(str, "rotate", 6) == 0) | ||||
| 			str += nsvg__parseRotate(t, str); | ||||
| 			len = nsvg__parseRotate(t, str); | ||||
| 		else if (strncmp(str, "skewX", 5) == 0) | ||||
| 			str += nsvg__parseSkewX(t, str); | ||||
| 			len = nsvg__parseSkewX(t, str); | ||||
| 		else if (strncmp(str, "skewY", 5) == 0) | ||||
| 			str += nsvg__parseSkewY(t, str); | ||||
| 			len = nsvg__parseSkewY(t, str); | ||||
| 		else{ | ||||
| 			++str; | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (len != 0) { | ||||
| 			str += len; | ||||
| 		} else { | ||||
| 			++str; | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		nsvg__xformPremultiply(xform, t); | ||||
| 	} | ||||
|  | @ -1627,9 +1697,9 @@ static void nsvg__parseUrl(char* id, const char* str) | |||
| { | ||||
| 	int i = 0; | ||||
| 	str += 4; // "url(";
 | ||||
| 	if (*str == '#') | ||||
| 	if (*str && *str == '#') | ||||
| 		str++; | ||||
| 	while (i < 63 && *str != ')') { | ||||
| 	while (i < 63 && *str && *str != ')') { | ||||
| 		id[i] = *str++; | ||||
| 		i++; | ||||
| 	} | ||||
|  | @ -1878,8 +1948,11 @@ static int nsvg__getArgsPerElement(char cmd) | |||
| 		case 'a': | ||||
| 		case 'A': | ||||
| 			return 7; | ||||
| 		case 'z': | ||||
| 		case 'Z': | ||||
| 			return 0; | ||||
| 	} | ||||
| 	return 0; | ||||
| 	return -1; | ||||
| } | ||||
| 
 | ||||
| static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) | ||||
|  | @ -2160,7 +2233,12 @@ static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, | |||
| 	// The loop assumes an iteration per end point (including start and end), this +1.
 | ||||
| 	ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); | ||||
| 	hda = (da / (float)ndivs) / 2.0f; | ||||
| 	kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); | ||||
| 	// Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite)
 | ||||
| 	if ((hda < 1e-3f) && (hda > -1e-3f)) | ||||
| 		hda *= 0.5f; | ||||
| 	else | ||||
| 		hda = (1.0f - cosf(hda)) / sinf(hda); | ||||
| 	kappa = fabsf(4.0f / 3.0f * hda); | ||||
| 	if (da < 0.0f) | ||||
| 		kappa = -kappa; | ||||
| 
 | ||||
|  | @ -2189,6 +2267,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 	float args[10]; | ||||
| 	int nargs; | ||||
| 	int rargs = 0; | ||||
| 	char initPoint; | ||||
| 	float cpx, cpy, cpx2, cpy2; | ||||
| 	const char* tmp[4]; | ||||
| 	char closedFlag; | ||||
|  | @ -2211,13 +2290,18 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 		nsvg__resetPath(p); | ||||
| 		cpx = 0; cpy = 0; | ||||
| 		cpx2 = 0; cpy2 = 0; | ||||
| 		initPoint = 0; | ||||
| 		closedFlag = 0; | ||||
| 		nargs = 0; | ||||
| 
 | ||||
| 		while (*s) { | ||||
| 			s = nsvg__getNextPathItem(s, item); | ||||
| 			item[0] = '\0'; | ||||
| 			if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4)) | ||||
| 				s = nsvg__getNextPathItemWhenArcFlag(s, item); | ||||
| 			if (!*item) | ||||
| 				s = nsvg__getNextPathItem(s, item); | ||||
| 			if (!*item) break; | ||||
| 			if (nsvg__isnum(item[0])) { | ||||
| 			if (cmd != '\0' && nsvg__isCoordinate(item)) { | ||||
| 				if (nargs < 10) | ||||
| 					args[nargs++] = (float)nsvg__atof(item); | ||||
| 				if (nargs >= rargs) { | ||||
|  | @ -2230,6 +2314,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 							cmd = (cmd == 'm') ? 'l' : 'L'; | ||||
| 							rargs = nsvg__getArgsPerElement(cmd); | ||||
| 							cpx2 = cpx; cpy2 = cpy; | ||||
| 							initPoint = 1; | ||||
| 							break; | ||||
| 						case 'l': | ||||
| 						case 'L': | ||||
|  | @ -2279,7 +2364,6 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 				} | ||||
| 			} else { | ||||
| 				cmd = item[0]; | ||||
| 				rargs = nsvg__getArgsPerElement(cmd); | ||||
| 				if (cmd == 'M' || cmd == 'm') { | ||||
| 					// Commit path.
 | ||||
| 					if (p->npts > 0) | ||||
|  | @ -2288,7 +2372,11 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 					nsvg__resetPath(p); | ||||
| 					closedFlag = 0; | ||||
| 					nargs = 0; | ||||
| 				} else if (cmd == 'Z' || cmd == 'z') { | ||||
| 				} else if (initPoint == 0) { | ||||
| 					// Do not allow other commands until initial point has been set (moveTo called once).
 | ||||
| 					cmd = '\0'; | ||||
| 				} | ||||
| 				if (cmd == 'Z' || cmd == 'z') { | ||||
| 					closedFlag = 1; | ||||
| 					// Commit path.
 | ||||
| 					if (p->npts > 0) { | ||||
|  | @ -2304,6 +2392,12 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr) | |||
| 					closedFlag = 0; | ||||
| 					nargs = 0; | ||||
| 				} | ||||
| 				rargs = nsvg__getArgsPerElement(cmd); | ||||
| 				if (rargs == -1) { | ||||
| 					// Command not recognized
 | ||||
| 					cmd = '\0'; | ||||
| 					rargs = 0; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		// Commit path.
 | ||||
|  | @ -2550,7 +2644,7 @@ static void nsvg__parseSVG(NSVGparser* p, const char** attr) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) | ||||
| static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type) | ||||
| { | ||||
| 	int i; | ||||
| 	NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); | ||||
|  | @ -2875,6 +2969,36 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void nsvg__createGradients(NSVGparser* p) | ||||
| { | ||||
| 	NSVGshape* shape; | ||||
| 
 | ||||
| 	for (shape = p->image->shapes; shape != NULL; shape = shape->next) { | ||||
| 		if (shape->fill.type == NSVG_PAINT_UNDEF) { | ||||
| 			if (shape->fillGradient[0] != '\0') { | ||||
| 				float inv[6], localBounds[4]; | ||||
| 				nsvg__xformInverse(inv, shape->xform); | ||||
| 				nsvg__getLocalBounds(localBounds, shape, inv); | ||||
| 				shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type); | ||||
| 			} | ||||
| 			if (shape->fill.type == NSVG_PAINT_UNDEF) { | ||||
| 				shape->fill.type = NSVG_PAINT_NONE; | ||||
| 			} | ||||
| 		} | ||||
| 		if (shape->stroke.type == NSVG_PAINT_UNDEF) { | ||||
| 			if (shape->strokeGradient[0] != '\0') { | ||||
| 				float inv[6], localBounds[4]; | ||||
| 				nsvg__xformInverse(inv, shape->xform); | ||||
| 				nsvg__getLocalBounds(localBounds, shape, inv); | ||||
| 				shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type); | ||||
| 			} | ||||
| 			if (shape->stroke.type == NSVG_PAINT_UNDEF) { | ||||
| 				shape->stroke.type = NSVG_PAINT_NONE; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ||||
| { | ||||
| 	NSVGparser* p; | ||||
|  | @ -2888,6 +3012,9 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi) | |||
| 
 | ||||
| 	nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); | ||||
| 
 | ||||
| 	// Create gradients after all definitions have been parsed
 | ||||
| 	nsvg__createGradients(p); | ||||
| 
 | ||||
| 	// Scale to viewBox
 | ||||
| 	nsvg__scaleToViewbox(p, units); | ||||
| 
 | ||||
|  | @ -2899,8 +3026,6 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi) | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| 
 | ||||
| NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) | ||||
| { | ||||
| 	FILE* fp = NULL; | ||||
|  | @ -2908,8 +3033,8 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) | |||
| 	char* data = NULL; | ||||
| 	NSVGimage* image = NULL; | ||||
| 
 | ||||
|     fp = boost::nowide::fopen(filename, "rb"); | ||||
|     if (!fp) goto error; | ||||
| 	fp = fopen(filename, "rb"); | ||||
| 	if (!fp) goto error; | ||||
| 	fseek(fp, 0, SEEK_END); | ||||
| 	size = ftell(fp); | ||||
| 	fseek(fp, 0, SEEK_SET); | ||||
|  | @ -2918,9 +3043,9 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) | |||
| 	if (fread(data, 1, size, fp) != size) goto error; | ||||
| 	data[size] = '\0';	// Must be null terminated.
 | ||||
| 	fclose(fp); | ||||
| 
 | ||||
| 	image = nsvgParse(data, units, dpi); | ||||
| 	free(data); | ||||
| 
 | ||||
| 	return image; | ||||
| 
 | ||||
| error: | ||||
|  | @ -2976,4 +3101,6 @@ void nsvgDelete(NSVGimage* image) | |||
| 	free(image); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| #endif // NANOSVG_IMPLEMENTATION
 | ||||
| 
 | ||||
| #endif // NANOSVG_H
 | ||||
|  |  | |||
|  | @ -22,9 +22,17 @@ | |||
|  * | ||||
|  */ | ||||
| 
 | ||||
| /* Modified by FLTK to support non-square X,Y axes scaling.
 | ||||
|  * | ||||
|  * Added: nsvgRasterizeXY() | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| #ifndef NANOSVGRAST_H | ||||
| #define NANOSVGRAST_H | ||||
| 
 | ||||
| #include "nanosvg.h" | ||||
| 
 | ||||
| #ifndef NANOSVGRAST_CPLUSPLUS | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
|  | @ -44,16 +52,19 @@ typedef struct NSVGrasterizer NSVGrasterizer; | |||
| 	unsigned char* img = malloc(w*h*4); | ||||
| 	// Rasterize
 | ||||
| 	nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); | ||||
| 
 | ||||
| 	// For non-square X,Y scaling, use
 | ||||
| 	nsvgRasterizeXY(rast, image, 0,0,1,1, img, w, h, w*4); | ||||
| */ | ||||
| 
 | ||||
| // Allocated rasterizer context.
 | ||||
| NSVGrasterizer* nsvgCreateRasterizer(); | ||||
| NSVGrasterizer* nsvgCreateRasterizer(void); | ||||
| 
 | ||||
| // Rasterizes SVG image, returns RGBA image (non-premultiplied alpha)
 | ||||
| //   r - pointer to rasterizer context
 | ||||
| //   image - pointer to image to rasterize
 | ||||
| //   tx,ty - image offset (applied after scaling)
 | ||||
| //   scale - image scale
 | ||||
| //   scale - image scale (assumes square aspect ratio)
 | ||||
| //   dst - pointer to destination image data, 4 bytes per pixel (RGBA)
 | ||||
| //   w - width of the image to render
 | ||||
| //   h - height of the image to render
 | ||||
|  | @ -62,6 +73,12 @@ void nsvgRasterize(NSVGrasterizer* r, | |||
| 				   NSVGimage* image, float tx, float ty, float scale, | ||||
| 				   unsigned char* dst, int w, int h, int stride); | ||||
| 
 | ||||
| // As above, but allow X and Y axes to scale independently for non-square aspects
 | ||||
| void nsvgRasterizeXY(NSVGrasterizer* r, | ||||
| 				   NSVGimage* image, float tx, float ty, | ||||
| 				   float sx, float sy, | ||||
| 				   unsigned char* dst, int w, int h, int stride); | ||||
| 
 | ||||
| // Deletes rasterizer context.
 | ||||
| void nsvgDeleteRasterizer(NSVGrasterizer*); | ||||
| 
 | ||||
|  | @ -72,11 +89,11 @@ void nsvgDeleteRasterizer(NSVGrasterizer*); | |||
| #endif | ||||
| #endif | ||||
| 
 | ||||
| #endif // NANOSVGRAST_H
 | ||||
| 
 | ||||
| #ifdef NANOSVGRAST_IMPLEMENTATION | ||||
| 
 | ||||
| #include <math.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #define NSVG__SUBSAMPLES	5 | ||||
| #define NSVG__FIXSHIFT		10 | ||||
|  | @ -112,7 +129,7 @@ typedef struct NSVGmemPage { | |||
| } NSVGmemPage; | ||||
| 
 | ||||
| typedef struct NSVGcachedPaint { | ||||
| 	char type; | ||||
| 	signed char type; | ||||
| 	char spread; | ||||
| 	float xform[6]; | ||||
| 	unsigned int colors[256]; | ||||
|  | @ -148,7 +165,7 @@ struct NSVGrasterizer | |||
| 	int width, height, stride; | ||||
| }; | ||||
| 
 | ||||
| NSVGrasterizer* nsvgCreateRasterizer() | ||||
| NSVGrasterizer* nsvgCreateRasterizer(void) | ||||
| { | ||||
| 	NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); | ||||
| 	if (r == NULL) goto error; | ||||
|  | @ -368,7 +385,7 @@ static void nsvg__flattenCubicBez(NSVGrasterizer* r, | |||
| 	nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); | ||||
| } | ||||
| 
 | ||||
| static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) | ||||
| static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy) | ||||
| { | ||||
| 	int i, j; | ||||
| 	NSVGpath* path; | ||||
|  | @ -376,13 +393,13 @@ static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) | |||
| 	for (path = shape->paths; path != NULL; path = path->next) { | ||||
| 		r->npoints = 0; | ||||
| 		// Flatten path
 | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0); | ||||
| 		for (i = 0; i < path->npts-1; i += 3) { | ||||
| 			float* p = &path->pts[i*2]; | ||||
| 			nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); | ||||
| 			nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, 0); | ||||
| 		} | ||||
| 		// Close path
 | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0); | ||||
| 		// Build edges
 | ||||
| 		for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) | ||||
| 			nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); | ||||
|  | @ -732,7 +749,7 @@ static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoi | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) | ||||
| static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy) | ||||
| { | ||||
| 	int i, j, closed; | ||||
| 	NSVGpath* path; | ||||
|  | @ -740,15 +757,16 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float | |||
| 	float miterLimit = shape->miterLimit; | ||||
| 	int lineJoin = shape->strokeLineJoin; | ||||
| 	int lineCap = shape->strokeLineCap; | ||||
| 	float lineWidth = shape->strokeWidth * scale; | ||||
| 	const float sw = (sx + sy) / 2; // average scaling factor
 | ||||
| 	const float lineWidth = shape->strokeWidth * sw; // FIXME (?)
 | ||||
| 
 | ||||
| 	for (path = shape->paths; path != NULL; path = path->next) { | ||||
| 		// Flatten path
 | ||||
| 		r->npoints = 0; | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); | ||||
| 		nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, NSVG_PT_CORNER); | ||||
| 		for (i = 0; i < path->npts-1; i += 3) { | ||||
| 			float* p = &path->pts[i*2]; | ||||
| 			nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); | ||||
| 			nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, NSVG_PT_CORNER); | ||||
| 		} | ||||
| 		if (r->npoints < 2) | ||||
| 			continue; | ||||
|  | @ -794,7 +812,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float | |||
| 				dashOffset -= shape->strokeDashArray[idash]; | ||||
| 				idash = (idash + 1) % shape->strokeDashCount; | ||||
| 			} | ||||
| 			dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; | ||||
| 			dashLen = (shape->strokeDashArray[idash] - dashOffset) * sw; | ||||
| 
 | ||||
| 			for (j = 1; j < r->npoints2; ) { | ||||
| 				float dx = r->points2[j].x - cur.x; | ||||
|  | @ -816,7 +834,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float | |||
| 					// Advance dash pattern
 | ||||
| 					dashState = !dashState; | ||||
| 					idash = (idash+1) % shape->strokeDashCount; | ||||
| 					dashLen = shape->strokeDashArray[idash] * scale; | ||||
| 					dashLen = shape->strokeDashArray[idash] * sw; | ||||
| 					// Restart
 | ||||
| 					cur.x = x; | ||||
| 					cur.y = y; | ||||
|  | @ -956,7 +974,7 @@ static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a | |||
| 
 | ||||
| static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) | ||||
| { | ||||
| 	return (r) | (g << 8) | (b << 16) | (a << 24); | ||||
| 	return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); | ||||
| } | ||||
| 
 | ||||
| static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) | ||||
|  | @ -985,7 +1003,7 @@ static inline int nsvg__div255(int x) | |||
| } | ||||
| 
 | ||||
| static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, | ||||
| 								float tx, float ty, float scale, NSVGcachedPaint* cache) | ||||
| 								float tx, float ty, float sx, float sy, NSVGcachedPaint* cache) | ||||
| { | ||||
| 
 | ||||
| 	if (cache->type == NSVG_PAINT_COLOR) { | ||||
|  | @ -1026,9 +1044,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co | |||
| 		int i, cr, cg, cb, ca; | ||||
| 		unsigned int c; | ||||
| 
 | ||||
| 		fx = ((float)x - tx) / scale; | ||||
| 		fy = ((float)y - ty) / scale; | ||||
| 		dx = 1.0f / scale; | ||||
| 		fx = ((float)x - tx) / sx; | ||||
| 		fy = ((float)y - ty) / sy; | ||||
| 		dx = 1.0f / sx; | ||||
| 
 | ||||
| 		for (i = 0; i < count; i++) { | ||||
| 			int r,g,b,a,ia; | ||||
|  | @ -1071,9 +1089,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co | |||
| 		int i, cr, cg, cb, ca; | ||||
| 		unsigned int c; | ||||
| 
 | ||||
| 		fx = ((float)x - tx) / scale; | ||||
| 		fy = ((float)y - ty) / scale; | ||||
| 		dx = 1.0f / scale; | ||||
| 		fx = ((float)x - tx) / sx; | ||||
| 		fy = ((float)y - ty) / sy; | ||||
| 		dx = 1.0f / sx; | ||||
| 
 | ||||
| 		for (i = 0; i < count; i++) { | ||||
| 			int r,g,b,a,ia; | ||||
|  | @ -1112,7 +1130,7 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) | ||||
| static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float sx, float sy, NSVGcachedPaint* cache, char fillRule) | ||||
| { | ||||
| 	NSVGactiveEdge *active = NULL; | ||||
| 	int y, s; | ||||
|  | @ -1194,7 +1212,7 @@ static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, fl | |||
| 		if (xmin < 0) xmin = 0; | ||||
| 		if (xmax > r->width-1) xmax = r->width-1; | ||||
| 		if (xmin <= xmax) { | ||||
| 			nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); | ||||
| 			nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, sx, sy, cache); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1362,8 +1380,9 @@ static void dumpEdges(NSVGrasterizer* r, const char* name) | |||
| } | ||||
| */ | ||||
| 
 | ||||
| void nsvgRasterize(NSVGrasterizer* r, | ||||
| 				   NSVGimage* image, float tx, float ty, float scale, | ||||
| void nsvgRasterizeXY(NSVGrasterizer* r, | ||||
| 				   NSVGimage* image, float tx, float ty, | ||||
| 				   float sx, float sy, | ||||
| 				   unsigned char* dst, int w, int h, int stride) | ||||
| { | ||||
| 	NSVGshape *shape = NULL; | ||||
|  | @ -1394,7 +1413,7 @@ void nsvgRasterize(NSVGrasterizer* r, | |||
| 			r->freelist = NULL; | ||||
| 			r->nedges = 0; | ||||
| 
 | ||||
| 			nsvg__flattenShape(r, shape, scale); | ||||
| 			nsvg__flattenShape(r, shape, sx, sy); | ||||
| 
 | ||||
| 			// Scale and translate edges
 | ||||
| 			for (i = 0; i < r->nedges; i++) { | ||||
|  | @ -1406,19 +1425,20 @@ void nsvgRasterize(NSVGrasterizer* r, | |||
| 			} | ||||
| 
 | ||||
| 			// Rasterize edges
 | ||||
| 			qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); | ||||
| 			if (r->nedges != 0) | ||||
| 				qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); | ||||
| 
 | ||||
| 			// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
 | ||||
| 			nsvg__initPaint(&cache, &shape->fill, shape->opacity); | ||||
| 
 | ||||
| 			nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); | ||||
| 			nsvg__rasterizeSortedEdges(r, tx,ty, sx, sy, &cache, shape->fillRule); | ||||
| 		} | ||||
| 		if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { | ||||
| 		if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * sx) > 0.01f) { | ||||
| 			nsvg__resetPool(r); | ||||
| 			r->freelist = NULL; | ||||
| 			r->nedges = 0; | ||||
| 
 | ||||
| 			nsvg__flattenShapeStroke(r, shape, scale); | ||||
| 			nsvg__flattenShapeStroke(r, shape, sx, sy); | ||||
| 
 | ||||
| //			dumpEdges(r, "edge.svg");
 | ||||
| 
 | ||||
|  | @ -1432,12 +1452,13 @@ void nsvgRasterize(NSVGrasterizer* r, | |||
| 			} | ||||
| 
 | ||||
| 			// Rasterize edges
 | ||||
| 			qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); | ||||
| 			if (r->nedges != 0) | ||||
| 				qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); | ||||
| 
 | ||||
| 			// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
 | ||||
| 			nsvg__initPaint(&cache, &shape->stroke, shape->opacity); | ||||
| 
 | ||||
| 			nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); | ||||
| 			nsvg__rasterizeSortedEdges(r, tx,ty,sx, sy, &cache, NSVG_FILLRULE_NONZERO); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1449,4 +1470,13 @@ void nsvgRasterize(NSVGrasterizer* r, | |||
| 	r->stride = 0; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| void nsvgRasterize(NSVGrasterizer* r, | ||||
| 				   NSVGimage* image, float tx, float ty, float scale, | ||||
| 				   unsigned char* dst, int w, int h, int stride) | ||||
| { | ||||
| 	nsvgRasterizeXY(r,image, tx, ty, scale, scale, dst, w, h, stride); | ||||
| } | ||||
| 
 | ||||
| #endif // NANOSVGRAST_IMPLEMENTATION
 | ||||
| 
 | ||||
| #endif // NANOSVGRAST_H
 | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| ///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
 | ||||
| ///|/
 | ||||
| ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
 | ||||
| ///|/
 | ||||
| //BBS:add i18n
 | ||||
| #include "I18N.hpp" | ||||
| //BBS: add fstream for debug output
 | ||||
|  | @ -416,7 +420,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::stri | |||
|     glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); | ||||
|     glsafe(::glGenTextures(1, &m_id)); | ||||
|     glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); | ||||
|     if (compress && GLEW_EXT_texture_compression_s3tc) | ||||
|     if (compress && OpenGLManager::are_compressed_textures_supported()) | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|     else | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|  | @ -534,7 +538,7 @@ bool GLTexture::generate_from_text(const std::string &text_str, wxFont &font, wx | |||
|     glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); | ||||
|     glsafe(::glGenTextures(1, &m_id)); | ||||
|     glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); | ||||
|     if (GLEW_EXT_texture_compression_s3tc) | ||||
|     if (OpenGLManager::are_compressed_textures_supported()) | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|     else | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|  | @ -635,7 +639,7 @@ bool GLTexture::generate_texture_from_text(const std::string& text_str, wxFont& | |||
|     glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); | ||||
|     glsafe(::glGenTextures(1, &m_id)); | ||||
|     glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); | ||||
|     if (GLEW_EXT_texture_compression_s3tc) | ||||
|     if (OpenGLManager::are_compressed_textures_supported()) | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|     else | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
|  | @ -695,9 +699,29 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, | |||
|     glsafe(::glDisable(GL_BLEND)); | ||||
| } | ||||
| 
 | ||||
| static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) | ||||
| { | ||||
|     auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; | ||||
|     auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; | ||||
| 
 | ||||
|     int new_w = std::max(w, h); | ||||
|     if (!is_power_of_two(new_w)) | ||||
|         new_w = upper_power_of_two(new_w); | ||||
| 
 | ||||
|     while (new_w > max_size_px) { | ||||
|         new_w /= 2; | ||||
|     } | ||||
| 
 | ||||
|     const int new_h = new_w; | ||||
|     const bool ret = (new_w != w || new_h != h); | ||||
|     w = new_w; | ||||
|     h = new_h; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) | ||||
| { | ||||
|     bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc; | ||||
|     const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); | ||||
| 
 | ||||
|     // Load a PNG with an alpha channel.
 | ||||
|     wxImage image; | ||||
|  | @ -711,6 +735,11 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo | |||
| 
 | ||||
|     bool requires_rescale = false; | ||||
| 
 | ||||
|     if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) { | ||||
|         if (to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), OpenGLManager::get_gl_info().get_max_tex_size(), m_width, m_height)) | ||||
|             requires_rescale = true; | ||||
|     } | ||||
| 
 | ||||
|     if (compression_enabled && compression_type == MultiThreaded) { | ||||
|         // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
 | ||||
|         int width_rem = m_width % 4; | ||||
|  | @ -837,7 +866,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo | |||
| 
 | ||||
|     m_source = filename; | ||||
| 
 | ||||
|     if (compression_enabled && compression_type == MultiThreaded) | ||||
|     if (compression_type == MultiThreaded) | ||||
|         // start asynchronous compression
 | ||||
|         m_compressor.start_compressing(); | ||||
| 
 | ||||
|  | @ -846,7 +875,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo | |||
| 
 | ||||
| bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) | ||||
| { | ||||
|     bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; | ||||
|     const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); | ||||
| 
 | ||||
|     NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); | ||||
|     if (image == nullptr) { | ||||
|  | @ -854,11 +883,17 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     float scale = (float)max_size_px / std::max(image->width, image->height); | ||||
|     const float scale = (float)max_size_px / std::max(image->width, image->height); | ||||
| 
 | ||||
|     m_width = (int)(scale * image->width); | ||||
|     m_height = (int)(scale * image->height); | ||||
| 
 | ||||
|     if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) | ||||
|         to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), max_size_px, m_width, m_height); | ||||
| 
 | ||||
|     float scale_w = (float)m_width / image->width; | ||||
|     float scale_h = (float)m_height / image->height; | ||||
| 
 | ||||
|     if (compression_enabled) { | ||||
|         // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
 | ||||
|         int width_rem = m_width % 4; | ||||
|  | @ -871,7 +906,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
|             m_height += (4 - height_rem); | ||||
|     } | ||||
| 
 | ||||
|     int n_pixels = m_width * m_height; | ||||
|     const int n_pixels = m_width * m_height; | ||||
| 
 | ||||
|     if (n_pixels <= 0) { | ||||
|         reset(); | ||||
|  | @ -888,7 +923,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
| 
 | ||||
|     // creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps
 | ||||
|     std::vector<unsigned char> data(n_pixels * 4, 0); | ||||
|     nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4); | ||||
|     nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), m_width, m_height, m_width * 4); | ||||
| 
 | ||||
|     // sends data to gpu
 | ||||
|     glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); | ||||
|  | @ -910,7 +945,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
|     else | ||||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); | ||||
| 
 | ||||
|     if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { | ||||
|     if (use_mipmaps) { | ||||
|         // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
 | ||||
|         int lod_w = m_width; | ||||
|         int lod_h = m_height; | ||||
|  | @ -920,11 +955,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
| 
 | ||||
|             lod_w = std::max(lod_w / 2, 1); | ||||
|             lod_h = std::max(lod_h / 2, 1); | ||||
|             scale /= 2.0f; | ||||
|             scale_w /= 2.0f; | ||||
|             scale_h /= 2.0f; | ||||
| 
 | ||||
|             data.resize(lod_w * lod_h * 4); | ||||
| 
 | ||||
|             nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); | ||||
|             nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), lod_w, lod_h, lod_w * 4); | ||||
|             if (compression_enabled) { | ||||
|                 // initializes the texture on GPU
 | ||||
|                 glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); | ||||
|  | @ -939,9 +975,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo | |||
|             glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); | ||||
|             glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); | ||||
|         } | ||||
|     } else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { | ||||
|         glGenerateMipmap(GL_TEXTURE_2D); | ||||
|     } else { | ||||
|     } | ||||
|     else { | ||||
|         glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); | ||||
|         glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| ///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
 | ||||
| ///|/
 | ||||
| ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
 | ||||
| ///|/
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "OpenGLManager.hpp" | ||||
| 
 | ||||
|  | @ -212,7 +216,7 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const | |||
| 
 | ||||
| OpenGLManager::GLInfo OpenGLManager::s_gl_info; | ||||
| bool OpenGLManager::s_compressed_textures_supported = false; | ||||
| bool OpenGLManager::m_use_manually_generated_mipmaps = true; | ||||
| bool OpenGLManager::s_force_power_of_two_textures = false; | ||||
| OpenGLManager::EMultisampleState OpenGLManager::s_multisample = OpenGLManager::EMultisampleState::Unknown; | ||||
| OpenGLManager::EFramebufferType OpenGLManager::s_framebuffers_type = OpenGLManager::EFramebufferType::Unknown; | ||||
| 
 | ||||
|  | @ -295,31 +299,22 @@ bool OpenGLManager::init_gl(bool popup_error) | |||
| 
 | ||||
| #ifdef _WIN32 | ||||
|         // Since AMD driver version 22.7.1, there is probably some bug in the driver that causes the issue with the missing
 | ||||
|         // texture of the bed. It seems that this issue only triggers when mipmaps are generated manually
 | ||||
|         // (combined with a texture compression) and when mipmaps are generated through OpenGL glGenerateMipmap is working.
 | ||||
|         // So, for newer drivers than 22.6.1, the last working driver version, we use mipmaps generated through OpenGL.
 | ||||
|         if (const auto gl_info = OpenGLManager::get_gl_info(); boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.")) { | ||||
|             // WHQL drivers seem to have one more version number at the end besides non-WHQL drivers.
 | ||||
|             //     WHQL: 4.6.14800 Compatibility Profile Context 22.6.1 30.0.21023.1015
 | ||||
|             // Non-WHQL: 4.6.0 Compatibility Profile Context 22.8.1.220810
 | ||||
|             std::regex version_rgx(R"(Compatibility\sProfile\sContext\s(\d+)\.(\d+)\.(\d+))"); | ||||
|             if (std::smatch matches; std::regex_search(gl_info.get_version(), matches, version_rgx) && matches.size() == 4) { | ||||
|                 int version_major = std::stoi(matches[1].str()); | ||||
|                 int version_minor = std::stoi(matches[2].str()); | ||||
|                 int version_patch = std::stoi(matches[3].str()); | ||||
|                 BOOST_LOG_TRIVIAL(debug) << "Found AMD driver version: " << version_major << "." << version_minor << "." << version_patch; | ||||
| 
 | ||||
|                 if (version_major > 22 || (version_major == 22 && version_minor > 6) || (version_major == 22 && version_minor == 6 && version_patch > 1)) { | ||||
|                     m_use_manually_generated_mipmaps = false; | ||||
|                     BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; | ||||
|                 } | ||||
|             } else { | ||||
|                 BOOST_LOG_TRIVIAL(error) << "Not recognized format of version."; | ||||
|             } | ||||
|         } else { | ||||
|             BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "not AMD driver."; | ||||
|         } | ||||
| #endif | ||||
|         // texture of the bed (see: https://github.com/prusa3d/PrusaSlicer/issues/8417).
 | ||||
|         // It seems that this issue only triggers when mipmaps are generated manually
 | ||||
|         // (combined with a texture compression) with texture size not being power of two.
 | ||||
|         // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine,
 | ||||
|         // but the mipmap generation is quite slow on some machines.
 | ||||
|         // There is no an easy way to detect the driver version without using Win32 API because the strings returned by OpenGL
 | ||||
|         // have no standardized format, only some of them contain the driver version.
 | ||||
|         // Until we do not know that driver will be fixed (if ever) we force the use of power of two textures on all cards
 | ||||
|         // 1) containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER)
 | ||||
|         // 2) containing the string 'Custom' in the string returned by glGetString(GL_RENDERER)
 | ||||
|         const auto& gl_info = OpenGLManager::get_gl_info(); | ||||
|         if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && | ||||
|            (boost::contains(gl_info.get_renderer(), "Radeon") || | ||||
|             boost::contains(gl_info.get_renderer(), "Custom"))) | ||||
|             s_force_power_of_two_textures = true; | ||||
| #endif // _WIN32
 | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| ///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
 | ||||
| ///|/
 | ||||
| ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
 | ||||
| ///|/
 | ||||
| #ifndef slic3r_OpenGLManager_hpp_ | ||||
| #define slic3r_OpenGLManager_hpp_ | ||||
| 
 | ||||
|  | @ -83,10 +87,11 @@ private: | |||
|     static OSInfo s_os_info; | ||||
| #endif //__APPLE__
 | ||||
|     static bool s_compressed_textures_supported; | ||||
|     static bool s_force_power_of_two_textures; | ||||
| 
 | ||||
|     static EMultisampleState s_multisample; | ||||
|     static EFramebufferType s_framebuffers_type; | ||||
| 
 | ||||
|     static bool m_use_manually_generated_mipmaps; | ||||
| public: | ||||
|     OpenGLManager() = default; | ||||
|     ~OpenGLManager(); | ||||
|  | @ -103,7 +108,7 @@ public: | |||
|     static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } | ||||
|     static wxGLCanvas* create_wxglcanvas(wxWindow& parent); | ||||
|     static const GLInfo& get_gl_info() { return s_gl_info; } | ||||
|     static bool use_manually_generated_mipmaps() { return m_use_manually_generated_mipmaps; } | ||||
|     static bool force_power_of_two_textures() { return s_force_power_of_two_textures; } | ||||
| 
 | ||||
| private: | ||||
|     static void detect_multisample(int* attribList); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 SoftFever
						SoftFever