--------------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.OpenGL.GL.Framebuffer
-- Copyright   :  (c) Sven Panne 2002-2009
-- License     :  BSD-style (see the file libraries/OpenGL/LICENSE)
--
-- Maintainer  :  [email protected]
-- Stability   :  stable
-- Portability :  portable
--
-- This module corresponds to section 4.2 (Whole Framebuffer Operations) of the
-- OpenGL 2.1 specs.
--
--------------------------------------------------------------------------------

module Graphics.Rendering.OpenGL.GL.Framebuffer (
   -- * Querying the Buffer Configuration
   auxBuffers, doubleBuffer, stereoBuffer,
   rgbaBits, stencilBits, depthBits, accumBits, rgbaSignedComponents,

   -- * Selecting a Buffer for Writing
   DrawBufferIndex, BufferMode(..), drawBuffer, drawBuffers, drawBufferi, maxDrawBuffers,

   -- * Fine Control of Buffer Updates
   indexMask, colorMask, colorMaski, stencilMask, stencilMaskSeparate, depthMask,

   -- * Clearing the Buffers
   ClearBuffer(..), clear,
   clearColor, clearIndex, clearStencil, clearDepth, clearAccum,

   -- * The Accumulation Buffer
   AccumOp(..), accum
) where

import Control.Monad
import Data.List
import Data.Maybe
import Foreign.Marshal.Array
import Graphics.Rendering.OpenGL.GL.StateVar
import Graphics.Rendering.OpenGL.GL.BufferMode
import Graphics.Rendering.OpenGL.GL.Capability
import Graphics.Rendering.OpenGL.GL.Face
import Graphics.Rendering.OpenGL.GL.GLboolean
import Graphics.Rendering.OpenGL.GL.QueryUtils
import Graphics.Rendering.OpenGL.GL.VertexSpec
import Graphics.Rendering.OpenGL.GLU.ErrorsInternal
import Graphics.Rendering.OpenGL.Raw.ARB.Compatibility (
   glAccum, glClearAccum, glClearIndex, glIndexMask, gl_ACCUM,
   gl_ACCUM_BUFFER_BIT, gl_ADD, gl_LOAD, gl_MULT, gl_RETURN )
import Graphics.Rendering.OpenGL.Raw.Core31

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

-- | The implementation and context dependent number of auxiliary buffers.

auxBuffers :: GettableStateVar GLsizei
auxBuffers = makeGettableStateVar $ getSizei1 id GetAuxBuffers

-- | 'True' if front and back buffers exist.

doubleBuffer :: GettableStateVar Bool
doubleBuffer =
   makeGettableStateVar $ getBoolean1 unmarshalGLboolean GetDoublebuffer

-- | 'True' if left and right buffers exist.

stereoBuffer :: GettableStateVar Bool
stereoBuffer =
    makeGettableStateVar $ getBoolean1 unmarshalGLboolean GetStereo

rgbaBits :: GettableStateVar (Color4 GLsizei)
rgbaBits =
   makeGettableStateVar $
      liftM4 Color4 (getSizei1 id GetRedBits)
                    (getSizei1 id GetGreenBits)
                    (getSizei1 id GetBlueBits)
                    (getSizei1 id GetAlphaBits)

stencilBits :: GettableStateVar GLsizei
stencilBits = makeGettableStateVar $ getSizei1 id GetStencilBits

depthBits :: GettableStateVar GLsizei
depthBits = makeGettableStateVar $ getSizei1 id GetDepthBits

accumBits :: GettableStateVar (Color4 GLsizei)
accumBits =
   makeGettableStateVar $
      liftM4 Color4 (getSizei1 id GetAccumRedBits)
                    (getSizei1 id GetAccumGreenBits)
                    (getSizei1 id GetAccumBlueBits)
                    (getSizei1 id GetAccumAlphaBits)

rgbaSignedComponents :: GettableStateVar (Color4 Bool)
rgbaSignedComponents =
   makeGettableStateVar $
      getInteger4 (\r g b a -> Color4 (unmarshalGLboolean r)
                                      (unmarshalGLboolean g)
                                      (unmarshalGLboolean b)
                                      (unmarshalGLboolean a))
                  GetRGBASignedComponents

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

-- | When colors are written to the framebuffer, they are written into the color
-- buffers specified by 'drawBuffer'.
--
-- If more than one color buffer is selected for drawing, then blending or
-- logical operations are computed and applied independently for each color
-- buffer and can produce different results in each buffer.
--
-- Monoscopic contexts include only left buffers, and stereoscopic contexts
-- include both left and right buffers. Likewise, single-buffered contexts
-- include only front buffers, and double-buffered contexts include both front
-- and back buffers. The context is selected at GL initialization.
--
-- The initial value is 'FrontBuffers' for single-buffered contexts, and
-- 'BackBuffers' for double-buffered contexts.

