mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
757 lines
22 KiB
757 lines
22 KiB
/* |
|
Copyright (c) 2013, Broadcom Europe Ltd |
|
Copyright (c) 2013, Tim Gover |
|
All rights reserved. |
|
|
|
Redistribution and use in source and binary forms, with or without |
|
modification, are permitted provided that the following conditions are met: |
|
* Redistributions of source code must retain the above copyright |
|
notice, this list of conditions and the following disclaimer. |
|
* Redistributions in binary form must reproduce the above copyright |
|
notice, this list of conditions and the following disclaimer in the |
|
documentation and/or other materials provided with the distribution. |
|
* Neither the name of the copyright holder nor the |
|
names of its contributors may be used to endorse or promote products |
|
derived from this software without specific prior written permission. |
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
#include "RaspiTex.h" |
|
#include "RaspiCLI.h" |
|
#include <math.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/time.h> |
|
#include <EGL/egl.h> |
|
#include <EGL/eglext.h> |
|
#include <GLES/gl.h> |
|
#include <GLES/glext.h> |
|
#include "RaspiTexUtil.h" |
|
#include "interface/vcos/vcos.h" |
|
#include "interface/mmal/mmal_buffer.h" |
|
#include "interface/mmal/util/mmal_util.h" |
|
#include "interface/mmal/util/mmal_util_params.h" |
|
#include "tga.h" |
|
|
|
#include "gl_scenes/mirror.h" |
|
#include "gl_scenes/sobel.h" |
|
#include "gl_scenes/square.h" |
|
#include "gl_scenes/teapot.h" |
|
#include "gl_scenes/vcsm_square.h" |
|
#include "gl_scenes/yuv.h" |
|
|
|
/** |
|
* \file RaspiTex.c |
|
* |
|
* A simple framework for extending a MMAL application to render buffers via |
|
* OpenGL. |
|
* |
|
* MMAL buffers are often in YUV colour space and in either a planar or |
|
* tile format which is not supported directly by V3D. Instead of copying |
|
* the buffer from the GPU and doing a colour space / pixel format conversion |
|
* the GL_OES_EGL_image_external is used. This allows an EGL image to be |
|
* created from GPU buffer handle (MMAL opaque buffer handle). The EGL image |
|
* may then be used to create a texture (glEGLImageTargetTexture2DOES) and |
|
* drawn by either OpenGL ES 1.0 or 2.0 contexts. |
|
* |
|
* Notes: |
|
* 1) GL_OES_EGL_image_external textures always return pixels in RGBA format. |
|
* This is also the case when used from a fragment shader. |
|
* |
|
* 2) The driver implementation creates a new RGB_565 buffer and does the color |
|
* space conversion from YUV. This happens in GPU memory using the vector |
|
* processor. |
|
* |
|
* 3) Each EGL external image in use will consume GPU memory for the RGB 565 |
|
* buffer. In addition, the GL pipeline might require more than one EGL image |
|
* to be retained in GPU memory until the drawing commands are flushed. |
|
* |
|
* Typically 128 MB of GPU memory is sufficient for 720p viewfinder and 720p |
|
* GL surface. If both the viewfinder and the GL surface are 1080p then |
|
* 256MB of GPU memory is recommended, otherwise, for non-trivial scenes |
|
* the system can run out of GPU memory whilst the camera is running. |
|
* |
|
* 4) It is important to make sure that the MMAL opaque buffer is not returned |
|
* to MMAL before the GL driver has completed the asynchronous call to |
|
* glEGLImageTargetTexture2DOES. Deferring destruction of the EGL image and |
|
* the buffer return to MMAL until after eglSwapBuffers is the recommended. |
|
* |
|
* See also: http://www.khronos.org/registry/gles/extensions/OES/OES_EGL_image_external.txt |
|
*/ |
|
|
|
#define DEFAULT_WIDTH 640 |
|
#define DEFAULT_HEIGHT 480 |
|
|
|
enum |
|
{ |
|
CommandGLScene, |
|
CommandGLWin |
|
}; |
|
|
|
static COMMAND_LIST cmdline_commands[] = |
|
{ |
|
{ CommandGLScene, "-glscene", "gs", "GL scene square,teapot,mirror,yuv,sobel,vcsm_square", 1 }, |
|
{ CommandGLWin, "-glwin", "gw", "GL window settings <'x,y,w,h'>", 1 }, |
|
}; |
|
|
|
static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); |
|
|
|
/** |
|
* Parse a possible command pair - command and parameter |
|
* @param arg1 Command |
|
* @param arg2 Parameter (could be NULL) |
|
* @return How many parameters were used, 0,1,2 |
|
*/ |
|
int raspitex_parse_cmdline(RASPITEX_STATE *state, |
|
const char *arg1, const char *arg2) |
|
{ |
|
int command_id, used = 0, num_parameters; |
|
|
|
if (!arg1) |
|
return 0; |
|
|
|
command_id = raspicli_get_command_id(cmdline_commands, |
|
cmdline_commands_size, arg1, &num_parameters); |
|
|
|
// If invalid command, or we are missing a parameter, drop out |
|
if (command_id==-1 || (command_id != -1 && num_parameters > 0 && arg2 == NULL)) |
|
return 0; |
|
|
|
switch (command_id) |
|
{ |
|
case CommandGLWin: // Allows a GL window to be different to preview-res |
|
{ |
|
int tmp; |
|
tmp = sscanf(arg2, "%d,%d,%d,%d", |
|
&state->x, &state->y, &state->width, &state->height); |
|
if (tmp != 4) |
|
{ |
|
// Default to safe size on parse error |
|
state->x = state->y = 0; |
|
state->width = DEFAULT_WIDTH; |
|
state->height = DEFAULT_HEIGHT; |
|
} |
|
else |
|
{ |
|
state->gl_win_defined = 1; |
|
} |
|
|
|
used = 2; |
|
break; |
|
} |
|
|
|
case CommandGLScene: // Selects the GL scene |
|
{ |
|
if (strcmp(arg2, "square") == 0) |
|
state->scene_id = RASPITEX_SCENE_SQUARE; |
|
else if (strcmp(arg2, "teapot") == 0) |
|
state->scene_id = RASPITEX_SCENE_TEAPOT; |
|
else if (strcmp(arg2, "mirror") == 0) |
|
state->scene_id = RASPITEX_SCENE_MIRROR; |
|
else if (strcmp(arg2, "yuv") == 0) |
|
state->scene_id = RASPITEX_SCENE_YUV; |
|
else if (strcmp(arg2, "sobel") == 0) |
|
state->scene_id = RASPITEX_SCENE_SOBEL; |
|
else if (strcmp(arg2, "vcsm_square") == 0) |
|
state->scene_id = RASPITEX_SCENE_VCSM_SQUARE; |
|
else |
|
vcos_log_error("Unknown scene %s", arg2); |
|
|
|
used = 2; |
|
break; |
|
} |
|
} |
|
return used; |
|
} |
|
|
|
/** |
|
* Display help for command line options |
|
*/ |
|
void raspitex_display_help() |
|
{ |
|
fprintf(stdout, "\nGL parameter commands\n\n"); |
|
raspicli_display_help(cmdline_commands, cmdline_commands_size); |
|
} |
|
|
|
static void update_fps() |
|
{ |
|
static int frame_count = 0; |
|
static long long time_start = 0; |
|
long long time_now; |
|
struct timeval te; |
|
float fps; |
|
|
|
frame_count++; |
|
|
|
gettimeofday(&te, NULL); |
|
time_now = te.tv_sec * 1000LL + te.tv_usec / 1000; |
|
|
|
if (time_start == 0) |
|
{ |
|
time_start = time_now; |
|
} |
|
else if (time_now - time_start > 5000) |
|
{ |
|
fps = (float) frame_count / ((time_now - time_start) / 1000.0); |
|
frame_count = 0; |
|
time_start = time_now; |
|
vcos_log_error("%3.2f FPS", fps); |
|
} |
|
} |
|
|
|
/** |
|
* Captures the frame-buffer if requested. |
|
* @param state RASPITEX STATE |
|
* @return Zero if successful. |
|
*/ |
|
static void raspitex_do_capture(RASPITEX_STATE *state) |
|
{ |
|
uint8_t *buffer = NULL; |
|
size_t size = 0; |
|
|
|
if (state->capture.request) |
|
{ |
|
if (state->ops.capture(state, &buffer, &size) == 0) |
|
{ |
|
/* Pass ownership of buffer to main thread via capture state */ |
|
state->capture.buffer = buffer; |
|
state->capture.size = size; |
|
} |
|
else |
|
{ |
|
state->capture.buffer = NULL; // Null indicates an error |
|
state->capture.size = 0; |
|
} |
|
|
|
state->capture.request = 0; // Always clear request and post sem |
|
vcos_semaphore_post(&state->capture.completed_sem); |
|
} |
|
} |
|
|
|
/** |
|
* Checks if there is at least one valid EGL image. |
|
* @param state RASPITEX STATE |
|
* @return Zero if successful. |
|
*/ |
|
static int check_egl_image(RASPITEX_STATE *state) |
|
{ |
|
if (state->egl_image == EGL_NO_IMAGE_KHR && |
|
state->y_egl_image == EGL_NO_IMAGE_KHR && |
|
state->u_egl_image == EGL_NO_IMAGE_KHR && |
|
state->v_egl_image == EGL_NO_IMAGE_KHR) |
|
return -1; |
|
else |
|
return 0; |
|
} |
|
|
|
/** |
|
* Draws the next preview frame. If a new preview buffer is available then the |
|
* preview texture is updated first. |
|
* |
|
* @param state RASPITEX STATE |
|
* @param video_frame MMAL buffer header containing the opaque buffer handle. |
|
* @return Zero if successful. |
|
*/ |
|
static int raspitex_draw(RASPITEX_STATE *state, MMAL_BUFFER_HEADER_T *buf) |
|
{ |
|
int rc = 0; |
|
|
|
/* If buf is non-NULL then there is a new viewfinder frame available |
|
* from the camera so the texture should be updated. |
|
* |
|
* Although it's possible to have multiple textures mapped to different |
|
* viewfinder frames this can consume a lot of GPU memory for high-resolution |
|
* viewfinders. |
|
*/ |
|
if (buf) |
|
{ |
|
/* Update the texture to the new viewfinder image which should */ |
|
if (state->ops.update_texture) |
|
{ |
|
rc = state->ops.update_texture(state, (EGLClientBuffer) buf->data); |
|
if (rc != 0) |
|
{ |
|
vcos_log_error("%s: Failed to update RGBX texture %d", |
|
VCOS_FUNCTION, rc); |
|
goto end; |
|
} |
|
} |
|
|
|
if (state->ops.update_y_texture) |
|
{ |
|
rc = state->ops.update_y_texture(state, (EGLClientBuffer) buf->data); |
|
if (rc != 0) |
|
{ |
|
vcos_log_error("%s: Failed to update Y' plane texture %d", VCOS_FUNCTION, rc); |
|
goto end; |
|
} |
|
} |
|
|
|
if (state->ops.update_u_texture) |
|
{ |
|
rc = state->ops.update_u_texture(state, (EGLClientBuffer) buf->data); |
|
if (rc != 0) |
|
{ |
|
vcos_log_error("%s: Failed to update U plane texture %d", VCOS_FUNCTION, rc); |
|
goto end; |
|
} |
|
} |
|
|
|
if (state->ops.update_v_texture) |
|
{ |
|
rc = state->ops.update_v_texture(state, (EGLClientBuffer) buf->data); |
|
if (rc != 0) |
|
{ |
|
vcos_log_error("%s: Failed to update V texture %d", VCOS_FUNCTION, rc); |
|
goto end; |
|
} |
|
} |
|
|
|
/* Now return the PREVIOUS MMAL buffer header back to the camera preview. */ |
|
if (state->preview_buf) |
|
mmal_buffer_header_release(state->preview_buf); |
|
|
|
state->preview_buf = buf; |
|
} |
|
|
|
/* Do the drawing */ |
|
if (check_egl_image(state) == 0) |
|
{ |
|
rc = state->ops.update_model(state); |
|
if (rc != 0) |
|
goto end; |
|
|
|
rc = state->ops.redraw(state); |
|
if (rc != 0) |
|
goto end; |
|
|
|
raspitex_do_capture(state); |
|
|
|
eglSwapBuffers(state->display, state->surface); |
|
update_fps(); |
|
} |
|
else |
|
{ |
|
// vcos_log_trace("%s: No preview image", VCOS_FUNCTION); |
|
} |
|
|
|
end: |
|
return rc; |
|
} |
|
|
|
/** |
|
* Process preview buffers. |
|
* |
|
* Dequeue each available preview buffer in order and call current redraw |
|
* function. If no new buffers are available then the render function is |
|
* invoked anyway. |
|
* @param state The GL preview window state. |
|
* @return Zero if successful. |
|
*/ |
|
static int preview_process_returned_bufs(RASPITEX_STATE* state) |
|
{ |
|
MMAL_BUFFER_HEADER_T *buf; |
|
int new_frame = 0; |
|
int rc = 0; |
|
|
|
while ((buf = mmal_queue_get(state->preview_queue)) != NULL) |
|
{ |
|
if (state->preview_stop == 0) |
|
{ |
|
new_frame = 1; |
|
rc = raspitex_draw(state, buf); |
|
if (rc != 0) |
|
{ |
|
vcos_log_error("%s: Error drawing frame. Stopping.", VCOS_FUNCTION); |
|
state->preview_stop = 1; |
|
return rc; |
|
} |
|
} |
|
} |
|
|
|
/* If there were no new frames then redraw the scene again with the previous |
|
* texture. Otherwise, go round the loop again to see if any new buffers |
|
* are returned. |
|
*/ |
|
if (! new_frame) |
|
rc = raspitex_draw(state, NULL); |
|
return rc; |
|
} |
|
|
|
/** Preview worker thread. |
|
* Ensures camera preview is supplied with buffers and sends preview frames to GL. |
|
* @param arg Pointer to state. |
|
* @return NULL always. |
|
*/ |
|
static void *preview_worker(void *arg) |
|
{ |
|
RASPITEX_STATE* state = arg; |
|
MMAL_PORT_T *preview_port = state->preview_port; |
|
MMAL_BUFFER_HEADER_T *buf; |
|
MMAL_STATUS_T st; |
|
int rc; |
|
|
|
vcos_log_trace("%s: port %p", VCOS_FUNCTION, preview_port); |
|
|
|
rc = state->ops.create_native_window(state); |
|
if (rc != 0) |
|
goto end; |
|
|
|
rc = state->ops.gl_init(state); |
|
if (rc != 0) |
|
goto end; |
|
|
|
while (state->preview_stop == 0) |
|
{ |
|
/* Send empty buffers to camera preview port */ |
|
while ((buf = mmal_queue_get(state->preview_pool->queue)) != NULL) |
|
{ |
|
st = mmal_port_send_buffer(preview_port, buf); |
|
if (st != MMAL_SUCCESS) |
|
{ |
|
vcos_log_error("Failed to send buffer to %s", preview_port->name); |
|
} |
|
} |
|
/* Process returned buffers */ |
|
if (preview_process_returned_bufs(state) != 0) |
|
{ |
|
vcos_log_error("Preview error. Exiting."); |
|
state->preview_stop = 1; |
|
} |
|
} |
|
|
|
end: |
|
/* Make sure all buffers are returned on exit */ |
|
while ((buf = mmal_queue_get(state->preview_queue)) != NULL) |
|
mmal_buffer_header_release(buf); |
|
|
|
/* Tear down GL */ |
|
state->ops.gl_term(state); |
|
vcos_log_trace("Exiting preview worker"); |
|
return NULL; |
|
} |
|
|
|
/** |
|
* MMAL Callback from camera preview output port. |
|
* @param port The camera preview port. |
|
* @param buf The new preview buffer. |
|
**/ |
|
static void preview_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf) |
|
{ |
|
RASPITEX_STATE *state = (RASPITEX_STATE*) port->userdata; |
|
|
|
if (buf->length == 0) |
|
{ |
|
vcos_log_trace("%s: zero-length buffer => EOS", port->name); |
|
state->preview_stop = 1; |
|
mmal_buffer_header_release(buf); |
|
} |
|
else if (buf->data == NULL) |
|
{ |
|
vcos_log_trace("%s: zero buffer handle", port->name); |
|
mmal_buffer_header_release(buf); |
|
} |
|
else |
|
{ |
|
/* Enqueue the preview frame for rendering and return to |
|
* avoid blocking MMAL core. |
|
*/ |
|
mmal_queue_put(state->preview_queue, buf); |
|
} |
|
} |
|
|
|
/* Registers a callback on the camera preview port to receive |
|
* notifications of new frames. |
|
* This must be called before rapitex_start and may not be called again |
|
* without calling raspitex_destroy first. |
|
* |
|
* @param state Pointer to the GL preview state. |
|
* @param port Pointer to the camera preview port |
|
* @return Zero if successful. |
|
*/ |
|
int raspitex_configure_preview_port(RASPITEX_STATE *state, |
|
MMAL_PORT_T *preview_port) |
|
{ |
|
MMAL_STATUS_T status; |
|
vcos_log_trace("%s port %p", VCOS_FUNCTION, preview_port); |
|
|
|
/* Enable ZERO_COPY mode on the preview port which instructs MMAL to only |
|
* pass the 4-byte opaque buffer handle instead of the contents of the opaque |
|
* buffer. |
|
* The opaque handle is resolved on VideoCore by the GL driver when the EGL |
|
* image is created. |
|
*/ |
|
status = mmal_port_parameter_set_boolean(preview_port, |
|
MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE); |
|
if (status != MMAL_SUCCESS) |
|
{ |
|
vcos_log_error("Failed to enable zero copy on camera preview port"); |
|
goto end; |
|
} |
|
|
|
status = mmal_port_format_commit(preview_port); |
|
if (status != MMAL_SUCCESS) |
|
{ |
|
vcos_log_error("camera viewfinder format couldn't be set"); |
|
goto end; |
|
} |
|
|
|
/* For GL a pool of opaque buffer handles must be allocated in the client. |
|
* These buffers are used to create the EGL images. |
|
*/ |
|
state->preview_port = preview_port; |
|
preview_port->buffer_num = preview_port->buffer_num_recommended; |
|
preview_port->buffer_size = preview_port->buffer_size_recommended; |
|
|
|
vcos_log_trace("Creating buffer pool for GL renderer num %d size %d", |
|
preview_port->buffer_num, preview_port->buffer_size); |
|
|
|
/* Pool + queue to hold preview frames */ |
|
state->preview_pool = mmal_port_pool_create(preview_port, |
|
preview_port->buffer_num, preview_port->buffer_size); |
|
|
|
if (! state->preview_pool) |
|
{ |
|
vcos_log_error("Error allocating pool"); |
|
status = MMAL_ENOMEM; |
|
goto end; |
|
} |
|
|
|
/* Place filled buffers from the preview port in a queue to render */ |
|
state->preview_queue = mmal_queue_create(); |
|
if (! state->preview_queue) |
|
{ |
|
vcos_log_error("Error allocating queue"); |
|
status = MMAL_ENOMEM; |
|
goto end; |
|
} |
|
|
|
/* Enable preview port callback */ |
|
preview_port->userdata = (struct MMAL_PORT_USERDATA_T*) state; |
|
status = mmal_port_enable(preview_port, preview_output_cb); |
|
if (status != MMAL_SUCCESS) |
|
{ |
|
vcos_log_error("Failed to camera preview port"); |
|
goto end; |
|
} |
|
end: |
|
return (status == MMAL_SUCCESS ? 0 : -1); |
|
} |
|
|
|
/* Initialises GL preview state and creates the dispmanx native window. |
|
* @param state Pointer to the GL preview state. |
|
* @return Zero if successful. |
|
*/ |
|
int raspitex_init(RASPITEX_STATE *state) |
|
{ |
|
VCOS_STATUS_T status; |
|
int rc; |
|
vcos_init(); |
|
|
|
vcos_log_register("RaspiTex", VCOS_LOG_CATEGORY); |
|
vcos_log_set_level(VCOS_LOG_CATEGORY, |
|
state->verbose ? VCOS_LOG_INFO : VCOS_LOG_WARN); |
|
vcos_log_trace("%s", VCOS_FUNCTION); |
|
|
|
status = vcos_semaphore_create(&state->capture.start_sem, |
|
"glcap_start_sem", 1); |
|
if (status != VCOS_SUCCESS) |
|
goto error; |
|
|
|
status = vcos_semaphore_create(&state->capture.completed_sem, |
|
"glcap_completed_sem", 0); |
|
if (status != VCOS_SUCCESS) |
|
goto error; |
|
|
|
switch (state->scene_id) |
|
{ |
|
case RASPITEX_SCENE_SQUARE: |
|
rc = square_open(state); |
|
break; |
|
case RASPITEX_SCENE_MIRROR: |
|
rc = mirror_open(state); |
|
break; |
|
case RASPITEX_SCENE_TEAPOT: |
|
rc = teapot_open(state); |
|
break; |
|
case RASPITEX_SCENE_YUV: |
|
rc = yuv_open(state); |
|
break; |
|
case RASPITEX_SCENE_SOBEL: |
|
rc = sobel_open(state); |
|
break; |
|
case RASPITEX_SCENE_VCSM_SQUARE: |
|
rc = vcsm_square_open(state); |
|
break; |
|
default: |
|
rc = -1; |
|
break; |
|
} |
|
if (rc != 0) |
|
goto error; |
|
|
|
return 0; |
|
|
|
error: |
|
vcos_log_error("%s: failed", VCOS_FUNCTION); |
|
return -1; |
|
} |
|
|
|
/* Destroys the pools of buffers used by the GL renderer. |
|
* @param state Pointer to the GL preview state. |
|
*/ |
|
void raspitex_destroy(RASPITEX_STATE *state) |
|
{ |
|
vcos_log_trace("%s", VCOS_FUNCTION); |
|
if (state->preview_pool) |
|
{ |
|
mmal_pool_destroy(state->preview_pool); |
|
state->preview_pool = NULL; |
|
} |
|
|
|
if (state->preview_queue) |
|
{ |
|
mmal_queue_destroy(state->preview_queue); |
|
state->preview_queue = NULL; |
|
} |
|
|
|
if (state->ops.destroy_native_window) |
|
state->ops.destroy_native_window(state); |
|
|
|
if (state->ops.close) |
|
state->ops.close(state); |
|
|
|
vcos_semaphore_delete(&state->capture.start_sem); |
|
vcos_semaphore_delete(&state->capture.completed_sem); |
|
} |
|
|
|
/* Initialise the GL / window state to sensible defaults. |
|
* Also initialise any rendering parameters e.g. the scene |
|
* |
|
* @param state Pointer to the GL preview state. |
|
* @return Zero if successful. |
|
*/ |
|
void raspitex_set_defaults(RASPITEX_STATE *state) |
|
{ |
|
memset(state, 0, sizeof(*state)); |
|
state->version_major = RASPITEX_VERSION_MAJOR; |
|
state->version_minor = RASPITEX_VERSION_MINOR; |
|
state->display = EGL_NO_DISPLAY; |
|
state->surface = EGL_NO_SURFACE; |
|
state->context = EGL_NO_CONTEXT; |
|
state->egl_image = EGL_NO_IMAGE_KHR; |
|
state->y_egl_image = EGL_NO_IMAGE_KHR; |
|
state->u_egl_image = EGL_NO_IMAGE_KHR; |
|
state->v_egl_image = EGL_NO_IMAGE_KHR; |
|
state->opacity = 255; |
|
state->width = DEFAULT_WIDTH; |
|
state->height = DEFAULT_HEIGHT; |
|
state->scene_id = RASPITEX_SCENE_SQUARE; |
|
|
|
state->ops.create_native_window = raspitexutil_create_native_window; |
|
state->ops.gl_init = raspitexutil_gl_init_1_0; |
|
state->ops.update_model = raspitexutil_update_model; |
|
state->ops.redraw = raspitexutil_redraw; |
|
state->ops.capture = raspitexutil_capture_bgra; |
|
state->ops.gl_term = raspitexutil_gl_term; |
|
state->ops.destroy_native_window = raspitexutil_destroy_native_window; |
|
state->ops.close = raspitexutil_close; |
|
} |
|
|
|
/* Stops the rendering loop and destroys MMAL resources |
|
* @param state Pointer to the GL preview state. |
|
*/ |
|
void raspitex_stop(RASPITEX_STATE *state) |
|
{ |
|
if (! state->preview_stop) |
|
{ |
|
vcos_log_trace("Stopping GL preview"); |
|
state->preview_stop = 1; |
|
vcos_thread_join(&state->preview_thread, NULL); |
|
} |
|
} |
|
|
|
/** |
|
* Starts the worker / GL renderer thread. |
|
* @pre raspitex_init was successful |
|
* @pre raspitex_configure_preview_port was successful |
|
* @param state Pointer to the GL preview state. |
|
* @return Zero on success, otherwise, -1 is returned |
|
* */ |
|
int raspitex_start(RASPITEX_STATE *state) |
|
{ |
|
VCOS_STATUS_T status; |
|
|
|
vcos_log_trace("%s", VCOS_FUNCTION); |
|
status = vcos_thread_create(&state->preview_thread, "preview-worker", |
|
NULL, preview_worker, state); |
|
|
|
if (status != VCOS_SUCCESS) |
|
vcos_log_error("%s: Failed to start worker thread %d", |
|
VCOS_FUNCTION, status); |
|
|
|
return (status == VCOS_SUCCESS ? 0 : -1); |
|
} |
|
|
|
/** |
|
* Writes the next GL frame-buffer to a RAW .ppm formatted file |
|
* using the specified file-handle. |
|
* @param state Pointer to the GL preview state. |
|
* @param outpt_file Output file handle for the ppm image. |
|
* @return Zero on success. |
|
*/ |
|
int raspitex_capture(RASPITEX_STATE *state, FILE *output_file) |
|
{ |
|
int rc = 0; |
|
uint8_t *buffer = NULL; |
|
size_t size = 0; |
|
|
|
vcos_log_trace("%s: state %p file %p", VCOS_FUNCTION, |
|
state, output_file); |
|
|
|
if (state && output_file) |
|
{ |
|
/* Only request one capture at a time */ |
|
vcos_semaphore_wait(&state->capture.start_sem); |
|
state->capture.request = 1; |
|
|
|
/* Wait for capture to start */ |
|
vcos_semaphore_wait(&state->capture.completed_sem); |
|
|
|
/* Take ownership of the captured buffer */ |
|
buffer = state->capture.buffer; |
|
size = state->capture.size; |
|
|
|
state->capture.request = 0; |
|
state->capture.buffer = 0; |
|
state->capture.size = 0; |
|
|
|
/* Allow another capture to be requested */ |
|
vcos_semaphore_post(&state->capture.start_sem); |
|
} |
|
if (size == 0 || ! buffer) |
|
{ |
|
vcos_log_error("%s: capture failed", VCOS_FUNCTION); |
|
rc = -1; |
|
goto end; |
|
} |
|
|
|
raspitexutil_brga_to_rgba(buffer, size); |
|
rc = write_tga(output_file, state->width, state->height, buffer, size); |
|
fflush(output_file); |
|
|
|
end: |
|
free(buffer); |
|
return rc; |
|
}
|
|
|