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