drawBuffer :: StateVar BufferMode
drawBuffer =
   makeStateVar
      (getEnum1 unmarshalBufferMode GetDrawBuffer)
      (maybe recordInvalidValue glDrawBuffer . marshalBufferMode)

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

type DrawBufferIndex = GLuint

-- | 'drawBuffers' defines the draw buffers to which all fragment colors are
-- written. The draw buffers being defined correspond in order to the respective
-- fragment colors. The draw buffer for fragment colors beyond those specified
-- is set to 'NoBuffers'.
--
-- Except for 'NoBuffers', a buffer may not appear more then once in the given
-- list. Specifying a buffer more then once will result in an
-- 'Graphics.Rendering.OpenGL.GLU.Errors.InvalidOperation'.
--
-- If fixed-function fragment shading is being performed, 'drawBuffers'
-- specifies a set of draw buffers into which the fragment color is written.
--
-- If a fragment shader writes to @gl_FragColor@, 'drawBuffers' specifies a set
-- of draw buffers into which the single fragment color defined by
-- @gl_FragColor@ is written. If a fragment shader writes to @gl_FragData@,
-- 'drawBuffers' specifies a set of draw buffers into which each of the multiple
-- fragment colors defined by @gl_FragData@ are separately written. If a
-- fragment shader writes to neither @gl_FragColor@ nor @gl_FragData@, the
-- values of the fragment colors following shader execution are undefined, and
-- may differ for each fragment color.

drawBuffers :: StateVar [BufferMode]
drawBuffers = makeStateVar getDrawBuffers setDrawBuffers

getDrawBuffers :: IO [BufferMode]
getDrawBuffers = do
   n <- get maxDrawBuffers
   mapM (getEnum1 unmarshalBufferMode . GetDrawBufferN) [ 0 .. n ]

setDrawBuffers :: [BufferMode] -> IO ()
setDrawBuffers modes = do
   let ms = map marshalBufferMode modes
   if all isJust ms
      then withArray (map fromJust ms) $
              glDrawBuffers (genericLength ms)
      else recordInvalidValue

-- | 'drawBufferi' is a fast query function. For indices in the range 0..maxDrawBuffers it's results
-- are the same as selection the index from the list returned by drawBuffers. Though this function
-- only uses one gl-function call instead of maxDrawBuffers + 1.
drawBufferi :: DrawBufferIndex -> GettableStateVar BufferMode
drawBufferi ind = makeGettableStateVar
    (getEnum1 unmarshalBufferMode . GetDrawBufferN $ fromIntegral ind)


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

-- | Contains the maximum number of buffers that can activated via 'drawBuffers'
-- or which can be simultaneously written into from within a fragment shader
-- using the special output variable array @gl_FragData@. This constant
-- effectively defines the size of the @gl_FragData@ array. The minimum legal
-- value is 1.

maxDrawBuffers :: GettableStateVar GLsizei
maxDrawBuffers = makeGettableStateVar $ getSizei1 id GetMaxDrawBuffers

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

-- | Controls the writing of individual bits in the color index buffers. The
-- least significant /n/ bits of its value, where /n/ is the number of bits in a
-- color index buffer, specify a mask.  Where a 1 appears in the mask, it is
-- possible to write to the corresponding bit in the color index buffer (or
-- buffers). Where a 0 appears, the corresponding bit is write-protected.
--
-- This mask is used only in color index mode, and it affects only the buffers
-- currently selected for writing (see 'drawBuffer'). Initially, all bits are
-- enabled for writing.

indexMask :: StateVar GLuint
indexMask =
   makeStateVar (getInteger1 fromIntegral GetIndexWritemask) glIndexMask

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

-- | Controls whether the individual color components in the framebuffer can or
-- cannot be written. If the red flag is 'Disabled', for example, no change is
-- made to the red component of any pixel in any of the color buffers,
-- regardless of the drawing operation attempted. Initially, all color
-- components can be written.
--
-- Changes to individual bits of components cannot be controlled. Rather,
-- changes are either enabled or disabled for entire color components.
-- Furthermore, this mask is used only in RGBA mode.

colorMask :: StateVar (Color4 Capability)
colorMask =
   makeStateVar
      (getBoolean4 (\r g b a -> Color4 (unmarshalCapability r)
                                       (unmarshalCapability g)
                                       (unmarshalCapability b)
                                       (unmarshalCapability a))
                                      GetColorWritemask)
      (\(Color4 r g b a) -> glColorMask (marshalCapability r)
                                        (marshalCapability g)
                                        (marshalCapability b)
                                        (marshalCapability a))
