From 7fe60435bce6595a9c58a9bfd8244d74b5320e96 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Tue, 15 Jan 2013 08:46:13 +0100 Subject: Import DirectFB141_2k11R3_beta5 --- Source/DirectFB/gfxdrivers/davinci/davinci_video.c | 744 +++++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100755 Source/DirectFB/gfxdrivers/davinci/davinci_video.c (limited to 'Source/DirectFB/gfxdrivers/davinci/davinci_video.c') diff --git a/Source/DirectFB/gfxdrivers/davinci/davinci_video.c b/Source/DirectFB/gfxdrivers/davinci/davinci_video.c new file mode 100755 index 0000000..1a284a0 --- /dev/null +++ b/Source/DirectFB/gfxdrivers/davinci/davinci_video.c @@ -0,0 +1,744 @@ +/* + TI Davinci driver - Video Layer + + (c) Copyright 2007 Telio AG + + Written by Denis Oliver Kropp + + Code is derived from VMWare driver. + + (c) Copyright 2001-2009 The world wide DirectFB Open Source Community (directfb.org) + (c) Copyright 2000-2004 Convergence (integrated media) GmbH + + All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +//#define DIRECT_ENABLE_DEBUG + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "davincifb.h" + +#include "davinci_gfxdriver.h" +#include "davinci_video.h" + + +#define D_VIDERROR(x...) do {} while (0) + + +D_DEBUG_DOMAIN( Davinci_Video, "Davinci/Video", "TI Davinci Video" ); + +/**********************************************************************************************************************/ + +static DFBResult ShowBuffer( DavinciDriverData *ddrv, + DavinciVideoLayerData *dvid, + CoreSurfaceBufferLock *lock, + const DFBRectangle *area, + DFBSurfaceFlipFlags flags ); + +static void SetupResizerParams( vpfe_resizer_params_t *params, + int srcWidth, int srcHeight, + int outWidth, int outHeight, + int *ret_outWidth, + int *ret_outHeight ); + +/**********************************************************************************************************************/ + +static int +videoLayerDataSize( void ) +{ + return sizeof(DavinciVideoLayerData); +} + +static DFBResult +videoInitLayer( CoreLayer *layer, + void *driver_data, + void *layer_data, + DFBDisplayLayerDescription *description, + DFBDisplayLayerConfig *config, + DFBColorAdjustment *adjustment ) +{ + int ret; + DavinciDriverData *ddrv = driver_data; + DavinciVideoLayerData *dvid = layer_data; + + D_DEBUG_AT( Davinci_Video, "%s()\n", __FUNCTION__ ); + + /* Initialize with configuration from VID0 to start with a fullscreen (unscaled) layer */ + ret = ioctl( ddrv->fb[VID0].fd, FBIOGET_VSCREENINFO, &dvid->var ); + if (ret) { + D_PERROR( "Davinci/Video: FBIOGET_VSCREENINFO (fb%d) failed!\n", VID0 ); + return DFB_INIT; + } + + /* Disable VID0 (unused) */ + ret = ioctl( ddrv->fb[VID0].fd, FBIO_ENABLE_DISABLE_WIN, 0 ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_ENABLE_DISABLE_WIN (fb%d - %d)!\n", VID0, 0 ); + + /* Disable VID1 (our layer) */ + ret = ioctl( ddrv->fb[VID1].fd, FBIO_ENABLE_DISABLE_WIN, 0 ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_ENABLE_DISABLE_WIN (fb%d - %d)!\n", VID1, 0 ); + + /* set capabilities and type */ + description->caps = DLCAPS_SURFACE | DLCAPS_SCREEN_LOCATION; + description->type = DLTF_VIDEO | DLTF_STILL_PICTURE; + + /* set name */ + snprintf( description->name, DFB_DISPLAY_LAYER_DESC_NAME_LENGTH, "TI Davinci Video" ); + + /* fill out the default configuration */ + config->flags = DLCONF_WIDTH | DLCONF_HEIGHT | + DLCONF_PIXELFORMAT | DLCONF_BUFFERMODE | DLCONF_OPTIONS; + config->width = dvid->var.xres; + config->height = dvid->var.yres; + config->pixelformat = DSPF_UYVY; + config->buffermode = DLBM_FRONTONLY; + config->options = DLOP_NONE; + + return DFB_OK; +} + +static DFBResult +videoTestRegion( CoreLayer *layer, + void *driver_data, + void *layer_data, + CoreLayerRegionConfig *config, + CoreLayerRegionConfigFlags *failed ) +{ + CoreLayerRegionConfigFlags fail = 0; + + D_DEBUG_AT( Davinci_Video, "%s()\n", __FUNCTION__ ); + + DFB_CORE_LAYER_REGION_CONFIG_DEBUG_AT( Davinci_Video, config ); + + if (config->options & ~DAVINCI_VIDEO_SUPPORTED_OPTIONS) + fail |= CLRCF_OPTIONS; + + switch (config->format) { + case DSPF_UYVY: + break; + + default: + fail |= CLRCF_FORMAT; + } + + if (config->width < 8 || config->width > 1920) + fail |= CLRCF_WIDTH; + + if (config->height < 8 || config->height > 1080) + fail |= CLRCF_HEIGHT; + + if (config->dest.x < 0 || config->dest.y < 0) + fail |= CLRCF_DEST; + + if (config->dest.x + config->dest.w > 1920) + fail |= CLRCF_DEST; + + if (config->dest.y + config->dest.h > 1080) + fail |= CLRCF_DEST; + + if (failed) + *failed = fail; + + if (fail) { + D_DEBUG_AT( Davinci_Video, " -> FAILED (0x%08x)\n", fail ); + return DFB_UNSUPPORTED; + } + + D_DEBUG_AT( Davinci_Video, " -> OK\n" ); + + return DFB_OK; +} + +static DFBResult +videoSetRegion( CoreLayer *layer, + void *driver_data, + void *layer_data, + void *region_data, + CoreLayerRegionConfig *config, + CoreLayerRegionConfigFlags updated, + CoreSurface *surface, + CorePalette *palette, + CoreSurfaceBufferLock *lock ) +{ + int ret; + DavinciDriverData *ddrv = driver_data; + DavinciDeviceData *ddev = ddrv->ddev; + DavinciVideoLayerData *dvid = layer_data; + CoreLayerRegionConfig *old = &dvid->config; + + D_DEBUG_AT( Davinci_Video, "%s( updated 0x%08x, surface %p )\n", __FUNCTION__, updated, surface ); + + DFB_CORE_LAYER_REGION_CONFIG_DEBUG_AT( Davinci_Video, config ); + + D_ASSERT( ddrv != NULL ); + D_ASSERT( ddev != NULL ); + D_ASSERT( dvid != NULL ); + + /* Update output size? */ + if ((updated & CLRCF_DEST) && (config->dest.w != old->dest.w || config->dest.h != old->dest.h)) { + vpbe_window_position_t win_pos; + + D_DEBUG_AT( Davinci_Video, " => dest %4dx%4d\n", config->dest.w, config->dest.h ); + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_ENABLE_DISABLE_WIN, 0 ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_ENABLE_DISABLE_WIN (fb%d - %d)!\n", VID1, 0 ); + + dvid->enabled = false; + +/*********************************** Start workaround ***********************************/ + win_pos.xpos = 0; + win_pos.ypos = 0; + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_SETPOS, &win_pos ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_SETPOS (fb%d - %d,%d) failed!\n", VID1, win_pos.xpos, win_pos.ypos ); + + dvid->var.yoffset = 0; +/*********************************** End workaround ***********************************/ + + /* Set output width and height. */ + dvid->var.xres = config->dest.w; + dvid->var.yres = config->dest.h; + + dvid->var.yres_virtual = ddrv->fb[VID1].size / lock->pitch; + + ret = ioctl( ddrv->fb[VID1].fd, FBIOPUT_VSCREENINFO, &dvid->var ); + if (ret) + D_PERROR( "Davinci/Video: FBIOPUT_VSCREENINFO (fb%d) failed!\n", VID1 ); + + /* Read back new pitch etc. */ + ret = ioctl( ddrv->fb[VID1].fd, FBIOGET_FSCREENINFO, &ddev->fix[VID1] ); + if (ret) + D_PERROR( "Davinci/Video: FBIOGET_FSCREENINFO (fb%d) failed!\n", VID1 ); + } + + /* Update output position? */ + if (updated & CLRCF_DEST) { + vpbe_window_position_t win_pos; + + D_DEBUG_AT( Davinci_Video, " => dest %4d,%4d\n", config->dest.x, config->dest.y ); + + if (dvid->enabled) + ioctl( ddrv->fb[VID1].fd, FBIO_WAITFORVSYNC ); + + /* Set horizontal and vertical offset. */ + win_pos.xpos = config->dest.x; + win_pos.ypos = config->dest.y; + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_SETPOS, &win_pos ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_SETPOS (fb%d - %d,%d) failed!\n", VID1, config->dest.x, config->dest.y ); + } + + /* Update format? */ + if (updated & CLRCF_FORMAT) { + vpbe_video_config_params_t params; + + params.cb_cr_order = (config->format == DSPF_YUY2) ? 1 : 0; + + params.exp_info.horizontal = VPBE_DISABLE; + params.exp_info.vertical = VPBE_DISABLE; + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_SET_VIDEO_CONFIG_PARAMS, ¶ms ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_SET_VIDEO_CONFIG_PARAMS (fb%d - %s) failed!\n", + VID1, params.cb_cr_order ? "CrCb" : "CbCr" ); + } + + /* Update scaling parameters? */ + if ((updated & (CLRCF_SOURCE | CLRCF_DEST)) && + (config->source.w != old->source.w || config->source.h != old->source.h || + config->dest.w != old->dest.w || config->dest.h != old->dest.h) && + (config->dest.w != config->source.w || config->dest.h != config->source.h)) + { + D_DEBUG_AT( Davinci_Video, " => scaling %4dx%4d -> %4dx%4d\n", + config->source.w, config->source.h, config->dest.w, config->dest.h ); + + SetupResizerParams( &dvid->resizer, config->source.w, config->source.h, + config->dest.w, config->dest.h, &dvid->resized.w, &dvid->resized.h ); + + dvid->offset.x = (config->dest.w - dvid->resized.w) / 2; + dvid->offset.y = (config->dest.h - dvid->resized.h) / 2; + + D_DEBUG_AT( Davinci_Video, " => resized %4dx%4d, centered %d,%d\n", + dvid->resized.w, dvid->resized.h, dvid->offset.x, dvid->offset.y ); + + dvid->offset.x += dvid->offset.x & 1; /* Round up to multiple of two */ + + D_DEBUG_AT( Davinci_Video, " => offset %4d,%4d\n", dvid->offset.x, dvid->offset.y ); + + davincifb_pan_display( &ddrv->fb[VID1], &dvid->var, NULL, DSFLIP_NONE, 0, 0 ); + } + + dvid->enable = true; + dvid->config = *config; + + return DFB_OK; +} + +static DFBResult +videoRemoveRegion( CoreLayer *layer, + void *driver_data, + void *layer_data, + void *region_data ) +{ + int ret; + DavinciDriverData *ddrv = driver_data; + DavinciVideoLayerData *dvid = layer_data; + + D_DEBUG_AT( Davinci_Video, "%s()\n", __FUNCTION__ ); + + D_ASSERT( ddrv != NULL ); + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_ENABLE_DISABLE_WIN, 0 ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_ENABLE_DISABLE_WIN (fb%d - %d)!\n", VID1, 0 ); + + dvid->enabled = false; + dvid->enable = false; + + return DFB_OK; +} + +static DFBResult +videoFlipRegion( CoreLayer *layer, + void *driver_data, + void *layer_data, + void *region_data, + CoreSurface *surface, + DFBSurfaceFlipFlags flags, + CoreSurfaceBufferLock *lock ) +{ + DFBResult ret; + DavinciDriverData *ddrv = driver_data; + DavinciVideoLayerData *dvid = layer_data; + + D_ASSERT( surface != NULL ); + D_ASSERT( lock != NULL ); + D_ASSERT( ddrv != NULL ); + D_ASSERT( dvid != NULL ); + + D_DEBUG_AT( Davinci_Video, "%s( 0x%08lx [%d] 0x%04x [%4dx%4d] )\n", __FUNCTION__, + lock->phys, lock->pitch, flags, dvid->config.width, dvid->config.height ); + + ret = ShowBuffer( ddrv, dvid, lock, NULL, flags ); + if (ret) + return ret; + + dfb_surface_flip( surface, false ); + + return DFB_OK; +} + +static DFBResult +videoUpdateRegion( CoreLayer *layer, + void *driver_data, + void *layer_data, + void *region_data, + CoreSurface *surface, + const DFBRegion *update, + CoreSurfaceBufferLock *lock ) +{ + DavinciDriverData *ddrv = driver_data; + DavinciVideoLayerData *dvid = layer_data; + + D_ASSERT( surface != NULL ); + D_ASSERT( lock != NULL ); + D_ASSERT( ddrv != NULL ); + D_ASSERT( dvid != NULL ); + + if (update) { + DFBRectangle area = DFB_RECTANGLE_INIT_FROM_REGION( update ); + + D_DEBUG_AT( Davinci_Video, "%s( 0x%08lx [%d], %4d,%4d-%4dx%4d )\n", __FUNCTION__, + lock->phys, lock->pitch, DFB_RECTANGLE_VALS( &area ) ); + + if (!dfb_rectangle_intersect( &area, &dvid->config.source )) { + D_DEBUG_AT( Davinci_Video, " -> NO INTERSECTION with %4d,%4d-%4dx%4d\n", + DFB_RECTANGLE_VALS( &dvid->config.source ) ); + + return DFB_OK; + } + + if (!DFB_RECTANGLE_EQUAL( area, dvid->config.source )) + return ShowBuffer( ddrv, dvid, lock, &area, DSFLIP_NONE ); + } + else + D_DEBUG_AT( Davinci_Video, "%s( 0x%08lx [%d], %4dx%4d )\n", __FUNCTION__, + lock->phys, lock->pitch, dvid->config.width, dvid->config.height ); + + return ShowBuffer( ddrv, dvid, lock, NULL, DSFLIP_NONE ); +} + +const DisplayLayerFuncs davinciVideoLayerFuncs = { + .LayerDataSize = videoLayerDataSize, + .InitLayer = videoInitLayer, + + .TestRegion = videoTestRegion, + .SetRegion = videoSetRegion, + .RemoveRegion = videoRemoveRegion, + .FlipRegion = videoFlipRegion, + .UpdateRegion = videoUpdateRegion, +}; + +/*********************************************************************************************************************** +** Frame Output +*/ + +static void +enable_video( DavinciDriverData *ddrv, + DavinciVideoLayerData *dvid ) +{ + if (dvid->enable && !dvid->enabled) { + ioctl( ddrv->fb[VID1].fd, FBIO_WAITFORVSYNC ); + + if (ioctl( ddrv->fb[VID1].fd, FBIO_ENABLE_DISABLE_WIN, 1 )) + D_VIDERROR( "Davinci/Video: FBIO_ENABLE_DISABLE_WIN (fb%d - %d)!\n", VID1, 1 ); + + dvid->enabled = true; + } +} + +static DFBResult +ShowBuffer( DavinciDriverData *ddrv, + DavinciVideoLayerData *dvid, + CoreSurfaceBufferLock *lock, + const DFBRectangle *area, + DFBSurfaceFlipFlags flags ) +{ + const CoreLayerRegionConfig *config = &dvid->config; + + if (area) + D_DEBUG_AT( Davinci_Video, "%s( 0x%08lx [%d], %4d,%4d-%4dx%4d )\n", __FUNCTION__, + lock->phys, lock->pitch, DFB_RECTANGLE_VALS( area ) ); + else + D_DEBUG_AT( Davinci_Video, "%s( 0x%08lx [%d] )\n", __FUNCTION__, lock->phys, lock->pitch ); + + if (config->dest.w == config->source.w && config->dest.h == config->source.h) { + /* + * Unscaled video, buffer displayed directly + */ + D_DEBUG_AT( Davinci_Video, " -> unscaled %4dx%4d <- %4d,%4d [%4dx%4d]\n", + config->source.w, config->source.h, config->source.x, config->source.y, + config->width, config->height ); + + /* Partial update, assuming proper buffer is shown, saving system calls */ + if (area && dvid->enabled) + return DFB_OK; + + davincifb_pan_display( &ddrv->fb[VID1], &dvid->var, lock, flags, config->source.x, config->source.y ); + } + else { + int ret; + DavinciDeviceData *ddev = ddrv->ddev; + CoreSurfaceBuffer *buffer = lock->buffer; + vpfe_resizer_params_t *params = &dvid->resizer; + + /* + * Scaled video, buffer scaled to output buffer by resizer + */ + D_DEBUG_AT( Davinci_Video, " -> scaled %4dx%4d -> %4dx%4d <- %4d,%4d [%4dx%4d]\n", + config->source.w, config->source.h, config->dest.w, config->dest.h, + config->source.x, config->source.y, config->width, config->height ); + + /* FIXME: Implement scaled partial updates! */ + if (area) + D_UNIMPLEMENTED(); + + params->sdr_inoff = lock->pitch; + params->sdr_inadd = lock->phys + DFB_BYTES_PER_LINE( buffer->format, config->source.x ) + + config->source.y * params->sdr_inoff; + + params->sdr_outoff = ddev->fix[VID1].line_length; + params->sdr_outadd = ddev->fix[VID1].smem_start + dvid->offset.x * 2 + + dvid->offset.y * params->sdr_outoff; + + params->in_start = (params->sdr_outadd & 0x1f) / 2; + params->sdr_outadd &= ~0x1f; + + D_DEBUG_AT( Davinci_Video, " -> FBIO_RESIZER running...\n" ); + + ret = ioctl( ddrv->fb[VID1].fd, FBIO_RESIZER, params ); + if (ret) + D_VIDERROR( "Davinci/Video: FBIO_RESIZER (fb%d)!\n", VID1 ); + + D_DEBUG_AT( Davinci_Video, " => FBIO_RESIZER returned %d\n", ret ); + } + + enable_video( ddrv, dvid ); + + return DFB_OK; +} + +/*********************************************************************************************************************** +** Scaling Setup +*/ + +static int +limitInput(int rsz,int inSize,int outSize,int* pInSize) +{ + int phases; + int phaseShift; + int taps; + int phaseMask; + int coarseShift; + int halfCoarse; + int tmp; + + do { + if (rsz<=512) { + //1/2x to 4x resize uses 8 phase, 4 taps + phaseShift = 3; + taps = 4; + } + else { + //4-phase, 7 taps + phaseShift = 2; + taps = 7; + } + phases = 1<>8) + taps; + if (tmp <= inSize) break; + rsz--; + } while (1); + + *pInSize = tmp; + + return rsz; +} + +static void +SetupCoef(unsigned int* pCoef,int rsz) +{ + int startCoef; + int highCoef; + int c; + int phases; + int taps; + if (rsz<=512) { + //1/2x to 4x resize uses 8 phase, 4 taps + highCoef = 0x100; + c=1; + phases=8; + taps=4; + } + else { + //4-phase, 7 taps + if (rsz<=(256*3)) { + highCoef = 0x100/2; c=2; + } + else { + highCoef = 0x100/4; c=1; + } + phases=4; + taps=7; + } + startCoef = highCoef>>1; + while (phases) { + int prev = startCoef; + int tapNum=0; + int rem=256 - startCoef; + while ( tapNum < (c-1)) { + *pCoef++ = 0; + tapNum+=2; + } + if (c&1) { + *pCoef++ = prev<<16; + tapNum+=2; + } + else { + tapNum++; + } + while ( tapNum < taps) { + int min = (rem (highCoef>>3)) startCoef -= (highCoef>>3); + else { + startCoef = highCoef; c++; + } + phases--; + } +} + +#define SDRAM_SRC (1<<28) +#define BILINEAR (1<<29) + +static void +SetupResizerParams( vpfe_resizer_params_t *params, + int srcWidth, int srcHeight, + int outWidth, int outHeight, + int *ret_outWidth, + int *ret_outHeight ) +{ + int rsz; + int hrsz; + int vrsz; + int tmp; + + D_DEBUG_AT( Davinci_Video, "%s( %4dx%4d->%4dx%4d )\n", __FUNCTION__, srcWidth, srcHeight, outWidth, outHeight ); + + params->sdr_inadd = 0; + params->sdr_inoff = 0; + + params->sdr_outadd = 0; + params->sdr_outoff = 0; + + params->in_start = (0<<16)|(0); + params->yenh = 0; + + params->rsz_cnt = SDRAM_SRC; + + + + + //find scale factor + rsz = (srcWidth<<8)/outWidth; + if (rsz<64) { + //too much upscaling, reduce destination size + rsz = 64; + } + else if (rsz>1024) { + //too much down scaling, reduce source size + rsz=1024; + srcWidth = (outWidth * rsz)>>8; + } + + tmp = ((srcWidth<<8)+255)/rsz; + if (tmp > outWidth) tmp = outWidth; + tmp &= ~1; //force even + if (rsz>256) { + //upsize in vertical direction requires a multiple of 16 bytes (8 pixels) + tmp &= ~0x7; + } + do { + int t; + hrsz = limitInput(rsz,srcWidth,tmp,&t); + if (hrsz>=64) { + srcWidth = t; + break; + } + tmp-=2; + } while (1); + outWidth = tmp; + + if (srcWidth==outWidth) { + int i=0; + params->rsz_cnt |= ((256-1)<<0); //1 to 1 + params->in_size = (srcWidth+3); //4 taps + while (i<16) { + params->hfilt[i] = i? 0 : 0x100; //2 coefficient written at a time + i++; + } + } + else { + SetupCoef(¶ms->hfilt[0],hrsz); + params->rsz_cnt |= ((hrsz-1)<<0) | ((hrsz<256)? BILINEAR : 0); + params->in_size = (srcWidth); + } + + + + + //find scale factor + rsz = (srcHeight<<8)/outHeight; + if (rsz<64) { + //too much upscaling, reduce destination size + rsz = 64; + } + else if (rsz>1024) { + //too much down scaling, reduce source size + rsz=1024; + srcHeight = (outHeight * rsz)>>8; + } + + tmp = ((srcHeight<<8)+255)/rsz; + if (tmp > outHeight) tmp = outHeight; + do { + int t; + vrsz = limitInput(rsz,srcHeight,tmp,&t); + if (vrsz>=64) { + srcHeight = t; + break; + } + tmp--; + } while (1); + outHeight = tmp; + + if (srcHeight==outHeight) { + int i=0; + params->rsz_cnt |= ((256-1)<<10); //1 to 1 + params->in_size |= ((srcHeight+3)<<16); //4 taps + while (i<16) { + params->vfilt[i] = i? 0 : 0x100; //2 coefficient written at a time + i++; + } + } + else { + SetupCoef(¶ms->vfilt[0],vrsz); + params->rsz_cnt |= ((vrsz-1)<<10); + params->in_size |= (srcHeight<<16); + } + + + params->out_size = (outHeight<<16)|(outWidth); + + D_DEBUG_AT( Davinci_Video, " => %4dx%4d->%4dx%4d\n", srcWidth, srcHeight, outWidth, outHeight ); + + if (ret_outWidth) + *ret_outWidth = outWidth; + + if (ret_outHeight) + *ret_outHeight = outHeight; +} + -- cgit