Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPU-based antialiased vector rendering #550

Open
11 of 22 tasks
soywiz opened this issue Feb 19, 2022 · 4 comments
Open
11 of 22 tasks

GPU-based antialiased vector rendering #550

soywiz opened this issue Feb 19, 2022 · 4 comments

Comments

@soywiz
Copy link
Member

soywiz commented Feb 19, 2022

For rendering huge scaled vectors, we might want to implement gpu-based vector rendering:
https://github.com/memononen/nanovg

For text, there are some tricks (either distances, or passing bezier curves to the shaders [this would require not supporting it in webgl1]):
https://github.com/azsn/gllabel

  • Even-odd winding shape rendering
  • Color fills
  • Bitmap fills
  • Linear gradient fills
  • Radial gradient fills
  • Sweep gradient fills
  • Clipping/masks
  • Proper bounds for gpu shapes view
  • Non-zero winding shape rendering
  • Strokes
  • Antialiasing, either MSAA on render buffers or extra stencil tricks like nanovg that should give better performance
  • Stroke filling doesn't have the right transform (specially noticeable on AA border of fills)
  • Convert cubic beziers into quadratic ones
  • Curve rendering at the shader level (less points, and more accurate, requires approximating cubic into quadratic)
  • TODO: we have to shrink shapes a bit to draw the AA stroke. We can know the shrink direction by checking each joint if going in one direction or another falls inside the polygon with existing functionality. This should work with both even-odd and non-zero winding modes
  • TODO: cache stroke points too (already done for fills)
  • TODO: detect convex shapes to draw them without stencil. Special care for (self-intersecting loop - pseudo-convex polygon) - https://math.stackexchange.com/questions/1743995/determine-whether-a-polygon-is-convex-based-on-its-vertices
  • TODO: since this requires a lot of batch calls, let's implement AG queues with incremental states and multithreaded rendering: AG improvements #465 multithreaded renderer soywiz-archive/korge-next#516
  • TODO: Antialiased clipping (maybe we can use an intermediate render buffer and composite it)
  • TODO: orthogonal vertical/horizontal strokes shouldn't have antialiasing
  • Composite modes
  • Verify it is functionality-complete to cover all HTML canvas features: https://simon.html5.org/dump/html5-canvas-cheat-sheet.html
@LeHaine
Copy link
Contributor

LeHaine commented Feb 19, 2022

Just to add on the GPU based text rendering - I did a port of the that gllabel implementation in Kotlin here which may be useful as an additional resource.

@soywiz
Copy link
Member Author

soywiz commented Feb 23, 2022

Thanks @LeHaine ! I'll have a look when implementing this.
I have seen that there are limitations on the number of curves and some wasted space. Maybe indices could be tightly packed and each glyph be an index reference to the indices table.

What I did for now in the meantime is this experiment:

https://glslsandbox.com/e#79489.2

#extension GL_OES_standard_derivatives : enable

precision highp float;

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

bool intersectionX(vec2 pos, vec2 a, vec2 b) {
	float minY = min(a.y, b.y);
	float maxY = max(a.y, b.y);
	
	if (pos.y >= minY && pos.y <= maxY) {
		float x;
		
		if (a.x == b.x) {
			x = a.x;
		} else {
			float m = (a.y - b.y) / (a.x - b.x);
			// y = m*x + b
			// y - b = m*x
			// x = (y - b) / m
			// b = y - m*x
			float b = (a.y) - (m * a.x);
			
			x = (pos.y - b) / m;
		}
		
		return x >= pos.x;
	} else {
		return false;
	}
}

void main( void ) {

	vec2 position = ( gl_FragCoord.xy / resolution.xy );

	bool odd = false;
	if (intersectionX(position, vec2(0.1, 0.3), vec2(0.3, 0.6))) odd = !odd;
	if (intersectionX(position, vec2(0.3, 0.3), vec2(0.4, 0.6))) odd = !odd;

	if (intersectionX(position, vec2(0.1, 0.2), vec2(0.8, 0.6))) odd = !odd;
	if (intersectionX(position, vec2(0.2, 0.2), vec2(0.9, 0.6))) odd = !odd;

	if (intersectionX(position, vec2(0.8, 0.2), vec2(0.8, 0.6))) odd = !odd;
	if (intersectionX(position, vec2(0.9, 0.2), vec2(0.9, 0.6))) odd = !odd;

	if (odd) {
		gl_FragColor = vec4(1, 0, 0, 1);
	} else {
		gl_FragColor = vec4(0, 0, 1, 1);
	}		
}

@soywiz
Copy link
Member Author

soywiz commented Mar 31, 2022

To do antialiasing, I’m going to use the nanovg approach. Create a stroke around the shape lines in the center, then draw only outside the stencil with an alpha based on the distance to the center (the line) to approximate the pixel coverage.

Once this is working, we can try to draw the inner of the shapes with the current approach, and then draw triangles with the curves later. This will reduce required points a lot, with less memory and points sent to the gpu.

@soywiz
Copy link
Member Author

soywiz commented Mar 31, 2022

Non-zero winding can be implemented as what nanovg does (and Ejecta): do separate stencil operations: for front faces increment the stencil, for back faces decrement the stencil.
Since we use a bit for masks, we might move that bit to the last bit and use another stencil op function for checking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

No branches or pull requests

2 participants