-- | 'colorMaski' is a version of 'colorMask' that only applies to the specified drawbuffer
colorMaski :: DrawBufferIndex -> StateVar (Color4 Capability)
colorMaski x = makeStateVar
      (getBoolean4i (\r g b a -> Color4 (unmarshalCapability r)
                                       (unmarshalCapability g)
                                       (unmarshalCapability b)
                                       (unmarshalCapability a))
                    GetColorWritemask x)
      (\(Color4 r g b a) -> glColorMaski (x) (marshalCapability r)
                                        (marshalCapability g)
                                        (marshalCapability b)
                                        (marshalCapability a))

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

-- | Controls whether the depth buffer is enabled for writing. The initial state
-- is 'Enabled'.

depthMask :: StateVar Capability
depthMask = makeStateVar (getBoolean1 unmarshalCapability GetDepthWritemask)
                         (glDepthMask . marshalCapability)

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

-- | Controls the writing of individual bits in the stencil planes. The least
-- significant /n/ bits of its value, where /n/ is the number of bits in the
-- stencil buffer, specify a mask. Where a 1 appears in the mask, it is
-- possible to write to the corresponding bit in the stencil buffer. Where a 0
-- appears, the corresponding bit is write-protected.
-- Initially, all bits are enabled for writing.

stencilMask :: StateVar GLuint
stencilMask =
   makeStateVar (getInteger1 fromIntegral GetStencilWritemask) glStencilMask

stencilMaskSeparate :: Face -> SettableStateVar GLuint
stencilMaskSeparate face =
   makeSettableStateVar $
      glStencilMaskSeparate (marshalFace face)

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

-- | The buffers which can be cleared with 'clear'.

data ClearBuffer =
     ColorBuffer   -- ^ The buffers currently enabled for color writing.
   | AccumBuffer   -- ^ The accumulation buffer.
   | StencilBuffer -- ^ The stencil buffer.
   | DepthBuffer   -- ^ The depth buffer.
   deriving ( Eq, Ord, Show )

marshalClearBuffer :: ClearBuffer -> GLbitfield
marshalClearBuffer x = fromIntegral $ case x of
   ColorBuffer -> gl_COLOR_BUFFER_BIT
   AccumBuffer -> gl_ACCUM_BUFFER_BIT
   StencilBuffer -> gl_STENCIL_BUFFER_BIT
   DepthBuffer -> gl_DEPTH_BUFFER_BIT

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

-- | Set the bitplane area of the window to values previously selected by
-- 'clearColor', 'clearIndex', 'clearDepth', 'clearStencil', and 'clearAccum'.
-- Multiple color buffers can be cleared simultaneously by selecting more than
-- one buffer at a time using 'drawBuffer'.
--
-- The pixel ownership test, the scissor test, dithering, and the buffer
-- writemasks affect the operation of 'clear'. The scissor box bounds the
-- cleared region. Alpha function, blend function, logical operation,
-- stenciling, texure mapping, and depth-buffering are ignored by 'clear'.
--
-- 'clear' takes a list of buffers, indicating which buffers are to be cleared.
-- If a buffer is not present, then a 'clear' directed at that buffer has no
-- effect.
--
-- The value to which each buffer is cleared depends on the setting of the clear
-- value for that buffer.

clear :: [ClearBuffer] -> IO ()
clear = glClear . sum . map marshalClearBuffer

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

-- | Controls the red, green, blue, and alpha values used by 'clear' to clear
-- the color buffers. Values written into 'clearColor' are clamped to the range
-- [0, 1]. Initially, all values are 0.

clearColor :: StateVar (Color4 GLclampf)
clearColor = makeStateVar (getClampf4 Color4 GetColorClearValue)
                          (\(Color4 r g b a) -> glClearColor r g b a)

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

-- | Controls the index /c/ used by 'clear' to clear the color index buffers.
-- /c/ is not clamped. Rather, /c/ is converted to a fixed-point value with
-- unspecified precision to the right of the binary point. The integer part of
-- this value is then masked with 2^/m/-1, where /m/ is the number of bits in a
-- color index stored in the framebuffer. Initially, the value is 0.

clearIndex :: StateVar (Index1 GLfloat)
clearIndex = makeStateVar (getFloat1 Index1 GetIndexClearValue)
                          (\(Index1 i) -> glClearIndex i)

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

