server_misc.c revision 05e2d532b00db03e631595029308c4c621a11e43
/* Copyright (c) 2001, Stanford University
* All rights reserved
*
* See the file LICENSE.txt for information on redistributing this software.
*/
#include "server_dispatch.h"
#include "server.h"
#include "cr_error.h"
#include "cr_mem.h"
#include "cr_string.h"
#include "cr_pixeldata.h"
void SERVER_DISPATCH_APIENTRY crServerDispatchSelectBuffer( GLsizei size, GLuint *buffer )
{
(void) size;
(void) buffer;
crError( "Unsupported network glSelectBuffer call." );
}
void SERVER_DISPATCH_APIENTRY crServerDispatchGetChromiumParametervCR(GLenum target, GLuint index, GLenum type, GLsizei count, GLvoid *values)
{
GLubyte local_storage[4096];
GLint bytes = 0;
switch (type) {
case GL_BYTE:
case GL_UNSIGNED_BYTE:
bytes = count * sizeof(GLbyte);
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
bytes = count * sizeof(GLshort);
break;
case GL_INT:
case GL_UNSIGNED_INT:
bytes = count * sizeof(GLint);
break;
case GL_FLOAT:
bytes = count * sizeof(GLfloat);
break;
case GL_DOUBLE:
bytes = count * sizeof(GLdouble);
break;
default:
crError("Bad type in crServerDispatchGetChromiumParametervCR");
}
CRASSERT(bytes >= 0);
CRASSERT(bytes < 4096);
cr_server.head_spu->dispatch_table.GetChromiumParametervCR( target, index, type, count, local_storage );
crServerReturnValue( local_storage, bytes );
}
void SERVER_DISPATCH_APIENTRY crServerDispatchChromiumParametervCR(GLenum target, GLenum type, GLsizei count, const GLvoid *values)
{
CRMuralInfo *mural = cr_server.curClient->currentMural;
static int gather_connect_count = 0;
switch (target) {
case GL_SET_MAX_VIEWPORT_CR:
{
GLint *maxDims = (GLint *)values;
cr_server.limits.maxViewportDims[0] = maxDims[0];
cr_server.limits.maxViewportDims[1] = maxDims[1];
}
break;
case GL_TILE_INFO_CR:
/* message from tilesort SPU to set new tile bounds */
{
GLint numTiles, muralWidth, muralHeight, server, tiles;
GLint *tileBounds;
CRASSERT(count >= 4);
CRASSERT((count - 4) % 4 == 0); /* must be multiple of four */
CRASSERT(type == GL_INT);
numTiles = (count - 4) / 4;
tileBounds = (GLint *) values;
server = tileBounds[0];
muralWidth = tileBounds[1];
muralHeight = tileBounds[2];
tiles = tileBounds[3];
CRASSERT(tiles == numTiles);
tileBounds += 4; /* skip over header values */
/*crServerNewMuralTiling(mural, muralWidth, muralHeight, numTiles, tileBounds);
mural->viewportValidated = GL_FALSE;*/
}
break;
case GL_GATHER_DRAWPIXELS_CR:
if (cr_server.only_swap_once && cr_server.curClient != cr_server.clients[0])
break;
cr_server.head_spu->dispatch_table.ChromiumParametervCR( target, type, count, values );
break;
case GL_GATHER_CONNECT_CR:
/*
* We want the last connect to go through,
* otherwise we might deadlock in CheckWindowSize()
* in the readback spu
*/
gather_connect_count++;
if (cr_server.only_swap_once && (gather_connect_count != cr_server.numClients))
{
break;
}
cr_server.head_spu->dispatch_table.ChromiumParametervCR( target, type, count, values );
gather_connect_count = 0;
break;
case GL_SERVER_VIEW_MATRIX_CR:
/* Set this server's view matrix which will get premultiplied onto the
* modelview matrix. For non-planar tilesort and stereo.
*/
CRASSERT(count == 18);
CRASSERT(type == GL_FLOAT);
/* values[0] is the server index. Ignored here but used in tilesort SPU */
/* values[1] is the left/right eye index (0 or 1) */
{
const GLfloat *v = (const GLfloat *) values;
const int eye = v[1] == 0.0 ? 0 : 1;
crMatrixInitFromFloats(&cr_server.viewMatrix[eye], v + 2);
crDebug("Got GL_SERVER_VIEW_MATRIX_CR:\n"
" %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f",
cr_server.viewMatrix[eye].m00,
cr_server.viewMatrix[eye].m10,
cr_server.viewMatrix[eye].m20,
cr_server.viewMatrix[eye].m30,
cr_server.viewMatrix[eye].m01,
cr_server.viewMatrix[eye].m11,
cr_server.viewMatrix[eye].m21,
cr_server.viewMatrix[eye].m31,
cr_server.viewMatrix[eye].m02,
cr_server.viewMatrix[eye].m12,
cr_server.viewMatrix[eye].m22,
cr_server.viewMatrix[eye].m32,
cr_server.viewMatrix[eye].m03,
cr_server.viewMatrix[eye].m13,
cr_server.viewMatrix[eye].m23,
cr_server.viewMatrix[eye].m33);
}
cr_server.viewOverride = GL_TRUE;
break;
case GL_SERVER_PROJECTION_MATRIX_CR:
/* Set this server's projection matrix which will get replace the user's
* projection matrix. For non-planar tilesort and stereo.
*/
CRASSERT(count == 18);
CRASSERT(type == GL_FLOAT);
/* values[0] is the server index. Ignored here but used in tilesort SPU */
/* values[1] is the left/right eye index (0 or 1) */
{
const GLfloat *v = (const GLfloat *) values;
const int eye = v[1] == 0.0 ? 0 : 1;
crMatrixInitFromFloats(&cr_server.projectionMatrix[eye], v + 2);
crDebug("Got GL_SERVER_PROJECTION_MATRIX_CR:\n"
" %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f",
cr_server.projectionMatrix[eye].m00,
cr_server.projectionMatrix[eye].m10,
cr_server.projectionMatrix[eye].m20,
cr_server.projectionMatrix[eye].m30,
cr_server.projectionMatrix[eye].m01,
cr_server.projectionMatrix[eye].m11,
cr_server.projectionMatrix[eye].m21,
cr_server.projectionMatrix[eye].m31,
cr_server.projectionMatrix[eye].m02,
cr_server.projectionMatrix[eye].m12,
cr_server.projectionMatrix[eye].m22,
cr_server.projectionMatrix[eye].m32,
cr_server.projectionMatrix[eye].m03,
cr_server.projectionMatrix[eye].m13,
cr_server.projectionMatrix[eye].m23,
cr_server.projectionMatrix[eye].m33);
if (cr_server.projectionMatrix[eye].m33 == 0.0f) {
float x = cr_server.projectionMatrix[eye].m00;
float y = cr_server.projectionMatrix[eye].m11;
float a = cr_server.projectionMatrix[eye].m20;
float b = cr_server.projectionMatrix[eye].m21;
float c = cr_server.projectionMatrix[eye].m22;
float d = cr_server.projectionMatrix[eye].m32;
float znear = -d / (1.0f - c);
float zfar = (c - 1.0f) * znear / (c + 1.0f);
float left = znear * (a - 1.0f) / x;
float right = 2.0f * znear / x + left;
float bottom = znear * (b - 1.0f) / y;
float top = 2.0f * znear / y + bottom;
crDebug("Frustum: left, right, bottom, top, near, far: %f, %f, %f, %f, %f, %f", left, right, bottom, top, znear, zfar);
}
else {
/* Todo: Add debug output for orthographic projection*/
}
}
cr_server.projectionOverride = GL_TRUE;
break;
default:
/* Pass the parameter info to the head SPU */
cr_server.head_spu->dispatch_table.ChromiumParametervCR( target, type, count, values );
break;
}
}
void SERVER_DISPATCH_APIENTRY crServerDispatchChromiumParameteriCR(GLenum target, GLint value)
{
switch (target) {
case GL_SHARE_CONTEXT_RESOURCES_CR:
crStateShareContext(value);
break;
case GL_SHARED_DISPLAY_LISTS_CR:
cr_server.sharedDisplayLists = value;
break;
case GL_SHARED_TEXTURE_OBJECTS_CR:
cr_server.sharedTextureObjects = value;
break;
case GL_SHARED_PROGRAMS_CR:
cr_server.sharedPrograms = value;
break;
case GL_SERVER_CURRENT_EYE_CR:
cr_server.currentEye = value ? 1 : 0;
break;
default:
/* Pass the parameter info to the head SPU */
cr_server.head_spu->dispatch_table.ChromiumParameteriCR( target, value );
}
}
void SERVER_DISPATCH_APIENTRY crServerDispatchChromiumParameterfCR(GLenum target, GLfloat value)
{
switch (target) {
case GL_SHARED_DISPLAY_LISTS_CR:
cr_server.sharedDisplayLists = (int) value;
break;
case GL_SHARED_TEXTURE_OBJECTS_CR:
cr_server.sharedTextureObjects = (int) value;
break;
case GL_SHARED_PROGRAMS_CR:
cr_server.sharedPrograms = (int) value;
break;
default:
/* Pass the parameter info to the head SPU */
cr_server.head_spu->dispatch_table.ChromiumParameterfCR( target, value );
}
}
void crServerCreateInfoDeleteCB(void *data)
{
CRCreateInfo_t *pCreateInfo = (CRCreateInfo_t *) data;
if (pCreateInfo->pszDpyName)
crFree(pCreateInfo->pszDpyName);
}
GLint crServerGenerateID(GLint *pCounter)
{
return (*pCounter)++;
}
/*#define CR_DUMP_BLITS*/
#ifdef CR_DUMP_BLITS
static int blitnum=0;
static int copynum=0;
#endif
void SERVER_DISPATCH_APIENTRY
crServerDispatchCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
{
/*@todo pbo/fbo disabled for now as it's slower, check on other gpus*/
static int siHavePBO = 0;
static int siHaveFBO = 0;
if ((target!=GL_TEXTURE_2D) || (height>=0))
{
cr_server.head_spu->dispatch_table.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
#ifdef CR_DUMP_BLITS
{
SPUDispatchTable *gl = &cr_server.head_spu->dispatch_table;
void *img;
GLint w, h;
char fname[200];
copynum++;
gl->GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
gl->GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
img = crAlloc(w*h*4);
CRASSERT(img);
gl->GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, img);
sprintf(fname, "copy_blit%i_copy_%i.tga", blitnum, copynum);
crDumpNamedTGA(fname, w, h, img);
crFree(img);
}
#endif
}
else /* negative height, means we have to Yinvert the source pixels while copying */
{
SPUDispatchTable *gl = &cr_server.head_spu->dispatch_table;
if (siHavePBO<0)
{
const char *ext = (const char*)gl->GetString(GL_EXTENSIONS);
siHavePBO = crStrstr(ext, "GL_ARB_pixel_buffer_object") ? 1:0;
}
if (siHaveFBO<0)
{
const char *ext = (const char*)gl->GetString(GL_EXTENSIONS);
siHaveFBO = crStrstr(ext, "GL_EXT_framebuffer_object") ? 1:0;
}
if (siHavePBO==0 && siHaveFBO==0)
{
#if 1
GLint dRow, sRow;
for (dRow=yoffset, sRow=y-height-1; dRow<yoffset-height; dRow++, sRow--)
{
gl->CopyTexSubImage2D(target, level, xoffset, dRow, x, sRow, width, 1);
}
#else
{
GLint w, h, i;
char *img1, *img2, *sPtr, *dPtr;
CRContext *ctx = crStateGetCurrent();
w = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->level[0][level].width;
h = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->level[0][level].height;
img1 = crAlloc(4*w*h);
img2 = crAlloc(4*width*(-height));
CRASSERT(img1 && img2);
gl->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, -height);
gl->GetTexImage(target, level, GL_RGBA, GL_UNSIGNED_BYTE, img1);
sPtr=img1+4*xoffset+4*w*yoffset;
dPtr=img2+4*width*(-height-1);
for (i=0; i<-height; ++i)
{
crMemcpy(dPtr, sPtr, 4*width);
sPtr += 4*w;
dPtr -= 4*width;
}
gl->TexSubImage2D(target, level, xoffset, yoffset, width, -height, GL_RGBA, GL_UNSIGNED_BYTE, img2);
crFree(img1);
crFree(img2);
}
#endif
}
else if (siHaveFBO==1) /*@todo more states to set and restore here*/
{
GLuint tID, fboID;
GLenum status;
CRContext *ctx = crStateGetCurrent();
gl->GenTextures(1, &tID);
gl->BindTexture(target, tID);
gl->CopyTexImage2D(target, level, GL_RGBA, x, y, width, -height, 0);
gl->GenFramebuffersEXT(1, &fboID);
gl->BindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
gl->FramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target,
ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->hwid, level);
status = gl->CheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
crWarning("Framebuffer status 0x%x", status);
}
gl->Enable(target);
gl->PushAttrib(GL_VIEWPORT_BIT);
gl->Viewport(xoffset, yoffset, width, -height);
gl->MatrixMode(GL_PROJECTION);
gl->PushMatrix();
gl->LoadIdentity();
gl->MatrixMode(GL_MODELVIEW);
gl->PushMatrix();
gl->LoadIdentity();
gl->Disable(GL_DEPTH_TEST);
gl->Disable(GL_CULL_FACE);
gl->Disable(GL_STENCIL_TEST);
gl->Disable(GL_SCISSOR_TEST);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl->TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
gl->Begin(GL_QUADS);
gl->TexCoord2f(0.0f, 1.0f);
gl->Vertex2f(-1.0, -1.0);
gl->TexCoord2f(0.0f, 0.0f);
gl->Vertex2f(-1.0f, 1.0f);
gl->TexCoord2f(1.0f, 0.0f);
gl->Vertex2f(1.0f, 1.0f);
gl->TexCoord2f(1.0f, 1.0f);
gl->Vertex2f(1.0f, -1.0f);
gl->End();
gl->PopMatrix();
gl->MatrixMode(GL_PROJECTION);
gl->PopMatrix();
gl->PopAttrib();
gl->FramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, 0, level);
gl->BindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->framebufferobject.drawFB ? ctx->framebufferobject.drawFB->hwid:0);
gl->BindTexture(target, ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->hwid);
gl->DeleteFramebuffersEXT(1, &fboID);
gl->DeleteTextures(1, &tID);
#if 0
{
GLint dRow, sRow, w, h;
void *img1, *img2;
w = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->level[0][level].width;
h = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->level[0][level].height;
img1 = crAlloc(4*w*h);
img2 = crAlloc(4*w*h);
CRASSERT(img1 && img2);
gl->GetTexImage(target, level, GL_BGRA, GL_UNSIGNED_BYTE, img1);
for (dRow=yoffset, sRow=y-height-1; dRow<yoffset-height; dRow++, sRow--)
{
gl->CopyTexSubImage2D(target, level, xoffset, dRow, x, sRow, width, 1);
}
gl->GetTexImage(target, level, GL_BGRA, GL_UNSIGNED_BYTE, img2);
if (crMemcmp(img1, img2, 4*w*h))
{
crDebug("MISMATCH! (%x, %i, ->%i,%i <-%i, %i [%ix%i])", target, level, xoffset, yoffset, x, y, width, height);
crDumpTGA(w, h, img1);
crDumpTGA(w, h, img2);
DebugBreak();
}
crFree(img1);
crFree(img2);
}
#endif
}
else
{
GLuint pboId, dRow, sRow;
CRContext *ctx = crStateGetCurrent();
gl->GenBuffersARB(1, &pboId);
gl->BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboId);
gl->BufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, -width*height*4, 0, GL_STATIC_COPY_ARB);
#if 1
gl->ReadPixels(x, y, width, -height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
gl->BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, ctx->bufferobject.packBuffer->hwid);
gl->BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboId);
for (dRow=yoffset, sRow=-height-1; dRow<yoffset-height; dRow++, sRow--)
{
gl->TexSubImage2D(target, level, xoffset, dRow, width, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)((uintptr_t)sRow*width*4));
}
#else /*few times slower again*/
for (dRow=0, sRow=y-height-1; dRow<-height; dRow++, sRow--)
{
gl->ReadPixels(x, sRow, width, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)((uintptr_t)dRow*width*4));
}
gl->BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, ctx->bufferobject.packBuffer->hwid);
gl->BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboId);
gl->TexSubImage2D(target, level, xoffset, yoffset, width, -height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
#endif
gl->BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->bufferobject.unpackBuffer->hwid);
gl->DeleteBuffersARB(1, &pboId);
}
}
}
void SERVER_DISPATCH_APIENTRY
crServerDispatchBlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter)
{
#ifdef CR_DUMP_BLITS
SPUDispatchTable *gl = &cr_server.head_spu->dispatch_table;
GLint rfb=0, dfb=0, dtex=0, dlev=-1, rb=0, db=0, ppb=0, pub=0, vp[4], otex, dstw, dsth;
GLenum status;
char fname[200];
void *img;
blitnum++;
crDebug("[%i]BlitFramebufferEXT(%i, %i, %i, %i, %i, %i, %i, %i, %x, %x)", blitnum, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
crDebug("%i, %i <-> %i, %i", srcX1-srcX0, srcY1-srcY0, dstX1-dstX0, dstY1-dstY0);
gl->GetIntegerv(GL_READ_FRAMEBUFFER_BINDING_EXT, &rfb);
gl->GetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, &dfb);
gl->GetIntegerv(GL_READ_BUFFER, &rb);
gl->GetIntegerv(GL_DRAW_BUFFER, &db);
gl->GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &ppb);
gl->GetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &pub);
gl->GetIntegerv(GL_VIEWPORT, &vp[0]);
gl->GetIntegerv(GL_TEXTURE_BINDING_2D, &otex);
CRASSERT(!rfb && dfb);
gl->GetFramebufferAttachmentParameterivEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT, &dtex);
gl->GetFramebufferAttachmentParameterivEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT, &dlev);
status = gl->CheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT);
CRASSERT(status==GL_FRAMEBUFFER_COMPLETE_EXT
&& db==GL_COLOR_ATTACHMENT0_EXT
&& (rb==GL_FRONT || rb==GL_BACK)
&& !rfb && dfb && dtex && !dlev
&& !ppb && !pub);
crDebug("Src[rb 0x%x, fbo %i] Dst[db 0x%x, fbo %i(0x%x), tex %i.%i]", rb, rfb, db, dfb, status, dtex, dlev);
crDebug("Viewport [%i, %i, %i, %i]", vp[0], vp[1], vp[2], vp[3]);
gl->PixelStorei(GL_PACK_ROW_LENGTH, 0);
gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
gl->PixelStorei(GL_PACK_SKIP_PIXELS, 0);
gl->PixelStorei(GL_PACK_SKIP_ROWS, 0);
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
gl->PixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
gl->PixelStorei(GL_UNPACK_SKIP_ROWS, 0);
gl->BindTexture(GL_TEXTURE_2D, dtex);
gl->GetTexLevelParameteriv(GL_TEXTURE_2D, dlev, GL_TEXTURE_WIDTH, &dstw);
gl->GetTexLevelParameteriv(GL_TEXTURE_2D, dlev, GL_TEXTURE_HEIGHT, &dsth);
gl->BindTexture(GL_TEXTURE_2D, otex);
crDebug("Dst is %i, %i", dstw, dsth);
CRASSERT(vp[2]>=dstw && vp[3]>=dsth);
img = crAlloc(vp[2]*vp[3]*4);
CRASSERT(img);
gl->ReadPixels(0, 0, vp[2], vp[3], GL_BGRA, GL_UNSIGNED_BYTE, img);
sprintf(fname, "blit%iA_src.tga", blitnum);
crDumpNamedTGA(fname, vp[2], vp[3], img);
gl->BindTexture(GL_TEXTURE_2D, dtex);
gl->GetTexImage(GL_TEXTURE_2D, dlev, GL_BGRA, GL_UNSIGNED_BYTE, img);
sprintf(fname, "blit%iB_dst.tga", blitnum);
crDumpNamedTGA(fname, dstw, dsth, img);
gl->BindTexture(GL_TEXTURE_2D, otex);
#endif
cr_server.head_spu->dispatch_table.BlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1,
mask, filter);
#ifdef CR_DUMP_BLITS
gl->BindTexture(GL_TEXTURE_2D, dtex);
gl->GetTexImage(GL_TEXTURE_2D, dlev, GL_BGRA, GL_UNSIGNED_BYTE, img);
sprintf(fname, "blit%iC_res.tga", blitnum);
crDumpNamedTGA(fname, dstw, dsth, img);
gl->BindTexture(GL_TEXTURE_2D, otex);
crFree(img);
#endif
}