packages feed

lambdacube-engine-0.1.1: Graphics/LambdaCube/RenderSystem/GL/GLTexture.hs

module Graphics.LambdaCube.RenderSystem.GL.GLTexture where

import qualified Data.Set as Set
import Data.Maybe
import Control.Monad
import Foreign.Ptr

import Graphics.Rendering.OpenGL (($=))
import qualified Graphics.Rendering.OpenGL as GL
import System.Log.Logger

import Graphics.LambdaCube.Types
import Graphics.LambdaCube.PixelFormat
import Graphics.LambdaCube.Texture
import Graphics.LambdaCube.Image
import Graphics.LambdaCube.HardwareBuffer
import Graphics.LambdaCube.RenderSystemCapabilities
import Graphics.LambdaCube.RenderSystem.GL.GLUtils

data GLTexture
    = GLTexture
    { gltxName                      :: String
    , gltxWidth                     :: Int
    , gltxHeight                    :: Int
    , gltxDepth                     :: Int
    , gltxNumRequestedMipmaps       :: TextureMipmap
    , gltxNumMipmaps                :: Int
    , gltxMipmapsHardwareGenerated  :: Bool
    , gltxGamma                     :: FloatType
    , gltxHwGamma                   :: Bool
    , gltxFSAA                      :: Int
    , gltxFSAAHint                  :: String
    , gltxTextureType               :: TextureType
    , gltxFormat                    :: PixelFormat
    , gltxUsage                     :: TextureUsage
    , gltxSrcFormat                 :: PixelFormat
    , gltxSrcWidth                  :: Int
    , gltxSrcHeight                 :: Int
    , gltxSrcDepth                  :: Int

    , gltxDesiredFormat             :: PixelFormat
    , gltxDesiredIntegerBitDepth    :: Int
    , gltxDesiredFloatBitDepth      :: Int
    , gltxTreatLuminanceAsAlpha     :: Bool
    
    , gltxTextureObject             :: GL.TextureObject
    }

instance Eq GLTexture where
    (==) a b    = gltxTextureObject a == gltxTextureObject b

instance HardwareBuffer GLTexture

instance Texture GLTexture where
    txName                      = gltxName
    txWidth                     = gltxWidth
    txHeight                    = gltxHeight
    txDepth                     = gltxDepth
    txNumRequestedMipmaps       = gltxNumRequestedMipmaps
    txNumMipmaps                = gltxNumMipmaps
    txMipmapsHardwareGenerated  = gltxMipmapsHardwareGenerated
    txGamma                     = gltxGamma
    txHwGamma                   = gltxHwGamma
    txFSAA                      = gltxFSAA
    txFSAAHint                  = gltxFSAAHint
    txTextureType               = gltxTextureType
    txFormat                    = gltxFormat
--    txUsage                     = gltxUsage
    txSrcFormat                 = gltxSrcFormat
    txSrcWidth                  = gltxSrcWidth
    txSrcHeight                 = gltxSrcHeight
    txSrcDepth                  = gltxSrcDepth

    txDesiredFormat             = gltxDesiredFormat
    txDesiredIntegerBitDepth    = gltxDesiredIntegerBitDepth
    txDesiredFloatBitDepth      = gltxDesiredFloatBitDepth
    txTreatLuminanceAsAlpha     = gltxTreatLuminanceAsAlpha

