Finally committing support for compressed textures on the GPU (DDS+DXT). This patch started out as a patch by me, then cleaned up by Kupoman during his work on Cucumber.

One important thing to keep in mind when using this feature is that you'll need to flip your textures vertically (both the GIMP and Photoshop DDS tools I've seen have support for this on export). This is a quirk in using a texture format originally made for DirectX/DirectDraw, and flipping the compressed data is a real headache. Another quick fix for this issue is to change the Y value for the Size in the Mapping panel in the Texture properties to -1 (default is 1).
This commit is contained in:
Mitchell Stokes 2012-06-30 04:34:34 +00:00
parent 2d7efed014
commit 436f02ab9c
12 changed files with 222 additions and 17 deletions

@ -71,5 +71,10 @@ endif()
add_definitions(-DGLEW_STATIC)
if(WITH_IMAGE_DDS)
add_definitions(-DWITH_DDS)
endif()
blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}")

@ -122,6 +122,9 @@ void GPU_paint_update_image(struct Image *ima, int x, int y, int w, int h, int m
void GPU_update_images_framechange(void);
int GPU_update_image_time(struct Image *ima, double time);
int GPU_verify_image(struct Image *ima, struct ImageUser *iuser, int tftile, int compare, int mipmap);
void GPU_create_gl_tex(unsigned int *bind, unsigned int *pix, float *frect, int rectw, int recth, int mipmap, int use_hight_bit_depth, struct Image *ima);
void GPU_create_gl_tex_compressed(unsigned int *bind, unsigned int *pix, int x, int y, int mipmap, struct Image *ima, struct ImBuf *ibuf);
int GPU_upload_dxt_texture(struct ImBuf *ibuf);
void GPU_free_image(struct Image *ima);
void GPU_free_images(void);
void GPU_free_images_anim(void);

@ -17,4 +17,7 @@ incs += ' ' + env['BF_OPENGL_INC']
if env['WITH_BF_SMOKE']:
defs.append('WITH_SMOKE')
if env['WITH_BF_DDS']:
defs.append('WITH_DDS')
env.BlenderLib ( 'bf_gpu', sources, Split(incs), defines = defs, libtype=['core','player'], priority=[160,110] )

@ -427,8 +427,8 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
ImBuf *ibuf = NULL;
unsigned int *bind = NULL;
int rectw, recth, tpx=0, tpy=0, y;
unsigned int *tilerect= NULL, *scalerect= NULL, *rect= NULL;
float *ftilerect= NULL, *fscalerect = NULL, *frect = NULL;
unsigned int *tilerect= NULL, *rect= NULL;
float *ftilerect= NULL, *frect = NULL;
float *srgb_frect = NULL;
short texwindx, texwindy, texwinsx, texwinsy;
/* flag to determine whether high resolution format is used */
@ -611,7 +611,32 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
rect= tilerect;
}
}
#ifdef WITH_DDS
if (ibuf->ftype & DDS)
GPU_create_gl_tex_compressed(bind, rect, rectw, recth, mipmap, ima, ibuf);
else
#endif
GPU_create_gl_tex(bind, rect, frect, rectw, recth, mipmap, use_high_bit_depth, ima);
/* clean up */
if (tilerect)
MEM_freeN(tilerect);
if (ftilerect)
MEM_freeN(ftilerect);
if (srgb_frect)
MEM_freeN(srgb_frect);
return *bind;
}
void GPU_create_gl_tex(unsigned int *bind, unsigned int *pix, float * frect, int rectw, int recth, int mipmap, int use_high_bit_depth, Image *ima)
{
unsigned int *scalerect = NULL;
float *fscalerect = NULL;
int tpx = rectw;
int tpy = recth;
/* scale if not a power of two. this is not strictly necessary for newer
* GPUs (OpenGL version >= 2.0) since they support non-power-of-two-textures */
if (!is_pow2_limit(rectw) || !is_pow2_limit(recth)) {
@ -626,9 +651,9 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
}
else {
scalerect= MEM_mallocN(rectw*recth*sizeof(*scalerect), "scalerect");
gluScaleImage(GL_RGBA, tpx, tpy, GL_UNSIGNED_BYTE, rect, rectw, recth, GL_UNSIGNED_BYTE, scalerect);
gluScaleImage(GL_RGBA, tpx, tpy, GL_UNSIGNED_BYTE, pix, rectw, recth, GL_UNSIGNED_BYTE, scalerect);
rect= scalerect;
pix= scalerect;
}
}
@ -640,7 +665,7 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
if (use_high_bit_depth)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, rectw, recth, 0, GL_RGBA, GL_FLOAT, frect);
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, rect);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
}
@ -649,14 +674,14 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
if (use_high_bit_depth)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, rectw, recth, 0, GL_RGBA, GL_FLOAT, frect);
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, rect);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix);
glGenerateMipmapEXT(GL_TEXTURE_2D);
} else {
if (use_high_bit_depth)
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA16, rectw, recth, GL_RGBA, GL_FLOAT, frect);
else
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, rectw, recth, GL_RGBA, GL_UNSIGNED_BYTE, rect);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, rectw, recth, GL_RGBA, GL_UNSIGNED_BYTE, pix);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gpu_get_mipmap_filter(0));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
@ -668,21 +693,84 @@ int GPU_verify_image(Image *ima, ImageUser *iuser, int tftile, int compare, int
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, GPU_get_anisotropic());
/* set to modulate with vertex color */
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
/* clean up */
if (tilerect)
MEM_freeN(tilerect);
if (ftilerect)
MEM_freeN(ftilerect);
if (scalerect)
MEM_freeN(scalerect);
if (fscalerect)
MEM_freeN(fscalerect);
if (srgb_frect)
MEM_freeN(srgb_frect);
return *bind;
}
/**
* GPU_upload_dxt_texture() assumes that the texture is already bound and ready to go.
* This is so the viewport and the BGE can share some code.
* Returns 0 if the provided ImBuf doesn't have a supported DXT compression format
*/
int GPU_upload_dxt_texture(ImBuf *ibuf)
{
GLint format, err;
int blocksize, height, width, i, size, offset = 0;
height = ibuf->x;
width = ibuf->y;
if (ibuf->dds_data.fourcc == FOURCC_DXT1)
format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
else if (ibuf->dds_data.fourcc == FOURCC_DXT3)
format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
else if (ibuf->dds_data.fourcc == FOURCC_DXT5)
format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
else {
printf("Unable to find a suitable DXT compression, falling back to uncompressed\n");
return 0;
}
blocksize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
for (i=0; i<ibuf->dds_data.nummipmaps && (width||height); ++i) {
if (width == 0)
width = 1;
if (height == 0)
height = 1;
size = ((width+3)/4)*((height+3)/4)*blocksize;
glCompressedTexImage2D(GL_TEXTURE_2D, i, format, width, height,
0, size, ibuf->dds_data.data + offset);
err = glGetError();
if (err != GL_NO_ERROR)
printf("OpenGL error: %s\nFormat: %x\n", gluErrorString(err), format);
offset += size;
width >>= 1;
height >>= 1;
}
return 1;
}
void GPU_create_gl_tex_compressed(unsigned int *bind, unsigned int *pix, int x, int y, int mipmap, Image *ima, ImBuf *ibuf)
{
#ifndef WITH_DDS
// Fall back to uncompressed if DDS isn't enabled
GPU_create_gl_tex(bind, pix, NULL, x, y, mipmap, 0, ima);
#else
glGenTextures(1, (GLuint *)bind);
glBindTexture(GL_TEXTURE_2D, *bind);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if (GPU_upload_dxt_texture(ibuf) == 0) {
glDeleteTextures(1, (GLuint*)bind);
GPU_create_gl_tex(bind, pix, NULL, x, y, mipmap, 0, ima);
}
#endif
}
static void gpu_verify_repeat(Image *ima)
{
/* set either clamp or repeat in X/Y */

@ -50,6 +50,13 @@ struct ImMetaData;
#define IB_MIPMAP_LEVELS 20
#define IB_FILENAME_SIZE 1024
typedef struct DDSData {
unsigned int fourcc; /* DDS fourcc info */
unsigned int nummipmaps; /* The number of mipmaps in the dds file */
unsigned char *data; /* The compressed image data */
unsigned int size; /* The size of the compressed data */
} DDSData;
/**
* \ingroup imbuf
* This is the abstraction of an image. ImBuf is the basic type used for all
@ -119,6 +126,9 @@ typedef struct ImBuf {
unsigned char *encodedbuffer; /* Compressed image only used with png currently */
unsigned int encodedsize; /* Size of data written to encodedbuffer */
unsigned int encodedbuffersize; /* Size of encodedbuffer */
/* information for compressed textures */
struct DDSData dds_data;
} ImBuf;
/* Moved from BKE_bmfont_types.h because it is a userflag bit mask. */
@ -215,6 +225,28 @@ typedef struct ImBuf {
#define IB_PROFILE_SRGB 2
#define IB_PROFILE_CUSTOM 3
/* dds */
#ifdef WITH_DDS
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3)\
((unsigned long)(unsigned char)(ch0) | \
((unsigned long)(unsigned char)(ch1) << 8) | \
((unsigned long)(unsigned char)(ch2) << 16) | \
((unsigned long)(unsigned char)(ch3) << 24))
#endif //MAKEFOURCC
/*
* FOURCC codes for DX compressed-texture pixel formats
*/
#define FOURCC_DDS (MAKEFOURCC('D','D','S',' '))
#define FOURCC_DXT1 (MAKEFOURCC('D','X','T','1'))
#define FOURCC_DXT2 (MAKEFOURCC('D','X','T','2'))
#define FOURCC_DXT3 (MAKEFOURCC('D','X','T','3'))
#define FOURCC_DXT4 (MAKEFOURCC('D','X','T','4'))
#define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
#endif // DDS
extern const char *imb_ext_image[];
extern const char *imb_ext_image_qt[];
extern const char *imb_ext_movie[];

