| 1 | /* | 
| 2 | Copyright (c) 2013, Broadcom Europe Ltd | 
| 3 | Copyright (c) 2016, 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 | /* Make the render output CPU accessible by defining a framebuffer texture | 
| 30 |  * stored in a VCSM (VideoCore shared memory) EGL image. | 
| 31 |  * | 
| 32 |  * This example just demonstrates how to use use the APIs by using the CPU. | 
| 33 |  * to blit an animated rectangle into frame-buffer texture in shared memory. | 
| 34 |  * | 
| 35 |  * A more realistic example would be to do a blur, edge-detect in GLSL then pass | 
| 36 |  * the buffer to OpenCV. There may be some benefit in using multiple GL contexts | 
| 37 |  * to reduce the impact of serializing operations with a glFlush. | 
| 38 |  * | 
| 39 |  * N.B VCSM textures are raster scan order textures. This makes it very | 
| 40 |  * convenient to read and modify VCSM frame-buffer textures from the CPU. | 
| 41 |  * However, if the output of the CPU stage is drawn again as a texture that | 
| 42 |  * is rotated or scaled then it can sometimes be better to use glTexImage2D | 
| 43 |  * to allow the driver to convert this back into the native texture format. | 
| 44 |  * | 
| 45 |  * Example usage | 
| 46 |  * raspistill -p 0,0,1024,1024 -gw 0,0,1024,1024 -t 10000 --gl -gs vcsm_square | 
| 47 |  */ | 
| 48 | /* Uncomment the next line to compare with the glReadPixels implementation. VCSM | 
| 49 |  * should run at about 40fps with a 1024x1024 texture compared to about 20fps | 
| 50 |  * using glReadPixels. | 
| 51 |  */ | 
| 52 | //#define USE_READPIXELS | 
| 53 |  | 
| 54 | #include "vcsm_square.h" | 
| 55 | #include <GLES2/gl2.h> | 
| 56 | #include <EGL/egl.h> | 
| 57 | #include <EGL/eglext.h> | 
| 58 | #include "RaspiTex.h" | 
| 59 | #include "RaspiTexUtil.h" | 
| 60 | #include "user-vcsm.h" | 
| 61 |  | 
| 62 | /* Draw a scaled quad showing the entire texture with the | 
| 63 |  * origin defined as an attribute */ | 
| 64 | static RASPITEXUTIL_SHADER_PROGRAM_T vcsm_square_oes_shader = | 
| 65 | { | 
| 66 |     .vertex_source = | 
| 67 |     "attribute vec2 vertex;\n"  | 
| 68 |     "varying vec2 texcoord;\n"  | 
| 69 |     "void main(void) {\n"  | 
| 70 |     "   texcoord = 0.5 * (vertex + 1.0);\n"  \ | 
| 71 |     "   gl_Position = vec4(vertex, 0.0, 1.0);\n"  | 
| 72 |     "}\n" , | 
| 73 |  | 
| 74 |     .fragment_source = | 
| 75 |     "#extension GL_OES_EGL_image_external : require\n"  | 
| 76 |     "uniform samplerExternalOES tex;\n"  | 
| 77 |     "varying vec2 texcoord;\n"  | 
| 78 |     "void main(void) {\n"  | 
| 79 |     "    gl_FragColor = texture2D(tex, texcoord);\n"  | 
| 80 |     "}\n" , | 
| 81 |     .uniform_names = {"tex" }, | 
| 82 |     .attribute_names = {"vertex" }, | 
| 83 | }; | 
| 84 | static RASPITEXUTIL_SHADER_PROGRAM_T vcsm_square_shader = | 
| 85 | { | 
| 86 |     .vertex_source = | 
| 87 |     "attribute vec2 vertex;\n"  | 
| 88 |     "varying vec2 texcoord;\n"  | 
| 89 |     "void main(void) {\n"  | 
| 90 |     "   texcoord = 0.5 * (vertex + 1.0);\n"  \ | 
| 91 |     "   gl_Position = vec4(vertex, 0.0, 1.0);\n"  | 
| 92 |     "}\n" , | 
| 93 |  | 
| 94 |     .fragment_source = | 
| 95 |     "uniform sampler2D tex;\n"  | 
| 96 |     "varying vec2 texcoord;\n"  | 
| 97 |     "void main(void) {\n"  | 
| 98 |     "    gl_FragColor = texture2D(tex, texcoord);\n"  | 
| 99 |     "}\n" , | 
| 100 |     .uniform_names = {"tex" }, | 
| 101 |     .attribute_names = {"vertex" }, | 
| 102 | }; | 
| 103 |  | 
| 104 | static GLfloat quad_varray[] = { | 
| 105 |    -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, | 
| 106 |    -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, | 
| 107 | }; | 
| 108 |  | 
| 109 | static GLuint quad_vbo; | 
| 110 |  | 
| 111 | #ifdef USE_READPIXELS | 
| 112 | unsigned char *pixel_buffer; | 
| 113 | #else | 
| 114 | static struct egl_image_brcm_vcsm_info vcsm_info; | 
| 115 | static EGLImageKHR eglFbImage; | 
| 116 | #endif | 
| 117 | static GLuint fb_tex_name; | 
| 118 | static GLuint fb_name; | 
| 119 |  | 
| 120 |  | 
| 121 | // VCSM buffer dimensions must be a power of two. Use glViewPort to draw NPOT | 
| 122 | // rectangles within the VCSM buffer. | 
| 123 | static int fb_width = 1024; | 
| 124 | static int fb_height = 1024; | 
| 125 |  | 
| 126 | static const EGLint vcsm_square_egl_config_attribs[] = | 
| 127 | { | 
| 128 |     EGL_RED_SIZE,   8, | 
| 129 |     EGL_GREEN_SIZE, 8, | 
| 130 |     EGL_BLUE_SIZE,  8, | 
| 131 |     EGL_ALPHA_SIZE, 8, | 
| 132 |     EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, | 
| 133 |     EGL_NONE | 
| 134 | }; | 
| 135 |  | 
| 136 | static int vcsm_square_init(RASPITEX_STATE *raspitex_state) | 
| 137 | { | 
| 138 |     int rc = vcsm_init(); | 
| 139 |     vcos_log_trace("%s: vcsm_init %d" , VCOS_FUNCTION, rc); | 
| 140 |  | 
| 141 |     raspitex_state->egl_config_attribs = vcsm_square_egl_config_attribs; | 
| 142 |     rc = raspitexutil_gl_init_2_0(raspitex_state); | 
| 143 |  | 
| 144 |     if (rc != 0) | 
| 145 |         goto end; | 
| 146 |  | 
| 147 |     // Shader for drawing the YUV OES texture | 
| 148 |     rc = raspitexutil_build_shader_program(&vcsm_square_oes_shader); | 
| 149 |     GLCHK(glUseProgram(vcsm_square_oes_shader.program)); | 
| 150 |     GLCHK(glUniform1i(vcsm_square_oes_shader.uniform_locations[0], 0)); // tex unit | 
| 151 |  | 
| 152 |     // Shader for drawing VCSM sampler2D texture | 
| 153 |     rc = raspitexutil_build_shader_program(&vcsm_square_shader); | 
| 154 |     GLCHK(glUseProgram(vcsm_square_shader.program)); | 
| 155 |     GLCHK(glUniform1i(vcsm_square_shader.uniform_locations[0], 0)); // tex unit | 
| 156 |  | 
| 157 |     GLCHK(glGenFramebuffers(1, &fb_name)); | 
| 158 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, fb_name)); | 
| 159 |  | 
| 160 |     GLCHK(glGenTextures(1, &fb_tex_name)); | 
| 161 |     GLCHK(glBindTexture(GL_TEXTURE_2D, fb_tex_name)); | 
| 162 |     GLCHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); | 
| 163 |     GLCHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); | 
| 164 |  | 
| 165 | #ifdef USE_READPIXELS | 
| 166 |     printf("Using glReadPixels\n" ); | 
| 167 |     GLCHK(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb_width, fb_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); | 
| 168 |     pixel_buffer = malloc(fb_width * fb_height * 4); | 
| 169 |     if (! pixel_buffer) { | 
| 170 |         rc = -1; | 
| 171 |         goto end; | 
| 172 |     } | 
| 173 | #else /* USE_READPIXELS */ | 
| 174 |     printf("Using VCSM\n" ); | 
| 175 |     vcsm_info.width = fb_width; | 
| 176 |     vcsm_info.height = fb_height; | 
| 177 |     eglFbImage = eglCreateImageKHR(raspitex_state->display, EGL_NO_CONTEXT, | 
| 178 |             EGL_IMAGE_BRCM_VCSM, &vcsm_info, NULL); | 
| 179 |     if (eglFbImage == EGL_NO_IMAGE_KHR || vcsm_info.vcsm_handle == 0) { | 
| 180 |         vcos_log_error("%s: Failed to create EGL VCSM image\n" , VCOS_FUNCTION); | 
| 181 |         rc = -1; | 
| 182 |         goto end; | 
| 183 |     } | 
| 184 |  | 
| 185 |     GLCHK(glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglFbImage)); | 
| 186 | #endif /* USE_READPIXELS */ | 
| 187 |  | 
| 188 |     GLCHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_tex_name, 0)); | 
| 189 |     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | 
| 190 |         vcos_log_error("GL_FRAMEBUFFER is not complete\n" ); | 
| 191 |         rc = -1; | 
| 192 |         goto end; | 
| 193 |     } | 
| 194 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, 0)); | 
| 195 |     GLCHK(glGenBuffers(1, &quad_vbo)); | 
| 196 |     GLCHK(glBindBuffer(GL_ARRAY_BUFFER, quad_vbo)); | 
| 197 |     GLCHK(glBufferData(GL_ARRAY_BUFFER, sizeof(quad_varray), quad_varray, GL_STATIC_DRAW)); | 
| 198 |  | 
| 199 |     GLCHK(glClearColor(0, 0, 0, 0)); | 
| 200 | end: | 
| 201 |     return rc; | 
| 202 | } | 
| 203 |  | 
| 204 | // Write the shared memory texture writing something to each line. This is | 
| 205 | // just to show that the buffer really is CPU modifiable. | 
| 206 | static void vcsm_square_draw_pattern(unsigned char *buffer) | 
| 207 | { | 
| 208 |     static unsigned x_offset; | 
| 209 |  | 
| 210 |     unsigned char *line_start = (unsigned char *) buffer; | 
| 211 |     unsigned width = fb_width > 32 ? 32 : fb_width; | 
| 212 |     int i = 0; | 
| 213 |     size_t stride = fb_width  << 2; | 
| 214 |  | 
| 215 |     x_offset = (x_offset + 1) % (fb_width - width); | 
| 216 |     for (i = 0; i < fb_height; i++) { | 
| 217 |         memset(line_start + (x_offset << 2), ~0, width << 2); | 
| 218 |         line_start += stride; | 
| 219 |     } | 
| 220 | } | 
| 221 |  | 
| 222 | #ifdef USE_READPIXELS | 
| 223 | static int vcsm_square_redraw_readpixels(RASPITEX_STATE *raspitex_state) | 
| 224 | { | 
| 225 |     vcos_log_trace("%s" , VCOS_FUNCTION); | 
| 226 |  | 
| 227 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, fb_name)); | 
| 228 |     GLCHK(glViewport(0,0,fb_width,fb_height)); | 
| 229 |     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | 
| 230 |  | 
| 231 |     // Fill the viewport with the camFill the viewport with the camera image | 
| 232 |     GLCHK(glUseProgram(vcsm_square_oes_shader.program)); | 
| 233 |     GLCHK(glActiveTexture(GL_TEXTURE0)); | 
| 234 |     GLCHK(glBindTexture(GL_TEXTURE_EXTERNAL_OES, raspitex_state->y_texture)); | 
| 235 |     GLCHK(glBindBuffer(GL_ARRAY_BUFFER, quad_vbo)); | 
| 236 |     GLCHK(glEnableVertexAttribArray(vcsm_square_oes_shader.attribute_locations[0])); | 
| 237 |     GLCHK(glVertexAttribPointer(vcsm_square_oes_shader.attribute_locations[0], 2, GL_FLOAT, GL_FALSE, 0, 0)); | 
| 238 |     GLCHK(glDrawArrays(GL_TRIANGLES, 0, 6)); | 
| 239 |  | 
| 240 |     GLCHK(glReadPixels(0, 0, fb_width, fb_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer)); | 
| 241 |  | 
| 242 |     vcsm_square_draw_pattern(pixel_buffer); | 
| 243 |  | 
| 244 |     // Enable default window surface | 
| 245 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, 0)); | 
| 246 |  | 
| 247 |     // Draw the modified texture buffer to the screen | 
| 248 |     GLCHK(glViewport(raspitex_state->x, raspitex_state->y, raspitex_state->width, raspitex_state->height)); | 
| 249 |     GLCHK(glUseProgram(vcsm_square_shader.program)); | 
| 250 |     GLCHK(glActiveTexture(GL_TEXTURE0)); | 
| 251 |     GLCHK(glBindTexture(GL_TEXTURE_2D, fb_tex_name)); | 
| 252 |     GLCHK(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_width, fb_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer)); | 
| 253 |     GLCHK(glEnableVertexAttribArray(vcsm_square_shader.attribute_locations[0])); | 
| 254 |     GLCHK(glVertexAttribPointer(vcsm_square_shader.attribute_locations[0], 2, GL_FLOAT, GL_FALSE, 0, 0)); | 
| 255 |     GLCHK(glDrawArrays(GL_TRIANGLES, 0, 6)); | 
| 256 |  | 
| 257 |     GLCHK(glDisableVertexAttribArray(vcsm_square_shader.attribute_locations[0])); | 
| 258 |     GLCHK(glUseProgram(0)); | 
| 259 |  | 
| 260 |     return 0; | 
| 261 | } | 
| 262 | #else /* USE_READPIXELS */ | 
| 263 | static int vcsm_square_redraw(RASPITEX_STATE *raspitex_state) | 
| 264 | { | 
| 265 |     unsigned char *vcsm_buffer = NULL; | 
| 266 |     VCSM_CACHE_TYPE_T cache_type; | 
| 267 |  | 
| 268 |     vcos_log_trace("%s" , VCOS_FUNCTION); | 
| 269 |  | 
| 270 |     glClearColor(255, 0, 0, 255); | 
| 271 |  | 
| 272 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, fb_name)); | 
| 273 |     GLCHK(glViewport(0, 0, fb_width, fb_height)); | 
| 274 |     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | 
| 275 |  | 
| 276 |     // Fill the viewport with the camFill the viewport with the camera image | 
| 277 |     GLCHK(glUseProgram(vcsm_square_oes_shader.program)); | 
| 278 |     GLCHK(glActiveTexture(GL_TEXTURE0)); | 
| 279 |     GLCHK(glBindTexture(GL_TEXTURE_EXTERNAL_OES, raspitex_state->y_texture)); | 
| 280 |     GLCHK(glBindBuffer(GL_ARRAY_BUFFER, quad_vbo)); | 
| 281 |     GLCHK(glEnableVertexAttribArray(vcsm_square_oes_shader.attribute_locations[0])); | 
| 282 |     GLCHK(glVertexAttribPointer(vcsm_square_oes_shader.attribute_locations[0], 2, GL_FLOAT, GL_FALSE, 0, 0)); | 
| 283 |     GLCHK(glDrawArrays(GL_TRIANGLES, 0, 6)); | 
| 284 |  | 
| 285 |     GLCHK(glFinish()); | 
| 286 |  | 
| 287 |     // Make the buffer CPU addressable with host cache enabled | 
| 288 |     vcsm_buffer = (unsigned char *) vcsm_lock_cache(vcsm_info.vcsm_handle, VCSM_CACHE_TYPE_HOST, &cache_type); | 
| 289 |     if (! vcsm_buffer) { | 
| 290 |         vcos_log_error("Failed to lock VCSM buffer for handle %d\n" , vcsm_info.vcsm_handle); | 
| 291 |         return -1; | 
| 292 |     } | 
| 293 |     vcos_log_trace("Locked vcsm handle %d at %p\n" , vcsm_info.vcsm_handle, vcsm_buffer); | 
| 294 |  | 
| 295 |     vcsm_square_draw_pattern(vcsm_buffer); | 
| 296 |  | 
| 297 |     // Release the locked texture memory to flush the CPU cache and allow GPU | 
| 298 |     // to read it | 
| 299 |     vcsm_unlock_ptr(vcsm_buffer); | 
| 300 |  | 
| 301 |     // Enable default window surface | 
| 302 |     GLCHK(glBindFramebuffer(GL_FRAMEBUFFER, 0)); | 
| 303 |  | 
| 304 |     // Draw the modified texture buffer to the screen | 
| 305 |     GLCHK(glViewport(raspitex_state->x, raspitex_state->y, raspitex_state->width, raspitex_state->height)); | 
| 306 |     GLCHK(glUseProgram(vcsm_square_shader.program)); | 
| 307 |     GLCHK(glActiveTexture(GL_TEXTURE0)); | 
| 308 |     GLCHK(glBindTexture(GL_TEXTURE_2D, fb_tex_name)); | 
| 309 |     GLCHK(glEnableVertexAttribArray(vcsm_square_shader.attribute_locations[0])); | 
| 310 |     GLCHK(glVertexAttribPointer(vcsm_square_shader.attribute_locations[0], 2, GL_FLOAT, GL_FALSE, 0, 0)); | 
| 311 |     GLCHK(glDrawArrays(GL_TRIANGLES, 0, 6)); | 
| 312 |  | 
| 313 |     GLCHK(glDisableVertexAttribArray(vcsm_square_shader.attribute_locations[0])); | 
| 314 |     GLCHK(glUseProgram(0)); | 
| 315 |  | 
| 316 |     return 0; | 
| 317 | } | 
| 318 | #endif /* USE_READPIXELS */ | 
| 319 |  | 
| 320 | int vcsm_square_open(RASPITEX_STATE *raspitex_state) | 
| 321 | { | 
| 322 |     vcos_log_trace("%s" , VCOS_FUNCTION); | 
| 323 |  | 
| 324 |     raspitex_state->ops.gl_init = vcsm_square_init; | 
| 325 | #ifdef USE_READPIXELS | 
| 326 |     raspitex_state->ops.redraw = vcsm_square_redraw_readpixels; | 
| 327 | #else | 
| 328 |     raspitex_state->ops.redraw = vcsm_square_redraw; | 
| 329 | #endif /* USE_READPIXELS */ | 
| 330 |     raspitex_state->ops.update_y_texture = raspitexutil_update_y_texture; | 
| 331 |     return 0; | 
| 332 | } | 
| 333 |  |