1 // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
2 //
3 // Permission to use, copy, modify, and distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 //
15 // Aegisub Project http://www.aegisub.org/
16 
17 /// @file gl_wrap.cpp
18 /// @brief Convenience functions for drawing various geometric primitives on an OpenGL surface
19 /// @ingroup video_output
20 ///
21 
22 #include "gl_wrap.h"
23 
24 #include <wx/colour.h>
25 
26 #ifdef HAVE_OPENGL_GL_H
27 #include <OpenGL/gl.h>
28 #include <OpenGL/glext.h>
29 #else
30 #include <GL/gl.h>
31 #include "gl/glext.h"
32 #endif
33 
34 static const float deg2rad = 3.1415926536f / 180.f;
35 static const float pi = 3.1415926535897932384626433832795f;
36 
37 #ifdef __WIN32__
38 #define glGetProc(a) wglGetProcAddress(a)
39 #elif !defined(__APPLE__)
40 #include <GL/glx.h>
41 #define glGetProc(a) glXGetProcAddress((const GLubyte *)(a))
42 #endif
43 
44 #if defined(__APPLE__)
45 // Not required on OS X.
46 #define APIENTRY
47 #define GL_EXT(type, name)
48 #else
49 #define GL_EXT(type, name) \
50 	static type name = reinterpret_cast<type>(glGetProc(#name)); \
51 	if (!name) { \
52 	name = reinterpret_cast<type>(& name ## Fallback); \
53 	}
54 #endif
55 
56 class VertexArray {
57 	std::vector<float> data;
58 	size_t dim;
59 public:
VertexArray(size_t dims,size_t elems)60 	VertexArray(size_t dims, size_t elems) {
61 		SetSize(dims, elems);
62 	}
63 
SetSize(size_t dims,size_t elems)64 	void SetSize(size_t dims, size_t elems) {
65 		dim = dims;
66 		data.resize(elems * dim);
67 	}
68 
Set(size_t i,float x,float y)69 	void Set(size_t i, float x, float y) {
70 		data[i * dim] = x;
71 		data[i * dim + 1] = y;
72 	}
73 
Set(size_t i,float x,float y,float z)74 	void Set(size_t i, float x, float y, float z) {
75 		data[i * dim] = x;
76 		data[i * dim + 1] = y;
77 		data[i * dim + 2] = z;
78 	}
79 
Set(size_t i,Vector2D p)80 	void Set(size_t i, Vector2D p) {
81 		data[i * dim] = p.X();
82 		data[i * dim + 1] = p.Y();
83 	}
84 
Draw(GLenum mode,bool clear=true)85 	void Draw(GLenum mode, bool clear = true) {
86 		glEnableClientState(GL_VERTEX_ARRAY);
87 		glVertexPointer(dim, GL_FLOAT, 0, &data[0]);
88 		glDrawArrays(mode, 0, data.size() / dim);
89 		glDisableClientState(GL_VERTEX_ARRAY);
90 		if (clear)
91 			data.clear();
92 	}
93 };
94 
OpenGLWrapper()95 OpenGLWrapper::OpenGLWrapper() {
96 	line_r = line_g = line_b = line_a = 1.f;
97 	fill_r = fill_g = fill_b = fill_a = 1.f;
98 	line_width = 1;
99 	transform_pushed = false;
100 	smooth = true;
101 }
102 
DrawLine(Vector2D p1,Vector2D p2) const103 void OpenGLWrapper::DrawLine(Vector2D p1, Vector2D p2) const {
104 	SetModeLine();
105 	VertexArray buf(2, 2);
106 	buf.Set(0, p1);
107 	buf.Set(1, p2);
108 	buf.Draw(GL_LINES);
109 }
110 
interp(Vector2D p1,Vector2D p2,float t)111 static inline Vector2D interp(Vector2D p1, Vector2D p2, float t) {
112 	return t * p1 + (1 - t) * p2;
113 }
114 
DrawDashedLine(Vector2D p1,Vector2D p2,float step) const115 void OpenGLWrapper::DrawDashedLine(Vector2D p1, Vector2D p2, float step) const {
116 	step /= (p2 - p1).Len();
117 	for (float t = 0; t < 1.f; t += 2 * step) {
118 		DrawLine(interp(p1, p2, t), interp(p1, p2, t + step));
119 	}
120 }
121 
DrawEllipse(Vector2D center,Vector2D radius) const122 void OpenGLWrapper::DrawEllipse(Vector2D center, Vector2D radius) const {
123 	DrawRing(center, radius.Y(), radius.Y(), radius.X() / radius.Y());
124 }
125 
DrawRectangle(Vector2D p1,Vector2D p2) const126 void OpenGLWrapper::DrawRectangle(Vector2D p1, Vector2D p2) const {
127 	VertexArray buf(2, 4);
128 	buf.Set(0, p1);
129 	buf.Set(1, Vector2D(p2, p1));
130 	buf.Set(2, p2);
131 	buf.Set(3, Vector2D(p1, p2));
132 
133 	// Fill
134 	if (fill_a != 0.0) {
135 		SetModeFill();
136 		buf.Draw(GL_QUADS, false);
137 	}
138 	// Outline
139 	if (line_a != 0.0) {
140 		SetModeLine();
141 		buf.Draw(GL_LINE_LOOP);
142 	}
143 }
144 
DrawTriangle(Vector2D p1,Vector2D p2,Vector2D p3) const145 void OpenGLWrapper::DrawTriangle(Vector2D p1, Vector2D p2, Vector2D p3) const {
146 	VertexArray buf(2, 3);
147 	buf.Set(0, p1);
148 	buf.Set(1, p2);
149 	buf.Set(2, p3);
150 
151 	// Fill
152 	if (fill_a != 0.0) {
153 		SetModeFill();
154 		buf.Draw(GL_TRIANGLES, false);
155 	}
156 	// Outline
157 	if (line_a != 0.0) {
158 		SetModeLine();
159 		buf.Draw(GL_LINE_LOOP);
160 	}
161 }
162 
DrawRing(Vector2D center,float r1,float r2,float ar,float arc_start,float arc_end) const163 void OpenGLWrapper::DrawRing(Vector2D center, float r1, float r2, float ar, float arc_start, float arc_end) const {
164 	if (r2 > r1)
165 		std::swap(r1, r2);
166 
167 	// Arc range
168 	bool needs_end_caps = arc_start != arc_end;
169 
170 	arc_end *= deg2rad;
171 	arc_start *= deg2rad;
172 	if (arc_end <= arc_start)
173 		arc_end += 2.f * pi;
174 	float range = arc_end - arc_start;
175 
176 	// Math
177 	int steps = std::max<int>(((r1 + r1 * ar) * range / (2.f * pi)) * 4, 12);
178 	float step = range / steps;
179 	float cur_angle = arc_start;
180 
181 	VertexArray buf(2, steps);
182 
183 	Vector2D scale_inner = Vector2D(ar, 1) * r1;
184 	Vector2D scale_outer = Vector2D(ar, 1) * r2;
185 
186 	if (fill_a != 0.0) {
187 		SetModeFill();
188 
189 		// Annulus
190 		if (r1 != r2) {
191 			buf.SetSize(2, (steps + 1) * 2);
192 			for (int i = 0; i <= steps; i++) {
193 				Vector2D offset = Vector2D::FromAngle(cur_angle);
194 				buf.Set(i * 2 + 0, center + offset * scale_inner);
195 				buf.Set(i * 2 + 1, center + offset * scale_outer);
196 				cur_angle += step;
197 			}
198 			buf.Draw(GL_QUAD_STRIP);
199 		}
200 		// Circle
201 		else {
202 			buf.SetSize(2, steps);
203 			for (int i = 0; i < steps; i++) {
204 				buf.Set(i, center + Vector2D::FromAngle(cur_angle) * scale_inner);
205 				cur_angle += step;
206 			}
207 			buf.Draw(GL_POLYGON);
208 		}
209 
210 		cur_angle = arc_start;
211 	}
212 
213 	if (line_a == 0.0) return;
214 
215 	// Outer
216 	steps++;
217 	buf.SetSize(2, steps);
218 
219 	SetModeLine();
220 	for (int i = 0; i < steps; i++) {
221 		buf.Set(i, center + Vector2D::FromAngle(cur_angle) * scale_outer);
222 		cur_angle += step;
223 	}
224 	buf.Draw(GL_LINE_STRIP);
225 
226 	// Inner
227 	if (r1 == r2) return;
228 
229 	cur_angle = arc_start;
230 	buf.SetSize(2, steps);
231 	for (int i = 0; i < steps; i++) {
232 		buf.Set(i, center + Vector2D::FromAngle(cur_angle) * scale_inner);
233 		cur_angle += step;
234 	}
235 	buf.Draw(GL_LINE_STRIP);
236 
237 	if (!needs_end_caps) return;
238 
239 	buf.SetSize(2, 4);
240 	buf.Set(0, center + Vector2D::FromAngle(arc_start) * scale_inner);
241 	buf.Set(1, center + Vector2D::FromAngle(arc_start) * scale_outer);
242 	buf.Set(2, center + Vector2D::FromAngle(arc_end) * scale_inner);
243 	buf.Set(3, center + Vector2D::FromAngle(arc_end) * scale_outer);
244 	buf.Draw(GL_LINES);
245 }
246 
SetLineColour(wxColour col,float alpha,int width)247 void OpenGLWrapper::SetLineColour(wxColour col, float alpha, int width) {
248 	line_r = col.Red() / 255.f;
249 	line_g = col.Green() / 255.f;
250 	line_b = col.Blue() / 255.f;
251 	line_a = alpha;
252 	line_width = width;
253 }
254 
SetFillColour(wxColour col,float alpha)255 void OpenGLWrapper::SetFillColour(wxColour col, float alpha) {
256 	fill_r = col.Red() / 255.f;
257 	fill_g = col.Green() / 255.f;
258 	fill_b = col.Blue() / 255.f;
259 	fill_a = alpha;
260 }
261 
SetModeLine() const262 void OpenGLWrapper::SetModeLine() const {
263 	glColor4f(line_r, line_g, line_b, line_a);
264 	glEnable(GL_BLEND);
265 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
266 	glLineWidth(line_width);
267 	if (smooth)
268 		glEnable(GL_LINE_SMOOTH);
269 	else
270 		glDisable(GL_LINE_SMOOTH);
271 }
272 
SetModeFill() const273 void OpenGLWrapper::SetModeFill() const {
274 	glColor4f(fill_r, fill_g, fill_b, fill_a);
275 	if (fill_a == 1.f) glDisable(GL_BLEND);
276 	else {
277 		glEnable(GL_BLEND);
278 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
279 	}
280 }
281 
SetInvert()282 void OpenGLWrapper::SetInvert() {
283 	glEnable(GL_COLOR_LOGIC_OP);
284 	glLogicOp(GL_INVERT);
285 
286 	// GL_LINE_SMOOTH combines badly with inverting
287 	smooth = false;
288 }
289 
ClearInvert()290 void OpenGLWrapper::ClearInvert() {
291 	glDisable(GL_COLOR_LOGIC_OP);
292 	smooth = true;
293 }
294 
IsExtensionSupported(const char * ext)295 bool OpenGLWrapper::IsExtensionSupported(const char *ext) {
296 	char *extList = (char * )glGetString(GL_EXTENSIONS);
297 	return extList && !!strstr(extList, ext);
298 }
299 
DrawLines(size_t dim,std::vector<float> const & lines)300 void OpenGLWrapper::DrawLines(size_t dim, std::vector<float> const& lines) {
301 	DrawLines(dim, &lines[0], lines.size() / dim);
302 }
303 
DrawLines(size_t dim,std::vector<float> const & lines,size_t c_dim,std::vector<float> const & colors)304 void OpenGLWrapper::DrawLines(size_t dim, std::vector<float> const& lines, size_t c_dim, std::vector<float> const& colors) {
305 	glShadeModel(GL_SMOOTH);
306 	glEnableClientState(GL_COLOR_ARRAY);
307 	glColorPointer(c_dim, GL_FLOAT, 0, &colors[0]);
308 	DrawLines(dim, &lines[0], lines.size() / dim);
309 	glDisableClientState(GL_COLOR_ARRAY);
310 	glShadeModel(GL_FLAT);
311 }
312 
DrawLines(size_t dim,const float * lines,size_t n)313 void OpenGLWrapper::DrawLines(size_t dim, const float *lines, size_t n) {
314 	SetModeLine();
315 	glEnableClientState(GL_VERTEX_ARRAY);
316 	glVertexPointer(dim, GL_FLOAT, 0, lines);
317 	glDrawArrays(GL_LINES, 0, n);
318 	glDisableClientState(GL_VERTEX_ARRAY);
319 }
320 
DrawLineStrip(size_t dim,std::vector<float> const & lines)321 void OpenGLWrapper::DrawLineStrip(size_t dim, std::vector<float> const& lines) {
322 	SetModeLine();
323 	glEnableClientState(GL_VERTEX_ARRAY);
324 	glVertexPointer(dim, GL_FLOAT, 0, &lines[0]);
325 	glDrawArrays(GL_LINE_STRIP, 0, lines.size() / dim);
326 	glDisableClientState(GL_VERTEX_ARRAY);
327 }
328 
329 // Substitute for glMultiDrawArrays for sub-1.4 OpenGL
330 // Not required on OS X.
331 #ifndef __APPLE__
glMultiDrawArraysFallback(GLenum mode,const GLint * first,const GLsizei * count,GLsizei primcount)332 static void APIENTRY glMultiDrawArraysFallback(GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount) {
333 	for (int i = 0; i < primcount; ++i) {
334 		glDrawArrays(mode, *first++, *count++);
335 	}
336 }
337 #endif
338 
DrawMultiPolygon(std::vector<float> const & points,std::vector<int> & start,std::vector<int> & count,Vector2D video_pos,Vector2D video_size,bool invert)339 void OpenGLWrapper::DrawMultiPolygon(std::vector<float> const& points, std::vector<int> &start, std::vector<int> &count, Vector2D video_pos, Vector2D video_size, bool invert) {
340 	GL_EXT(PFNGLMULTIDRAWARRAYSPROC, glMultiDrawArrays);
341 
342 	float real_line_a = line_a;
343 	line_a = 0;
344 
345 	// The following is nonzero winding-number PIP based on stencils
346 
347 	// Draw to stencil only
348 	glEnable(GL_STENCIL_TEST);
349 	glColorMask(0, 0, 0, 0);
350 
351 	// GL_INCR_WRAP was added in 1.4, so instead set the entire stencil to 128
352 	// and wobble from there
353 	glStencilFunc(GL_NEVER, 128, 0xFF);
354 	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
355 
356 	Vector2D video_max = video_pos + video_size;
357 	DrawRectangle(video_pos, video_max);
358 
359 	// Increment the winding number for each forward facing triangle
360 	glStencilOp(GL_INCR, GL_INCR, GL_INCR);
361 	glEnable(GL_CULL_FACE);
362 
363 	glCullFace(GL_BACK);
364 	glEnableClientState(GL_VERTEX_ARRAY);
365 	glVertexPointer(2, GL_FLOAT, 0, &points[0]);
366 	glMultiDrawArrays(GL_TRIANGLE_FAN, &start[0], &count[0], start.size());
367 
368 	// Decrement the winding number for each backfacing triangle
369 	glStencilOp(GL_DECR, GL_DECR, GL_DECR);
370 	glCullFace(GL_FRONT);
371 	glMultiDrawArrays(GL_TRIANGLE_FAN, &start[0], &count[0], start.size());
372 	glDisable(GL_CULL_FACE);
373 
374 	// Draw the actual rectangle
375 	glColorMask(1, 1, 1, 1);
376 
377 	// VSFilter draws when the winding number is nonzero, so we want to draw the
378 	// mask when the winding number is zero (where 128 is zero due to the lack of
379 	// wrapping combined with unsigned numbers)
380 	glStencilFunc(invert ? GL_EQUAL : GL_NOTEQUAL, 128, 0xFF);
381 	DrawRectangle(video_pos, video_max);
382 	glDisable(GL_STENCIL_TEST);
383 
384 	// Draw lines
385 	line_a = real_line_a;
386 	SetModeLine();
387 	glEnableClientState(GL_VERTEX_ARRAY);
388 	glVertexPointer(2, GL_FLOAT, 0, &points[0]);
389 	glMultiDrawArrays(GL_LINE_LOOP, &start[0], &count[0], start.size());
390 
391 	glDisableClientState(GL_VERTEX_ARRAY);
392 }
393 
SetOrigin(Vector2D origin)394 void OpenGLWrapper::SetOrigin(Vector2D origin) {
395 	PrepareTransform();
396 	glTranslatef(origin.X(), origin.Y(), -1.f);
397 }
398 
SetScale(Vector2D scale)399 void OpenGLWrapper::SetScale(Vector2D scale) {
400 	PrepareTransform();
401 	glScalef(scale.X() / 100.f, scale.Y() / 100.f, 1.f);
402 }
403 
SetRotation(float x,float y,float z)404 void OpenGLWrapper::SetRotation(float x, float y, float z) {
405 	PrepareTransform();
406 	float matrix[16] = { 2500, 0, 0, 0, 0, 2500, 0, 0, 0, 0, 1, 1, 0, 0, 2500, 2500 };
407 	glMultMatrixf(matrix);
408 	glScalef(1.f, 1.f, 8.f);
409 	glRotatef(y, 0.f, -1.f, 0.f);
410 	glRotatef(x, -1.f, 0.f, 0.f);
411 	glRotatef(z, 0.f, 0.f, -1.f);
412 }
413 
SetShear(float x,float y)414 void OpenGLWrapper::SetShear(float x, float y) {
415 	PrepareTransform();
416 	float matrix[16] = {
417 		1, y, 0, 0,
418 		x, 1, 0, 0,
419 		0, 0, 1, 0,
420 		0, 0, 0, 1
421 	};
422 	glMultMatrixf(matrix);
423 }
424 
PrepareTransform()425 void OpenGLWrapper::PrepareTransform() {
426 	if (!transform_pushed) {
427 		glMatrixMode(GL_MODELVIEW);
428 		glPushMatrix();
429 		glLoadIdentity();
430 		transform_pushed = true;
431 	}
432 }
433 
ResetTransform()434 void OpenGLWrapper::ResetTransform() {
435 	if (transform_pushed) {
436 		glPopMatrix();
437 		transform_pushed = false;
438 	}
439 }
440