-- | Controls the depth value used by 'clear' to clear the depth buffer. Values
-- written into 'clearDepth' are clamped to the range [0, 1]. The initial value
-- is 1.

clearDepth :: StateVar GLclampd
clearDepth = makeStateVar (getClampd1 id GetDepthClearValue) glClearDepth

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

-- | Controls the value /s/ used by 'clear' to clear the stencil buffer. /s/ is
-- masked with 2^/m/-1, where /m/ is the number of bits in the stencil buffer.
-- Initially, the value is 0.

clearStencil :: StateVar GLint
clearStencil = makeStateVar (getInteger1 id GetStencilClearValue) glClearStencil

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

-- | Controls the red, green, blue, and alpha values used by 'clear' to clear
-- the accumulation buffer. Values written into 'clearAccum' are clamped to the
-- range [-1, 1]. The initial values are all 0.

clearAccum :: StateVar (Color4 GLfloat)
clearAccum =
   makeStateVar (getFloat4 Color4 GetAccumClearValue)
                (\(Color4 r g b a) -> glClearAccum r g b a)

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

-- | An operation on the accumulation buffer.

data AccumOp =
     Accum
     -- ^ Obtains /R/, /G/, /B/, and /A/ values from the buffer currently
     -- selected for reading (see
     -- 'Graphics.Rendering.OpenGL.GL.ReadCopyPixels.readBuffer'). Each
     -- component value is divided by 2^/n/-1, where /n/ is the number of bits
     -- allocated to each color component in the currently selected buffer. The
     -- result is a floating-point value in the range [0, 1], which is
     -- multiplied by the value given to 'accum' and added to the corresponding
     -- pixel component in the accumulation buffer, thereby updating the
     -- accumulation buffer.
   | Load
     -- ^ Similar to 'Accum', except that the current value in the accumulation
     -- buffer is not used in the calculation of the new value. That is, the
     -- /R/, /G/, /B/, and /A/ values from the currently selected buffer are
     -- divided by 2^/n/-1, multiplied by the value given to 'accum', and then
     -- stored in the corresponding accumulation buffer cell, overwriting the
     -- current value.
   | Return
     -- ^ Transfers accumulation buffer values to the color buffer or buffers
     -- currently selected for writing. Each /R/, /G/, /B/, and /A/ component
     -- is multiplied by the value given to 'accum', then multiplied by 2^/n/-1,
     -- clamped to the range [0, 2^/n/-1], and stored in the corresponding
     -- display buffer cell. The only fragment operations that are applied to
     -- this transfer are pixel ownership, scissor, dithering, and color
     -- writemasks.
   | Mult
     -- ^ Multiplies each /R/, /G/, /B/, and /A/ in the accumulation buffer by
     -- the value given to 'accum' and returns the scaled component to its
     -- corresponding accumulation buffer location.
   | Add
     -- ^ Adds the value given to 'accum' to each /R/, /G/, /B/, and /A/ in the
     -- accumulation buffer.
   deriving ( Eq, Ord, Show )

marshalAccumOp :: AccumOp -> GLenum
marshalAccumOp x = case x of
   Accum -> gl_ACCUM
   Load -> gl_LOAD
   Return -> gl_RETURN
   Mult -> gl_MULT
   Add -> gl_ADD

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

-- | The accumulation buffer is an extended-range color buffer. Images are not
-- rendered into it. Rather, images rendered into one of the color buffers are
-- added to the contents of the accumulation buffer after rendering. Effects
-- such as antialiasing (of points, lines, and polygons), motion blur, and
-- depth of field can be created by accumulating images generated with different
-- transformation matrices.
--
-- Each pixel in the accumulation buffer consists of red, green, blue, and alpha
-- values. The number of bits per component in the accumulation buffer depends
-- on the implementation (see 'accumBits'). Regardless of the number of bits per
-- component, the range of values stored by each component is [-1, 1]. The
-- accumulation buffer pixels are mapped one-to-one with frame buffer pixels.
--
-- 'accum' operates on the accumulation buffer. The first argument selects an
-- accumulation buffer operation. The second argument, is a floating-point value
-- to be used in that operation, see 'AccumOp'.
--
-- All accumulation buffer operations are limited to the area of the current
-- scissor box and applied identically to the red, green, blue, and alpha
-- components of each pixel. If an 'accum' operation results in a value outside
-- the range [-1, 1], the contents of an accumulation buffer pixel component
-- are undefined.
--
-- To clear the accumulation buffer, use 'clearAccum' to specify the clear
-- value, then call 'clear' with the accumulation buffer enabled.

accum :: AccumOp -> GLfloat -> IO ()
accum = glAccum . marshalAccumOp