caramia-0.7.2.0: src/Graphics/Caramia/Texture.hs
-- | Textures.
--
{-# LANGUAGE RecordWildCards, ScopedTypeVariables, NoImplicitPrelude #-}
{-# LANGUAGE MultiWayIf, ViewPatterns, DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
module Graphics.Caramia.Texture
(
-- * Creating textures
newTexture
, Texture()
, TextureSpecification(..)
, textureSpecification
, Topology(..)
-- * Uploading to textures
, uploadToTexture
, Uploading(..)
, uploading1D
, uploading2D
, uploading3D
, UploadFormat(..)
, CubeSide(..)
-- * Texture units
, TextureUnit
-- * Mipmapping
, generateMipmaps
-- * Texture parameters
, setWrapping
, getWrapping
, setMinFilter
, setMagFilter
, getMinFilter
, getMagFilter
, setAnisotropy
, getAnisotropy
, setCompareMode
, getCompareMode
, MinFilter(..)
, MagFilter(..)
, Wrapping(..)
, CompareMode(..)
-- * Views
, viewSpecification
, viewWidth
, viewHeight
, viewDepth
, viewMipmapLevels
, viewSize2D
, viewSize3D
-- * Utilities
, maxMipmapLevels )
where
import Control.Monad.IO.Class
import Control.Monad.Catch
import Data.Data ( Data )
import GHC.Generics
import qualified Graphics.Caramia.Buffer.Internal as Buf
import Graphics.Caramia.ImageFormats.Internal
import Graphics.Caramia.Internal.Exception
import Graphics.Caramia.Internal.TexStorage
import Graphics.Caramia.Internal.OpenGLCApi
import Graphics.Caramia.Prelude
import Graphics.Caramia.Resource
import Graphics.Caramia.Texture.Internal
import Graphics.GL.Ext.ARB.TextureBufferObject
import Graphics.GL.Ext.EXT.TextureFilterAnisotropic
import Graphics.GL.Ext.ARB.TextureMultisample
import Graphics.GL.Ext.ARB.TextureStorage
import Foreign
import Linear.V2 ( V2(..) )
import Linear.V3 ( V3(..) )
textureSpecification :: TextureSpecification
textureSpecification = TextureSpecification {
topology = error "textureSpecification: topology is not set."
, imageFormat = error "textureSpecification: image format is not set."
, mipmapLevels = 1 }
-- | Returns the width of a texture.
viewWidth :: Texture -> Int
viewWidth (viewSpecification -> spec) = viewWidth' (topology spec)
where
viewWidth' (Tex1D {..}) = width1D
viewWidth' (Tex2D {..}) = width2D
viewWidth' (Tex3D {..}) = width3D
viewWidth' (Tex1DArray {..}) = width1DArray
viewWidth' (Tex2DArray {..}) = width2DArray
viewWidth' (Tex2DMultisample {..}) = width2DMS
viewWidth' (Tex2DMultisampleArray {..}) = width2DMSArray
viewWidth' (TexCube {..}) = widthCube
viewWidth' (TexBuffer {}) =
error "viewWidth: buffer texture has no meaningful width."
-- TODO: you can actually infer that from the buffer size
-- so implement it
-- | Returns the size of a texture, as a `V2`. Width and height.
--
-- @
-- viewSize2D tex = V2 (viewWidth tex) (viewHeight tex)
-- @
viewSize2D :: Texture -> V2 Int
viewSize2D tex = V2 (viewWidth tex) (viewHeight tex)
-- | Returns the size of a texture, as a `V3`. Width, height and depth.
--
-- @
-- viewSize3D tex = V3 (viewWidth tex) (viewHeight tex) (viewDepth tex)
-- @
viewSize3D :: Texture -> V3 Int
viewSize3D tex = V3 (viewWidth tex) (viewHeight tex) (viewDepth tex)
-- | Returns the height of a texture.
--
-- This is 1 for one-dimensional textures.
viewHeight :: Texture -> Int
viewHeight (viewSpecification -> spec) = viewHeight' (topology spec)
where
viewHeight' (Tex1D {..}) = 1
viewHeight' (Tex2D {..}) = height2D
viewHeight' (Tex3D {..}) = height3D
viewHeight' (Tex1DArray {..}) = 1
viewHeight' (Tex2DArray {..}) = height2DArray
viewHeight' (Tex2DMultisample {..}) = height2DMS
viewHeight' (Tex2DMultisampleArray {..}) = height2DMSArray
viewHeight' (TexCube {..}) = widthCube
viewHeight' (TexBuffer {}) = 1
-- | Returns the depth of a 3D texture or number of layers in array textures.
--
-- This is 1 for any other type of texture.
viewDepth :: Texture -> Int
viewDepth (viewSpecification -> spec) = viewDepth' (topology spec)
where
viewDepth' (Tex1D {..}) = 1
viewDepth' (Tex2D {..}) = 1
viewDepth' (Tex3D {..}) = depth3D
viewDepth' (Tex1DArray {..}) = layers1D
viewDepth' (Tex2DArray {..}) = layers2D
viewDepth' (Tex2DMultisample {..}) = 1
viewDepth' (Tex2DMultisampleArray {..}) = layers2DMS
viewDepth' (TexCube {..}) = 1
viewDepth' (TexBuffer {}) = 1
viewMipmapLevels :: Texture -> Int
viewMipmapLevels = mipmapLevels . viewSpecification
isMultisamplingTopology :: Topology -> Bool
isMultisamplingTopology (Tex2DMultisample {..}) = True
isMultisamplingTopology (Tex2DMultisampleArray {..}) = True
isMultisamplingTopology _ = False
-- | Creates a new texture.
--
-- Initially the contents of the texture are undefined.
--
-- Texture dimensions must be positive.
newTexture :: MonadIO m
=> TextureSpecification
-> m Texture
newTexture spec = liftIO $ mask_ $ do
topologySanityCheck (topology spec)
when (not (isMultisamplingTopology (topology spec)) &&
mipmapLevels spec < 1) $
error "newTexture: mipmapLevels is not positive."
res <- newResource creator
deleter
(return ())
index <- newUnique
return Texture { resource = res
, ordIndex = index
, viewSpecification = spec }
where
num_mipmaps = mipmapLevels spec
-- a lot of code just to check that all the dimensions are positive...
topologySanityCheck t@(Tex1D {..})
| width1D <= 0 = badTopology t
| not (isValidMipmap width1D num_mipmaps) = badMipmaps
| otherwise = return ()
topologySanityCheck t@(Tex2D {..})
| width2D <= 0 || height2D <= 0 = badTopology t
| not (isValidMipmap (max width2D height2D) num_mipmaps) = badMipmaps
| otherwise = return ()
topologySanityCheck t@(Tex3D {..})
| width3D <= 0 || height3D <= 0 || depth3D <= 0 = badTopology t
| not (isValidMipmap (max width3D $ max height3D depth3D) num_mipmaps) =
badMipmaps
| otherwise = return ()
topologySanityCheck t@(Tex1DArray {..})
| width1DArray <= 0 || layers1D <= 0 = badTopology t
| not (isValidMipmap width1DArray num_mipmaps) =
badMipmaps
| otherwise = return ()
topologySanityCheck t@(Tex2DArray {..})
| width2DArray <= 0 || height2DArray <= 0 ||
layers2D <= 0 = badTopology t
| not (isValidMipmap (max width2DArray height2DArray) num_mipmaps) =
badMipmaps
| otherwise = return ()
topologySanityCheck t@(Tex2DMultisample {..})
| width2DMS <= 0 || height2DMS <= 0 = badTopology t
| otherwise = return ()
topologySanityCheck t@(Tex2DMultisampleArray {..})
| width2DMSArray <= 0 || height2DMSArray <= 0 ||
layers2DMS <= 0 = badTopology t
| otherwise = return ()
topologySanityCheck t@(TexCube {..})
| widthCube <= 0 = badTopology t
| not (isValidMipmap widthCube num_mipmaps) =
badMipmaps
| otherwise = return ()
topologySanityCheck (TexBuffer {}) = return ()
badTopology _ = error "newTexture: bad topology."
badMipmaps =
error $ "newTexture: bad number of mipmap levels: " <> show num_mipmaps
deleter (Texture_ name) =
with name $ glDeleteTextures 1
creator = do
name <- bracketOnError
(alloca $ \name_ptr ->
glGenTextures 1 name_ptr *> peek name_ptr)
(deleter . Texture_ )
(\name -> do
if gl_ARB_texture_storage
then createByTopologyTexStorage name (topology spec)
else createByTopologyFakeTextureStorage name (topology spec)
return name)
return (Texture_ name)
createByTopologyFakeTextureStorage :: GLuint -> Topology -> IO ()
createByTopologyFakeTextureStorage name (Tex1D {..}) =
fakeTextureStorage1D name
GL_TEXTURE_1D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width1D)
createByTopologyFakeTextureStorage name (Tex2D {..}) =
fakeTextureStorage2D name
GL_TEXTURE_2D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width2D)
(safeFromIntegral height2D)
createByTopologyFakeTextureStorage name (Tex3D {..}) =
fakeTextureStorage3D name
GL_TEXTURE_3D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width3D)
(safeFromIntegral height3D)
(safeFromIntegral depth3D)
createByTopologyFakeTextureStorage name (Tex1DArray {..}) =
fakeTextureStorage2D name
GL_TEXTURE_1D_ARRAY
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width1DArray)
(safeFromIntegral layers1D)
createByTopologyFakeTextureStorage name (Tex2DArray {..}) =
fakeTextureStorage3D name
GL_TEXTURE_2D_ARRAY
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width2DArray)
(safeFromIntegral height2DArray)
(safeFromIntegral layers2D)
createByTopologyFakeTextureStorage name tex@(Tex2DMultisample {..}) =
createByTopologyTexStorage name tex
createByTopologyFakeTextureStorage name tex@(Tex2DMultisampleArray {..}) =
createByTopologyTexStorage name tex
createByTopologyFakeTextureStorage name (TexCube {..}) =
fakeTextureStorage2D name
GL_TEXTURE_CUBE_MAP
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral widthCube)
(safeFromIntegral widthCube)
createByTopologyFakeTextureStorage name tex@(TexBuffer {..}) =
createByTopologyTexStorage name tex
-- TODO: use DSA when available, perhaps add mglTextureStorage* functions
-- to Caramia.Internal.OpenGLCApi?
createByTopologyTexStorage :: GLuint -> Topology -> IO ()
createByTopologyTexStorage name (Tex1D {..}) =
withBinding GL_TEXTURE_1D GL_TEXTURE_BINDING_1D name $
glTexStorage1D GL_TEXTURE_1D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width1D)
createByTopologyTexStorage name (Tex2D {..}) =
withBinding GL_TEXTURE_2D GL_TEXTURE_BINDING_2D name $
glTexStorage2D GL_TEXTURE_2D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width2D)
(safeFromIntegral height2D)
createByTopologyTexStorage name (Tex3D {..}) =
withBinding GL_TEXTURE_3D GL_TEXTURE_BINDING_3D name $
glTexStorage3D GL_TEXTURE_3D
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width3D)
(safeFromIntegral height3D)
(safeFromIntegral depth3D)
createByTopologyTexStorage name (Tex1DArray {..}) =
withBinding GL_TEXTURE_1D_ARRAY GL_TEXTURE_BINDING_1D_ARRAY name $
glTexStorage2D GL_TEXTURE_1D_ARRAY
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width1DArray)
(safeFromIntegral layers1D)
createByTopologyTexStorage name (Tex2DArray {..}) =
withBinding GL_TEXTURE_2D_ARRAY GL_TEXTURE_BINDING_2D_ARRAY name $
glTexStorage3D GL_TEXTURE_2D_ARRAY
(safeFromIntegral num_mipmaps)
(toConstantIF (imageFormat spec))
(safeFromIntegral width2DArray)
(safeFromIntegral height2DArray)
(safeFromIntegral layers2D)
createByTopologyTexStorage name (Tex2DMultisample {..}) =
checkOpenGLOrExtensionM (OpenGLVersion 3 2)
"GL_ARB_texture_multisample"
gl_ARB_texture_multisample $
withBinding GL_TEXTURE_2D_MULTISAMPLE
GL_TEXTURE_BINDING_2D_MULTISAMPLE
name $
glTexImage2DMultisample
GL_TEXTURE_2D_MULTISAMPLE
(safeFromIntegral samples2DMS)
(fromIntegral $ toConstantIF (imageFormat spec))
(safeFromIntegral width2DMS)
(safeFromIntegral height2DMS)
(if fixedSampleLocations2DMS
then 1 else 0)
createByTopologyTexStorage name (Tex2DMultisampleArray {..}) =
checkOpenGLOrExtensionM (OpenGLVersion 3 2)
"GL_ARB_texture_multisample"
gl_ARB_texture_multisample $
withBinding GL_TEXTURE_2D_MULTISAMPLE_ARRAY
GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY
name $
glTexImage3DMultisample
GL_TEXTURE_2D_MULTISAMPLE_ARRAY
(safeFromIntegral samples2DMSArray)
(fromIntegral $ toConstantIF (imageFormat spec))
(safeFromIntegral width2DMSArray)
(safeFromIntegral height2DMSArray)
(safeFromIntegral layers2DMS)
(if fixedSampleLocations2DMSArray
then 1 else 0)
createByTopologyTexStorage name (TexCube {..}) =
withBinding GL_TEXTURE_CUBE_MAP
GL_TEXTURE_BINDING_CUBE_MAP
name $
glTexStorage2D GL_TEXTURE_CUBE_MAP
(safeFromIntegral num_mipmaps)
(fromIntegral $ toConstantIF (imageFormat spec))
(safeFromIntegral widthCube)
(safeFromIntegral widthCube)
createByTopologyTexStorage name (TexBuffer {..}) =
checkOpenGLOrExtensionM (OpenGLVersion 3 1)
"GL_ARB_texture_buffer_object"
gl_ARB_texture_buffer_object $
withBinding GL_TEXTURE_BUFFER
GL_TEXTURE_BINDING_BUFFER
name $
withResource (Buf.resource texBuffer) $ \(Buf.Buffer_ bufname) ->
glTexBuffer GL_TEXTURE_BUFFER
(fromIntegral $ toConstantIF (imageFormat spec))
bufname
-- | Generate all mipmaps for a texture. If mipmap levels were specified, that
-- is.
generateMipmaps :: (MonadIO m, MonadMask m) => Texture -> m ()
generateMipmaps = flip withBindingByTopology glGenerateMipmap
-- | Specifies the format in which buffer data is for the purposes of uploading
-- said data to a texture.
data UploadFormat =
UR -- ^ Just red.
| URG -- ^ Red and green.
| URGB -- ^ You know the drill.
| URGBA
| UBGR
| UBGRA
| UDEPTH_COMPONENT -- ^ Depth values.
| USTENCIL_INDEX -- ^ Stencil values.
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic )
-- TODO: add UDEPTH_STENCIL when `SpecificationType` has special interpretation
-- formats.
toConstantUF :: UploadFormat -> GLenum
toConstantUF UR = GL_RED
toConstantUF URG = GL_RG
toConstantUF URGB = GL_RGB
toConstantUF URGBA = GL_RGBA
toConstantUF UBGR = GL_BGR
toConstantUF UBGRA = GL_BGRA
toConstantUF UDEPTH_COMPONENT = GL_DEPTH_COMPONENT
toConstantUF USTENCIL_INDEX = GL_STENCIL_INDEX
-- | Used to specify how to move the data from a `Buffer` to a `Texture` in
-- `uploadToTexture`.
--
-- This is common for all texture topologies. However, some fields are ignored
-- depending on the topology.
--
-- For example, if you upload into a 1D texture, then all fields that deal with
-- higher dimensions (`yOffset`, `zOffset`, `uHeight` etc.) are ignored.
--
-- It is recommended that you use one of the smart constructors as they
-- implement the common use cases so you don't have to fill all these fields by
-- yourself.
data Uploading = Uploading
{ fromBuffer :: !Buf.Buffer -- ^ From which buffer to upload.
, bufferOffset :: !Int -- ^ Offset in the buffer, in bytes,
-- from where to start uploading.
, toMipmapLevel :: !Int -- ^ To which mipmap level to upload.
-- (0 = base level).
, specificationType :: !SpecificationType
-- ^ What data type is used for each component value in a pixel.
, uploadFormat :: !UploadFormat
-- ^ What format is the source data in.
, xOffset :: !Int -- ^ X offset where to put the data.
, yOffset :: !Int -- ^ Y offset where to put the data.
, zOffset :: !Int -- ^ Z offset where to put the data.
, uWidth :: !Int -- ^ Width of the data to put.
, uHeight :: !Int -- ^ Height of the data to put.
, uDepth :: !Int -- ^ Number of 2D images to put.
, cubeSide :: CubeSide -- ^ Only used for cube map textures.
-- Specifies which side of the cube to upload.
-- Not evaluated if the texture is not a cube
-- texture.
, numColumns :: !Int
-- ^ Number of columns in the image in the source buffer. This value is
-- also sometimes known as \'pitch\'. It is the same as `uWidth` except in
-- cases where the next row in source data does not come immediately after
-- the current row but after `numColumns` from the first pixel in the row.
, numRows :: !Int
-- ^ Same as `numColumns` but for images in 3D uploading.
, pixelAlignment :: !Int
-- ^ Alignment in which the source texture data is. Every row is aligned to
-- this value. Allowed values are 1, 2, 4 and 8. The default value in smart
-- constructors is 1.
}
deriving ( Eq, Typeable )
-- | Values of this type refer to sides of a cube.
data CubeSide =
PositiveY
| NegativeY
| PositiveX
| NegativeX
| PositiveZ
| NegativeZ
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic )
toConstantCS :: CubeSide -> GLenum
toConstantCS PositiveX = GL_TEXTURE_CUBE_MAP_POSITIVE_X
toConstantCS NegativeX = GL_TEXTURE_CUBE_MAP_NEGATIVE_X
toConstantCS PositiveY = GL_TEXTURE_CUBE_MAP_POSITIVE_Y
toConstantCS NegativeY = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
toConstantCS PositiveZ = GL_TEXTURE_CUBE_MAP_POSITIVE_Z
toConstantCS NegativeZ = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
-- | Constructs a common 1D uploading.
uploading1D :: Buf.Buffer
-> Int -- ^ How many pixels to upload.
-> SpecificationType
-> UploadFormat
-> Uploading
uploading1D buffer pixels stype uf =
Uploading {
fromBuffer = buffer
, bufferOffset = 0
, specificationType = stype
, uploadFormat = uf
, toMipmapLevel = 0
, xOffset = 0
, yOffset = 0
, zOffset = 0
, uWidth = pixels
, uHeight = 1
, uDepth = 1
, numColumns = pixels
, cubeSide = PositiveY
, numRows = 1
, pixelAlignment = 1 }
-- | Constructs a common 2D uploading.
--
-- This can also be used for uploading into 1D texture arrays.
uploading2D :: Buf.Buffer
-> Int -- ^ Width of the image to upload.
-> Int -- ^ Height of the image to upload.
-> SpecificationType
-> UploadFormat
-> Uploading
uploading2D buffer width height stype uf =
Uploading {
fromBuffer = buffer
, bufferOffset = 0
, specificationType = stype
, uploadFormat = uf
, toMipmapLevel = 0
, xOffset = 0
, yOffset = 0
, zOffset = 0
, uWidth = width
, uHeight = height
, uDepth = 1
, numColumns = width
, cubeSide = PositiveY
, numRows = height
, pixelAlignment = 1 }
-- | Constructs a common 3D uploading.
--
-- This can also be used for uploading into 2D texture arrays.
uploading3D :: Buf.Buffer
-> Int -- ^ Width of the image to upload.
-> Int -- ^ Height of the image to upload.
-> Int -- ^ Number of images to upload.
-> SpecificationType
-> UploadFormat
-> Uploading
uploading3D buffer width height depth stype uf =
Uploading {
fromBuffer = buffer
, bufferOffset = 0
, specificationType = stype
, uploadFormat = uf
, toMipmapLevel = 0
, xOffset = 0
, yOffset = 0
, zOffset = 0
, uWidth = width
, uHeight = height
, uDepth = depth
, numColumns = width
, cubeSide = PositiveY
, numRows = height
, pixelAlignment = 1 }
-- | Uploads an image to a texture.
uploadToTexture :: MonadIO m
=> Uploading
-> Texture
-> m ()
uploadToTexture uploading tex = liftIO $ mask_ $
withResource (Buf.resource (fromBuffer uploading)) $ \(Buf.Buffer_ buf) ->
withBoundPixelUnpackBuffer buf $ do
old_num_cols <- fromIntegral <$> gi GL_UNPACK_ROW_LENGTH
old_num_rows <- fromIntegral <$> gi GL_UNPACK_IMAGE_HEIGHT
old_alignment <- fromIntegral <$> gi GL_UNPACK_ALIGNMENT
glPixelStorei GL_UNPACK_ROW_LENGTH
(safeFromIntegral $ numColumns uploading)
flip finally (glPixelStorei GL_UNPACK_ROW_LENGTH old_num_cols) $ do
glPixelStorei GL_UNPACK_IMAGE_HEIGHT
(safeFromIntegral $ numRows uploading)
flip finally (glPixelStorei GL_UNPACK_IMAGE_HEIGHT old_num_rows) $ do
glPixelStorei GL_UNPACK_ALIGNMENT
(safeFromIntegral $ pixelAlignment uploading)
flip finally (glPixelStorei GL_UNPACK_ALIGNMENT old_alignment) $
withResource (resource tex) $ \(Texture_ texname) ->
case topology $ viewSpecification tex of
Tex1D {..} ->
upload1D GL_TEXTURE_1D GL_TEXTURE_BINDING_1D
texname uploading
Tex2D {..} ->
upload2D GL_TEXTURE_2D GL_TEXTURE_BINDING_2D
texname uploading
Tex3D {..} ->
upload3D GL_TEXTURE_3D GL_TEXTURE_BINDING_3D
texname uploading
Tex1DArray {..} ->
upload2D GL_TEXTURE_1D_ARRAY GL_TEXTURE_BINDING_1D_ARRAY
texname uploading
Tex2DArray {..} ->
upload3D GL_TEXTURE_2D_ARRAY GL_TEXTURE_BINDING_2D_ARRAY
texname uploading
Tex2DMultisample {..} ->
error $ "uploadToTexture: cannot upload to " <>
"multisampling textures."
Tex2DMultisampleArray {..} ->
error $ "uploadToTexture: cannot upload to " <>
"multisampling array textures."
TexCube {..} ->
uploadCube GL_TEXTURE_CUBE_MAP
GL_TEXTURE_BINDING_CUBE_MAP
texname uploading
TexBuffer {..} ->
error $ "uploadToTexture: cannot upload to " <>
"buffer textures. (please upload directly to the " <>
"associated buffer instead.)"
upload1D :: GLenum -> GLenum -> GLuint -> Uploading -> IO ()
upload1D target binding tex (Uploading {..}) =
withBinding target binding tex $
glTexSubImage1D target
(safeFromIntegral toMipmapLevel)
(safeFromIntegral xOffset)
(safeFromIntegral uWidth)
(toConstantUF uploadFormat)
(toConstantST specificationType)
(intPtrToPtr $
fromIntegral bufferOffset)
upload2D :: GLenum -> GLenum -> GLuint -> Uploading -> IO ()
upload2D target binding tex (Uploading {..}) =
withBinding target binding tex $
glTexSubImage2D target
(safeFromIntegral toMipmapLevel)
(safeFromIntegral xOffset)
(safeFromIntegral yOffset)
(safeFromIntegral uWidth)
(safeFromIntegral uHeight)
(toConstantUF uploadFormat)
(toConstantST specificationType)
(intPtrToPtr $
fromIntegral bufferOffset)
upload3D :: GLenum -> GLenum -> GLuint -> Uploading -> IO ()
upload3D target binding tex (Uploading {..}) =
withBinding target binding tex $
glTexSubImage3D target
(safeFromIntegral toMipmapLevel)
(safeFromIntegral xOffset)
(safeFromIntegral yOffset)
(safeFromIntegral zOffset)
(safeFromIntegral uWidth)
(safeFromIntegral uHeight)
(safeFromIntegral uDepth)
(toConstantUF uploadFormat)
(toConstantST specificationType)
(intPtrToPtr $
fromIntegral bufferOffset)
uploadCube :: GLenum -> GLenum -> GLuint -> Uploading -> IO ()
uploadCube target binding tex (Uploading {..}) =
withBinding target binding tex $
glTexSubImage2D (toConstantCS cubeSide)
(safeFromIntegral toMipmapLevel)
(safeFromIntegral xOffset)
(safeFromIntegral yOffset)
(safeFromIntegral uWidth)
(safeFromIntegral uHeight)
(toConstantUF uploadFormat)
(toConstantST specificationType)
(intPtrToPtr $
fromIntegral bufferOffset)
isValidMipmap :: Int -> Int -> Bool
isValidMipmap w level
| w <= 0 = False
| level < 0 = False
| level > floor (logBase (2 :: Double) (fromIntegral w)) + 1 = False
| otherwise = True
-- | Returns the maximal number of mipmap levels when given a side length.
maxMipmapLevels :: Int -> Int
maxMipmapLevels width
| width <= 0 = 0
| otherwise = floor (logBase (2 :: Double) (fromIntegral width)) + 1
class TexParam a where
tpEnum :: a -> GLenum
tpToConstant :: a -> GLenum
tpFromConstant :: GLenum -> a
data MinFilter =
MiNearest
| MiLinear
| MiNearestMipmapNearest
| MiLinearMipmapNearest
| MiNearestMipmapLinear
| MiLinearMipmapLinear
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic, Enum )
data MagFilter =
MaNearest
| MaLinear
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic, Enum )
data Wrapping =
Clamp
| Repeat
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic, Enum )
-- | Texture comparison modes.
--
-- See @ glTexParameteri @ documentation in OpenGL.
data CompareMode
= NoCompare
| CompareRefToTexture
deriving ( Eq, Ord, Show, Read, Typeable, Data, Generic, Enum )
toConstantC :: CompareMode -> GLenum
toConstantC NoCompare = GL_NONE
toConstantC CompareRefToTexture = GL_COMPARE_REF_TO_TEXTURE
toConstantW :: Wrapping -> GLenum
toConstantW Clamp = GL_CLAMP_TO_EDGE
toConstantW Repeat = GL_REPEAT
instance TexParam MinFilter where
tpEnum _ = GL_TEXTURE_MIN_FILTER
tpToConstant MiNearest = GL_NEAREST
tpToConstant MiLinear = GL_LINEAR
tpToConstant MiNearestMipmapNearest = GL_NEAREST_MIPMAP_NEAREST
tpToConstant MiLinearMipmapNearest = GL_LINEAR_MIPMAP_NEAREST
tpToConstant MiNearestMipmapLinear = GL_NEAREST_MIPMAP_LINEAR
tpToConstant MiLinearMipmapLinear = GL_LINEAR_MIPMAP_LINEAR
tpFromConstant c
| c == GL_NEAREST = MiNearest
| c == GL_LINEAR = MiLinear
| c == GL_NEAREST_MIPMAP_NEAREST = MiNearestMipmapNearest
| c == GL_LINEAR_MIPMAP_NEAREST = MiLinearMipmapNearest
| c == GL_NEAREST_MIPMAP_LINEAR = MiNearestMipmapLinear
| c == GL_LINEAR_MIPMAP_LINEAR = MiLinearMipmapLinear
| otherwise = error "MinFilter: unexpected filtering value."
instance TexParam MagFilter where
tpEnum _ = GL_TEXTURE_MAG_FILTER
tpToConstant MaNearest = GL_NEAREST
tpToConstant MaLinear = GL_LINEAR
tpFromConstant c
| c == GL_NEAREST = MaNearest
| c == GL_LINEAR = MaLinear
| otherwise = error "MagFilter: unexpected filtering value."
setMinFilter :: (MonadIO m, MonadMask m) => MinFilter -> Texture -> m ()
setMinFilter = setTexParam
setMagFilter :: (MonadIO m, MonadMask m) => MagFilter -> Texture -> m ()
setMagFilter = setTexParam
getMinFilter :: MonadIO m => Texture -> m MinFilter
getMinFilter = getTexParam
getMagFilter :: MonadIO m => Texture -> m MagFilter
getMagFilter = getTexParam
setTexParam :: (MonadIO m, MonadMask m, TexParam a) => a -> Texture -> m ()
setTexParam param tex = withBindingByTopology tex $ \target ->
glTexParameteri target (tpEnum param) (fromIntegral $ tpToConstant param)
getTexParam :: forall m a. (MonadIO m, TexParam a) => Texture -> m a
getTexParam tex = liftIO $ withBindingByTopology tex $ \target ->
alloca $ \result_ptr -> do
glGetTexParameteriv target (tpEnum (undefined :: a)) result_ptr
tpFromConstant . fromIntegral <$> peek result_ptr
setWrapping :: (MonadIO m, MonadMask m) => Wrapping -> Texture -> m ()
setWrapping wrapping tex = withBindingByTopology tex $ \target -> do
glTexParameteri target GL_TEXTURE_WRAP_S
(fromIntegral $ toConstantW wrapping)
glTexParameteri target GL_TEXTURE_WRAP_T
(fromIntegral $ toConstantW wrapping)
glTexParameteri target GL_TEXTURE_WRAP_R
(fromIntegral $ toConstantW wrapping)
setCompareMode :: (MonadIO m, MonadMask m) => CompareMode -> Texture -> m ()
setCompareMode cmp_mode tex = withBindingByTopology tex $ \target ->
glTexParameteri target GL_TEXTURE_COMPARE_MODE
(fromIntegral $ toConstantC cmp_mode)
getCompareMode :: (MonadIO m, MonadMask m) => Texture -> m CompareMode
getCompareMode tex = liftIO $ withBindingByTopology tex $ \target ->
alloca $ \result_ptr -> do
glGetTexParameteriv target GL_TEXTURE_COMPARE_MODE result_ptr
result <- peek result_ptr
return $ if
| result == GL_NONE -> NoCompare
| result == GL_COMPARE_REF_TO_TEXTURE -> CompareRefToTexture
| otherwise -> error "getCompareMode: unexpected comparing mode."
getWrapping :: (MonadIO m, MonadMask m) => Texture -> m Wrapping
getWrapping tex = liftIO $ withBindingByTopology tex $ \target ->
alloca $ \result_ptr -> do
glGetTexParameteriv target GL_TEXTURE_WRAP_S result_ptr
result <- peek result_ptr
return $ if
| result == GL_CLAMP_TO_EDGE -> Clamp
| result == GL_REPEAT -> Repeat
| otherwise -> error "getWrapping: unexpected wrapping mode."
setAnisotropy :: (MonadIO m, MonadMask m) => Float -> Texture -> m ()
setAnisotropy ani tex = withBindingByTopology tex $ \target ->
glTexParameterf target GL_TEXTURE_MAX_ANISOTROPY_EXT ani
getAnisotropy :: (MonadIO m, MonadMask m) => Texture -> m Float
getAnisotropy tex = liftIO $ withBindingByTopology tex $ \target ->
alloca $ \ani_ptr -> do
glGetTexParameterfv target GL_TEXTURE_MAX_ANISOTROPY_EXT ani_ptr
peek ani_ptr
{-
nextMipmapLevel :: Int -> Int
nextMipmapLevel 0 = 0
nextMipmapLevel 1 = 1
nextMipmapLevel x = max 1 (x `div` 2)
nthMipmapLevel :: Int -> Int -> Int
nthMipmapLevel x 0 = x
nthMipmapLevel x n = nthMipmapLevel (nextMipmapLevel x) (n-1)
-}