@ -162,6 +162,8 @@ void IMB_freeImBuf(ImBuf *ibuf)
IMB_freezbuffloatImBuf(ibuf);
freeencodedbufferImBuf(ibuf);
IMB_metadata_free(ibuf);
if (ibuf->dds_data.data != NULL)
free(ibuf->dds_data.data); /* dds_data.data is allocated by DirectDrawSurface::readData(), so don't use MEM_freeN! */
MEM_freeN(ibuf);
}
}

@ -1016,6 +1016,10 @@ uint DirectDrawSurface::mipmapCount() const
else return 1;
}
uint DirectDrawSurface::fourCC() const
{
return header.pf.fourcc;
}
uint DirectDrawSurface::width() const
{
@ -1131,6 +1135,29 @@ void DirectDrawSurface::mipmap(Image * img, uint face, uint mipmap)
}
}
// It was easier to copy this function from upstream than to resync.
// This should be removed if a resync ever occurs.
void* DirectDrawSurface::readData(uint &rsize)
{
uint header_size = 128; // sizeof(DDSHeader);
if (header.hasDX10Header())
{
header_size += 20; // sizeof(DDSHeader10);
}
uint size = stream.size - header_size;
rsize = size;
unsigned char *data = new unsigned char[size];
stream.seek(header_size);
mem_read(stream, data, size);
// Maybe check if size == rsize? assert() isn't in this scope...
return data;
}
void DirectDrawSurface::readLinearImage(Image * img)
{

@ -158,6 +158,7 @@ public:
bool hasAlpha() const;
uint mipmapCount() const;
uint fourCC() const;
uint width() const;
uint height() const;
uint depth() const;
@ -171,6 +172,7 @@ public:
void setUserVersion(int version);
void mipmap(Image * img, uint f, uint m);
void* readData(uint &size);
// void mipmap(FloatImage * img, uint f, uint m);
void printInfo() const;

@ -123,6 +123,8 @@ struct ImBuf *imb_load_dds(unsigned char *mem, size_t size, int flags)
ibuf->ftype = DDS;
ibuf->profile = IB_PROFILE_SRGB;
ibuf->dds_data.fourcc = dds.fourCC();
ibuf->dds_data.nummipmaps = dds.mipmapCount();
if ((flags & IB_test) == 0) {
if (!imb_addrectImBuf(ibuf)) return(ibuf);
@ -136,10 +138,18 @@ struct ImBuf *imb_load_dds(unsigned char *mem, size_t size, int flags)
cp[0] = pixel.r; /* set R component of col */
cp[1] = pixel.g; /* set G component of col */
cp[2] = pixel.b; /* set B component of col */
if (bits_per_pixel == 32)
if (dds.hasAlpha())
cp[3] = pixel.a; /* set A component of col */
rect[i] = col;
}
if (ibuf->dds_data.fourcc != FOURCC_DDS)
ibuf->dds_data.data = (unsigned char*)dds.readData(ibuf->dds_data.size);
else {
ibuf->dds_data.data = NULL;
ibuf->dds_data.size = 0;
}
IMB_flipy(ibuf);
}

