{-# LANGUAGE ForeignFunctionInterface, CPP #-}
--------------------------------------------------------------------------------
-- |
-- Copyright   :  (c) Edward Kmett 2014, (c) Sven Panne 2013
-- License     :  BSD3
--
-- Maintainer  :  Edward Kmett <ekmett@gmail.com>
-- Stability   :  stable
-- Portability :  portable
--
-- This module offers a portable way to retrieve OpenGL extension entries,
-- providing a portability layer upon platform-specific mechanisms like
-- @glXGetProcAddress@, @wglGetProcAddress@ or @NSAddressOfSymbol@.
--
-- This internal module offers convenience functions and re-exports for OpenGL
-- extension loading.
--
--------------------------------------------------------------------------------

module Graphics.GL.Internal.Proc
  ( getProcAddress
  , Invoker
  , extensions
  ) where

import Control.Monad
import Data.Functor
import Data.Set as Set
import Foreign.C.String
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import Graphics.GL.Internal.FFI
  ( ffienumIOPtrubyte
  , ffienumuintIOPtrubyte
  , ffienumPtrintIOV
  )
import System.IO.Unsafe

--------------------------------------------------------------------------------

-- | Retrieve an OpenGL extension entry by name. Returns 'nullFunPtr' when no
-- extension entry with the given name was found.
getProcAddress :: String -> IO (FunPtr a)
getProcAddress extensionEntry =
  withCString extensionEntry hs_gl_getProcAddress

foreign import ccall unsafe "hs_gl_getProcAddress"
  hs_gl_getProcAddress :: CString -> IO (FunPtr a)

--------------------------------------------------------------------------------

type Invoker a = FunPtr a -> a

extensions :: Set String
extensions = unsafePerformIO $ do
  -- glGetStringi is only present in OpenGL 3.0 and OpenGL ES 3.0, and newer.
  glGetStringiFunPtr <- getProcAddress "glGetStringi"
  if glGetStringiFunPtr == nullFunPtr then do
    glGetString <- ffienumIOPtrubyte <$> getProcAddress "glGetString"
    supported <- glGetString 0x1F03 >>= peekCString . castPtr
    return $ Set.fromList (words supported)
  else do
    let glGetStringi = ffienumuintIOPtrubyte glGetStringiFunPtr
    glGetIntegerv <- ffienumPtrintIOV <$> getProcAddress "glGetIntegerv"
    numExtensions <- alloca $ \p -> glGetIntegerv 0x821D p >> peek p
    supported <- forM [0..fromIntegral numExtensions-1] $ glGetStringi 0x1F03 >=> peekCString . castPtr
    return $ Set.fromList supported
{-# NOINLINE extensions #-}