1 /*
2 * Copyright (c) 2012 Arvin Schnell <arvin.schnell@gmail.com>
3 * Copyright (c) 2012 Rob Clark <rob@ti.com>
4 * Copyright (c) 2015 Tomi Valkeinen <tomi.valkeinen@ti.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sub license,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the
14 * next paragraph) shall be included in all copies or substantial portions
15 * of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 */
26 /* Based on a egl cube test app originally written by Arvin Schnell */
28 #include <chrono>
29 #include <cstdio>
30 #include <vector>
31 #include <memory>
32 #include <algorithm>
34 #include <xf86drm.h>
35 #include <xf86drmMode.h>
36 #include <gbm.h>
38 #include "esUtil.h"
40 #include <kms++.h>
41 #include "test.h"
43 using namespace kms;
44 using namespace std;
46 static bool s_verbose;
47 static int s_flip_pending;
48 static bool s_need_exit;
50 class GbmDevice
51 {
52 public:
53 GbmDevice(Card& card)
54 {
55 m_dev = gbm_create_device(card.fd());
56 FAIL_IF(!m_dev, "failed to create gbm device");
57 }
59 ~GbmDevice()
60 {
61 gbm_device_destroy(m_dev);
62 }
64 GbmDevice(const GbmDevice& other) = delete;
65 GbmDevice& operator=(const GbmDevice& other) = delete;
67 struct gbm_device* handle() const { return m_dev; }
69 private:
70 struct gbm_device* m_dev;
71 };
73 class GbmSurface
74 {
75 public:
76 GbmSurface(GbmDevice& gdev, int width, int height)
77 {
78 m_surface = gbm_surface_create(gdev.handle(), width, height,
79 GBM_FORMAT_XRGB8888,
80 GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
81 FAIL_IF(!m_surface, "failed to create gbm surface");
82 }
84 ~GbmSurface()
85 {
86 gbm_surface_destroy(m_surface);
87 }
89 GbmSurface(const GbmSurface& other) = delete;
90 GbmSurface& operator=(const GbmSurface& other) = delete;
92 gbm_bo* lock_front_buffer()
93 {
94 return gbm_surface_lock_front_buffer(m_surface);
95 }
97 void release_buffer(gbm_bo *bo)
98 {
99 gbm_surface_release_buffer(m_surface, bo);
100 }
102 struct gbm_surface* handle() const { return m_surface; }
104 private:
105 struct gbm_surface* m_surface;
106 };
108 class EglState
109 {
110 public:
111 EglState(EGLNativeDisplayType display_id)
112 {
113 EGLBoolean b;
114 EGLint major, minor, n;
116 static const EGLint context_attribs[] = {
117 EGL_CONTEXT_CLIENT_VERSION, 2,
118 EGL_NONE
119 };
121 static const EGLint config_attribs[] = {
122 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
123 EGL_RED_SIZE, 8,
124 EGL_GREEN_SIZE, 8,
125 EGL_BLUE_SIZE, 8,
126 EGL_ALPHA_SIZE, 0,
127 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
128 EGL_NONE
129 };
131 m_display = eglGetDisplay(display_id);
132 FAIL_IF(!m_display, "failed to get egl display");
134 b = eglInitialize(m_display, &major, &minor);
135 FAIL_IF(!b, "failed to initialize");
137 if (s_verbose) {
138 printf("Using display %p with EGL version %d.%d\n", m_display, major, minor);
140 printf("EGL_VENDOR: %s\n", eglQueryString(m_display, EGL_VENDOR));
141 printf("EGL_VERSION: %s\n", eglQueryString(m_display, EGL_VERSION));
142 printf("EGL_EXTENSIONS: %s\n", eglQueryString(m_display, EGL_EXTENSIONS));
143 printf("EGL_CLIENT_APIS: %s\n", eglQueryString(m_display, EGL_CLIENT_APIS));
144 }
146 b = eglBindAPI(EGL_OPENGL_ES_API);
147 FAIL_IF(!b, "failed to bind api EGL_OPENGL_ES_API");
149 b = eglChooseConfig(m_display, config_attribs, &m_config, 1, &n);
150 FAIL_IF(!b || n != 1, "failed to choose config");
152 auto getconf = [this](EGLint a) { EGLint v = -1; eglGetConfigAttrib(m_display, m_config, a, &v); return v; };
154 if (s_verbose) {
155 printf("EGL Config %d: color buf %d/%d/%d/%d = %d, depth %d, stencil %d\n",
156 getconf(EGL_CONFIG_ID),
157 getconf(EGL_ALPHA_SIZE),
158 getconf(EGL_RED_SIZE),
159 getconf(EGL_GREEN_SIZE),
160 getconf(EGL_BLUE_SIZE),
161 getconf(EGL_BUFFER_SIZE),
162 getconf(EGL_DEPTH_SIZE),
163 getconf(EGL_STENCIL_SIZE));
164 }
166 m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs);
167 FAIL_IF(!m_context, "failed to create context");
169 eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_context);
170 }
172 ~EglState()
173 {
174 eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
175 eglTerminate(m_display);
176 }
178 EGLDisplay display() const { return m_display; }
179 EGLConfig config() const { return m_config; }
180 EGLContext context() const { return m_context; }
182 private:
183 EGLDisplay m_display;
184 EGLConfig m_config;
185 EGLContext m_context;
186 };
188 class GlScene
189 {
190 public:
191 GlScene()
192 {
193 GLuint vertex_shader, fragment_shader;
194 GLint ret;
196 #include "cube.h"
198 static const char *vertex_shader_source =
199 "uniform mat4 modelviewMatrix; \n"
200 "uniform mat4 modelviewprojectionMatrix;\n"
201 "uniform mat3 normalMatrix; \n"
202 " \n"
203 "attribute vec4 in_position; \n"
204 "attribute vec3 in_normal; \n"
205 "attribute vec4 in_color; \n"
206 "\n"
207 "vec4 lightSource = vec4(2.0, 2.0, 20.0, 0.0);\n"
208 " \n"
209 "varying vec4 vVaryingColor; \n"
210 " \n"
211 "void main() \n"
212 "{ \n"
213 " gl_Position = modelviewprojectionMatrix * in_position;\n"
214 " vec3 vEyeNormal = normalMatrix * in_normal;\n"
215 " vec4 vPosition4 = modelviewMatrix * in_position;\n"
216 " vec3 vPosition3 = vPosition4.xyz / vPosition4.w;\n"
217 " vec3 vLightDir = normalize(lightSource.xyz - vPosition3);\n"
218 " float diff = max(0.0, dot(vEyeNormal, vLightDir));\n"
219 " vVaryingColor = vec4(diff * in_color.rgb, 1.0);\n"
220 "} \n";
222 static const char *fragment_shader_source =
223 "precision mediump float; \n"
224 " \n"
225 "varying vec4 vVaryingColor; \n"
226 " \n"
227 "void main() \n"
228 "{ \n"
229 " gl_FragColor = vVaryingColor; \n"
230 "} \n";
233 if (s_verbose) {
234 printf("GL_VENDOR: %s\n", glGetString(GL_VENDOR));
235 printf("GL_VERSION: %s\n", glGetString(GL_VERSION));
236 printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
237 printf("GL_EXTENSIONS: %s\n", glGetString(GL_EXTENSIONS));
238 }
240 vertex_shader = glCreateShader(GL_VERTEX_SHADER);
242 glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
243 glCompileShader(vertex_shader);
245 glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &ret);
246 FAIL_IF(!ret, "vertex shader compilation failed!");
248 fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
250 glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
251 glCompileShader(fragment_shader);
253 glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &ret);
254 FAIL_IF(!ret, "fragment shader compilation failed!");
256 GLuint program = glCreateProgram();
258 glAttachShader(program, vertex_shader);
259 glAttachShader(program, fragment_shader);
261 glBindAttribLocation(program, 0, "in_position");
262 glBindAttribLocation(program, 1, "in_normal");
263 glBindAttribLocation(program, 2, "in_color");
265 glLinkProgram(program);
267 glGetProgramiv(program, GL_LINK_STATUS, &ret);
268 FAIL_IF(!ret, "program linking failed!");
270 glUseProgram(program);
272 m_modelviewmatrix = glGetUniformLocation(program, "modelviewMatrix");
273 m_modelviewprojectionmatrix = glGetUniformLocation(program, "modelviewprojectionMatrix");
274 m_normalmatrix = glGetUniformLocation(program, "normalMatrix");
276 glEnable(GL_CULL_FACE);
278 GLintptr positionsoffset = 0;
279 GLintptr colorsoffset = sizeof(vVertices);
280 GLintptr normalsoffset = sizeof(vVertices) + sizeof(vColors);
281 GLuint vbo;
283 glGenBuffers(1, &vbo);
284 glBindBuffer(GL_ARRAY_BUFFER, vbo);
285 glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices) + sizeof(vColors) + sizeof(vNormals), 0, GL_STATIC_DRAW);
286 glBufferSubData(GL_ARRAY_BUFFER, positionsoffset, sizeof(vVertices), &vVertices[0]);
287 glBufferSubData(GL_ARRAY_BUFFER, colorsoffset, sizeof(vColors), &vColors[0]);
288 glBufferSubData(GL_ARRAY_BUFFER, normalsoffset, sizeof(vNormals), &vNormals[0]);
289 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)positionsoffset);
290 glEnableVertexAttribArray(0);
291 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)normalsoffset);
292 glEnableVertexAttribArray(1);
293 glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)colorsoffset);
294 glEnableVertexAttribArray(2);
295 }
297 GlScene(const GlScene& other) = delete;
298 GlScene& operator=(const GlScene& other) = delete;
300 void set_viewport(uint32_t width, uint32_t height)
301 {
302 m_width = width;
303 m_height = height;
304 }
306 void draw(uint32_t framenum)
307 {
308 glViewport(0, 0, m_width, m_height);
310 glClearColor(0.5, 0.5, 0.5, 1.0);
311 glClear(GL_COLOR_BUFFER_BIT);
313 ESMatrix modelview;
315 esMatrixLoadIdentity(&modelview);
316 esTranslate(&modelview, 0.0f, 0.0f, -8.0f);
317 esRotate(&modelview, 45.0f + (0.75f * framenum), 1.0f, 0.0f, 0.0f);
318 esRotate(&modelview, 45.0f - (0.5f * framenum), 0.0f, 1.0f, 0.0f);
319 esRotate(&modelview, 10.0f + (0.45f * framenum), 0.0f, 0.0f, 1.0f);
321 GLfloat aspect = (float)m_height / m_width;
323 ESMatrix projection;
324 esMatrixLoadIdentity(&projection);
325 esFrustum(&projection, -2.8f, +2.8f, -2.8f * aspect, +2.8f * aspect, 6.0f, 10.0f);
327 ESMatrix modelviewprojection;
328 esMatrixLoadIdentity(&modelviewprojection);
329 esMatrixMultiply(&modelviewprojection, &modelview, &projection);
331 float normal[9];
332 normal[0] = modelview.m[0][0];
333 normal[1] = modelview.m[0][1];
334 normal[2] = modelview.m[0][2];
335 normal[3] = modelview.m[1][0];
336 normal[4] = modelview.m[1][1];
337 normal[5] = modelview.m[1][2];
338 normal[6] = modelview.m[2][0];
339 normal[7] = modelview.m[2][1];
340 normal[8] = modelview.m[2][2];
342 glUniformMatrix4fv(m_modelviewmatrix, 1, GL_FALSE, &modelview.m[0][0]);
343 glUniformMatrix4fv(m_modelviewprojectionmatrix, 1, GL_FALSE, &modelviewprojection.m[0][0]);
344 glUniformMatrix3fv(m_normalmatrix, 1, GL_FALSE, normal);
346 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
347 glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
348 glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
349 glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
350 glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
351 glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
352 }
354 private:
355 GLint m_modelviewmatrix, m_modelviewprojectionmatrix, m_normalmatrix;
357 uint32_t m_width;
358 uint32_t m_height;
359 };
361 class GbmEglSurface
362 {
363 public:
364 GbmEglSurface(Card& card, GbmDevice& gdev, const EglState& egl, int width, int height)
365 : card(card), gdev(gdev), egl(egl), m_width(width), m_height(height),
366 bo_prev(0), bo_next(0)
367 {
368 gsurface = unique_ptr<GbmSurface>(new GbmSurface(gdev, width, height));
369 esurface = eglCreateWindowSurface(egl.display(), egl.config(), gsurface->handle(), NULL);
370 FAIL_IF(esurface == EGL_NO_SURFACE, "failed to create egl surface");
371 }
373 ~GbmEglSurface()
374 {
375 if (bo_next)
376 gsurface->release_buffer(bo_next);
377 eglDestroySurface(egl.display(), esurface);
378 }
380 void make_current()
381 {
382 eglMakeCurrent(egl.display(), esurface, esurface, egl.context());
383 }
385 void swap_buffers()
386 {
387 eglSwapBuffers(egl.display(), esurface);
388 }
390 static void drm_fb_destroy_callback(struct gbm_bo *bo, void *data)
391 {
392 auto fb = reinterpret_cast<Framebuffer*>(data);
393 delete fb;
394 }
396 static Framebuffer* drm_fb_get_from_bo(struct gbm_bo *bo, Card& card)
397 {
398 auto fb = reinterpret_cast<Framebuffer*>(gbm_bo_get_user_data(bo));
399 if (fb)
400 return fb;
402 uint32_t width = gbm_bo_get_width(bo);
403 uint32_t height = gbm_bo_get_height(bo);
404 uint32_t stride = gbm_bo_get_stride(bo);
405 uint32_t handle = gbm_bo_get_handle(bo).u32;
407 fb = new ExtFramebuffer(card, width, height, 24, 32, stride, handle);
409 gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback);
411 return fb;
412 }
414 struct Framebuffer* lock_next()
415 {
416 bo_prev = bo_next;
417 bo_next = gsurface->lock_front_buffer();
418 FAIL_IF(!bo_next, "could not lock gbm buffer");
419 return drm_fb_get_from_bo(bo_next, card);
420 }
422 void free_prev()
423 {
424 if (bo_prev) {
425 gsurface->release_buffer(bo_prev);
426 bo_prev = 0;
427 }
428 }
430 uint32_t width() const { return m_width; }
431 uint32_t height() const { return m_height; }
433 private:
434 Card& card;
435 GbmDevice& gdev;
436 const EglState& egl;
438 unique_ptr<GbmSurface> gsurface;
439 EGLSurface esurface;
441 int m_width;
442 int m_height;
444 struct gbm_bo* bo_prev;
445 struct gbm_bo* bo_next;
446 };
448 class OutputHandler : private PageFlipHandlerBase
449 {
450 public:
451 OutputHandler(Card& card, GbmDevice& gdev, const EglState& egl, Connector* connector, Crtc* crtc, Videomode& mode, Plane* plane, float rotation_mult)
452 : m_frame_num(0), m_connector(connector), m_crtc(crtc), m_plane(plane), m_mode(mode),
453 m_rotation_mult(rotation_mult)
454 {
455 m_surface1 = unique_ptr<GbmEglSurface>(new GbmEglSurface(card, gdev, egl, mode.hdisplay, mode.vdisplay));
456 m_scene1 = unique_ptr<GlScene>(new GlScene());
457 m_scene1->set_viewport(m_surface1->width(), m_surface1->height());
459 if (m_plane) {
460 m_surface2 = unique_ptr<GbmEglSurface>(new GbmEglSurface(card, gdev, egl, 400, 400));
461 m_scene2 = unique_ptr<GlScene>(new GlScene());
462 m_scene2->set_viewport(m_surface2->width(), m_surface2->height());
463 }
464 }
466 OutputHandler(const OutputHandler& other) = delete;
467 OutputHandler& operator=(const OutputHandler& other) = delete;
469 void setup()
470 {
471 int ret;
473 m_surface1->make_current();
474 m_surface1->swap_buffers();
475 struct Framebuffer* fb = m_surface1->lock_next();
477 struct Framebuffer* planefb = 0;
479 if (m_plane) {
480 m_surface2->make_current();
481 m_surface2->swap_buffers();
482 planefb = m_surface2->lock_next();
483 }
486 ret = m_crtc->set_mode(m_connector, *fb, m_mode);
487 FAIL_IF(ret, "failed to set mode");
489 if (m_crtc->card().has_atomic()) {
490 Plane* root_plane = 0;
491 for (Plane* p : m_crtc->get_possible_planes()) {
492 if (p->crtc_id() == m_crtc->id()) {
493 root_plane = p;
494 break;
495 }
496 }
498 FAIL_IF(!root_plane, "No primary plane for crtc %d", m_crtc->id());
500 m_root_plane = root_plane;
501 }
503 if (m_plane) {
504 ret = m_crtc->set_plane(m_plane, *planefb,
505 0, 0, planefb->width(), planefb->height(),
506 0, 0, planefb->width(), planefb->height());
507 FAIL_IF(ret, "failed to set plane");
508 }
509 }
511 void start_flipping()
512 {
513 m_t1 = chrono::steady_clock::now();
514 queue_next();
515 }
517 private:
518 void handle_page_flip(uint32_t frame, double time)
519 {
520 ++m_frame_num;
522 if (m_frame_num % 100 == 0) {
523 auto t2 = chrono::steady_clock::now();
524 chrono::duration<float> fsec = t2 - m_t1;
525 printf("fps: %f\n", 100.0 / fsec.count());
526 m_t1 = t2;
527 }
529 s_flip_pending--;
531 m_surface1->free_prev();
532 if (m_plane)
533 m_surface2->free_prev();
535 if (s_need_exit)
536 return;
538 queue_next();
539 }
541 void queue_next()
542 {
543 m_surface1->make_current();
544 m_scene1->draw(m_frame_num * m_rotation_mult);
545 m_surface1->swap_buffers();
546 struct Framebuffer* fb = m_surface1->lock_next();
548 struct Framebuffer* planefb = 0;
550 if (m_plane) {
551 m_surface2->make_current();
552 m_scene2->draw(m_frame_num * m_rotation_mult * 2);
553 m_surface2->swap_buffers();
554 planefb = m_surface2->lock_next();
555 }
557 if (m_crtc->card().has_atomic()) {
558 int r;
560 AtomicReq req(m_crtc->card());
562 req.add(m_root_plane, "FB_ID", fb->id());
563 if (m_plane)
564 req.add(m_plane, "FB_ID", planefb->id());
566 r = req.test();
567 FAIL_IF(r, "atomic test failed");
569 r = req.commit(this);
570 FAIL_IF(r, "atomic commit failed");
571 } else {
572 int ret;
574 ret = m_crtc->page_flip(*fb, this);
575 FAIL_IF(ret, "failed to queue page flip");
577 if (m_plane) {
578 ret = m_crtc->set_plane(m_plane, *planefb,
579 0, 0, planefb->width(), planefb->height(),
580 0, 0, planefb->width(), planefb->height());
581 FAIL_IF(ret, "failed to set plane");
582 }
583 }
585 s_flip_pending++;
586 }
588 int m_frame_num;
589 chrono::steady_clock::time_point m_t1;
591 Connector* m_connector;
592 Crtc* m_crtc;
593 Plane* m_plane;
594 Videomode m_mode;
595 Plane* m_root_plane;
597 unique_ptr<GbmEglSurface> m_surface1;
598 unique_ptr<GbmEglSurface> m_surface2;
600 unique_ptr<GlScene> m_scene1;
601 unique_ptr<GlScene> m_scene2;
603 float m_rotation_mult;
604 };
606 int main(int argc, char *argv[])
607 {
608 for (int i = 1; i < argc; ++i) {
609 if (argv[i] == string("-v"))
610 s_verbose = true;
611 }
613 Card card;
615 GbmDevice gdev(card);
616 EglState egl(gdev.handle());
618 vector<unique_ptr<OutputHandler>> outputs;
619 vector<Plane*> used_planes;
621 float rot_mult = 1;
623 for (auto pipe : card.get_connected_pipelines()) {
624 auto connector = pipe.connector;
625 auto crtc = pipe.crtc;
626 auto mode = connector->get_default_mode();
628 Plane* plane = 0;
630 for (Plane* p : crtc->get_possible_planes()) {
631 if (find(used_planes.begin(), used_planes.end(), p) != used_planes.end())
632 continue;
634 if (p->plane_type() != PlaneType::Overlay)
635 continue;
637 plane = p;
638 break;
639 }
641 if (plane)
642 used_planes.push_back(plane);
644 auto out = new OutputHandler(card, gdev, egl, connector, crtc, mode, plane, rot_mult);
645 outputs.emplace_back(out);
647 rot_mult *= 1.33;
648 }
650 for (auto& out : outputs)
651 out->setup();
653 for (auto& out : outputs)
654 out->start_flipping();
656 while (!s_need_exit || s_flip_pending) {
657 fd_set fds;
658 FD_ZERO(&fds);
659 if (!s_need_exit)
660 FD_SET(0, &fds);
661 FD_SET(card.fd(), &fds);
663 int ret = select(card.fd() + 1, &fds, NULL, NULL, NULL);
665 FAIL_IF(ret < 0, "select error: %d", ret);
666 FAIL_IF(ret == 0, "select timeout");
668 if (FD_ISSET(0, &fds))
669 s_need_exit = true;
671 if (FD_ISSET(card.fd(), &fds))
672 card.call_page_flip_handlers();
673 }
675 return 0;
676 }