The Pedigree Project 0.1
gears.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/*
21 * Copyright (C) 1999-2001 Brian Paul All Rights Reserved.
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a
24 * copy of this software and associated documentation files (the "Software"),
25 * to deal in the Software without restriction, including without limitation
26 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
27 * and/or sell copies of the Software, and to permit persons to whom the
28 * Software is furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included
31 * in all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
34 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
36 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
37 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
38 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39 */
40/* $XFree86: xc/programs/glxgears/glxgears.c,v 1.4 2003/10/24 20:38:11 tsi Exp $ */
41
42/*
43 * This is a port of the infamous "gears" demo to straight GLX (i.e. no GLUT)
44 * Port by Brian Paul 23 March 2001
45 *
46 * Command line options:
47 * -info print GL implementation information
48 *
49 */
50
51// Simplified, courtesy of Kevin Lange.
52
53#define _USE_MATH_DEFINES
54#include <cstdint>
55#include <math.h>
56#include <optional>
57#include <sched.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <string.h>
61#include <unistd.h>
62
63#include <pedigree/log.h>
64#include <sys/time.h>
65
66#include <GL/gl.h>
67#include <GL/osmesa.h>
68
69#include "demo-app.h"
70
71static GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
72static GLint gear1, gear2, gear3;
73static GLfloat angle = 0.0;
74
75static unsigned int frames = 0;
76static unsigned int start_time = 0;
77
78void fps()
79{
80 struct timeval now;
81 gettimeofday(&now, NULL);
82 frames++;
83 if (!start_time)
84 {
85 start_time = now.tv_sec;
86 }
87 else if ((now.tv_sec - start_time) >= 5)
88 {
89 GLfloat seconds = now.tv_sec - start_time;
90 GLfloat fps = frames / seconds;
91 pedigree_log(LOG_INFO, "%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds, fps);
92 start_time = now.tv_sec;
93 frames = 0;
94 }
95}
96
97static void
98gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
99 GLint teeth, GLfloat tooth_depth)
100{
101 GLint i;
102 GLfloat r0, r1, r2;
103 GLfloat angle, da;
104 GLfloat u, v, len;
105
106 r0 = inner_radius;
107 r1 = outer_radius - tooth_depth / 2.0;
108 r2 = outer_radius + tooth_depth / 2.0;
109
110 da = 2.0 * M_PI / teeth / 4.0;
111
112 glShadeModel(GL_FLAT);
113
114 glNormal3f(0.0, 0.0, 1.0);
115
116 /* draw front face */
117 glBegin(GL_QUAD_STRIP);
118 for (i = 0; i <= teeth; i++) {
119 angle = i * 2.0 * M_PI / teeth;
120 glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
121 glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
122 if (i < teeth) {
123 glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
124 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
125 width * 0.5);
126 }
127 }
128 glEnd();
129
130 /* draw front sides of teeth */
131 glBegin(GL_QUADS);
132 da = 2.0 * M_PI / teeth / 4.0;
133 for (i = 0; i < teeth; i++) {
134 angle = i * 2.0 * M_PI / teeth;
135
136 glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
137 glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
138 glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
139 width * 0.5);
140 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
141 width * 0.5);
142 }
143 glEnd();
144
145 glNormal3f(0.0, 0.0, -1.0);
146
147 /* draw back face */
148 glBegin(GL_QUAD_STRIP);
149 for (i = 0; i <= teeth; i++) {
150 angle = i * 2.0 * M_PI / teeth;
151 glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
152 glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
153 if (i < teeth) {
154 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
155 -width * 0.5);
156 glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
157 }
158 }
159 glEnd();
160
161 /* draw back sides of teeth */
162 glBegin(GL_QUADS);
163 da = 2.0 * M_PI / teeth / 4.0;
164 for (i = 0; i < teeth; i++) {
165 angle = i * 2.0 * M_PI / teeth;
166
167 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
168 -width * 0.5);
169 glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
170 -width * 0.5);
171 glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
172 glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
173 }
174 glEnd();
175
176 /* draw outward faces of teeth */
177 glBegin(GL_QUAD_STRIP);
178 for (i = 0; i < teeth; i++) {
179 angle = i * 2.0 * M_PI / teeth;
180
181 glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
182 glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
183 u = r2 * cos(angle + da) - r1 * cos(angle);
184 v = r2 * sin(angle + da) - r1 * sin(angle);
185 len = sqrt(u * u + v * v);
186 u /= len;
187 v /= len;
188 glNormal3f(v, -u, 0.0);
189 glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
190 glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
191 glNormal3f(cos(angle), sin(angle), 0.0);
192 glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
193 width * 0.5);
194 glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
195 -width * 0.5);
196 u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
197 v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
198 glNormal3f(v, -u, 0.0);
199 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
200 width * 0.5);
201 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
202 -width * 0.5);
203 glNormal3f(cos(angle), sin(angle), 0.0);
204 }
205
206 glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
207 glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);
208
209 glEnd();
210
211 glShadeModel(GL_SMOOTH);
212
213 /* draw inside radius cylinder */
214 glBegin(GL_QUAD_STRIP);
215 for (i = 0; i <= teeth; i++) {
216 angle = i * 2.0 * M_PI / teeth;
217 glNormal3f(-cos(angle), -sin(angle), 0.0);
218 glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
219 glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
220 }
221 glEnd();
222}
223
224
225static void
226draw(void)
227{
228 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
229
230 glPushMatrix();
231 glRotatef(view_rotx, 1.0, 0.0, 0.0);
232 glRotatef(view_roty, 0.0, 1.0, 0.0);
233 glRotatef(view_rotz, 0.0, 0.0, 1.0);
234
235 glPushMatrix();
236 glTranslatef(-3.0, -2.0, 0.0);
237 glRotatef(angle, 0.0, 0.0, 1.0);
238 glCallList(gear1);
239 glPopMatrix();
240
241 glPushMatrix();
242 glTranslatef(3.1, -2.0, 0.0);
243 glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
244 glCallList(gear2);
245 glPopMatrix();
246
247 glPushMatrix();
248 glTranslatef(-3.1, 4.2, 0.0);
249 glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
250 glCallList(gear3);
251 glPopMatrix();
252
253 glPopMatrix();
254}
255
256
257
258/* new window size or exposure */
259static void
260reshape(int width, int height)
261{
262 GLfloat h = (GLfloat) height / (GLfloat) width;
263
264 glViewport(0, 0, (GLint) width, (GLint) height);
265 glMatrixMode(GL_PROJECTION);
266 glLoadIdentity();
267 glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
268 glMatrixMode(GL_MODELVIEW);
269 glLoadIdentity();
270 glTranslatef(0.0, 0.0, -40.0);
271}
272
273
274
275static void
276init(void)
277{
278 static GLfloat pos[4] = { 5.0, 5.0, 10.0, 0.0 };
279 static GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 };
280 static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 };
281 static GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 };
282
283 glLightfv(GL_LIGHT0, GL_POSITION, pos);
284 glEnable(GL_CULL_FACE);
285 glEnable(GL_LIGHTING);
286 glEnable(GL_LIGHT0);
287 glEnable(GL_DEPTH_TEST);
288
289 /* make the gears */
290 gear1 = glGenLists(1);
291 glNewList(gear1, GL_COMPILE);
292 glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
293 gear(1.0, 4.0, 1.0, 20, 0.7);
294 glEndList();
295
296 gear2 = glGenLists(1);
297 glNewList(gear2, GL_COMPILE);
298 glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
299 gear(0.5, 2.0, 2.0, 10, 0.7);
300 glEndList();
301
302 gear3 = glGenLists(1);
303 glNewList(gear3, GL_COMPILE);
304 glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
305 gear(1.3, 2.0, 0.5, 10, 0.7);
306 glEndList();
307
308 glEnable(GL_NORMALIZE);
309}
310
311class Gears {
312 public:
313 explicit Gears(std::uint32_t background)
314 : m_Context(OSMesaCreateContext(OSMESA_BGRA, NULL)),
315 m_Background(libui::unpackColor(background)) {}
316
317 ~Gears() {
318 if (m_Context) {
319 OSMesaDestroyContext(m_Context);
320 }
321 }
322
323 bool valid() const {
324 return m_Context != NULL;
325 }
326
327 bool render(libui::client::PaintContext& paint) {
328 cairo_surface_t* surface = cairo_get_target(paint.cairo());
329 if (!surface || cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE ||
330 cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32) {
331 fprintf(stderr, "gears requires an ARGB32 image surface.\n");
332 return false;
333 }
334
335 const int width = cairo_image_surface_get_width(surface);
336 const int height = cairo_image_surface_get_height(surface);
337 const int stride = cairo_image_surface_get_stride(surface);
338 if (width <= 0 || height <= 0 || stride <= 0 ||
339 (stride % static_cast<int>(sizeof(std::uint32_t))) != 0) {
340 fprintf(stderr, "gears received an invalid image surface.\n");
341 return false;
342 }
343
344 cairo_surface_flush(surface);
345 unsigned char* framebuffer = cairo_image_surface_get_data(surface);
346 if (!framebuffer ||
347 !OSMesaMakeCurrent(m_Context, framebuffer, GL_UNSIGNED_BYTE, width, height)) {
348 fprintf(stderr, "OSMesaMakeCurrent failed.\n");
349 return false;
350 }
351
352 // Cairo image rows can include padding beyond the visible width.
353 OSMesaPixelStore(OSMESA_ROW_LENGTH, stride / static_cast<int>(sizeof(std::uint32_t)));
354 OSMesaPixelStore(OSMESA_Y_UP, 0);
355
356 if (width != m_Width || height != m_Height) {
357 reshape(width, height);
358 m_Width = width;
359 m_Height = height;
360 }
361 if (!m_Initialized) {
362 glClearColor(static_cast<GLfloat>(m_Background.r) / 255.0F,
363 static_cast<GLfloat>(m_Background.g) / 255.0F,
364 static_cast<GLfloat>(m_Background.b) / 255.0F,
365 static_cast<GLfloat>(m_Background.a) / 255.0F);
366 init();
367 m_Initialized = true;
368 }
369
370 angle += 0.2;
371 draw();
372 glFinish();
373 cairo_surface_mark_dirty(surface);
374 return true;
375 }
376
377 bool keyDown(int keycode) {
378 switch (keycode) {
379 case 'a':
380 case 'A':
381 view_roty += 5.0;
382 return true;
383 case 'd':
384 case 'D':
385 view_roty -= 5.0;
386 return true;
387 case 'w':
388 case 'W':
389 view_rotx += 5.0;
390 return true;
391 case 's':
392 case 'S':
393 view_rotx -= 5.0;
394 return true;
395 default:
396 return false;
397 }
398 }
399
400 private:
401 OSMesaContext m_Context;
402 libui::Color m_Background;
403 bool m_Initialized = false;
404 int m_Width = 0;
405 int m_Height = 0;
406};
407
408int main(int argc, char** argv) {
409 const std::optional<demo::Options> options = demo::parseOptions(argc, argv);
410 if (!options) {
411 return 2;
412 }
413
414 libui::client::Client client = libui::client::Client::connect(options->socketPath);
415 if (!client.valid()) {
416 fprintf(stderr, "gears could not connect to compositor.\n");
417 return 1;
418 }
419
420 std::optional<libui::client::Window> createResult = demo::createWindow(client, *options);
421 if (!createResult) {
422 fprintf(stderr, "gears could not create a window.\n");
423 return 1;
424 }
425
426 Gears gears(options->background);
427 if (!gears.valid()) {
428 fprintf(stderr, "gears could not create an OSMesa context.\n");
429 return 1;
430 }
431
432 libui::client::Window& window = createResult.value();
433 bool closed = false;
434 bool renderFailed = false;
435 window.setWindowProc([&gears, &closed, &renderFailed](libui::client::Window& window,
436 const libui::client::Event& event) {
437 if (event.type() == libui::client::Event::Type::Close) {
438 closed = true;
439 return true;
440 }
441 if (event.type() == libui::client::Event::Type::Key) {
442 const libui::client::KeyEvent* key = event.get<libui::client::KeyEvent>();
443 return key && key->isDown && gears.keyDown(key->keycode);
444 }
445 if (event.type() == libui::client::Event::Type::Paint) {
446 libui::client::PaintContext* paint = window.back();
447 if (!paint || !gears.render(*paint)) {
448 renderFailed = true;
449 closed = true;
450 }
451 return true;
452 }
453 return false;
454 });
455
456 if (!client.setNonBlocking(true)) {
457 return 1;
458 }
459
460 constexpr int MaxEventsPerFrame = 64;
461 while (!closed && client.valid()) {
462 for (int count = 0; count < MaxEventsPerFrame && !closed; ++count) {
463 if (!client.dispatch(window, false, false)) {
464 break;
465 }
466 }
467
468 if (!closed && !client.paint(window)) {
469 renderFailed = true;
470 break;
471 }
472 fps();
473 sched_yield();
474 }
475
476 return (renderFailed || (!closed && !client.valid())) ? 1 : 0;
477}