{-
    virtual TexturePtr createManual(const String & name, const String& group,
        TextureType texType, uint width, uint height, uint depth, 
		int num_mips, PixelFormat format, int usage = TU_DEFAULT, ManualResourceLoader* loader = 0,
		bool hwGammaCorrection = false, uint fsaa = 0, const String& fsaaHint = StringUtil::BLANK);
-}
--createTexture                   :: a -> String -> TextureType -> Int -> Int -> Int -> Int -> PixelFormat -> TextueUsage -> Bool -> Int -> String -> IO t
{-
        virtual TexturePtr createManual(const String & name, const String& group,
            TextureType texType, uint width, uint height, uint depth, 
			int num_mips, PixelFormat format, int usage = TU_DEFAULT, ManualResourceLoader* loader = 0,
			bool hwGammaCorrection = false, uint fsaa = 0, const String& fsaaHint = StringUtil::BLANK);
            TextureUsage = bufferUsage::Usage, autoMipmap::Bool, isTarget::Bool
    createTexture                   :: a -> String -> TextureType -> Int -> Int -> Int -> Int -> PixelFormat -> Usage -> Bool -> Bool -> Bool -> Int -> String -> IO t
-}
mkGLTexture :: RenderSystemCapabilities -> String -> TextureType -> Int -> Int -> Int -> TextureMipmap -> PixelFormat -> TextureUsage -> Bool -> Int -> String -> Maybe [Image] -> IO GLTexture
--FIXME: image parameter is temporary!!!!
mkGLTexture rcaps name texType width height depth numMips format usage hwGammaCorrection fsaa fsaaHint mimage = do
    let caps = rscCapabilities rcaps
    debugM "createTexture" $ "create " ++ show texType ++ " texture: " ++ name
    
    when (texType == TEX_TYPE_3D && Set.notMember RSC_TEXTURE_3D caps) $ error "3D Textures not supported before OpenGL 1.2"

    (major,minor) <- GL.get $ GL.majorMinor GL.glVersion

    let glVer a b   = major > a || (major >= a && minor >= b)
        texTarget   = (getGLTextureTarget texType)
        -- Convert to nearest power-of-two size if required
        mWidth      = optionalPO2 rcaps width
        mHeight     = optionalPO2 rcaps height
        mDepth      = optionalPO2 rcaps depth

        -- Adjust format if required
        mFormat     = getNativeFormat texType format False -- TODO

        -- Check requested number of mipmaps
        maxMips     = getMaxMipmaps mWidth mHeight mDepth mFormat
        mNumMipmaps = maxMips -- TODO min maxMips numMips

    -- Generate texture name
    [mTextureID] <- GL.genObjectNames 1 

    -- Set texture type
    GL.textureBinding texTarget $= Just mTextureID
    
    -- This needs to be set otherwise the texture doesn't get rendered
    when (glVer 1 2) $ GL.textureLevelRange texTarget $= (0,fromIntegral mNumMipmaps)
    
    -- Set some misc default parameters so NVidia won't complain, these can of course be changed later
    GL.textureFilter texTarget $= ((GL.Nearest,Nothing),GL.Nearest)

    when (glVer 1 2) $ do
        GL.textureWrapMode texTarget GL.S $= (GL.Repeated, GL.ClampToEdge)
        GL.textureWrapMode texTarget GL.T $= (GL.Repeated, GL.ClampToEdge)

    -- If we can do automip generation and the user desires this, do so
    let mMipmapsHardwareGenerated = Set.member RSC_AUTOMIPMAP caps
    -- FIXME
    {-
		// NVIDIA 175.16 drivers break hardware mip generation for non-compressed
		// textures - disable until fixed
		// Leave hardware gen on compressed textures since that's the only way we
		// can realistically do it since GLU doesn't support DXT
		// However DON'T do this on Apple, their drivers aren't subject to this
		// problem yet and in fact software generation appears to cause a crash 
		// in some cases which I've yet to track down
if OGRE_PLATFORM != OGRE_PLATFORM_APPLE
		if (Root::getSingleton().getRenderSystem()->getCapabilities()->getVendor() == GPU_NVIDIA
			&& !PixelUtil::isCompressed(mFormat))
		{
			mMipmapsHardwareGenerated = false;
		}
endif
    -}
