{-| Module : Graphics.Rendering.DLP Copyright : (c) 2015 Brian W Bush License : MIT Maintainer : Brian W Bush Stability : Stable Portability : Portable Functions for using DLP stereo with 3-D Ready Sync projectors and OpenGL. This uses the specification \<\> and is based on the implementation for the stereo movie viewer Bino \<\>. In particular, note that this technique does not require a graphics card that supports @GL_STEREO@. Below is a skeletal example illustrating the use of frame-sequential DLP. See "Graphics.Rendering.DLP.Callbacks" for a simpler example that uses callbacks. @ main :: IO () main = do _ <- getArgsAndInitialize initialDisplayMode $= [WithDepthBuffer, DoubleBuffered] _ <- createWindow \"DLP Stereo OpenGL Example\" depthFunc $= Just Less dlp <- initDlp FrameSequential -- Initialize the DLP state. displayCallback $= display dlp -- The display callback needs the DLP state. idleCallback $= Just (postRedisplay Nothing) -- The idle callback must force redisplay for frame-sequential encoding. mainLoop display :: IORef DlpState -> DisplayCallback display dlp = do clear [ColorBuffer, DepthBuffer] isLeftEye <- showEye' LeftDlp dlp -- Determine whether to draw the view for the left or right eye. translate $ Vector3 (if isLeftEye then -0.05 else 0.05 :: GLfloat) 0 0 -- Shift the view slightly, depending on for which eye to draw. renderPrimitive . . . -- All of the rendering actions go here. drawDlp dlp -- Draw the colored DLP reference line just before swapping framebuffers. swapBuffers @ This code has been validated with the following configuration of hardware and software: * Optoma ML550 WXGA 500 Lumen 3D Ready Portable DLP LED Projector, running 120 Hz at 1024x768 resolution * Optoma ZD302 DLP Link Active Shutter 3D Glasses * Ubuntu 15.04, 64-bit * NVIDIA Driver Version 340.93, with @xorg.conf@ option @Stereo@ set to @8@ * GHC 7.6.3 * OpenGL == 2.8.0.0 * GLUT == 2.4.0.0 -} module Graphics.Rendering.DLP ( -- * DLP State and Encoding DlpEncoding(..) , DlpState , initDlp , drawDlp -- * Active Frame(s) , DlpEye(..) , showEye , showEye' , whichView , whichView' ) where import Control.Monad (unless, when) import Data.Bits ((.|.)) import Data.IORef (IORef, newIORef) import Data.Word (Word32) import Graphics.Rendering.OpenGL.GL (DataType(UnsignedInt8888Rev), PixelData(..), PixelFormat(BGRA), Position(..), Size(..), Vertex2(..), ($~!), drawPixels, get, viewport, windowPos) import qualified Data.Vector.Storable as V (fromList, unsafeWith) -- | The type of DLP encoding. See the specification \<\> for further details. data DlpEncoding = SideBySide -- ^ Side-by-side encoding, where the left image is stored to the left of the right image in the framebuffer. | FrameSequential -- ^ Frame-sequential encoding, where left and right images alternate, each filling the whole framebuffer. | TopAndBottom -- ^ Top-and-bottom encoding, where the top image is stored above the bottom image in the framebuffer. | LeftOnly -- ^ Monoscopic with only the left eye's view. | RightOnly -- ^ Monoscopic with only the right eye's view. | QuadBuffer -- ^ Instead of DLP, use a quad buffer stereo. deriving (Eq, Read, Show) -- | Labels for the left and right eyes' views. data DlpEye = LeftDlp -- ^ The left eye's view. | RightDlp -- ^ The right eye's view. deriving (Eq, Read, Show) -- | The DLP state, which tracks the sequence of frames. data DlpState = DlpState DlpEncoding Int -- | Initialize the DLP state. initDlp :: DlpEncoding -> IO (IORef DlpState) initDlp encoding = newIORef $ DlpState encoding 0 -- | Query whether to show the view from the specified eye for the current frame. Client code should call this function to determine which views to draw into the framebuffer. showEye :: DlpEye -- ^ The eye in question. -> DlpState -- ^ The current DLP state. -> Bool -- ^ Whether the view of the specified eye should be shown for the current frame. showEye LeftDlp (DlpState FrameSequential frame) = frame `mod` 2 == 0 showEye RightDlp (DlpState FrameSequential frame) = frame `mod` 2 /= 0 showEye RightDlp (DlpState LeftOnly _ ) = False showEye LeftDlp (DlpState RightOnly _ ) = False showEye LeftDlp (DlpState QuadBuffer frame) = frame `mod` 2 == 0 showEye RightDlp (DlpState QuadBuffer frame) = frame `mod` 2 /= 0 showEye _ _ = True -- | Query whether to show the view from the specified eye for the current frame. Client code should call this function to determine which views to draw into the framebuffer. showEye' :: DlpEye -- ^ The eye in question. -> IORef DlpState -- ^ A reference to the current DLP state. -> IO Bool -- ^ An action for determining whether the view of the specified eye should be shown for the current frame. showEye' eye = (showEye eye <$>) . get -- | Compute the viewport for the specified eye, given a DLP state. whichView :: DlpEye -- ^ The eye in question. -> DlpState -- ^ The current DLP state. -> IO (Position, Size) -- ^ An action for computing the viewport for the eye. whichView eye (DlpState SideBySide _) = do (Position x0 y0, Size w h) <- get viewport return (Position (if eye == LeftDlp then x0 else x0 + w `div` 2) y0, Size (w - w `div` 2) h) whichView eye (DlpState TopAndBottom _) = do (Position x0 y0, Size w h) <- get viewport return (Position x0 (if eye == LeftDlp then y0 else y0 + h `div` 2), Size w (h - h `div` 2)) whichView _ _ = get viewport -- | Compute the viewport for the specified eye, given a DLP state. whichView' :: DlpEye -- ^ The eye in question. -> IORef DlpState -- ^ A reference to the current DLP state. -> IO (Position, Size) -- ^ An actino for computing the viewport for the eye. whichView' eye = (whichView eye =<<) . get -- | Advance the DLP state one frame. advanceDlp :: IORef DlpState -- ^ A reference to the current DLP state. -> IO () -- ^ An action to advance the DLP state to the next frame. advanceDlp dlp = dlp $~! \(DlpState encoding frame) -> DlpState encoding $ (frame + 1) `mod` 4 -- | Color constants. red, green, blue, cyan, magenta, yellow, clear :: Word32 red = 0x00FF0000 green = 0x0000FF00 blue = 0x000000FF cyan = green .|. blue magenta = red .|. blue yellow = red .|. green clear = 0xFF000000 -- | Determine the correct color of the reference line for a given DLP encoding and DLP state. dlpColor :: DlpState -> Word32 dlpColor (DlpState SideBySide frame) = if frame `mod` 2 == 0 then red else cyan dlpColor (DlpState FrameSequential frame) = if frame `mod` 4 < 2 then green else magenta dlpColor (DlpState TopAndBottom frame) = if frame `mod` 2 == 0 then blue else yellow dlpColor (DlpState LeftOnly _ ) = undefined -- Safe because drawDlp never calls the function for this DLP mode. dlpColor (DlpState RightOnly _ ) = undefined -- Safe because drawDlp never acalls te function for this DLP mode. dlpColor (DlpState QuadBuffer _ ) = clear -- | Determine the correct color of the reference line for a given DLP encoding and DLP state. dlpColor' :: IORef DlpState -> IO Word32 dlpColor' = (dlpColor <$>) . get -- | Draw the DLP reference line. This action should be executed after all other drawing is complete, just before buffers are swapped. drawDlp :: IORef DlpState -- ^ A reference to the current DLP state. -> IO () -- ^ An action to draw the DLP reference line. drawDlp dlp = do DlpState encoding _ <- get dlp unless (encoding `elem` [LeftOnly, RightOnly, QuadBuffer]) $ do (Position x0 y0, Size w h) <- get viewport color <- dlpColor' dlp let pixels = V.fromList $ replicate (fromIntegral w) color drawLine = V.unsafeWith pixels $ drawPixels (Size w 1) . PixelData BGRA UnsignedInt8888Rev windowPos $ Vertex2 x0 y0 drawLine when (encoding == TopAndBottom) $ do windowPos $ Vertex2 x0 $ y0 + h `div` 2 drawLine advanceDlp dlp