@ -144,7 +144,15 @@ bool BL_Texture::InitFromImage(int unit, Image *img, bool mipmap)
mNeedsDeleted = 1;
glGenTextures(1, (GLuint*)&mTexture);
#ifdef WITH_DDS
if (ibuf->ftype & DDS)
InitGLCompressedTex(ibuf, mipmap);
else
InitGLTex(ibuf->rect, ibuf->x, ibuf->y, mipmap);
#else
InitGLTex(ibuf->rect, ibuf->x, ibuf->y, mipmap);
#endif
// track created units
BL_TextureObject obj;
@ -183,6 +191,26 @@ void BL_Texture::InitGLTex(unsigned int *pix,int x,int y,bool mipmap)
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
void BL_Texture::InitGLCompressedTex(ImBuf* ibuf, bool mipmap)
{
#ifndef WITH_DDS
// Fall back to uncompressed if DDS isn't enabled
InitGLTex(ibuf->rect, ibuf->x, ibuf->y, mipmap);
return;
#else
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if (GPU_upload_dxt_texture(ibuf) == 0) {
InitGLTex(ibuf->rect, ibuf->x, ibuf->y, mipmap);
return;
}
#endif
}
void BL_Texture::InitNonPow2Tex(unsigned int *pix,int x,int y,bool mipmap)
{

@ -35,6 +35,7 @@ private:
void InitNonPow2Tex(unsigned int *p,int x,int y,bool mipmap );
void InitGLTex(unsigned int *p,int x,int y,bool mipmap );
void InitGLCompressedTex(struct ImBuf *p, bool mipmap);
public:
BL_Texture();
~BL_Texture( );

@ -221,6 +221,10 @@ set(SRC
add_definitions(-DGLEW_STATIC)
if(WITH_IMAGE_DDS)
add_definitions(-DWITH_DDS)
endif()
if(WITH_SDL)
list(APPEND INC_SYS
${SDL_INCLUDE_DIR}