Finally committing support for compressed textures on the GPU (DDS+DXT). This patch...
authorMitchell Stokes <mogurijin@gmail.com>
Sat, 30 Jun 2012 04:34:34 +0000 (04:34 +0000)
committerMitchell Stokes <mogurijin@gmail.com>
Sat, 30 Jun 2012 04:34:34 +0000 (04:34 +0000)
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).

12 files changed:
source/blender/gpu/CMakeLists.txt
source/blender/gpu/GPU_draw.h
source/blender/gpu/SConscript
source/blender/gpu/intern/gpu_draw.c
source/blender/imbuf/IMB_imbuf_types.h
source/blender/imbuf/intern/allocimbuf.c
source/blender/imbuf/intern/dds/DirectDrawSurface.cpp
source/blender/imbuf/intern/dds/DirectDrawSurface.h
source/blender/imbuf/intern/dds/dds_api.cpp
source/gameengine/Ketsji/BL_Texture.cpp
source/gameengine/Ketsji/BL_Texture.h
source/gameengine/Ketsji/CMakeLists.txt

index 59e384cbd4b7a5bde24d7f8d2b9fc2007f35098e..d80e756f0b68c504075c84b0b2077a53c8b5d368 100644 (file)
@@ -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}")
 
index 438cfd6b741d6691f5dc8231255dfdbd35831a4f..59140b2be80913d96e6c90667a110496b5ddafc3 100644 (file)
@@ -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);
index cf1c91f25fea7f71ac6253d5d20b3269ff34ceca..7ab0c6cce46533849a6763581d5ec08bdbd67c73 100644 (file)
@@ -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] )
index 4197e1a3edbfe7619896a039ed7fd355c91cbd85..1e261e10ed6c6e88e6cedc084af85d6d0fe9bad2 100644 (file)
@@ -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 */
index 2cb1dfe149a4388e3ceed26336cf44e523146cab..dcb5cdd7d323886ba05e4f7cf3ffc34c43c8aa55 100644 (file)
@@ -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[];
index 5dff3e1aea0fa84970889fd4bbbcb83abdd440c4..68a094c26d0b13cc1c3fd61474edd4d9ab3ba180 100644 (file)
@@ -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);
                }
        }
index 438988fbe482b7b177e60b4bae28ee05800b09c2..82f355e1bb24275da033631ebf89cd1761d613d3 100644 (file)
@@ -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)
 {
        
index ddae882662037b1cd43f4c27defccbf66d3c5e64..a851533b1f34fc6affa043191c0500b90c290228 100644 (file)
@@ -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;
index 071d94c2076b93d5018053e55bfa1dce8f3e9d76..fba326f7865be2d1e3e552d61265d43ca1704a9d 100644 (file)
@@ -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);
        }
 
index 576da0d3f40fe6e6f41a4eda036cac1d90e5c07a..19247664dfae95cb540bebe3e91cc329bd42444e 100644 (file)
@@ -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)
 {
index 2673be2bc424d9b3015841182dc9a00c50c53ae8..a6bd354d2600d5efc0b6c51ec061a707e8e199bc 100644 (file)
@@ -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( );
index c7f54838c10e6a316a34863496361c4a75532e94..4b3426e0784c53a8bc75d5fcc595838629268d37 100644 (file)
@@ -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}