From f7ce39763e36af527d85683b47ff18ea2e534174 Mon Sep 17 00:00:00 2001 From: Torbjörn Andersson Date: Thu, 28 Aug 2003 06:36:15 +0000 Subject: Removed the Surface class in favor of small struct specially made for the block surfaces. (A block surface is a 64x64 tile of a parallax layer.) I've also done a few things to try and optimize the drawing: * The back buffer is no longer cleared between frames. This may cause regressions, but I do believe that the entire picture area is always completely re-rendered for each frame. As a result of this, the menu code is now responsible for clearing the icon areas itself. * A few unnecessary copy_rect() calls were commented out in favor of one big copy_rect() in ServiceWindows(). * Completely opaque block surfaces are copied with memcpy(), one line at a time. Unless we manage to add intelligent screen redrawing, I don't think it will get that much faster than this, though there is some unnecessary data copying in DrawSprite() that could be removed. And the game is still a terrible CPU hog. I believe the animation runs at approximately 12 fps. If there's still time left, it will pump out further frames to get smooth scrolling. We ought to put a cap on that, and if it has already reached the scroll target it should sleep for the rest of the render cycle. svn-id: r9886 --- sword2/driver/d_draw.cpp | 16 ++++--- sword2/driver/d_draw.h | 2 +- sword2/driver/driver96.h | 35 +-------------- sword2/driver/menu.cpp | 99 ++++++++++++++++++++++++++++++++---------- sword2/driver/rdwin.cpp | 7 ++- sword2/driver/render.cpp | 109 ++++++++++++++++++++--------------------------- sword2/driver/sprite.cpp | 26 +++++------ 7 files changed, 156 insertions(+), 138 deletions(-) diff --git a/sword2/driver/d_draw.cpp b/sword2/driver/d_draw.cpp index 752a06f304..573278ae27 100644 --- a/sword2/driver/d_draw.cpp +++ b/sword2/driver/d_draw.cpp @@ -28,7 +28,7 @@ #define SCREENYOFFSET 40 #define MILLISECSPERCYCLE 83 -Surface *lpBackBuffer; +byte *lpBackBuffer; /* static LPDIRECTDRAW lpDraw; // DirectDraw object @@ -119,7 +119,7 @@ int32 InitialiseDisplay(int16 width, int16 height, int16 colourDepth, int32 wind screenWide = width; screenDeep = height; - lpBackBuffer = new Surface(width, height); + lpBackBuffer = (byte *) malloc(screenWide * screenDeep); return(RD_OK); } @@ -205,9 +205,15 @@ int32 WaitForVbl(void) } int32 EraseBackBuffer( void ) { - debug(9, "EraseBackBuffer"); - lpBackBuffer->clear(); - return(RD_OK); + // Since the entire screen is redrawn each time, there probably isn't + // any need to actually clear the back buffer. + // + // At the very least, since the menu code now is solely responsible + // for its own parts of the screen, we'd only need to clear the + // picture area. + + // memset(lpBackBuffer + MENUDEEP * screnWide, 0, screenWide * (screenDeep - 2 * MENUDEEP)); + return RD_OK; } diff --git a/sword2/driver/d_draw.h b/sword2/driver/d_draw.h index 50b4afb82a..4cc1fe927e 100644 --- a/sword2/driver/d_draw.h +++ b/sword2/driver/d_draw.h @@ -78,7 +78,7 @@ extern "C" { extern uint8 *lpPalette; // palette -extern Surface *lpBackBuffer; // back surface +extern byte *lpBackBuffer; // back surface // extern Surface *lpPrimarySurface; // DirectDraw front buffer. extern uint8 *lpDD2; // DirectDraw2 object extern BOOL bFullScreen; // Defines whether the app is running in full screen mode or not. diff --git a/sword2/driver/driver96.h b/sword2/driver/driver96.h index 8f8a304aa9..5c30de2f29 100644 --- a/sword2/driver/driver96.h +++ b/sword2/driver/driver96.h @@ -1247,39 +1247,6 @@ typedef int BOOL; #define TRUE 1 #define FALSE 0 -// Surface class to help replace LPDIRECTDRAWSURFACE. -// -// This class should be used as little as possible since it introduces an -// extra layer of data copying. It should only be used where we decode -// something once and draw it many times, such as the parallax layers. -// -// Even then it's only necessary if we also need to keep track of the -// surface's dimensions. -// -// Since the building blocks of the parallax layers have constant size, -// expect this class to go away. - -class Surface { -public: - uint16 _width, _height; - uint16 _pitch; - byte *_pixels; - - Surface(uint width, uint height) { - _width = width; - _height = height; - _pixels = (byte *) calloc(_width, _height); - } - - void clear(); - void blit(Surface *s, ScummVM::Rect *r); - void blit(Surface *s, ScummVM::Rect *r, ScummVM::Rect *clip_rect); - - ~Surface() { - free(_pixels); - } -}; - // // Structure definitions // --------------------- @@ -1378,7 +1345,7 @@ typedef struct // LPDIRECTDRAW lpDraw; // LPDIRECTDRAW2 lpDD2; // Surface *lpPrimarySurface; - Surface *lpBackBuffer; + byte *lpBackBuffer; // LPDIRECTDRAWPALETTE lpPalette; int16 screenDeep; int16 screenWide; diff --git a/sword2/driver/menu.cpp b/sword2/driver/menu.cpp index 2f6d1d6314..142653fbea 100644 --- a/sword2/driver/menu.cpp +++ b/sword2/driver/menu.cpp @@ -122,7 +122,7 @@ static uint8 menuStatus[2] = { RDMENU_HIDDEN, RDMENU_HIDDEN }; -static uint8 *icons[2][RDMENU_MAXPOCKETS] = { +static byte *icons[2][RDMENU_MAXPOCKETS] = { { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; @@ -134,14 +134,32 @@ static uint8 pocketStatus[2][RDMENU_MAXPOCKETS] = { static uint8 iconCount = 0; +void ClearIconArea(int menu, int pocket, ScummVM::Rect *r) { + byte *dst; + int i; + + r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2; + r->bottom = r->top + RDMENU_ICONDEEP; + r->left = RDMENU_ICONSTART + pocket * (RDMENU_ICONWIDE + RDMENU_ICONSPACING); + r->right = r->left + RDMENU_ICONWIDE; + + dst = lpBackBuffer + r->top * screenWide + r->left; + + for (i = 0; i < RDMENU_ICONDEEP; i++) { + memset(dst, 0, RDMENU_ICONWIDE); + dst += screenWide; + } +} + int32 ProcessMenu(void) { + byte *src, *dst; uint8 menu; - uint8 i; + uint8 i, j; uint8 complete; uint8 frameCount; int32 curx, xoff; int32 cury, yoff; - ScummVM::Rect r; + ScummVM::Rect r1, r2; int32 delta; static int32 lastTime = 0; @@ -188,6 +206,11 @@ int32 ProcessMenu(void) { // Propagate the animation from the first icon. for (i = RDMENU_MAXPOCKETS - 1; i > 0; i--) { + if (icons[menu][i] && pocketStatus[menu][i] != 0 && pocketStatus[menu][i - 1] == 0) { + ClearIconArea(menu, i, &r1); + UploadRect(&r1); + } + pocketStatus[menu][i] = pocketStatus[menu][i - 1]; if (pocketStatus[menu][i] != 0) complete = 0; @@ -196,10 +219,16 @@ int32 ProcessMenu(void) { complete = 0; // ... and animate the first icon - if (pocketStatus[menu][0] != 0) + if (pocketStatus[menu][0] != 0) { pocketStatus[menu][0]--; - // Check to see if the menu is fully open + if (pocketStatus[menu][0] == 0) { + ClearIconArea(menu, 0, &r1); + UploadRect(&r1); + } + } + + // Check to see if the menu is fully closed if (complete) menuStatus[menu] = RDMENU_HIDDEN; } @@ -215,41 +244,49 @@ int32 ProcessMenu(void) { for (i = 0; i < RDMENU_MAXPOCKETS; i++) { if (icons[menu][i]) { + // Since we no longer clear the screen + // after each frame we need to clear + // the icon area. + + ClearIconArea(menu, i, &r1); + if (pocketStatus[menu][i] == MAXMENUANIMS) { xoff = (RDMENU_ICONWIDE / 2); - r.left = curx - xoff; - r.right = r.left + RDMENU_ICONWIDE; + r2.left = curx - xoff; + r2.right = r2.left + RDMENU_ICONWIDE; yoff = (RDMENU_ICONDEEP / 2); - r.top = cury - yoff; - r.bottom = r.top + RDMENU_ICONDEEP; + r2.top = cury - yoff; + r2.bottom = r2.top + RDMENU_ICONDEEP; } else { xoff = (RDMENU_ICONWIDE / 2) * pocketStatus[menu][i] / MAXMENUANIMS; - r.left = curx - xoff; - r.right = curx + xoff; + r2.left = curx - xoff; + r2.right = curx + xoff; yoff = (RDMENU_ICONDEEP / 2) * pocketStatus[menu][i] / MAXMENUANIMS; - r.top = cury - yoff; - r.bottom = cury + yoff; + r2.top = cury - yoff; + r2.bottom = cury + yoff; } if (xoff != 0 && yoff != 0) { - byte *dst = lpBackBuffer->_pixels + r.top * lpBackBuffer->_width + r.left; - byte *src = icons[menu][i]; + dst = lpBackBuffer + r2.top * screenWide + r2.left; + src = icons[menu][i]; if (pocketStatus[menu][i] != MAXMENUANIMS) { SquashImage( - dst, lpBackBuffer->_width, r.right - r.left, r.bottom - r.top, + dst, screenWide, r2.right - r2.left, r2.bottom - r2.top, src, RDMENU_ICONWIDE, RDMENU_ICONWIDE, RDMENU_ICONDEEP, NULL); } else { - for (int j = 0; j < RDMENU_ICONDEEP; j++) { + for (j = 0; j < RDMENU_ICONDEEP; j++) { memcpy(dst, src, RDMENU_ICONWIDE); src += RDMENU_ICONWIDE; - dst += lpBackBuffer->_width; + dst += screenWide; } } - UploadRect(&r); + UploadRect(&r1); } } curx += (RDMENU_ICONSPACING + RDMENU_ICONWIDE); + r1.left += (RDMENU_ICONSPACING + RDMENU_ICONWIDE); + r1.right += (RDMENU_ICONSPACING + RDMENU_ICONWIDE); } } } @@ -501,15 +538,31 @@ int32 HideMenu(uint8 menu) { return RD_OK; } -int32 CloseMenuImmediately(void) -{ +int32 CloseMenuImmediately(void) { + ScummVM::Rect r; + int i; + menuStatus[0] = RDMENU_HIDDEN; menuStatus[1] = RDMENU_HIDDEN; + + for (i = 0; i < RDMENU_MAXPOCKETS; i++) { + if (icons[0][i]) { + ClearIconArea(0, i, &r); + UploadRect(&r); + } + if (icons[1][i]) { + ClearIconArea(1, i, &r); + UploadRect(&r); + } + } + memset(pocketStatus, 0, sizeof(uint8) * 2 * RDMENU_MAXPOCKETS); - return (RD_OK); + return RD_OK; } int32 SetMenuIcon(uint8 menu, uint8 pocket, uint8 *icon) { + ScummVM::Rect r; + debug(5, "stub SetMenuIcon( %d, %d )", menu, pocket); // Check for invalid menu parameter. @@ -525,6 +578,8 @@ int32 SetMenuIcon(uint8 menu, uint8 pocket, uint8 *icon) { iconCount--; free(icons[menu][pocket]); icons[menu][pocket] = NULL; + ClearIconArea(menu, pocket, &r); + UploadRect(&r); } // Only put the icon in the pocket if it is not NULL diff --git a/sword2/driver/rdwin.cpp b/sword2/driver/rdwin.cpp index 898e0f0e58..83ff62f970 100644 --- a/sword2/driver/rdwin.cpp +++ b/sword2/driver/rdwin.cpp @@ -544,7 +544,12 @@ int32 ServiceWindows(void) { g_sword2->parseEvents(); FadeServer(); - g_sword2->_system->update_screen(); + + // FIXME: We re-render the entire picture area of the screen for each + // frame, which is pretty horrible. + + g_system->copy_rect(lpBackBuffer + MENUDEEP * screenWide, screenWide, 0, MENUDEEP, screenWide, screenDeep - 2 * MENUDEEP); + g_system->update_screen(); // warning("stub ServiceWindows"); // too noisy /* MSG msg; diff --git a/sword2/driver/render.cpp b/sword2/driver/render.cpp index eeb79188a0..5b05d4958f 100644 --- a/sword2/driver/render.cpp +++ b/sword2/driver/render.cpp @@ -277,36 +277,28 @@ uint8 xblocks[MAXLAYERS]; uint8 yblocks[MAXLAYERS]; uint8 restoreLayer[MAXLAYERS]; -// Each layer is composed by several sub-blocks +// blockSurfaces stores an array of sub-blocks for each of the parallax layers. -Surface **blockSurfaces[MAXLAYERS] = { 0, 0, 0, 0, 0 }; +typedef struct { + byte data[BLOCKWIDTH * BLOCKHEIGHT]; + bool transparent; +} BlockSurface; +BlockSurface **blockSurfaces[MAXLAYERS] = { 0, 0, 0, 0, 0 }; - -void Surface::clear() { - memset(_pixels, 0, _width * _height); - g_sword2->_system->copy_rect(_pixels, _width, 0, 0, _width, _height); -} - -void Surface::blit(Surface *s, ScummVM::Rect *r) { - ScummVM::Rect clip_rect; - - clip_rect.left = 0; - clip_rect.top = 0; - clip_rect.right = 640; - clip_rect.bottom = 480; - - blit(s, r, &clip_rect); +void UploadRect(ScummVM::Rect *r) { + g_system->copy_rect(lpBackBuffer + r->top * screenWide + r->left, + screenWide, r->left, r->top, r->right - r->left, r->bottom - r->top); } -void Surface::blit(Surface *s, ScummVM::Rect *r, ScummVM::Rect *clip_rect) { +void BlitBlockSurface(BlockSurface *s, ScummVM::Rect *r, ScummVM::Rect *clip_rect) { if (r->top > clip_rect->bottom || r->left > clip_rect->right || r->bottom <= clip_rect->top || r->right <= clip_rect->left) return; - byte *src = s->_pixels; + byte *src = s->data; if (r->top < clip_rect->top) { - src -= s->_width * (r->top - clip_rect->top); + src -= BLOCKWIDTH * (r->top - clip_rect->top); r->top = clip_rect->top; } if (r->left < clip_rect->left) { @@ -318,29 +310,27 @@ void Surface::blit(Surface *s, ScummVM::Rect *r, ScummVM::Rect *clip_rect) { if (r->right > clip_rect->right) r->right = clip_rect->right; - byte *dst = _pixels + r->top * _width + r->left; + byte *dst = lpBackBuffer + r->top * screenWide + r->left; int i, j; - // FIXME: We first render the data to the back buffer, and then copy - // it to the backend. Since the same area will probably be copied - // several times, as each new parallax layer is rendered, this may be - // a bit inefficient. - - for (i = 0; i < r->bottom - r->top; i++) { - for (j = 0; j < r->right - r->left; j++) { - if (src[j]) - dst[j] = src[j]; + if (s->transparent) { + for (i = 0; i < r->bottom - r->top; i++) { + for (j = 0; j < r->right - r->left; j++) { + if (src[j]) + dst[j] = src[j]; + } + src += BLOCKWIDTH; + dst += screenWide; + } + } else { + for (i = 0; i < r->bottom - r->top; i++) { + memcpy(dst, src, r->right - r->left); + src += BLOCKWIDTH; + dst += screenWide; } - src += s->_width; - dst += _width; } - UploadRect(r); -} - -void UploadRect(ScummVM::Rect *r) { - g_system->copy_rect(lpBackBuffer->_pixels + r->top * lpBackBuffer->_width + r->left, - lpBackBuffer->_width, r->left, r->top, r->right - r->left, r->bottom - r->top); + // UploadRect(r); } #define SCALE_MAXWIDTH 512 @@ -445,7 +435,7 @@ void SquashImage(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, dst[x] = QuickMatch((uint8) (red / count), (uint8) (green / count), (uint8) (blue / count)); } dst += dstPitch; - backbuf += lpBackBuffer->_width; + backbuf += screenWide; } } else { for (y = 0; y < dstHeight; y++) { @@ -606,7 +596,7 @@ int32 RestoreBackgroundLayer(_parallax *p, int16 l) if (blockSurfaces[l]) { for (i = 0; i < xblocks[l] * yblocks[l]; i++) if (blockSurfaces[l][i]) - delete blockSurfaces[l][i]; + free(blockSurfaces[l][i]); free(blockSurfaces[l]); blockSurfaces[l] = NULL; @@ -920,8 +910,6 @@ int32 RenderParallax(_parallax *p, int16 l) { int16 i, j; ScummVM::Rect r; - debug(9, "RenderParallax %d", l); - if (locationWide == screenWide) x = 0; else @@ -937,9 +925,9 @@ int32 RenderParallax(_parallax *p, int16 l) { // Leave enough space for the top and bottom menues clip_rect.left = 0; - clip_rect.right = 640; - clip_rect.top = 40; - clip_rect.bottom = 440; + clip_rect.right = screenWide; + clip_rect.top = MENUDEEP; + clip_rect.bottom = screenDeep - MENUDEEP; for (j = 0; j < yblocks[l]; j++) { for (i = 0; i < xblocks[l]; i++) { @@ -948,7 +936,7 @@ int32 RenderParallax(_parallax *p, int16 l) { r.right = r.left + BLOCKWIDTH; r.top = j * BLOCKHEIGHT - y + 40; r.bottom = r.top + BLOCKHEIGHT; - lpBackBuffer->blit(blockSurfaces[l][i + j * xblocks[l]], &r, &clip_rect); + BlitBlockSurface(blockSurfaces[l][i + j * xblocks[l]], &r, &clip_rect); } } } @@ -956,7 +944,7 @@ int32 RenderParallax(_parallax *p, int16 l) { parallaxScrollx = scrollx - x; parallaxScrolly = scrolly - y; - return(RD_OK); + return RD_OK; } @@ -1078,7 +1066,6 @@ int32 CopyScreenBuffer(void) { int32 InitialiseBackgroundLayer(_parallax *p) { uint8 *memchunk; - uint32 *quaddata; uint8 zeros; uint16 count; uint16 i, j, k; @@ -1105,7 +1092,7 @@ int32 InitialiseBackgroundLayer(_parallax *p) { xblocks[layer] = (p->w + BLOCKWIDTH - 1) >> BLOCKWBITS; yblocks[layer] = (p->h + BLOCKHEIGHT - 1) >> BLOCKHBITS; - blockSurfaces[layer] = (Surface **) calloc(xblocks[layer] * yblocks[layer], sizeof(Surface *)); + blockSurfaces[layer] = (BlockSurface **) calloc(xblocks[layer] * yblocks[layer], sizeof(BlockSurface *)); if (!blockSurfaces[layer]) return RDERR_OUTOFMEMORY; @@ -1160,36 +1147,34 @@ int32 InitialiseBackgroundLayer(_parallax *p) { for (i = 0; i < xblocks[layer] * yblocks[layer]; i++) { bool block_has_data = false; + bool block_is_transparent = false; data = memchunk + (p->w * BLOCKHEIGHT * (i / xblocks[layer])) + BLOCKWIDTH * (i % xblocks[layer]); - quaddata = (uint32 *) data; - for (j = 0; j < BLOCKHEIGHT; j++) { - for (k = 0; k < BLOCKWIDTH / 4; k++) { - if (*quaddata) { + for (k = 0; k < BLOCKWIDTH; k++) { + if (data[j * p->w + k]) block_has_data = true; - goto bailout; - } - quaddata++; + else + block_is_transparent = true; } - quaddata += ((p->w - BLOCKWIDTH) / 4); } -bailout: - // Only assign a surface to the block if it contains data. if (block_has_data) { - blockSurfaces[layer][i] = new Surface(BLOCKWIDTH, BLOCKHEIGHT); + blockSurfaces[layer][i] = (BlockSurface *) malloc(sizeof(BlockSurface)); // Copy the data into the surfaces. - dst = blockSurfaces[layer][i]->_pixels; + dst = blockSurfaces[layer][i]->data; for (j = 0; j < BLOCKHEIGHT; j++) { memcpy(dst, data, BLOCKWIDTH); data += p->w; dst += BLOCKWIDTH; } + + blockSurfaces[layer][i]->transparent = block_is_transparent; + } else blockSurfaces[layer][i] = NULL; } @@ -1209,7 +1194,7 @@ int32 CloseBackgroundLayer(void) { if (blockSurfaces[j]) { for (i = 0; i < xblocks[j] * yblocks[j]; i++) if (blockSurfaces[j][i]) - delete blockSurfaces[j][i]; + free(blockSurfaces[j][i]); free(blockSurfaces[j]); blockSurfaces[j] = NULL; } diff --git a/sword2/driver/sprite.cpp b/sword2/driver/sprite.cpp index 82d7a76a69..5054a6c782 100644 --- a/sword2/driver/sprite.cpp +++ b/sword2/driver/sprite.cpp @@ -1308,7 +1308,7 @@ int32 DrawSurface(_spriteInfo *s, uint8 *surface) { sprite = surface; src = sprite + rs.top * srcPitch + rs.left; - dst = lpBackBuffer->_pixels + lpBackBuffer->_width * rd.top + rd.left; + dst = lpBackBuffer + screenWide * rd.top + rd.left; if (s->type & RDSPR_TRANS) { for (y = 0; y < rd.bottom - rd.top; y++) { @@ -1317,19 +1317,19 @@ int32 DrawSurface(_spriteInfo *s, uint8 *surface) { dst[x] = src[x]; } src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } else { for (y = 0; y < rd.bottom - rd.top; y++) - memcpy(dst, src, lpBackBuffer->_width); + memcpy(dst, src, screenWide); src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } if (freeSprite) free(sprite); - UploadRect(&rd); + // UploadRect(&rd); return 0; } @@ -1463,7 +1463,7 @@ int32 DrawSprite(_spriteInfo *s) { if (scale != 256) { if ((renderCaps & RDBLTFX_ARITHMETICSTRETCH) && !clipped) - backbuf = lpBackBuffer->_pixels + lpBackBuffer->_width * rd.top + rd.left; + backbuf = lpBackBuffer + screenWide * rd.top + rd.left; if (s->scaledWidth > SCALE_MAXWIDTH || s->scaledHeight > SCALE_MAXHEIGHT) { @@ -1535,7 +1535,7 @@ int32 DrawSprite(_spriteInfo *s) { // ----------------------------------------------------------------- src = sprite + rs.top * srcPitch + rs.left; - dst = lpBackBuffer->_pixels + lpBackBuffer->_width * rd.top + rd.left; + dst = lpBackBuffer + screenWide * rd.top + rd.left; if (s->type & RDSPR_BLEND) { if (renderCaps & RDBLTFX_ALLHARDWARE) { @@ -1545,7 +1545,7 @@ int32 DrawSprite(_spriteInfo *s) { dst[j] = src[j]; } src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } else { if (s->blend & 0x01) { @@ -1560,7 +1560,7 @@ int32 DrawSprite(_spriteInfo *s) { } } src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } else if (s->blend & 0x02) { // FIXME: This case looks bogus to me. The @@ -1589,7 +1589,7 @@ int32 DrawSprite(_spriteInfo *s) { } } src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } else { warning("DrawSprite: Invalid blended sprite"); @@ -1606,13 +1606,13 @@ int32 DrawSprite(_spriteInfo *s) { dst[j] = src[j]; } src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } else { for (i = 0; i < rs.bottom - rs.top; i++) { memcpy(dst, src, rs.right - rs.left); src += srcPitch; - dst += lpBackBuffer->_width; + dst += screenWide; } } } @@ -1620,7 +1620,7 @@ int32 DrawSprite(_spriteInfo *s) { if (freeSprite) free(sprite); - UploadRect(&rd); + // UploadRect(&rd); /* -- cgit v1.2.3