--TODO
    let moreMips = case numMips of
            { MIP_UNLIMITED -> True
            ; MIP_DEFAULT   -> True -- TODO
            ; MIP_NUMBER n  -> n > 0
            }

    when (tuAutoMipmap usage && moreMips && mMipmapsHardwareGenerated) $ GL.generateMipmap texTarget $= GL.Enabled

    -- Allocate internal buffer so that glTexSubImageXD can be used
    -- Internal format
    let format' = getClosestGLInternalFormat mFormat hwGammaCorrection
        isCompressed _ = False -- TODO
    case isCompressed mFormat of
        { True  -> do
        {-
			// Compressed formats
			size_t size = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
			// Provide temporary buffer filled with zeroes as glCompressedTexImageXD does not
			// accept a 0 pointer like normal glTexImageXD
			// Run through this process for every mipmap to pregenerate mipmap piramid
			uint8 *tmpdata = new uint8[size];
			memset(tmpdata, 0, size);
			
			for(size_t mip=0; mip<=mNumMipmaps; mip++)
			{
				size = PixelUtil::getMemorySize(width, height, depth, mFormat);
				switch(mTextureType)
				{
					case TEX_TYPE_1D:
						glCompressedTexImage1DARB(GL_TEXTURE_1D, mip, format', 
							width, 0, 
							size, tmpdata);
						break;
					case TEX_TYPE_2D:
						glCompressedTexImage2DARB(GL_TEXTURE_2D, mip, format',
							width, height, 0, 
							size, tmpdata);
						break;
					case TEX_TYPE_3D:
						glCompressedTexImage3DARB(GL_TEXTURE_3D, mip, format',
							width, height, depth, 0, 
							size, tmpdata);
						break;
					case TEX_TYPE_CUBE_MAP:
						for(int face=0; face<6; face++) {
							glCompressedTexImage2DARB(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, format',
								width, height, 0, 
								size, tmpdata);
						}
						break;
				};
				if(width>1)		width = width/2;
				if(height>1)	height = height/2;
				if(depth>1)		depth = depth/2;
			}
			delete [] tmpdata;
        -}
            
            -- Compressed formats
            -- Provide temporary buffer filled with zeroes as glCompressedTexImageXD does not
            -- accept a 0 pointer like normal glTexImageXD
            -- TODO: support compressed textures
            {-
            let setMip tmpdata (w,h,d) mip = do
                    -- compressedTexImage1D :: Proxy -> Level -> TextureSize1D -> Border -> CompressedPixelData a -> IO ()
                    -- CompressedPixelData !CompressedTextureFormat GLsizei (Ptr a)
                    let size = getMemorySize w h d mFormat
                    case texType of
                        { TEX_TYPE_1D       -> GL.compressedTexImage1D GL.NoProxy mip (GL.TextureSize1D w) 0 (GL.CompressedPixelData (CompressedTextureFormat ) size tmpdata)
                        ; TEX_TYPE_2D       -> 
                        ; TEX_TYPE_3D       -> 
                        ; TEX_TYPE_CUBE_MAP -> do
                        }
                    return (max 1 (w `div` 2), max 1 (h `div` 2), max 1 (d `div` 2))
                setMips :: Ptr Word8 -> IO ()
                setMips tmpdata = do
                    pokeArray tmpdata $ replicate size 0
                    -- Run through this process for every mipmap to pregenerate mipmap piramid
                    foldM_ (setMip tmpdata) (mWidth,mHeight,mDepth) [0..mNumMipmaps]
            allocaArray (getMemorySize mWidth mHeight mDepth mFormat) setMips
            -}
            return ()
        ; False -> do
            let setMip (w,h,d) mip = do
                    -- Normal formats
                    case texType of
                        { TEX_TYPE_1D       -> GL.texImage1D GL.NoProxy mip format' (GL.TextureSize1D w) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                        ; TEX_TYPE_2D       -> GL.texImage2D Nothing GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                        ; TEX_TYPE_3D       -> GL.texImage3D GL.NoProxy mip format' (GL.TextureSize3D w h d) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                        ; TEX_TYPE_CUBE_MAP -> do
                            GL.texImage2D (Just GL.TextureCubeMapPositiveX) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                            GL.texImage2D (Just GL.TextureCubeMapNegativeX) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                            GL.texImage2D (Just GL.TextureCubeMapPositiveY) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                            GL.texImage2D (Just GL.TextureCubeMapNegativeY) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                            GL.texImage2D (Just GL.TextureCubeMapPositiveZ) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                            GL.texImage2D (Just GL.TextureCubeMapNegativeZ) GL.NoProxy mip format' (GL.TextureSize2D w h) 0 (GL.PixelData GL.RGBA GL.UnsignedByte nullPtr)
                        }
                    return (max 1 (w `div` 2), max 1 (h `div` 2), max 1 (d `div` 2))
                f = fromIntegral
            -- Run through this process to pregenerate mipmap piramid
            foldM_ setMip (f mWidth,f mHeight,f mDepth) [0..(fromIntegral mNumMipmaps)]
        }

    -- TODO
    -- TEMP CODE
    when (isJust mimage) $ do
        let images = fromJust mimage
            imInfo i = (fmt,f w,f h)
              where
                f   = fromIntegral
                fmt = case imFormat i of
                    { PF_L8         -> GL.Luminance
                    ; PF_BYTE_LA    -> GL.LuminanceAlpha
                    ; PF_R8G8B8     -> GL.RGB
                    ; PF_R8G8B8A8   -> GL.RGBA
                    }
                w   = imWidth i
                h   = imHeight i
            upload2D target i = do
                let (fmt,w,h) = imInfo i
                debugM "createTexture" $ "upload2d: " ++ imName i
                GL.texImage2D target GL.NoProxy 0 format' (GL.TextureSize2D w h) 0 (GL.PixelData fmt GL.UnsignedByte $ imData i)
                
        errl <- GL.get GL.errors
        unless (null errl) $ debugM "createTexture" $ "glerrors: \"" ++ concat [show c ++ " - " ++ m ++ "\n" | GL.Error c m <- errl] ++ "\""
        case texType of
            { TEX_TYPE_1D       -> do
                let (fmt,w,h) = imInfo image
                    image     = head images  
                GL.texImage1D GL.NoProxy 0 format' (GL.TextureSize1D w) 0 (GL.PixelData fmt GL.UnsignedByte $ imData image)
            ; TEX_TYPE_2D       -> upload2D Nothing $ head images
            ; TEX_TYPE_CUBE_MAP -> mapM_ (uncurry upload2D) $ zip [ Just GL.TextureCubeMapPositiveZ -- front
                                                                  , Just GL.TextureCubeMapNegativeZ -- back
                                                                  , Just GL.TextureCubeMapPositiveY -- up
                                                                  , Just GL.TextureCubeMapNegativeY -- down
                                                                  , Just GL.TextureCubeMapNegativeX -- left
                                                                  , Just GL.TextureCubeMapPositiveX -- right
                                                                  ] images
            ; _ -> return ()
            }            
    --_createSurfaceList();

    -- Get final internal format
    -- TODO
    --mFormat = getBuffer(0,0)->getFormat();
    debugM "createTexture" $ "created texture: " ++ name
    return GLTexture
        { gltxName                      = name
        , gltxWidth                     = 0 --TODO
        , gltxHeight                    = 0 --TODO
        , gltxDepth                     = 0 --TODO
        , gltxNumRequestedMipmaps       = MIP_DEFAULT --TODO
        , gltxNumMipmaps                = 0 --TODO
        , gltxMipmapsHardwareGenerated  = True --TODO
        , gltxGamma                     = 0 --TODO
        , gltxHwGamma                   = True --TODO
        , gltxFSAA                      = 0 --TODO
        , gltxFSAAHint                  = "" --TODO
        , gltxTextureType               = texType
        , gltxFormat                    = PF_R8G8B8 -- TODO
        , gltxUsage                     = TextureUsage HBU_WRITE_ONLY False False-- TODO
        , gltxSrcFormat                 = PF_R8G8B8 -- TODO
        , gltxSrcWidth                  = 0 --TODO
        , gltxSrcHeight                 = 0 --TODO
        , gltxSrcDepth                  = 0 --TODO
    
        , gltxDesiredFormat             = PF_R8G8B8 --TODO
        , gltxDesiredIntegerBitDepth    = 0 --TODO
        , gltxDesiredFloatBitDepth      = 0 --TODO
        , gltxTreatLuminanceAsAlpha     = True --TODO
        
        , gltxTextureObject             = mTextureID
        }
{-
	void GLTexture::_createSurfaceList()
	{
		mSurfaceList.clear();
		
		// For all faces and mipmaps, store surfaces as HardwarePixelBufferSharedPtr
		bool wantGeneratedMips = (mUsage & TU_AUTOMIPMAP)!=0;
		
		// Do mipmapping in software? (uses GLU) For some cards, this is still needed. Of course,
		// only when mipmap generation is desired.
		bool doSoftware = wantGeneratedMips && !mMipmapsHardwareGenerated && getNumMipmaps(); 
		
		for(size_t face=0; face<getNumFaces(); face++)
		{
			for(size_t mip=0; mip<=getNumMipmaps(); mip++)
			{
                GLHardwarePixelBuffer *buf = new GLTextureBuffer(mName, getGLTextureTarget(), mTextureID, face, mip,
						static_cast<HardwareBuffer::Usage>(mUsage), doSoftware && mip==0, mHwGamma, mFSAA);
				mSurfaceList.push_back(HardwarePixelBufferSharedPtr(buf));
                
                /// Check for error
                if(buf->getWidth()==0 || buf->getHeight()==0 || buf->getDepth()==0)
                {
                    OGRE_EXCEPT(
                        Exception::ERR_RENDERINGAPI_ERROR, 
                        "Zero sized texture surface on texture "+getName()+
                            " face "+StringConverter::toString(face)+
                            " mipmap "+StringConverter::toString(mip)+
                            ". Probably, the GL driver refused to create the texture.", 
                            "GLTexture::_createSurfaceList");
                }
			}
		}
	}
-}