packages feed

GPipe-Core-0.2.3.0: src/Graphics/GPipe/Internal/Compiler.hs

{-# LANGUAGE PatternGuards, PatternSynonyms #-}
module Graphics.GPipe.Internal.Compiler where

import Graphics.GPipe.Internal.Context
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Exception (MonadException)
import qualified Data.IntMap as Map
import qualified Data.IntSet as Set
import Data.IntMap ((!))
import Data.Maybe
import Control.Monad
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Except
import Control.Monad.Trans.Class

import Graphics.GL.Core45
import Graphics.GL.Types
import Foreign.Marshal.Utils
import Foreign.Marshal.Alloc (alloca)
import Foreign.Storable (peek)
import Foreign.C.String
import Foreign.Marshal.Array
import Foreign.Ptr (nullPtr)
import Data.Either
import Control.Exception (throwIO)
import Data.IORef
import Data.List (zip5)
import Data.Word
import Graphics.GPipe.Internal.Debug
import System.IO
import Control.Monad.IO.Class

-- public
type WinId = Int

{-
A Drawcall is an OpenGL shader program with its context. Drawcalls are produced
when evaluating a (GPipe) Shader and are intended to be "compiled" (sources
compiled and linked into a program used by a render action).
-}
-- public
data Drawcall s = Drawcall
    {   drawcallFbo :: s ->
        (   Either WinId
                (   IO FBOKeys
                ,   IO ()
                )
        ,   IO ()
        )
    ,   feedbackBuffer :: Maybe (s -> IO (GLuint, GLuint, GLuint, GLuint))
        -- Key for RenderIOState::inputArrayToRenderIOs.
    ,   primitiveName :: Int
        -- Key for RenderIOState::rasterizationNameToRenderIO.
    ,   rasterizationName :: Maybe Int
        -- Shader sources.
    ,   vertexSource :: String
    ,   optionalGeometrySource :: Maybe String
    ,   optionalFragmentSource :: Maybe String
        -- Inputs.
    ,   usedInputs :: [Int]
        -- Uniforms and texture units used in each shader.
    ,   usedVUniforms :: [Int],   usedVSamplers :: [Int]
    ,   usedGUniforms :: [Int],   usedGSamplers :: [Int]
    ,   usedFUniforms :: [Int],   usedFSamplers :: [Int]
        -- The size of the uniform buffer for the primitive stream (see USize in PrimitiveStream data).
    ,   primStrUBufferSize :: Int
    }

-- public
mapDrawcall :: (s -> s') -> Drawcall s' -> Drawcall s
mapDrawcall f dc = dc{ drawcallFbo = drawcallFbo dc . f, feedbackBuffer = feedbackBuffer' }
    where
        feedbackBuffer' = case feedbackBuffer dc of
            Nothing -> Nothing
            Just b -> Just (b . f)

-- index/binding refers to what is used in the final shader. Index space is
-- limited, usually 16 attribname is what was declared, but all might not be
-- used. Attribname share namespace with uniforms and textures (in all shaders)
-- and is unlimited(TM)
-- What? Contradiction.
-- Should be used elsewhere instead of Int (of are they pre-alloc Int?).
-- public
type Binding = Int

-- TODO: Add usedBuffers to RenderIOState, ie Map.IntMap (s -> (Binding -> IO
--       (), Int)) and the like then create a function that checks that none of
--       the input buffers are used as output, and throws if it is

{- Contains the interactions between a GPipeShader and its environment. It is
populated when creating a GPipeShader and queried when compiling it into a
rendering action. In other words, it’s not a state at all, but some kind of
environment connector or adaptor. It is simply called a state because it build
using a State monad.
-}
-- public
data RenderIOState s = RenderIOState
    {   -- Uniform buffer objects bindings. TODO Return buffer name here when we
        -- start writing to buffers during rendering (transform feedback, buffer
        -- textures) -> Ok, but uniform only?
        uniformNameToRenderIO :: Map.IntMap (s -> Binding -> IO ())
        -- Texture units bindings. IO returns texturename for validating that it
        -- isnt used as render target
    ,   samplerNameToRenderIO :: Map.IntMap (s -> Binding -> IO Int)
        -- Final rasterization operations (mostly setting the viewport).
    ,   rasterizationNameToRenderIO :: Map.IntMap (s -> IO ())
        -- Final vertex processiong stage.
    ,   transformFeedbackToRenderIO :: Map.IntMap (s -> GLuint -> IO ())
        -- VAO bindings.
    ,   inputArrayToRenderIO :: Map.IntMap (s ->
        [   (   [Binding] -- inputs (drawcall's usedInputs)
            ,   GLuint -- primitive stream uniforms buffer
            ,   Int -- primitive stream uniforms buffer size
            ) ->
            (   (   IO [VAOKey] -- VAO names?
                ,   IO () -- To bind the VAO?
                )
            ,   IO () -- To draw with it.
            )
        ])
    }

-- public
newRenderIOState :: RenderIOState s
newRenderIOState = RenderIOState Map.empty Map.empty Map.empty Map.empty Map.empty

-- Why 'map'? Wouldn’t 'inject' be a better name?
-- public
mapRenderIOState :: (s -> s') -> RenderIOState s' -> RenderIOState s -> RenderIOState s
mapRenderIOState f (RenderIOState a' b' c' d' e') (RenderIOState a b c d e) =
    let merge x x' = Map.union x $ Map.map (\ g -> g . f) x'
    in  RenderIOState (merge a a') (merge b b') (merge c c') (merge d d') (merge e e')

-- | May throw a GPipeException
-- The multiple drawcalls to be compiled are intended to use the same environment 's' (and only one is selected dynamically when rendering).
-- public
compileDrawcalls :: (Monad m, MonadIO m, MonadException m, ContextHandler ctx)
    => [IO (Drawcall s)] -- The proto drawcalls to generate and compile.
    -> RenderIOState s -- Interactions between the drawcalls and the environment 's'.
    -> ContextT ctx os m (s -> Render os ()) -- The compiled drawcall (OpenGL program shader actually) as a function on an environment.
compileDrawcalls protoDrawcalls state = do

    (drawcalls, limitErrors) <- liftNonWinContextIO $ safeGenerateDrawcalls protoDrawcalls
    compilationResults <- liftNonWinContextIO $ mapM (innerCompile state) drawcalls
    let (compilationErrors, compiledDrawcalls) = partitionEithers compilationResults
        (programNameAndDeleters, renderers) = unzip compiledDrawcalls
        compositeRenderer x = mapM_ ($ x) renderers
        allErrors = limitErrors ++ compilationErrors

    if null allErrors
        then do
            -- Register each deleter separately on their program finalization.
            forM_ programNameAndDeleters $ \ (programNameRef, deleter) -> do
                programName <- liftIO $ readIORef programNameRef
                addContextFinalizer programNameRef deleter
            -- Return a composite rendering action.
            return compositeRenderer
        else do
            -- Directly call all the deleters.
            liftNonWinContextAsyncIO $ forM_ programNameAndDeleters $ \ (_, deleter) -> do
                deleter
            -- Raise an error.
            liftIO $ throwIO $ GPipeException $ concat allErrors

{- Generate the drawcalls (a single one each time in fact) and check their
inputs don't exceed some OpenGL limits.
-}
-- private
safeGenerateDrawcalls :: [IO (Drawcall s)] -- The proto drawcalls to generate.
    ->  IO (
        [   (   Drawcall s -- A generated drawcall.
            ,   [Int] -- Its uniform buffers used.
            ,   [Int] -- Its textures units used.
            ,   [Int] -- Its allocated uniforms.
            ,   [Int] -- Its allocated texture units.
            )
        ]
        , [String] -- The raised errors regarding exceeded limits.
        )
safeGenerateDrawcalls protoDrawcalls = do

    -- Retrieve some limits from OpenGL.
    [   maxGUnis, maxGSamplers,
        maxVUnis, maxVSamplers,
        maxFUnis, maxFSamplers,
        maxUnis, maxSamplers ]
        <- liftIO $
            mapM (\t -> fromIntegral <$> alloca (\ ptr -> glGetIntegerv t ptr >> peek ptr))
                [ GL_MAX_GEOMETRY_UNIFORM_BLOCKS
                , GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS
                , GL_MAX_VERTEX_UNIFORM_BLOCKS
                , GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS
                , GL_MAX_FRAGMENT_UNIFORM_BLOCKS
                , GL_MAX_TEXTURE_IMAGE_UNITS
                , GL_MAX_COMBINED_UNIFORM_BLOCKS
                , GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
                ]

    -- Build the drawcalls.
    drawcalls <- liftIO $ sequence protoDrawcalls -- IO only for SNMap

    let
        -- Collect stats from the drawcalls (bound uniforms and texture units).
        gUnisPerDrawcall = map usedGUniforms drawcalls
        gSampsPerDrawcall = map usedGSamplers drawcalls
        vUnisPerDrawcall = map usedVUniforms drawcalls
        vSampsPerDrawcall = map usedVSamplers drawcalls
        fUnisPerDrawcall = map usedFUniforms drawcalls
        fSampsPerDrawcall = map usedFSamplers drawcalls

        -- Consolidate them for the whole program.
        unisPerDrawcall = zipWith orderedUnion (zipWith orderedUnion gUnisPerDrawcall vUnisPerDrawcall) fUnisPerDrawcall
        sampsPerDrawcall = zipWith orderedUnion (zipWith orderedUnion gSampsPerDrawcall vSampsPerDrawcall) fSampsPerDrawcall

        -- Produce an error message for each limit exceeded by at least one of the drawcalls.
        limitErrors = concat
            [ ["Too many uniform blocks used in a single geometry shader\n" | any (\ xs -> length xs >= maxGUnis) gUnisPerDrawcall]
            , ["Too many textures used in a single geometry shader\n" | any (\ xs -> length xs >= maxGSamplers) gSampsPerDrawcall]
            , ["Too many uniform blocks used in a single vertex shader\n" | any (\ xs -> length xs >= maxVUnis) vUnisPerDrawcall]
            , ["Too many textures used in a single vertex shader\n" | any (\ xs -> length xs >= maxVSamplers) vSampsPerDrawcall]
            , ["Too many uniform blocks used in a single fragment shader\n" | any (\ xs -> length xs >= maxFUnis) fUnisPerDrawcall]
            , ["Too many textures used in a single fragment shader\n" | any (\ xs -> length xs >= maxFSamplers) fSampsPerDrawcall]
            , ["Too many uniform blocks used in a single shader program\n" | any (\ xs -> length xs >= maxUnis) unisPerDrawcall]
            , ["Too many textures used in a single shader program\n" | any (\ xs -> length xs >= maxSamplers) sampsPerDrawcall]
            ]

        allocatedUniforms = allocateConsecutiveIndexes maxUnis unisPerDrawcall
        allocatedSamplers = allocateConsecutiveIndexes maxSamplers sampsPerDrawcall

    return (zip5 drawcalls unisPerDrawcall sampsPerDrawcall allocatedUniforms allocatedSamplers, limitErrors)

-- private
innerCompile :: RenderIOState s -- Interactions between the drawcall and the environment 's'.
    ->  ( Drawcall s -- A drawcall with:
        , [Int] -- its uniform buffers used,
        , [Int] -- its textures units used,
        , [Int] -- its allocated uniforms,
        , [Int] -- its allocated texture units.
        )
    ->  IO
        ( Either
            String -- A failure in case the program cannot be compiled in linked.
            ( (IORef GLuint, IO ()) -- The program name and its destructor.
            , s -> Render os () -- The program's renderer as a function on a render (OpenGL) state. Upper stage called this a 'CompiledShader'.
            )
        )
innerCompile state (drawcall, unis, samps, ubinds, sbinds) = do
    let vsource = vertexSource drawcall
        ogsource = optionalGeometrySource drawcall
        ofsource = optionalFragmentSource drawcall
        inputs = usedInputs drawcall

    -- Compile and link the shader program.
    errorOrProgramName <- do
        -- Compile the vertex shader.
        vShader <- glCreateShader GL_VERTEX_SHADER
        mErrV <- compileOpenGlShader vShader vsource
        -- Compile the optional geometry shader.
        (ogShader, mErrG) <- case ogsource of
            Nothing -> return (Nothing, Nothing)
            Just gsource -> do
                gShader <- glCreateShader GL_GEOMETRY_SHADER
                mErrG <- compileOpenGlShader gShader gsource
                return (Just gShader, mErrG)
        -- Compile the fragment shader.
        (ofShader, mErrF) <- case ofsource of
            Nothing -> return (Nothing, Nothing)
            Just fsource -> do
                fShader <- glCreateShader GL_FRAGMENT_SHADER
                mErrF <- compileOpenGlShader fShader fsource
                return (Just fShader, mErrF)

        if all isNothing [mErrV, mErrG, mErrF]
            then do
                pName <- glCreateProgram
                glAttachShader pName vShader

                whenJust' ogShader $ glAttachShader pName
                whenJust' ofShader $ glAttachShader pName
                mapM_ (\(name, ix) -> withCString ("in"++ show name) $ glBindAttribLocation pName ix) $ zip inputs [0..]

                case (feedbackBuffer drawcall, rasterizationName drawcall) of
                    (Nothing, Just _) -> return ()
                    (Just _, Just geoN) -> (transformFeedbackToRenderIO state ! geoN) undefined pName

                mPErr <- linkProgram pName

                glDetachShader pName vShader
                whenJust' ogShader $ glDetachShader pName
                whenJust' ofShader $ glDetachShader pName

                glDeleteShader vShader
                whenJust' ogShader $ glDeleteShader
                whenJust' ofShader $ glDeleteShader

                case mPErr of
                    Just errP -> do
                        glDeleteProgram pName
                        return $ Left $ "Linking a GPU progam failed:\n" ++ errP ++ concat
                            [ maybe "" (\e -> "\nVertex source:\n" ++ e ++ "\n") (Just vsource)
                            , maybe "" (\e -> "\nGeometry source:\n" ++ e ++ "\n") ogsource
                            , maybe "" (\e -> "\nFragment source:\n" ++ e ++ "\n") ofsource
                            ]
                    Nothing -> return $ Right pName
            else do
                glDeleteShader vShader
                whenJust' ogShader $ glDeleteShader
                whenJust' ofShader $ glDeleteShader

                let err = concat
                        [ maybe "" (\e -> "A vertex shader compilation failed:\n" ++ e ++ "\nSource:\n" ++ vsource) mErrV
                        , maybe "" (\e -> "A geometry shader compilation failed:\n" ++ e ++ "\nSource:\n" ++ fromJust ogsource) mErrG
                        , maybe "" (\e -> "A fragment shader compilation failed:\n" ++ e ++ "\nSource:\n" ++ fromJust ofsource) mErrF
                        ]
                return $ Left err

    case errorOrProgramName of
        -- Left: the failure.
        Left err -> return $ Left err
        -- Right: the program wrapped in a Render monad.
        Right pName -> Right <$> case (feedbackBuffer drawcall, rasterizationName drawcall) of
            (Nothing, Just rastN) -> createRenderer state (drawcall, unis, ubinds, samps, sbinds) pName rastN
            (Just getTransformFeedbackName, Just geoN) -> createFeedbackRenderer state (drawcall, unis, ubinds, samps, sbinds) pName getTransformFeedbackName geoN
            _ -> error "No rasterization nor feedback!"

-- private
createRenderer :: RenderIOState s -- Interactions between the drawcall and the environment 's'.
    ->  ( Drawcall s -- A drawcall with:
        , [Int] -- its uniform buffers used,
        , [Int] -- its textures units used,
        , [Int] -- its allocated uniforms,
        , [Int] -- its allocated texture units.
        )
    ->  GLuint -- pName
    ->  Int
    ->  IO  ( (IORef GLuint, IO ()) -- The program name and its destructor.
            , s -> Render os () -- The program's renderer as a function on a render (OpenGL) state.
            )
createRenderer state (drawcall, unis, ubinds, samps, sbinds) pName rastN = do
    let fboSetup = drawcallFbo drawcall
        primN = primitiveName drawcall
        inputs = usedInputs drawcall
        pstrUSize = primStrUBufferSize drawcall

    let pstrUSize' = if 0 `elem` unis then pstrUSize else 0
    pstrUBuf <- createUniformBuffer pstrUSize' -- Create uniform buffer for primiveStream uniforms

    forM_ (zip unis ubinds) $ \(name, bind) -> do
        uix <- withCString ("uBlock" ++ show name) $ glGetUniformBlockIndex pName
        glUniformBlockBinding pName uix (fromIntegral bind)

    glUseProgram pName -- For setting texture uniforms
    forM_ (zip samps sbinds) $ \(name, bind) -> do
        six <- withCString ("s" ++ show name) $ glGetUniformLocation pName
        glUniform1i six (fromIntegral bind)
    pNameRef <- newIORef pName

    let uNameToRenderIOMap = uniformNameToRenderIO state
        uNameToRenderIOMap' = addPrimitiveStreamUniform pstrUBuf pstrUSize' uNameToRenderIOMap

    -- Drawing with the program.
    let renderer = \x -> Render $ do
            rs <- lift $ lift get
            renv <- lift ask
            let (mFboKeyIO, blendIO) = fboSetup x

            let inwin windowId m = do
                    case Map.lookup windowId (perWindowRenderState rs) of
                        Nothing -> return () -- Window deleted
                        Just (ws, doAsync) -> do
                            lift $ lift $ put (rs { renderLastUsedWin = windowId })
                            mErr <- liftIO $ asSync doAsync $ do
                                pName' <- readIORef pNameRef -- Cant use pName, need to touch pNameRef
                                glUseProgram pName'
                                True <- bind uNameToRenderIOMap' (zip unis ubinds) x (const $ return True)
                                isOk <- bind (samplerNameToRenderIO state) (zip samps sbinds) x (return . not . (`Set.member` renderWriteTextures rs))
                                (rasterizationNameToRenderIO state ! rastN) x
                                blendIO
                                mErr2 <- m
                                let mErr = if isOk
                                        then Nothing
                                        else Just $ "Running shader that samples from texture that currently has an image borrowed from it."
                                            ++ "Try run this shader from a separate render call where no images from the same texture are drawn to or cleared.\n"
                                return $ mErr <> mErr2
                            case mErr of
                                Just e -> throwE e
                                Nothing -> return ()

            -- Bind the framebuffer.
            windowId <- case mFboKeyIO of
                Left wid -> do -- Bind correct context
                    inwin wid $ do
                        glBindFramebuffer GL_DRAW_FRAMEBUFFER 0
                        return Nothing
                    return wid
                Right (fboKeyIO, fboIO) -> do
                    -- Off-screen draw call, continue with last context
                    -- (something wrong here?)
                    (cwid, cd, doAsync) <- unRender getLastRenderWin
                    inwin cwid $ do
                        fbokey <- fboKeyIO
                        mfbo <- getFBO cd fbokey
                        case mfbo of
                            Just fbo -> do
                                fbo' <- readIORef fbo
                                glBindFramebuffer GL_DRAW_FRAMEBUFFER fbo'
                                return Nothing
                            Nothing -> do
                                fbo' <- alloca $ \ ptr -> glGenFramebuffers 1 ptr >> peek ptr
                                fbo <- newIORef fbo'
                                void $ mkWeakIORef fbo (doAsync $ with fbo' $ glDeleteFramebuffers 1)
                                setFBO cd fbokey fbo
                                glBindFramebuffer GL_DRAW_FRAMEBUFFER fbo'
                                glEnable GL_FRAMEBUFFER_SRGB
                                fboIO
                                let numColors = length $ fboColors fbokey
                                withArray [GL_COLOR_ATTACHMENT0 .. (GL_COLOR_ATTACHMENT0 + fromIntegral numColors - 1)] $
                                    glDrawBuffers (fromIntegral numColors)
                                getFboError
                    return cwid

            -- Draw each vertex array.
            forM_ (map ($ (inputs, pstrUBuf, pstrUSize)) ((inputArrayToRenderIO state ! primN) x)) $ \ ((keyIO, vaoIO), drawIO) -> do
                case Map.lookup windowId (perWindowRenderState rs) of
                    Nothing -> return () -- Window deleted
                    Just (ws, doAsync) ->
                        liftIO $ do
                            let cd = windowContextData ws
                            key <- keyIO
                            mvao <- getVAO cd key
                            case mvao of
                                Just vao -> do
                                    vao' <- readIORef vao
                                    glBindVertexArray vao'
                                Nothing -> do
                                    vao' <- alloca $ \ ptr -> glGenVertexArrays 1 ptr >> peek ptr
                                    vao <- newIORef vao'
                                    void $ mkWeakIORef vao (doAsync $ with vao' $ glDeleteVertexArrays 1)
                                    setVAO cd key vao
                                    glBindVertexArray vao'
                                    vaoIO
                            drawIO

    let deleter = do
            glDeleteProgram pName
            when (pstrUSize > 0) $ with pstrUBuf (glDeleteBuffers 1)

    return ((pNameRef, deleter), renderer)

-- private
createFeedbackRenderer :: RenderIOState s -- Interactions between the drawcall and the environment 's'.
    ->  ( Drawcall s -- A drawcall with:
        , [Int] -- its uniform buffers used,
        , [Int] -- its textures units used,
        , [Int] -- its allocated uniforms,
        , [Int] -- its allocated texture units.
        )
    ->  GLuint -- program name
    ->  (s -> IO (GLuint, GLuint, GLuint, GLuint)) -- transform feedback stuff
    ->  Int
    ->  IO  ( (IORef GLuint, IO ()) -- The program name and its destructor.
            , s -> Render os () -- The program's renderer as a function on a render (OpenGL) state.
            )
createFeedbackRenderer state (drawcall, unis, ubinds, samps, sbinds) pName getTransformFeedbackName geoN = do
    let fboSetup = drawcallFbo drawcall
        primN = primitiveName drawcall
        inputs = usedInputs drawcall
        pstrUSize = primStrUBufferSize drawcall

    let pstrUSize' = if 0 `elem` unis then pstrUSize else 0
    pstrUBuf <- createUniformBuffer pstrUSize' -- Create uniform buffer for primiveStream uniforms

    forM_ (zip unis ubinds) $ \(name, bind) -> do
        uix <- withCString ("uBlock" ++ show name) $ glGetUniformBlockIndex pName
        glUniformBlockBinding pName uix (fromIntegral bind)

    glUseProgram pName -- For setting texture uniforms
    forM_ (zip samps sbinds) $ \(name, bind) -> do
        six <- withCString ("s" ++ show name) $ glGetUniformLocation pName
        glUniform1i six (fromIntegral bind)
    pNameRef <- newIORef pName

    let uNameToRenderIOMap = uniformNameToRenderIO state
        uNameToRenderIOMap' = addPrimitiveStreamUniform pstrUBuf pstrUSize' uNameToRenderIOMap

    -- Drawing with the program.
    let renderer = \x -> Render $ do
            rs <- lift $ lift get
            renv <- lift ask
            let (Left windowId, blendIO) = fboSetup x
                transformFeedback = getTransformFeedbackName x

            case Map.lookup windowId (perWindowRenderState rs) of
                Nothing -> return () -- Window deleted
                Just (ws, doAsync) -> do
                    lift $ lift $ put (rs { renderLastUsedWin = windowId })
                    liftIO $ asSync doAsync $ do
                        pName' <- readIORef pNameRef -- Cant use pName, need to touch pNameRef
                        glUseProgram pName'
                        -- Too late: (transformFeedbackToRenderIO state ! geoN) x pName'
                        True <- bind uNameToRenderIOMap' (zip unis ubinds) x (const $ return True)
                        isOk <- bind (samplerNameToRenderIO state) (zip samps sbinds) x (return . not . (`Set.member` renderWriteTextures rs))
                        blendIO

                    -- Draw each vertex array.
                    forM_ (map ($ (inputs, pstrUBuf, pstrUSize)) ((inputArrayToRenderIO state ! primN) x)) $ \ ((keyIO, vaoIO), drawIO) -> liftIO $ do
                        let cd = windowContextData ws
                        key <- keyIO
                        mvao <- getVAO cd key
                        case mvao of
                            Just vao -> do
                                vao' <- readIORef vao
                                glBindVertexArray vao'
                            Nothing -> do
                                vao' <- alloca $ \ ptr -> glGenVertexArrays 1 ptr >> peek ptr
                                vao <- newIORef vao'
                                void $ mkWeakIORef vao (doAsync $ with vao' $ glDeleteVertexArrays 1)
                                setVAO cd key vao
                                glBindVertexArray vao'
                                vaoIO
                        (bName, tfName, tfqName, topology) <- transformFeedback
                        glBindTransformFeedback GL_TRANSFORM_FEEDBACK tfName
                        glBindBufferBase GL_TRANSFORM_FEEDBACK_BUFFER 0 bName
                        glBeginQuery GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN tfqName
                        -- liftIO $ hPutStrLn stderr $ "doing transform feedback"
                        glBeginTransformFeedback topology
                        glEnable GL_RASTERIZER_DISCARD
                        drawIO
                        glDisable GL_RASTERIZER_DISCARD
                        glEndTransformFeedback
                        glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
                        {-
                        l <- alloca $ \ptr -> do
                            glGetQueryObjectiv tfqName GL_QUERY_RESULT ptr
                            peek ptr
                        liftIO $ hPutStrLn stderr $ "generated primitive count: " ++ show l
                        -}

    let deleter = do
            glDeleteProgram pName
            when (pstrUSize > 0) $ with pstrUBuf (glDeleteBuffers 1)

    return ((pNameRef, deleter), renderer)

-- private
compileOpenGlShader :: GLuint -> String -> IO (Maybe String)
compileOpenGlShader name source = do
    -- writeFile ("shaders/" ++ show name ++ ".glsl") source -- For debug purposes only.
    withCStringLen source $ \ (ptr, len) ->
                                with ptr $ \ pptr ->
                                    with (fromIntegral len) $ \ plen ->
                                        glShaderSource name 1 pptr plen
    -- putStrLn $ "Compiling shader " ++ show name
    glCompileShader name
    -- putStrLn $ "Compiled shader " ++ show name
    compStatus <- alloca $ \ ptr -> glGetShaderiv name GL_COMPILE_STATUS ptr >> peek ptr
    if compStatus /= GL_FALSE
        then return Nothing
        else do
            logLen <- alloca $ \ ptr -> glGetShaderiv name GL_INFO_LOG_LENGTH ptr >> peek ptr
            let logLen' = fromIntegral logLen
            fmap Just $ allocaArray logLen' $ \ ptr -> do
                glGetShaderInfoLog name logLen nullPtr ptr
                peekCString ptr

-- private
linkProgram :: GLuint -> IO (Maybe String)
linkProgram name = do
    -- putStrLn $ "Linking program " ++ show name
    glLinkProgram name
    -- putStrLn $ "Linked program " ++ show name
    linkStatus <- alloca $ \ ptr -> glGetProgramiv name GL_LINK_STATUS ptr >> peek ptr
    if linkStatus /= GL_FALSE
        then return Nothing
        else do
            logLen <- alloca $ \ ptr -> glGetProgramiv name GL_INFO_LOG_LENGTH ptr >> peek ptr
            let logLen' = fromIntegral logLen
            fmap Just $ allocaArray logLen' $ \ ptr -> do
                glGetProgramInfoLog name logLen nullPtr ptr
                peekCString ptr

-- private
createUniformBuffer :: Integral a => a -> IO GLuint
createUniformBuffer 0 = return undefined
createUniformBuffer uSize = do
    bname <- alloca $ \ ptr -> glGenBuffers 1 ptr >> peek ptr
    glBindBuffer GL_COPY_WRITE_BUFFER bname
    glBufferData GL_COPY_WRITE_BUFFER (fromIntegral uSize) nullPtr GL_STREAM_DRAW
    return bname

-- private
addPrimitiveStreamUniform :: Word32 -> Int -> Map.IntMap (s -> Binding -> IO ()) -> Map.IntMap (s -> Binding -> IO ())
addPrimitiveStreamUniform _ 0 = id
addPrimitiveStreamUniform bname uSize = Map.insert 0 $ \_ bind -> glBindBufferRange GL_UNIFORM_BUFFER (fromIntegral bind) bname 0 (fromIntegral uSize)

-- private
bind :: Map.IntMap (s -> Binding -> IO x)
    -> [(Int, Int)]
    -> s
    -> (x -> IO Bool) -- Used to assert we may use textures bound as render targets
    -> IO Bool
bind iom ((n,b):xs) s a = do
    ok1 <- bind iom xs s a
    ok2 <- (iom ! n) s b >>= a
    return $ ok1 && ok2
bind _ [] _ _ = return True

-- private
orderedUnion :: Ord a => [a] -> [a] -> [a]
orderedUnion xxs@(x:xs) yys@(y:ys) | x == y    = x : orderedUnion xs ys
                                   | x < y     = x : orderedUnion xs yys
                                   | otherwise = y : orderedUnion xxs ys
orderedUnion xs [] = xs
orderedUnion [] ys = ys

-- private
oldAllocateWhichGiveStrangeResults :: Int -> [[Int]] -> [[Int]]
oldAllocateWhichGiveStrangeResults mx = allocate' Map.empty [] where
    allocate' m ys ((x:xs):xss)
        | Just a <- Map.lookup x m = allocate' m (a:ys) (xs:xss)
        | ms <- Map.size m, ms < mx = allocate' (Map.insert x ms m) (ms:ys) (xs:xss)
        | otherwise =
            let (ek,ev) = findLastUsed m mx (ys ++ xs ++ concat xss)
            in allocate' (Map.insert x ev (Map.delete ek m)) (ev:ys) (xs:xss)
    allocate' m ys (_:xss) = reverse ys : allocate' m [] xss
    allocate' _ _ [] = []

    findLastUsed m n (x:xs) | n > 1 =
        let (a, m') = Map.updateLookupWithKey (const $ const Nothing) x m
            n' = if isJust a then n-1 else n
        in findLastUsed m' n' xs
    findLastUsed m _ _ = head $ Map.toList m

{-
Map the input values into [0, mx[ with no gap. Two different values in the
input are mapped to two different values in the output as long as the `mx`
size of the output set is no reach. The `mx+1` nth value and beyond are
mapped to… I can't figure it!. Let's just raise an error instead.

Note that the fact that the values are stored in a list of lists doesn't
matter, we are just mapping a tree of values to another without caring about
the traversed structure.
-}
-- private
allocateConsecutiveIndexes :: Int -> [[Int]] -> [[Int]]
allocateConsecutiveIndexes mx values = evalState (mapM (mapM allocateIndex) values) Map.empty where
    allocateIndex n = do
        mapping <- get
        case Map.lookup n mapping of
            Nothing -> do
                let m = Map.size mapping
                if m < mx
                    then do
                        put $ Map.insert n m mapping
                        return m
                    else error "Not enough indexes available!"
            Just m -> return m

-- public
getFboError :: MonadIO m => m (Maybe String)
getFboError = do
    status <- glCheckFramebufferStatus GL_DRAW_FRAMEBUFFER
    return $ case status of
        GL_FRAMEBUFFER_COMPLETE -> Nothing
        GL_FRAMEBUFFER_UNSUPPORTED -> Just "The combination of draw images (FBO) used in the render call is unsupported by this graphics driver\n"
        _ -> error "GPipe internal FBO error"

-- | A 'whenJust' that accepts a monoidal return value.
-- private
whenJust' :: (Monad m, Monoid b) => Maybe a -> (a -> m b) -> m b
whenJust' = flip $ maybe (return mempty)