dynamic-graph-0.1.0.8: Graphics/DynamicGraph/Waterfall.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-| Draw and update waterfall plots with OpenGL. Useful for spectrograms.
Example usage:
> import Control.Monad
> import Control.Monad.Trans.Either
> import Control.Concurrent
> import Pipes
> import qualified Pipes.Prelude as P
> import System.Random
> import Graphics.Rendering.OpenGL
>
> import Graphics.DynamicGraph.Waterfall
> import Graphics.DynamicGraph.Window
>
> randomVect :: Producer [GLfloat] IO ()
> randomVect = P.repeatM $ do
> res <- replicateM 1000 randomIO
> threadDelay 10000
> return res
>
> main = eitherT putStrLn return $ do
> res <- lift setupGLFW
> unless res (left "Unable to initilize GLFW")
>
> waterfall <- window 1024 480 $ renderWaterfall 1000 1000 jet_mod
>
> lift $ runEffect $ randomVect >-> waterfall
-}
module Graphics.DynamicGraph.Waterfall (
renderWaterfall,
module Graphics.DynamicGraph.ColorMaps
) where
import Graphics.Rendering.OpenGL
import Graphics.GLUtil
import Foreign.Storable
import Foreign.Marshal.Array
import Pipes
import Graphics.DynamicGraph.ColorMaps
import Paths_dynamic_graph
{-| Returns a `Consumer` that renders a waterfall plot into the current OpenGL context.
All OpenGL based initialization of the rendering function (loading of shaders, etc) is performed before the Consumer is returned.
This function must be called with an OpenGL context currently set.
-}
renderWaterfall :: IsPixelData a
=> Int -- ^ waterfall columns
-> Int -- ^ waterfall rows
-> [GLfloat] -- ^ waterfall color map
-> IO (Consumer a IO ())
renderWaterfall width height colorMap = do
--Load the shaders
vertFN <- getDataFileName "shaders/waterfall.vert"
fragFN <- getDataFileName "shaders/waterfall.frag"
vs <- loadShader VertexShader vertFN
fs <- loadShader FragmentShader fragFN
p <- linkShaderProgram [vs, fs]
--Set stuff
currentProgram $= Just p
ab <- genObjectName
locc <- get $ attribLocation p "coord"
let stride = fromIntegral $ sizeOf (undefined::GLfloat) * 2
vad = VertexArrayDescriptor 2 Float stride offset0
bindBuffer ArrayBuffer $= Just ab
vertexAttribArray locc $= Enabled
vertexAttribPointer locc $= (ToFloat, vad)
let xCoords :: [GLfloat]
xCoords = [-1, -1, 1, -1, 1, 1, -1, 1]
withArray xCoords $ \ptr ->
bufferData ArrayBuffer $= (fromIntegral $ sizeOf(undefined::GLfloat) * 8, ptr, StaticDraw)
let yCoords :: [GLfloat]
yCoords = replicate (width * height) 0
activeTexture $= TextureUnit 0
texture Texture2D $= Enabled
to0 <- loadTexture (TexInfo (fromIntegral width) (fromIntegral height) TexMono yCoords)
loc <- get $ uniformLocation p "texture"
asUniform (0 :: GLint) loc
textureFilter Texture2D $= ((Linear', Nothing), Linear')
textureWrapMode Texture2D S $= (Repeated, ClampToEdge)
textureWrapMode Texture2D T $= (Repeated, Repeat)
activeTexture $= TextureUnit 1
texture Texture2D $= Enabled
to1 <- loadTexture (TexInfo (fromIntegral $ length colorMap `quot` 3) 1 TexRGB colorMap)
textureFilter Texture2D $= ((Linear', Nothing), Linear')
textureWrapMode Texture2D S $= (Repeated, ClampToEdge)
textureWrapMode Texture2D T $= (Repeated, ClampToEdge)
loc <- get $ uniformLocation p "colorMap"
asUniform (1 :: GLint) loc
let lcm :: GLfloat
lcm = fromIntegral $ length colorMap `quot` 3
loc <- get $ uniformLocation p "scale"
asUniform ((lcm - 1) / lcm) loc
loc <- get $ uniformLocation p "offset"
asUniform (0.5 / lcm) loc
loc <- get $ uniformLocation p "voffset"
let pipe yoffset = do
dat <- await
lift $ do
currentProgram $= Just p
let textureOffset = (yoffset + height - 1) `mod` height
withPixels dat $ \ptr ->
texSubImage2D Texture2D 0 (TexturePosition2D 0 (fromIntegral textureOffset)) (TextureSize2D (fromIntegral width) 1) (PixelData Red Float ptr)
asUniform (fromIntegral yoffset / fromIntegral height :: GLfloat) loc
bindBuffer ArrayBuffer $= Just ab
vertexAttribArray locc $= Enabled
vertexAttribPointer locc $= (ToFloat, vad)
activeTexture $= TextureUnit 0
textureBinding Texture2D $= Just to0
activeTexture $= TextureUnit 1
textureBinding Texture2D $= Just to1
--No idea why this is needed
activeTexture $= TextureUnit 0
drawArrays Quads 0 4
pipe $ if yoffset + 1 >= height then 0 else yoffset + 1
return $ pipe 0