vacuum-opengl-0.0: System/Vacuum/OpenGL/Server.hs
-- | The vacuum-opengl server. This compiles to the executable
-- \"vacuum-opengl-server\", which you need to start to be able
-- to use the
--
module System.Vacuum.OpenGL.Server where
--------------------------------------------------------------------------------
import Control.Monad
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Exception
import Data.Char
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Codec.Image.STB
import System.Cmd
import System.Directory
import System.Environment
import System.Exit
import System.IO
import System.IO.Unsafe
import Network
import Foreign
--------------------------------------------------------------------------------
defaultPort :: PortID
defaultPort = PortNumber VACUUM_OPENGL_DEFAULTPORT
--------------------------------------------------------------------------------
theTexture :: MVar (Maybe (TextureObject,(Int,Int)))
theTexture = unsafePerformIO $ newMVar Nothing
theImage :: MVar (Maybe Image)
theImage = unsafePerformIO $ newMVar Nothing
--------------------------------------------------------------------------------
showPort :: PortID -> String
showPort p = case p of
PortNumber n -> show n
-- Service s -> s
-- UnixSocket s -> s -- UnixSocket does not exists on Windows?
_ -> "unknown port type"
startServer :: PortID -> IO ()
startServer port = withSocketsDo $ do
putStrLn "vacuum-opengl-server started - press ESC to exit."
putStrLn $ "listening on port " ++ showPort port
listen port
listen port = do
socket <- listenOn port
sequence_ $ repeat $ acceptConnectionAndFork socket
showConn :: (Handle, HostName, PortNumber) -> String
showConn (handle, hostname, portnumber) = hostname ++ ":" ++ show portnumber
acceptConnectionAndFork :: Socket -> IO ()
acceptConnectionAndFork socket = do
conn <- accept socket
forkIO (server conn)
return ()
server conn@(handle, hostname, portnumber) = do
text <- hGetContents handle
length text `seq` view text
hClose handle
--------------------------------------------------------------------------------
graphvizPath :: MVar FilePath
graphvizPath = unsafePerformIO $ do
findExecutable "dot" >>= \m -> case m of
Nothing -> error "graphviz executable \"dot\" not found"
Just path -> newMVar path
runGraphviz :: [String] -> IO ()
runGraphviz params = do
path <- readMVar graphvizPath
exitcode <- rawSystem path params
when (exitcode /= ExitSuccess) $
error "error running graphviz"
return ()
convertToPNG :: String -> IO Image
convertToPNG input = do
tmp <- getTemporaryDirectory
let dot = tmp ++ "vacuum-opengl-temp.dot"
png = tmp ++ "vacuum-opengl-temp.png"
writeFile dot input
-- print dot
-- print png
runGraphviz [ "-Tpng" , "-o"++png , dot ]
img <- loadImage png >>= \m -> case m of
Left err -> error err
Right img -> return img
removeFile dot
removeFile png
return img
view :: String -> IO ()
view dot = do
img <- convertToPNG dot
swapMVar theImage (Just img)
win <- readMVar theWindow
postRedisplay (Just win)
return ()
--------------------------------------------------------------------------------
setTexture :: IO ()
setTexture = do
readMVar theImage >>= \m -> case m of
Nothing -> return ()
Just img -> do
swapMVar theImage Nothing
takeMVar theTexture >>= \m -> case m of
Just (old,_) -> deleteObjectNames [old]
Nothing -> return ()
b <- readMVar npot
let myWithImage = if b then withImage else withExtendedImage
tex <- myWithImage img $ \p (x,y) c -> do
let (pf,pif) = case c of
{ 1 -> ( Luminance, Luminance8 )
; 2 -> ( LuminanceAlpha, Luminance8Alpha8 )
; 3 -> ( RGB, RGB8 )
; 4 -> ( RGBA, RGBA8 )
}
let size = TextureSize2D (fromIntegral x) (fromIntegral y)
pdata = PixelData pf UnsignedByte p
[tex] <- genObjectNames 1
textureBinding Texture2D $= Just tex
texImage2D Nothing NoProxy 0 pif size 0 pdata
textureFilter Texture2D $= ((Linear',Nothing),Linear')
return tex
putMVar theTexture $ Just (tex,resolution img)
--------------------------------------------------------------------------------
vt :: Double -> Double -> IO ()
vt x y = vertex (Vertex2 x y)
tc :: Double -> Double -> IO ()
tc x y = texCoord (TexCoord2 x y)
-- display callback
display :: IO ()
display = do
win <- readMVar theWindow
currentWindow $= Just win
clearColor $= Color4 0.90 0.90 0.90 1
clear [ColorBuffer]
setTexture
readMVar theTexture >>= \m -> case m of
Nothing -> return ()
Just (tex,(xsize,ysize)) -> do
size@(Size xres yres) <- get windowSize
viewport $= ( Position 0 0 , size )
let winaspect = fromIntegral xres / fromIntegral yres :: Double
picaspect = fromIntegral xsize / fromIntegral ysize :: Double
matrixMode $= Projection >> loadIdentity
ortho (-1) 1 (-1) 1 (-1) 1
texture Texture2D $= Enabled
textureBinding Texture2D $= Just tex
b <- readMVar npot
let (x,y) = if picaspect > winaspect then (1,winaspect/picaspect) else (picaspect/winaspect,1)
myExtendDimension = if b then id else nextPowerOfTwo
u = fromIntegral xsize / fromIntegral (myExtendDimension xsize)
v = fromIntegral ysize / fromIntegral (myExtendDimension ysize)
renderPrimitive Quads $ do
tc 0 0 ; vt (-x) ( y)
tc u 0 ; vt ( x) ( y)
tc u v ; vt ( x) (-y)
tc 0 v ; vt (-x) (-y)
swapBuffers
--------------------------------------------------------------------------------
log2 :: Int -> Int
log2 n = case n of
0 -> -1
_ -> 1 + log2 (shiftR n 1)
nextPowerOfTwo :: Int -> Int
nextPowerOfTwo n = 2 ^ ( 1 + log2 (n-1) )
-- extend the image to have power-of-two sizes
withExtendedImage :: Image -> (Ptr Word8 -> (Int,Int) -> Int -> IO a) -> IO a
withExtendedImage img action = withImage img $ \p (oldx,oldy) c -> do
let (newx,newy) = (nextPowerOfTwo oldx, nextPowerOfTwo oldy)
allocaArray (newx*newy*c) $ \q -> do
forM_ [0..oldy-1] $ \i -> copyArray
(q `advancePtr` (i*c*newx)) -- destination
(p `advancePtr` (i*c*oldx)) -- source
(oldx*c) -- number of bytes
action q (newx,newy) c
--------------------------------------------------------------------------------
-- reshape callback
reshape _ = postRedisplay Nothing
-- keyboard callback
keyboard key keyState mod pos =
case key of
Char '\ESC' -> exitWith ExitSuccess
_ -> return ()
--------------------------------------------------------------------------------
theWindow :: MVar Window
theWindow = unsafePerformIO newEmptyMVar
npot :: MVar Bool
npot = unsafePerformIO newEmptyMVar
idle :: IO ()
idle = do
threadDelay 10000
ourInitialWinSize :: Size
ourInitialWinSize = Size 640 400
initGLUT :: IO ()
initGLUT = do
initialDisplayMode $= [ RGBAMode, DoubleBuffered ]
initialWindowSize $= ourInitialWinSize
prog <- getProgName
initialize prog []
win <- createWindow "vacuum-opengl-server"
exts <- get glExtensions
putMVar npot ("GL_ARB_texture_non_power_of_two" `elem` exts)
displayCallback $= display
idleCallback $= Just idle
reshapeCallback $= Just reshape
keyboardMouseCallback $= Just keyboard
drawBuffer $= BackBuffers
postRedisplay (Just win)
putMVar theWindow win
mainLoop
--------------------------------------------------------------------------------
serverMain :: IO ()
serverMain = do
args <- getArgs
let port = case args of
[] -> defaultPort
(x:_) -> PortNumber $ fromIntegral $ (read x :: Int)
forkOS (startServer port)
initGLUT
--------------------------------------------------------------------------------