1 | /* |
2 | Copyright (c) 2013, Broadcom Europe Ltd |
3 | Copyright (c) 2013, Tim Gover |
4 | All rights reserved. |
5 | |
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met: |
8 | * Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | * Redistributions in binary form must reproduce the above copyright |
11 | notice, this list of conditions and the following disclaimer in the |
12 | documentation and/or other materials provided with the distribution. |
13 | * Neither the name of the copyright holder nor the |
14 | names of its contributors may be used to endorse or promote products |
15 | derived from this software without specific prior written permission. |
16 | |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "RaspiTex.h" |
30 | #include "RaspiCLI.h" |
31 | #include <math.h> |
32 | #include <stdio.h> |
33 | #include <stdlib.h> |
34 | #include <string.h> |
35 | #include <sys/time.h> |
36 | #include <EGL/egl.h> |
37 | #include <EGL/eglext.h> |
38 | #include <GLES/gl.h> |
39 | #include <GLES/glext.h> |
40 | #include "RaspiTexUtil.h" |
41 | #include "interface/vcos/vcos.h" |
42 | #include "interface/mmal/mmal_buffer.h" |
43 | #include "interface/mmal/util/mmal_util.h" |
44 | #include "interface/mmal/util/mmal_util_params.h" |
45 | #include "tga.h" |
46 | |
47 | #include "gl_scenes/mirror.h" |
48 | #include "gl_scenes/sobel.h" |
49 | #include "gl_scenes/square.h" |
50 | #include "gl_scenes/teapot.h" |
51 | #include "gl_scenes/vcsm_square.h" |
52 | #include "gl_scenes/yuv.h" |
53 | |
54 | /** |
55 | * \file RaspiTex.c |
56 | * |
57 | * A simple framework for extending a MMAL application to render buffers via |
58 | * OpenGL. |
59 | * |
60 | * MMAL buffers are often in YUV colour space and in either a planar or |
61 | * tile format which is not supported directly by V3D. Instead of copying |
62 | * the buffer from the GPU and doing a colour space / pixel format conversion |
63 | * the GL_OES_EGL_image_external is used. This allows an EGL image to be |
64 | * created from GPU buffer handle (MMAL opaque buffer handle). The EGL image |
65 | * may then be used to create a texture (glEGLImageTargetTexture2DOES) and |
66 | * drawn by either OpenGL ES 1.0 or 2.0 contexts. |
67 | * |
68 | * Notes: |
69 | * 1) GL_OES_EGL_image_external textures always return pixels in RGBA format. |
70 | * This is also the case when used from a fragment shader. |
71 | * |
72 | * 2) The driver implementation creates a new RGB_565 buffer and does the color |
73 | * space conversion from YUV. This happens in GPU memory using the vector |
74 | * processor. |
75 | * |
76 | * 3) Each EGL external image in use will consume GPU memory for the RGB 565 |
77 | * buffer. In addition, the GL pipeline might require more than one EGL image |
78 | * to be retained in GPU memory until the drawing commands are flushed. |
79 | * |
80 | * Typically 128 MB of GPU memory is sufficient for 720p viewfinder and 720p |
81 | * GL surface. If both the viewfinder and the GL surface are 1080p then |
82 | * 256MB of GPU memory is recommended, otherwise, for non-trivial scenes |
83 | * the system can run out of GPU memory whilst the camera is running. |
84 | * |
85 | * 4) It is important to make sure that the MMAL opaque buffer is not returned |
86 | * to MMAL before the GL driver has completed the asynchronous call to |
87 | * glEGLImageTargetTexture2DOES. Deferring destruction of the EGL image and |
88 | * the buffer return to MMAL until after eglSwapBuffers is the recommended. |
89 | * |
90 | * See also: http://www.khronos.org/registry/gles/extensions/OES/OES_EGL_image_external.txt |
91 | */ |
92 | |
93 | #define DEFAULT_WIDTH 640 |
94 | #define DEFAULT_HEIGHT 480 |
95 | |
96 | enum |
97 | { |
98 | CommandGLScene, |
99 | CommandGLWin |
100 | }; |
101 | |
102 | static COMMAND_LIST cmdline_commands[] = |
103 | { |
104 | { CommandGLScene, "-glscene" , "gs" , "GL scene square,teapot,mirror,yuv,sobel,vcsm_square" , 1 }, |
105 | { CommandGLWin, "-glwin" , "gw" , "GL window settings <'x,y,w,h'>" , 1 }, |
106 | }; |
107 | |
108 | static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); |
109 | |
110 | /** |
111 | * Parse a possible command pair - command and parameter |
112 | * @param arg1 Command |
113 | * @param arg2 Parameter (could be NULL) |
114 | * @return How many parameters were used, 0,1,2 |
115 | */ |
116 | int raspitex_parse_cmdline(RASPITEX_STATE *state, |
117 | const char *arg1, const char *arg2) |
118 | { |
119 | int command_id, used = 0, num_parameters; |
120 | |
121 | if (!arg1) |
122 | return 0; |
123 | |
124 | command_id = raspicli_get_command_id(cmdline_commands, |
125 | cmdline_commands_size, arg1, &num_parameters); |
126 | |
127 | // If invalid command, or we are missing a parameter, drop out |
128 | if (command_id==-1 || (command_id != -1 && num_parameters > 0 && arg2 == NULL)) |
129 | return 0; |
130 | |
131 | switch (command_id) |
132 | { |
133 | case CommandGLWin: // Allows a GL window to be different to preview-res |
134 | { |
135 | int tmp; |
136 | tmp = sscanf(arg2, "%d,%d,%d,%d" , |
137 | &state->x, &state->y, &state->width, &state->height); |
138 | if (tmp != 4) |
139 | { |
140 | // Default to safe size on parse error |
141 | state->x = state->y = 0; |
142 | state->width = DEFAULT_WIDTH; |
143 | state->height = DEFAULT_HEIGHT; |
144 | } |
145 | else |
146 | { |
147 | state->gl_win_defined = 1; |
148 | } |
149 | |
150 | used = 2; |
151 | break; |
152 | } |
153 | |
154 | case CommandGLScene: // Selects the GL scene |
155 | { |
156 | if (strcmp(arg2, "square" ) == 0) |
157 | state->scene_id = RASPITEX_SCENE_SQUARE; |
158 | else if (strcmp(arg2, "teapot" ) == 0) |
159 | state->scene_id = RASPITEX_SCENE_TEAPOT; |
160 | else if (strcmp(arg2, "mirror" ) == 0) |
161 | state->scene_id = RASPITEX_SCENE_MIRROR; |
162 | else if (strcmp(arg2, "yuv" ) == 0) |
163 | state->scene_id = RASPITEX_SCENE_YUV; |
164 | else if (strcmp(arg2, "sobel" ) == 0) |
165 | state->scene_id = RASPITEX_SCENE_SOBEL; |
166 | else if (strcmp(arg2, "vcsm_square" ) == 0) |
167 | state->scene_id = RASPITEX_SCENE_VCSM_SQUARE; |
168 | else |
169 | vcos_log_error("Unknown scene %s" , arg2); |
170 | |
171 | used = 2; |
172 | break; |
173 | } |
174 | } |
175 | return used; |
176 | } |
177 | |
178 | /** |
179 | * Display help for command line options |
180 | */ |
181 | void raspitex_display_help() |
182 | { |
183 | fprintf(stdout, "\nGL parameter commands\n\n" ); |
184 | raspicli_display_help(cmdline_commands, cmdline_commands_size); |
185 | } |
186 | |
187 | static void update_fps() |
188 | { |
189 | static int frame_count = 0; |
190 | static long long time_start = 0; |
191 | long long time_now; |
192 | struct timeval te; |
193 | float fps; |
194 | |
195 | frame_count++; |
196 | |
197 | gettimeofday(&te, NULL); |
198 | time_now = te.tv_sec * 1000LL + te.tv_usec / 1000; |
199 | |
200 | if (time_start == 0) |
201 | { |
202 | time_start = time_now; |
203 | } |
204 | else if (time_now - time_start > 5000) |
205 | { |
206 | fps = (float) frame_count / ((time_now - time_start) / 1000.0); |
207 | frame_count = 0; |
208 | time_start = time_now; |
209 | vcos_log_error("%3.2f FPS" , fps); |
210 | } |
211 | } |
212 | |
213 | /** |
214 | * Captures the frame-buffer if requested. |
215 | * @param state RASPITEX STATE |
216 | * @return Zero if successful. |
217 | */ |
218 | static void raspitex_do_capture(RASPITEX_STATE *state) |
219 | { |
220 | uint8_t *buffer = NULL; |
221 | size_t size = 0; |
222 | |
223 | if (state->capture.request) |
224 | { |
225 | if (state->ops.capture(state, &buffer, &size) == 0) |
226 | { |
227 | /* Pass ownership of buffer to main thread via capture state */ |
228 | state->capture.buffer = buffer; |
229 | state->capture.size = size; |
230 | } |
231 | else |
232 | { |
233 | state->capture.buffer = NULL; // Null indicates an error |
234 | state->capture.size = 0; |
235 | } |
236 | |
237 | state->capture.request = 0; // Always clear request and post sem |
238 | vcos_semaphore_post(&state->capture.completed_sem); |
239 | } |
240 | } |
241 | |
242 | /** |
243 | * Checks if there is at least one valid EGL image. |
244 | * @param state RASPITEX STATE |
245 | * @return Zero if successful. |
246 | */ |
247 | static int check_egl_image(RASPITEX_STATE *state) |
248 | { |
249 | if (state->egl_image == EGL_NO_IMAGE_KHR && |
250 | state->y_egl_image == EGL_NO_IMAGE_KHR && |
251 | state->u_egl_image == EGL_NO_IMAGE_KHR && |
252 | state->v_egl_image == EGL_NO_IMAGE_KHR) |
253 | return -1; |
254 | else |
255 | return 0; |
256 | } |
257 | |
258 | /** |
259 | * Draws the next preview frame. If a new preview buffer is available then the |
260 | * preview texture is updated first. |
261 | * |
262 | * @param state RASPITEX STATE |
263 | * @param video_frame MMAL buffer header containing the opaque buffer handle. |
264 | * @return Zero if successful. |
265 | */ |
266 | static int raspitex_draw(RASPITEX_STATE *state, MMAL_BUFFER_HEADER_T *buf) |
267 | { |
268 | int rc = 0; |
269 | |
270 | /* If buf is non-NULL then there is a new viewfinder frame available |
271 | * from the camera so the texture should be updated. |
272 | * |
273 | * Although it's possible to have multiple textures mapped to different |
274 | * viewfinder frames this can consume a lot of GPU memory for high-resolution |
275 | * viewfinders. |
276 | */ |
277 | if (buf) |
278 | { |
279 | /* Update the texture to the new viewfinder image which should */ |
280 | if (state->ops.update_texture) |
281 | { |
282 | rc = state->ops.update_texture(state, (EGLClientBuffer) buf->data); |
283 | if (rc != 0) |
284 | { |
285 | vcos_log_error("%s: Failed to update RGBX texture %d" , |
286 | VCOS_FUNCTION, rc); |
287 | goto end; |
288 | } |
289 | } |
290 | |
291 | if (state->ops.update_y_texture) |
292 | { |
293 | rc = state->ops.update_y_texture(state, (EGLClientBuffer) buf->data); |
294 | if (rc != 0) |
295 | { |
296 | vcos_log_error("%s: Failed to update Y' plane texture %d" , VCOS_FUNCTION, rc); |
297 | goto end; |
298 | } |
299 | } |
300 | |
301 | if (state->ops.update_u_texture) |
302 | { |
303 | rc = state->ops.update_u_texture(state, (EGLClientBuffer) buf->data); |
304 | if (rc != 0) |
305 | { |
306 | vcos_log_error("%s: Failed to update U plane texture %d" , VCOS_FUNCTION, rc); |
307 | goto end; |
308 | } |
309 | } |
310 | |
311 | if (state->ops.update_v_texture) |
312 | { |
313 | rc = state->ops.update_v_texture(state, (EGLClientBuffer) buf->data); |
314 | if (rc != 0) |
315 | { |
316 | vcos_log_error("%s: Failed to update V texture %d" , VCOS_FUNCTION, rc); |
317 | goto end; |
318 | } |
319 | } |
320 | |
321 | /* Now return the PREVIOUS MMAL buffer header back to the camera preview. */ |
322 | if (state->preview_buf) |
323 | mmal_buffer_header_release(state->preview_buf); |
324 | |
325 | state->preview_buf = buf; |
326 | } |
327 | |
328 | /* Do the drawing */ |
329 | if (check_egl_image(state) == 0) |
330 | { |
331 | rc = state->ops.update_model(state); |
332 | if (rc != 0) |
333 | goto end; |
334 | |
335 | rc = state->ops.redraw(state); |
336 | if (rc != 0) |
337 | goto end; |
338 | |
339 | raspitex_do_capture(state); |
340 | |
341 | eglSwapBuffers(state->display, state->surface); |
342 | update_fps(); |
343 | } |
344 | else |
345 | { |
346 | // vcos_log_trace("%s: No preview image", VCOS_FUNCTION); |
347 | } |
348 | |
349 | end: |
350 | return rc; |
351 | } |
352 | |
353 | /** |
354 | * Process preview buffers. |
355 | * |
356 | * Dequeue each available preview buffer in order and call current redraw |
357 | * function. If no new buffers are available then the render function is |
358 | * invoked anyway. |
359 | * @param state The GL preview window state. |
360 | * @return Zero if successful. |
361 | */ |
362 | static int preview_process_returned_bufs(RASPITEX_STATE* state) |
363 | { |
364 | MMAL_BUFFER_HEADER_T *buf; |
365 | int new_frame = 0; |
366 | int rc = 0; |
367 | |
368 | while ((buf = mmal_queue_get(state->preview_queue)) != NULL) |
369 | { |
370 | if (state->preview_stop == 0) |
371 | { |
372 | new_frame = 1; |
373 | rc = raspitex_draw(state, buf); |
374 | if (rc != 0) |
375 | { |
376 | vcos_log_error("%s: Error drawing frame. Stopping." , VCOS_FUNCTION); |
377 | state->preview_stop = 1; |
378 | return rc; |
379 | } |
380 | } |
381 | } |
382 | |
383 | /* If there were no new frames then redraw the scene again with the previous |
384 | * texture. Otherwise, go round the loop again to see if any new buffers |
385 | * are returned. |
386 | */ |
387 | if (! new_frame) |
388 | rc = raspitex_draw(state, NULL); |
389 | return rc; |
390 | } |
391 | |
392 | /** Preview worker thread. |
393 | * Ensures camera preview is supplied with buffers and sends preview frames to GL. |
394 | * @param arg Pointer to state. |
395 | * @return NULL always. |
396 | */ |
397 | static void *preview_worker(void *arg) |
398 | { |
399 | RASPITEX_STATE* state = arg; |
400 | MMAL_PORT_T *preview_port = state->preview_port; |
401 | MMAL_BUFFER_HEADER_T *buf; |
402 | MMAL_STATUS_T st; |
403 | int rc; |
404 | |
405 | vcos_log_trace("%s: port %p" , VCOS_FUNCTION, preview_port); |
406 | |
407 | rc = state->ops.create_native_window(state); |
408 | if (rc != 0) |
409 | goto end; |
410 | |
411 | rc = state->ops.gl_init(state); |
412 | if (rc != 0) |
413 | goto end; |
414 | |
415 | while (state->preview_stop == 0) |
416 | { |
417 | /* Send empty buffers to camera preview port */ |
418 | while ((buf = mmal_queue_get(state->preview_pool->queue)) != NULL) |
419 | { |
420 | st = mmal_port_send_buffer(preview_port, buf); |
421 | if (st != MMAL_SUCCESS) |
422 | { |
423 | vcos_log_error("Failed to send buffer to %s" , preview_port->name); |
424 | } |
425 | } |
426 | /* Process returned buffers */ |
427 | if (preview_process_returned_bufs(state) != 0) |
428 | { |
429 | vcos_log_error("Preview error. Exiting." ); |
430 | state->preview_stop = 1; |
431 | } |
432 | } |
433 | |
434 | end: |
435 | /* Make sure all buffers are returned on exit */ |
436 | while ((buf = mmal_queue_get(state->preview_queue)) != NULL) |
437 | mmal_buffer_header_release(buf); |
438 | |
439 | /* Tear down GL */ |
440 | state->ops.gl_term(state); |
441 | vcos_log_trace("Exiting preview worker" ); |
442 | return NULL; |
443 | } |
444 | |
445 | /** |
446 | * MMAL Callback from camera preview output port. |
447 | * @param port The camera preview port. |
448 | * @param buf The new preview buffer. |
449 | **/ |
450 | static void preview_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) |
451 | { |
452 | RASPITEX_STATE *state = (RASPITEX_STATE*) port->userdata; |
453 | |
454 | if (buf->length == 0) |
455 | { |
456 | vcos_log_trace("%s: zero-length buffer => EOS" , port->name); |
457 | state->preview_stop = 1; |
458 | mmal_buffer_header_release(buf); |
459 | } |
460 | else if (buf->data == NULL) |
461 | { |
462 | vcos_log_trace("%s: zero buffer handle" , port->name); |
463 | mmal_buffer_header_release(buf); |
464 | } |
465 | else |
466 | { |
467 | /* Enqueue the preview frame for rendering and return to |
468 | * avoid blocking MMAL core. |
469 | */ |
470 | mmal_queue_put(state->preview_queue, buf); |
471 | } |
472 | } |
473 | |
474 | /* Registers a callback on the camera preview port to receive |
475 | * notifications of new frames. |
476 | * This must be called before rapitex_start and may not be called again |
477 | * without calling raspitex_destroy first. |
478 | * |
479 | * @param state Pointer to the GL preview state. |
480 | * @param port Pointer to the camera preview port |
481 | * @return Zero if successful. |
482 | */ |
483 | int raspitex_configure_preview_port(RASPITEX_STATE *state, |
484 | MMAL_PORT_T *preview_port) |
485 | { |
486 | MMAL_STATUS_T status; |
487 | vcos_log_trace("%s port %p" , VCOS_FUNCTION, preview_port); |
488 | |
489 | /* Enable ZERO_COPY mode on the preview port which instructs MMAL to only |
490 | * pass the 4-byte opaque buffer handle instead of the contents of the opaque |
491 | * buffer. |
492 | * The opaque handle is resolved on VideoCore by the GL driver when the EGL |
493 | * image is created. |
494 | */ |
495 | status = mmal_port_parameter_set_boolean(preview_port, |
496 | MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE); |
497 | if (status != MMAL_SUCCESS) |
498 | { |
499 | vcos_log_error("Failed to enable zero copy on camera preview port" ); |
500 | goto end; |
501 | } |
502 | |
503 | status = mmal_port_format_commit(preview_port); |
504 | if (status != MMAL_SUCCESS) |
505 | { |
506 | vcos_log_error("camera viewfinder format couldn't be set" ); |
507 | goto end; |
508 | } |
509 | |
510 | /* For GL a pool of opaque buffer handles must be allocated in the client. |
511 | * These buffers are used to create the EGL images. |
512 | */ |
513 | state->preview_port = preview_port; |
514 | preview_port->buffer_num = preview_port->buffer_num_recommended; |
515 | preview_port->buffer_size = preview_port->buffer_size_recommended; |
516 | |
517 | vcos_log_trace("Creating buffer pool for GL renderer num %d size %d" , |
518 | preview_port->buffer_num, preview_port->buffer_size); |
519 | |
520 | /* Pool + queue to hold preview frames */ |
521 | state->preview_pool = mmal_port_pool_create(preview_port, |
522 | preview_port->buffer_num, preview_port->buffer_size); |
523 | |
524 | if (! state->preview_pool) |
525 | { |
526 | vcos_log_error("Error allocating pool" ); |
527 | status = MMAL_ENOMEM; |
528 | goto end; |
529 | } |
530 | |
531 | /* Place filled buffers from the preview port in a queue to render */ |
532 | state->preview_queue = mmal_queue_create(); |
533 | if (! state->preview_queue) |
534 | { |
535 | vcos_log_error("Error allocating queue" ); |
536 | status = MMAL_ENOMEM; |
537 | goto end; |
538 | } |
539 | |
540 | /* Enable preview port callback */ |
541 | preview_port->userdata = (struct MMAL_PORT_USERDATA_T*) state; |
542 | status = mmal_port_enable(preview_port, preview_output_cb); |
543 | if (status != MMAL_SUCCESS) |
544 | { |
545 | vcos_log_error("Failed to camera preview port" ); |
546 | goto end; |
547 | } |
548 | end: |
549 | return (status == MMAL_SUCCESS ? 0 : -1); |
550 | } |
551 | |
552 | /* Initialises GL preview state and creates the dispmanx native window. |
553 | * @param state Pointer to the GL preview state. |
554 | * @return Zero if successful. |
555 | */ |
556 | int raspitex_init(RASPITEX_STATE *state) |
557 | { |
558 | VCOS_STATUS_T status; |
559 | int rc; |
560 | vcos_init(); |
561 | |
562 | vcos_log_register("RaspiTex" , VCOS_LOG_CATEGORY); |
563 | vcos_log_set_level(VCOS_LOG_CATEGORY, |
564 | state->verbose ? VCOS_LOG_INFO : VCOS_LOG_WARN); |
565 | vcos_log_trace("%s" , VCOS_FUNCTION); |
566 | |
567 | status = vcos_semaphore_create(&state->capture.start_sem, |
568 | "glcap_start_sem" , 1); |
569 | if (status != VCOS_SUCCESS) |
570 | goto error; |
571 | |
572 | status = vcos_semaphore_create(&state->capture.completed_sem, |
573 | "glcap_completed_sem" , 0); |
574 | if (status != VCOS_SUCCESS) |
575 | goto error; |
576 | |
577 | switch (state->scene_id) |
578 | { |
579 | case RASPITEX_SCENE_SQUARE: |
580 | rc = square_open(state); |
581 | break; |
582 | case RASPITEX_SCENE_MIRROR: |
583 | rc = mirror_open(state); |
584 | break; |
585 | case RASPITEX_SCENE_TEAPOT: |
586 | rc = teapot_open(state); |
587 | break; |
588 | case RASPITEX_SCENE_YUV: |
589 | rc = yuv_open(state); |
590 | break; |
591 | case RASPITEX_SCENE_SOBEL: |
592 | rc = sobel_open(state); |
593 | break; |
594 | case RASPITEX_SCENE_VCSM_SQUARE: |
595 | rc = vcsm_square_open(state); |
596 | break; |
597 | default: |
598 | rc = -1; |
599 | break; |
600 | } |
601 | if (rc != 0) |
602 | goto error; |
603 | |
604 | return 0; |
605 | |
606 | error: |
607 | vcos_log_error("%s: failed" , VCOS_FUNCTION); |
608 | return -1; |
609 | } |
610 | |
611 | /* Destroys the pools of buffers used by the GL renderer. |
612 | * @param state Pointer to the GL preview state. |
613 | */ |
614 | void raspitex_destroy(RASPITEX_STATE *state) |
615 | { |
616 | vcos_log_trace("%s" , VCOS_FUNCTION); |
617 | if (state->preview_pool) |
618 | { |
619 | mmal_pool_destroy(state->preview_pool); |
620 | state->preview_pool = NULL; |
621 | } |
622 | |
623 | if (state->preview_queue) |
624 | { |
625 | mmal_queue_destroy(state->preview_queue); |
626 | state->preview_queue = NULL; |
627 | } |
628 | |
629 | if (state->ops.destroy_native_window) |
630 | state->ops.destroy_native_window(state); |
631 | |
632 | if (state->ops.close) |
633 | state->ops.close(state); |
634 | |
635 | vcos_semaphore_delete(&state->capture.start_sem); |
636 | vcos_semaphore_delete(&state->capture.completed_sem); |
637 | } |
638 | |
639 | /* Initialise the GL / window state to sensible defaults. |
640 | * Also initialise any rendering parameters e.g. the scene |
641 | * |
642 | * @param state Pointer to the GL preview state. |
643 | * @return Zero if successful. |
644 | */ |
645 | void raspitex_set_defaults(RASPITEX_STATE *state) |
646 | { |
647 | memset(state, 0, sizeof(*state)); |
648 | state->version_major = RASPITEX_VERSION_MAJOR; |
649 | state->version_minor = RASPITEX_VERSION_MINOR; |
650 | state->display = EGL_NO_DISPLAY; |
651 | state->surface = EGL_NO_SURFACE; |
652 | state->context = EGL_NO_CONTEXT; |
653 | state->egl_image = EGL_NO_IMAGE_KHR; |
654 | state->y_egl_image = EGL_NO_IMAGE_KHR; |
655 | state->u_egl_image = EGL_NO_IMAGE_KHR; |
656 | state->v_egl_image = EGL_NO_IMAGE_KHR; |
657 | state->opacity = 255; |
658 | state->width = DEFAULT_WIDTH; |
659 | state->height = DEFAULT_HEIGHT; |
660 | state->scene_id = RASPITEX_SCENE_SQUARE; |
661 | |
662 | state->ops.create_native_window = raspitexutil_create_native_window; |
663 | state->ops.gl_init = raspitexutil_gl_init_1_0; |
664 | state->ops.update_model = raspitexutil_update_model; |
665 | state->ops.redraw = raspitexutil_redraw; |
666 | state->ops.capture = raspitexutil_capture_bgra; |
667 | state->ops.gl_term = raspitexutil_gl_term; |
668 | state->ops.destroy_native_window = raspitexutil_destroy_native_window; |
669 | state->ops.close = raspitexutil_close; |
670 | } |
671 | |
672 | /* Stops the rendering loop and destroys MMAL resources |
673 | * @param state Pointer to the GL preview state. |
674 | */ |
675 | void raspitex_stop(RASPITEX_STATE *state) |
676 | { |
677 | if (! state->preview_stop) |
678 | { |
679 | vcos_log_trace("Stopping GL preview" ); |
680 | state->preview_stop = 1; |
681 | vcos_thread_join(&state->preview_thread, NULL); |
682 | } |
683 | } |
684 | |
685 | /** |
686 | * Starts the worker / GL renderer thread. |
687 | * @pre raspitex_init was successful |
688 | * @pre raspitex_configure_preview_port was successful |
689 | * @param state Pointer to the GL preview state. |
690 | * @return Zero on success, otherwise, -1 is returned |
691 | * */ |
692 | int raspitex_start(RASPITEX_STATE *state) |
693 | { |
694 | VCOS_STATUS_T status; |
695 | |
696 | vcos_log_trace("%s" , VCOS_FUNCTION); |
697 | status = vcos_thread_create(&state->preview_thread, "preview-worker" , |
698 | NULL, preview_worker, state); |
699 | |
700 | if (status != VCOS_SUCCESS) |
701 | vcos_log_error("%s: Failed to start worker thread %d" , |
702 | VCOS_FUNCTION, status); |
703 | |
704 | return (status == VCOS_SUCCESS ? 0 : -1); |
705 | } |
706 | |
707 | /** |
708 | * Writes the next GL frame-buffer to a RAW .ppm formatted file |
709 | * using the specified file-handle. |
710 | * @param state Pointer to the GL preview state. |
711 | * @param outpt_file Output file handle for the ppm image. |
712 | * @return Zero on success. |
713 | */ |
714 | int raspitex_capture(RASPITEX_STATE *state, FILE *output_file) |
715 | { |
716 | int rc = 0; |
717 | uint8_t *buffer = NULL; |
718 | size_t size = 0; |
719 | |
720 | vcos_log_trace("%s: state %p file %p" , VCOS_FUNCTION, |
721 | state, output_file); |
722 | |
723 | if (state && output_file) |
724 | { |
725 | /* Only request one capture at a time */ |
726 | vcos_semaphore_wait(&state->capture.start_sem); |
727 | state->capture.request = 1; |
728 | |
729 | /* Wait for capture to start */ |
730 | vcos_semaphore_wait(&state->capture.completed_sem); |
731 | |
732 | /* Take ownership of the captured buffer */ |
733 | buffer = state->capture.buffer; |
734 | size = state->capture.size; |
735 | |
736 | state->capture.request = 0; |
737 | state->capture.buffer = 0; |
738 | state->capture.size = 0; |
739 | |
740 | /* Allow another capture to be requested */ |
741 | vcos_semaphore_post(&state->capture.start_sem); |
742 | } |
743 | if (size == 0 || ! buffer) |
744 | { |
745 | vcos_log_error("%s: capture failed" , VCOS_FUNCTION); |
746 | rc = -1; |
747 | goto end; |
748 | } |
749 | |
750 | raspitexutil_brga_to_rgba(buffer, size); |
751 | rc = write_tga(output_file, state->width, state->height, buffer, size); |
752 | fflush(output_file); |
753 | |
754 | end: |
755 | free(buffer); |
756 | return rc; |
757 | } |
758 | |