nkeynes@352: /** nkeynes@561: * $Id$ nkeynes@352: * nkeynes@352: * GL framebuffer-based driver shell. This requires the EXT_framebuffer_object nkeynes@352: * extension, but is much nicer/faster/etc than pbuffers when it's available. nkeynes@352: * This is (optionally) used indirectly by the top-level GLX driver. nkeynes@352: * nkeynes@352: * Strategy-wise, we maintain 2 framebuffers with up to 4 target colour nkeynes@352: * buffers a piece. Effectively this reserves one fb for display output, nkeynes@352: * and the other for texture output (each fb has a single size). nkeynes@352: * nkeynes@352: * Copyright (c) 2005 Nathan Keynes. nkeynes@352: * nkeynes@352: * This program is free software; you can redistribute it and/or modify nkeynes@352: * it under the terms of the GNU General Public License as published by nkeynes@352: * the Free Software Foundation; either version 2 of the License, or nkeynes@352: * (at your option) any later version. nkeynes@352: * nkeynes@352: * This program is distributed in the hope that it will be useful, nkeynes@352: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@352: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@352: * GNU General Public License for more details. nkeynes@352: */ nkeynes@352: nkeynes@424: #define GL_GLEXT_PROTOTYPES 1 nkeynes@424: nkeynes@477: #include nkeynes@477: #include "lxdream.h" nkeynes@352: #include "display.h" nkeynes@635: #include "drivers/video_gl.h" nkeynes@635: #include "pvr2/glutil.h" nkeynes@352: nkeynes@1210: #if defined(HAVE_OPENGL_FBO) || defined(HAVE_OPENGL_FBO_EXT) nkeynes@656: nkeynes@352: #define MAX_FRAMEBUFFERS 2 nkeynes@798: #define MAX_TEXTURES_PER_FB 16 nkeynes@352: nkeynes@805: static render_buffer_t gl_fbo_create_render_buffer( uint32_t width, uint32_t height, GLuint tex_id ); nkeynes@352: static void gl_fbo_destroy_render_buffer( render_buffer_t buffer ); nkeynes@352: static gboolean gl_fbo_set_render_target( render_buffer_t buffer ); nkeynes@805: static void gl_fbo_finish_render( render_buffer_t buffer ); nkeynes@669: static void gl_fbo_display_render_buffer( render_buffer_t buffer ); nkeynes@477: static void gl_fbo_load_frame_buffer( frame_buffer_t frame, render_buffer_t buffer ); nkeynes@669: static void gl_fbo_display_blank( uint32_t colour ); nkeynes@1166: static gboolean gl_fbo_test_framebuffer( ); nkeynes@477: static gboolean gl_fbo_read_render_buffer( unsigned char *target, render_buffer_t buffer, int rowstride, int format ); nkeynes@352: nkeynes@352: extern uint32_t video_width, video_height; nkeynes@352: nkeynes@352: /** nkeynes@352: * Framebuffer info structure nkeynes@352: */ nkeynes@352: struct gl_fbo_info { nkeynes@352: GLuint fb_id; nkeynes@352: GLuint depth_id; nkeynes@352: GLuint stencil_id; nkeynes@352: GLuint tex_ids[MAX_TEXTURES_PER_FB]; nkeynes@352: int width, height; nkeynes@352: }; nkeynes@352: nkeynes@798: static GLint gl_fbo_max_attachments = 0; nkeynes@1251: static GLint gl_fbo_depth_component; nkeynes@863: static gboolean gl_fbo_have_packed_stencil = FALSE; nkeynes@352: static struct gl_fbo_info fbo[MAX_FRAMEBUFFERS]; nkeynes@798: nkeynes@1210: #define ATTACHMENT_POINT(n) (GL_COLOR_ATTACHMENT0+(n)) nkeynes@352: static int last_used_fbo; nkeynes@352: nkeynes@352: gboolean gl_fbo_is_supported() nkeynes@352: { nkeynes@1245: return isGLExtensionSupported("GL_EXT_framebuffer_object") || isOpenGLES2(); nkeynes@352: } nkeynes@352: nkeynes@352: /** nkeynes@352: * Construct the initial frame buffers and allocate ids for everything. nkeynes@352: * The render handling driver methods are set to the fbo versions. nkeynes@352: */ nkeynes@352: void gl_fbo_init( display_driver_t driver ) nkeynes@352: { nkeynes@352: int i,j; nkeynes@424: GLuint fbids[MAX_FRAMEBUFFERS]; nkeynes@424: GLuint rbids[MAX_FRAMEBUFFERS*2]; /* depth buffer, stencil buffer per fb */ nkeynes@736: nkeynes@1223: gl_fbo_max_attachments = glGetMaxColourAttachments(); nkeynes@1210: glGenFramebuffers( MAX_FRAMEBUFFERS, &fbids[0] ); nkeynes@1210: glGenRenderbuffers( MAX_FRAMEBUFFERS*2, &rbids[0] ); nkeynes@352: for( i=0; icapabilities.stencil_bits = 8; nkeynes@863: gl_fbo_have_packed_stencil = TRUE; nkeynes@863: } else { nkeynes@863: driver->capabilities.stencil_bits = 0; nkeynes@863: gl_fbo_have_packed_stencil = FALSE; nkeynes@863: WARN( "Packed depth stencil not available - disabling shadow volumes" ); nkeynes@863: } nkeynes@1251: if( driver->capabilities.depth_bits >= 24 ) { nkeynes@1251: gl_fbo_depth_component = GL_DEPTH_COMPONENT24; nkeynes@1251: } else { nkeynes@1251: gl_fbo_depth_component = GL_DEPTH_COMPONENT16; nkeynes@1251: } nkeynes@863: nkeynes@352: driver->create_render_buffer = gl_fbo_create_render_buffer; nkeynes@352: driver->destroy_render_buffer = gl_fbo_destroy_render_buffer; nkeynes@352: driver->set_render_target = gl_fbo_set_render_target; nkeynes@805: driver->finish_render = gl_fbo_finish_render; nkeynes@352: driver->display_render_buffer = gl_fbo_display_render_buffer; nkeynes@477: driver->load_frame_buffer = gl_fbo_load_frame_buffer; nkeynes@352: driver->display_blank = gl_fbo_display_blank; nkeynes@352: driver->read_render_buffer = gl_fbo_read_render_buffer; nkeynes@445: nkeynes@1166: gl_fbo_test_framebuffer(); nkeynes@1210: glBindFramebuffer(GL_FRAMEBUFFER, 0); nkeynes@352: } nkeynes@352: nkeynes@352: void gl_fbo_shutdown() nkeynes@352: { nkeynes@352: int i; nkeynes@1210: glBindFramebuffer( GL_FRAMEBUFFER, 0 ); nkeynes@352: for( i=0; i= MAX_FRAMEBUFFERS ) { nkeynes@736: bufno = 0; nkeynes@736: } nkeynes@736: last_used_fbo = bufno; nkeynes@352: } nkeynes@352: if( fbo[bufno].width == width && fbo[bufno].height == height ) { nkeynes@1210: glBindFramebuffer( GL_FRAMEBUFFER, fbo[bufno].fb_id ); nkeynes@352: } else { nkeynes@736: gl_fbo_setup_framebuffer( bufno, width, height ); nkeynes@352: } nkeynes@352: return bufno; nkeynes@352: } nkeynes@352: nkeynes@352: /** nkeynes@352: * Attach a texture to the framebuffer. The texture must already be initialized nkeynes@352: * to the correct dimensions etc. nkeynes@352: */ nkeynes@352: static GLint gl_fbo_attach_texture( int fbo_no, GLint tex_id ) { nkeynes@352: int attach = -1, i; nkeynes@798: for( i=0; iwidth = width; nkeynes@352: buffer->height = height; nkeynes@805: buffer->tex_id = tex_id; nkeynes@805: if( tex_id == 0 ) { nkeynes@805: GLuint tex; nkeynes@805: glGenTextures( 1, &tex ); nkeynes@805: buffer->buf_id = tex; nkeynes@805: } else { nkeynes@805: buffer->buf_id = tex_id; nkeynes@1222: glBindTexture( GL_TEXTURE_2D, tex_id ); nkeynes@805: } nkeynes@1222: glBindTexture( GL_TEXTURE_2D, buffer->buf_id ); nkeynes@1222: glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); nkeynes@1223: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); nkeynes@1223: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); nkeynes@1222: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); nkeynes@1222: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); nkeynes@352: return buffer; nkeynes@352: } nkeynes@352: nkeynes@805: /** nkeynes@805: * Ensure the texture in the given render buffer is not attached to a nkeynes@805: * framebuffer (ie, so we can safely use it as a texture during the rendering nkeynes@805: * cycle, or before deletion). nkeynes@805: */ nkeynes@805: static void gl_fbo_detach_render_buffer( render_buffer_t buffer ) nkeynes@805: { nkeynes@805: int i,j; nkeynes@805: for( i=0; iwidth && fbo[i].height == buffer->height ) { nkeynes@805: for( j=0; jbuf_id ) { nkeynes@1210: glBindFramebuffer(GL_FRAMEBUFFER, fbo[i].fb_id); nkeynes@1210: glFramebufferTexture2D(GL_FRAMEBUFFER, ATTACHMENT_POINT(j), nkeynes@1222: GL_TEXTURE_2D, GL_NONE, 0 ); nkeynes@805: fbo[i].tex_ids[j] = -1; nkeynes@805: return; nkeynes@805: } nkeynes@805: } nkeynes@805: break; nkeynes@805: } nkeynes@805: } nkeynes@805: } nkeynes@805: nkeynes@352: static void gl_fbo_destroy_render_buffer( render_buffer_t buffer ) nkeynes@352: { nkeynes@805: gl_fbo_detach_render_buffer( buffer ); nkeynes@805: nkeynes@805: if( buffer->buf_id != buffer->tex_id ) { nkeynes@805: // If tex_id was set at buffer creation, we don't own the texture. nkeynes@805: // Otherwise, delete it now. nkeynes@805: GLuint tex = buffer->buf_id; nkeynes@805: glDeleteTextures( 1, &tex ); nkeynes@352: } nkeynes@352: buffer->buf_id = 0; nkeynes@805: buffer->tex_id = 0; nkeynes@352: free( buffer ); nkeynes@352: } nkeynes@352: nkeynes@352: static gboolean gl_fbo_set_render_target( render_buffer_t buffer ) nkeynes@352: { nkeynes@352: int fb = gl_fbo_get_framebuffer( buffer->width, buffer->height ); nkeynes@424: gl_fbo_attach_texture( fb, buffer->buf_id ); nkeynes@352: /* setup the gl context */ nkeynes@352: glViewport( 0, 0, buffer->width, buffer->height ); nkeynes@736: nkeynes@352: return TRUE; nkeynes@352: } nkeynes@352: nkeynes@805: static void gl_fbo_finish_render( render_buffer_t buffer ) nkeynes@805: { nkeynes@805: glFinish(); nkeynes@805: gl_fbo_detach_render_buffer(buffer); nkeynes@805: } nkeynes@805: nkeynes@352: /** nkeynes@352: * Render the texture holding the given buffer to the front window nkeynes@352: * buffer. nkeynes@352: */ nkeynes@669: static void gl_fbo_display_render_buffer( render_buffer_t buffer ) nkeynes@352: { nkeynes@477: gl_fbo_detach(); nkeynes@477: gl_display_render_buffer( buffer ); nkeynes@1251: if( display_driver->swap_buffers ) nkeynes@1251: display_driver->swap_buffers(); nkeynes@352: } nkeynes@352: nkeynes@477: static void gl_fbo_load_frame_buffer( frame_buffer_t frame, render_buffer_t buffer ) nkeynes@477: { nkeynes@477: gl_fbo_detach(); nkeynes@1298: gl_frame_buffer_to_tex( frame, buffer->buf_id ); nkeynes@477: } nkeynes@477: nkeynes@669: static void gl_fbo_display_blank( uint32_t colour ) nkeynes@477: { nkeynes@477: gl_fbo_detach(); nkeynes@669: gl_display_blank( colour ); nkeynes@1251: if( display_driver->swap_buffers ) nkeynes@1251: display_driver->swap_buffers(); nkeynes@477: } nkeynes@477: nkeynes@477: void gl_fbo_detach() nkeynes@352: { nkeynes@1210: glBindFramebuffer( GL_FRAMEBUFFER, 0 ); nkeynes@1076: /* Make sure texture attachment is not a current draw/read buffer */ nkeynes@1228: #ifdef HAVE_OPENGL_DRAW_BUFFER nkeynes@352: glDrawBuffer( GL_FRONT ); nkeynes@352: glReadBuffer( GL_FRONT ); nkeynes@1228: #endif nkeynes@477: } nkeynes@352: nkeynes@477: static gboolean gl_fbo_read_render_buffer( unsigned char *target, render_buffer_t buffer, nkeynes@736: int rowstride, int format ) nkeynes@352: { nkeynes@352: int fb = gl_fbo_get_framebuffer( buffer->width, buffer->height ); nkeynes@424: gl_fbo_attach_texture( fb, buffer->buf_id ); nkeynes@477: return gl_read_render_buffer( target, buffer, rowstride, format ); nkeynes@352: } nkeynes@352: nkeynes@656: #else nkeynes@656: gboolean gl_fbo_is_supported() nkeynes@656: { nkeynes@656: return FALSE; nkeynes@656: } nkeynes@656: nkeynes@656: void gl_fbo_init( display_driver_t driver ) nkeynes@656: { nkeynes@656: } nkeynes@656: nkeynes@656: #endif