gmndl-0.1: gmndl.hs
{-# LANGUAGE BangPatterns #-}
module Main (main) where
import Control.Concurrent (killThread)
import Control.Monad (when)
import Control.Monad.Trans (liftIO)
import Data.Array.IO (IOUArray, newArray, readArray, writeArray, inRange)
import Data.IORef (newIORef, readIORef, writeIORef)
import Data.List (isPrefixOf)
import Data.PriorityQueue (PriorityQueue, newPriorityQueue, enqueue, enqueueBatch, dequeue)
import Foreign (mallocBytes, nullPtr, plusPtr, pokeArray, pokeByteOff, Word8)
import Foreign.C (CDouble)
import GHC.Conc (forkOnIO, numCapabilities)
import Graphics.UI.Gtk
import Graphics.UI.Gtk.OpenGL
import qualified Graphics.Rendering.OpenGL as GL
import Graphics.Rendering.OpenGL (($=), GLfloat)
import Numeric.QD.DoubleDouble (DoubleDouble(DoubleDouble))
import Numeric.QD.QuadDouble (QuadDouble(QuadDouble))
import qualified Numeric.QD.DoubleDouble as DD
import qualified Numeric.QD.QuadDouble as QD
import Numeric.QD.FPU.Raw (fpu_fix_start)
import Unsafe.Coerce (unsafeCoerce)
type B = Word8
type N = Int
type R = Double
convert :: (Real a, Fractional b) => a -> b
convert = realToFrac
convertDouble2CDouble :: Double -> CDouble
convertDouble2CDouble !x = unsafeCoerce x
convertCDouble2Double :: CDouble -> Double
convertCDouble2Double !x = unsafeCoerce x
convertDouble2DoubleDouble :: Double -> DoubleDouble
convertDouble2DoubleDouble !x = convertCDouble2DoubleDouble . convertDouble2CDouble $ x
convertCDouble2DoubleDouble :: CDouble -> DoubleDouble
convertCDouble2DoubleDouble !x = DoubleDouble x 0
convertDoubleDouble2Double :: DoubleDouble -> Double
convertDoubleDouble2Double !(DoubleDouble x _) = convertCDouble2Double x
convertDoubleDouble2CDouble :: DoubleDouble -> CDouble
convertDoubleDouble2CDouble !(DoubleDouble x _) = x
{-# RULES "convert/Double2CDouble" convert = convertDouble2CDouble #-}
{-# RULES "convert/CDouble2Double" convert = convertCDouble2Double #-}
{-# RULES "convert/Double2DoubleDouble" convert = convertDouble2DoubleDouble #-}
{-# RULES "convert/CDouble2DoubleDouble" convert = convertCDouble2DoubleDouble #-}
{-# RULES "convert/DoubleDouble2Double" convert = convertDoubleDouble2Double #-}
{-# RULES "convert/DoubleDouble2CDouble" convert = convertDoubleDouble2CDouble #-}
data Complex c = {-# UNPACK #-} !c :+ {-# UNPACK #-} !c deriving (Read, Show, Eq)
instance Num c => Num (Complex c) where
{-# SPECIALIZE instance Num (Complex Float) #-}
{-# SPECIALIZE instance Num (Complex Double) #-}
{-# SPECIALIZE instance Num (Complex DoubleDouble) #-}
{-# SPECIALIZE instance Num (Complex QuadDouble) #-}
(!(a :+ b)) + (!(c :+ d)) = {-# SCC "C+" #-} ((a + c) :+ (b + d))
(!(a :+ b)) - (!(c :+ d)) = {-# SCC "C-" #-} ((a - c) :+ (b - d))
(!(a :+ b)) * (!(c :+ d)) = {-# SCC "C*" #-} ((a * c - b * d) :+ (a * d + b * c))
negate !(a :+ b) = (-a) :+ (-b)
abs x = error $ "Cx.abs: " ++ show x
signum x = error $ "Cx.signum: " ++ show x
fromInteger !x = fromInteger x :+ 0
class Num c => Turbo c where
sqr :: c -> c
sqr !x = x * x
twice :: c -> c
twice !x = x + x
instance Turbo Float where
instance Turbo Double where
instance Turbo CDouble where
instance Turbo c => Turbo (Complex c) where
{-# SPECIALIZE instance Turbo (Complex Float) #-}
{-# SPECIALIZE instance Turbo (Complex Double) #-}
{-# SPECIALIZE instance Turbo (Complex DoubleDouble) #-}
{-# SPECIALIZE instance Turbo (Complex QuadDouble) #-}
sqr !(r :+ i) = (sqr r - sqr i) :+ (twice (r * i))
twice !(r :+ i) = (twice r) :+ (twice i)
instance Turbo DoubleDouble where
sqr !x = DD.sqr x
twice !(DoubleDouble a b) = DoubleDouble (twice a) (twice b)
instance Turbo QuadDouble where
sqr !x = QD.sqr x
twice !(QuadDouble a b c d) = QuadDouble (twice a) (twice b) (twice c) (twice d)
hsv2rgb :: R -> R -> R -> (R, R, R)
hsv2rgb !h !s !v
| s == 0 = (v, v, v)
| h == 1 = hsv2rgb 0 s v
| otherwise =
let !i = floor (h * 6) `mod` 6 :: N
!f = (h * 6) - fromIntegral i
!p = v * (1 - s)
!q = v * (1 - s * f)
!t = v * (1 - s * (1 - f))
in case i of
0 -> (v, t, p)
1 -> (q, v, p)
2 -> (p, v, t)
3 -> (p, q, v)
4 -> (t, p, v)
5 -> (v, p, q)
_ -> (0, 0, 0)
colour :: Complex Double -> Complex Double -> N -> (B, B, B)
colour !(zr:+zi) !(dzr:+dzi) !n =
let !il2 = 1 / log 2
!zd2 = sqr zr + sqr zi
!dzd2 = sqr dzr + sqr dzi
!d = (fromIntegral n :: R) - log (log zd2 / log escapeR2) * il2
!dwell = fromIntegral (floor d :: N)
!finala = atan2 zi zr
!de = (log zd2 * il2) * sqrt zd2 / sqrt dzd2
!dscale = log de * il2 + 32
!hue = log d * il2 / 3
!saturation = 0 `max` (log d * il2 / 8) `min` 1
!value = 0 `max` (1 - dscale / 48) `min` 1
!h = hue - fromIntegral (floor hue :: N)
!k = dwell / 2
!satf = if k - fromIntegral (floor k :: N) >= (0.5 :: R) then 0.9 else 1
!valf = if finala < 0 then 0.9 else 1
(!r, !g, !b) = hsv2rgb h (satf * saturation) (valf * value)
!rr = floor $ 0 `max` 255 * r `min` 255
!gg = floor $ 0 `max` 255 * g `min` 255
!bb = floor $ 0 `max` 255 * b `min` 255
in (rr, gg, bb)
data Job c = Job !N !N !(Complex c) !(Complex c) !(Complex c) !N
priority :: Job c -> N
priority !(Job _ _ _ _ _ n) = n
addJob :: RealFloat c => N -> N -> Complex c -> N -> PriorityQueue IO (Job c) -> IOUArray (N,N) Bool -> N -> N -> IO ()
{-# SPECIALIZE addJob :: N -> N -> Complex Float -> N -> PriorityQueue IO (Job Float) -> IOUArray (N,N) Bool -> N -> N -> IO () #-}
{-# SPECIALIZE addJob :: N -> N -> Complex Double -> N -> PriorityQueue IO (Job Double) -> IOUArray (N,N) Bool -> N -> N -> IO () #-}
{-# SPECIALIZE addJob :: N -> N -> Complex DoubleDouble -> N -> PriorityQueue IO (Job DoubleDouble) -> IOUArray (N,N) Bool -> N -> N -> IO () #-}
{-# SPECIALIZE addJob :: N -> N -> Complex QuadDouble -> N -> PriorityQueue IO (Job QuadDouble) -> IOUArray (N,N) Bool -> N -> N -> IO () #-}
addJob !w !h !c !zoom todo sync !i !j = do
already <- readArray sync (j, i)
when (not already) $ do
writeArray sync (j, i) True
enqueue todo $! Job i j (coords w h c zoom i j) 0 0 0
renderer :: (Turbo c, RealFloat c) => ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex c -> N -> IO (IO ())
{-# SPECIALIZE renderer :: ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex Float -> N -> IO (IO ()) #-}
{-# SPECIALIZE renderer :: ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex Double -> N -> IO (IO ()) #-}
{-# SPECIALIZE renderer :: ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex DoubleDouble -> N -> IO (IO ()) #-}
{-# SPECIALIZE renderer :: ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex QuadDouble -> N -> IO (IO ()) #-}
renderer rng output !c !zoom = do
workerts <- mapM (\w -> forkOnIO w $ worker rng c zoom output w) [ 0 .. workers - 1 ]
return $ do
mapM_ killThread workerts
coords :: RealFloat c => N -> N -> Complex c -> N -> N -> N -> Complex c
{-# SPECIALIZE coords :: N -> N -> Complex Float -> N -> N -> N -> Complex Float #-}
{-# SPECIALIZE coords :: N -> N -> Complex Double -> N -> N -> N -> Complex Double #-}
{-# SPECIALIZE coords :: N -> N -> Complex DoubleDouble -> N -> N -> N -> Complex DoubleDouble #-}
{-# SPECIALIZE coords :: N -> N -> Complex QuadDouble -> N -> N -> N -> Complex QuadDouble #-}
coords !w !h !c !zoom !i !j = c + ( (fromIntegral (i - w`div`2) * k)
:+(fromIntegral (h`div`2 - j) * k))
where !k = convert (1/2^^zoom :: Double)
border :: N -> N -> [(N, N)]
border !w !h = concat $
[ [ (j, i) | i <- [ 0 .. w - 1 ], j <- [ 0 .. workers - 1 ] ]
, [ (j, i) | j <- [ 0 .. h - 1 ], i <- [ 0 .. workers - 1 ] ]
, [ (j, i) | j <- [ 0 .. h - 1 ], i <- [ w - workers .. w - 1 ] ]
, [ (j, i) | i <- [ 0 .. w - 1 ], j <- [ h - workers .. h - 1 ] ]
]
worker :: (Turbo c, RealFloat c) => ((N,N),(N,N)) -> Complex c -> N -> (N -> N -> B -> B -> B -> IO ()) -> N -> IO ()
{-# SPECIALIZE worker :: ((N,N),(N,N)) -> Complex Float -> N -> (N -> N -> B -> B -> B -> IO ()) -> N -> IO () #-}
{-# SPECIALIZE worker :: ((N,N),(N,N)) -> Complex Double -> N -> (N -> N -> B -> B -> B -> IO ()) -> N -> IO () #-}
{-# SPECIALIZE worker :: ((N,N),(N,N)) -> Complex DoubleDouble -> N -> (N -> N -> B -> B -> B -> IO ()) -> N -> IO () #-}
{-# SPECIALIZE worker :: ((N,N),(N,N)) -> Complex QuadDouble -> N -> (N -> N -> B -> B -> B -> IO ()) -> N -> IO () #-}
worker rng@((y0,x0),(y1,x1)) !c !zoom output !me = do
sync <- newArray rng False
queue <- newPriorityQueue priority
let addJ = addJob w h c zoom queue sync
js = filter mine (border w h)
w = x1 - x0 + 1
h = y1 - y0 + 1
mapM_ (flip (writeArray sync) True) js
enqueueBatch queue (map (\(j,i) -> Job i j (coords w h c zoom i j) 0 0 0) js)
compute rng addJ output queue
where
mine (j, _) = j `mod` workers == me
compute :: (Turbo c, RealFloat c) => ((N,N),(N,N)) -> (N -> N -> IO ()) -> (N -> N -> B -> B -> B -> IO ()) -> PriorityQueue IO (Job c) -> IO ()
{-# SPECIALIZE compute :: ((N,N),(N,N)) -> (N -> N -> IO ()) -> (N -> N -> B -> B -> B -> IO ()) -> PriorityQueue IO (Job Float) -> IO () #-}
{-# SPECIALIZE compute :: ((N,N),(N,N)) -> (N -> N -> IO ()) -> (N -> N -> B -> B -> B -> IO ()) -> PriorityQueue IO (Job Double) -> IO () #-}
{-# SPECIALIZE compute :: ((N,N),(N,N)) -> (N -> N -> IO ()) -> (N -> N -> B -> B -> B -> IO ()) -> PriorityQueue IO (Job DoubleDouble) -> IO () #-}
{-# SPECIALIZE compute :: ((N,N),(N,N)) -> (N -> N -> IO ()) -> (N -> N -> B -> B -> B -> IO ()) -> PriorityQueue IO (Job QuadDouble) -> IO () #-}
compute rng addJ output queue = do
mjob <- dequeue queue
case mjob of
Just (Job i j c z dz n) -> do
let done' !(zr:+zi) !(dzr:+dzi) !n' = do
let (r, g, b) = colour (convert zr :+ convert zi) (convert dzr :+ convert dzi) n'
output i j r g b
sequence_
[ addJ x y
| u <- spreadX
, v <- spreadY
, let x = i + u
, let y = j + v
, inRange rng (y, x)
]
todo' z' dz' n' = enqueue queue $! Job i j c z' dz' n'
calculate c limit z dz n done' todo'
compute rng addJ output queue
Nothing -> return ()
calculate :: (Turbo c, RealFloat c) => Complex c -> N -> Complex c -> Complex c -> N -> (Complex c -> Complex c -> N -> IO ()) -> (Complex c -> Complex c -> N -> IO ()) -> IO ()
{-# SPECIALIZE calculate :: Complex Float -> N -> Complex Float -> Complex Float -> N -> (Complex Float -> Complex Float -> N -> IO ()) -> (Complex Float -> Complex Float -> N -> IO ()) -> IO () #-}
{-# SPECIALIZE calculate :: Complex Double -> N -> Complex Double -> Complex Double -> N -> (Complex Double -> Complex Double -> N -> IO ()) -> (Complex Double -> Complex Double -> N -> IO ()) -> IO () #-}
{-# SPECIALIZE calculate :: Complex DoubleDouble -> N -> Complex DoubleDouble -> Complex DoubleDouble -> N -> (Complex DoubleDouble -> Complex DoubleDouble -> N -> IO ()) -> (Complex DoubleDouble -> Complex DoubleDouble -> N -> IO ()) -> IO () #-}
{-# SPECIALIZE calculate :: Complex QuadDouble -> N -> Complex QuadDouble -> Complex QuadDouble -> N -> (Complex QuadDouble -> Complex QuadDouble -> N -> IO ()) -> (Complex QuadDouble -> Complex QuadDouble -> N -> IO ()) -> IO () #-}
calculate !c !m0 !z0 !dz0 !n0 done todo = go m0 z0 dz0 n0
where
go !m !z@(zr:+zi) !dz !n
| not (sqr zr + sqr zi < er2) = done z dz n
| m <= 0 = todo z dz n
| otherwise = go (m - 1) (sqr z + c) (let !zdz = z * dz in twice zdz + 1) (n + 1)
!er2 = convert escapeR2
renderer' :: Real c => ((N,N),(N,N)) -> (N -> N -> B -> B -> B -> IO ()) -> Complex c -> N -> IO (IO ())
renderer' rng output !c !zoom
| zoom < 20 = renderer rng output (f c :: Complex Float ) zoom
| zoom < 50 = renderer rng output (f c :: Complex Double ) zoom
| zoom < 100 = renderer rng output (f c :: Complex DoubleDouble) zoom
| otherwise = renderer rng output (f c :: Complex QuadDouble ) zoom
where f !(cr :+ ci) = convert cr :+ convert ci
data Args = Args{ aWidth :: N, aHeight :: N }
defaultArgs :: Args
defaultArgs = Args{ aWidth = 788, aHeight = 576 }
combineArgs :: Args -> String -> Args
combineArgs a0 s
| "--width=" `isPrefixOf` s = a0{ aWidth = read $ "--width=" `dropPrefix` s }
| "--height=" `isPrefixOf` s = a0{ aHeight = read $ "--height=" `dropPrefix` s }
| "-w=" `isPrefixOf` s = a0{ aWidth = read $ "-w=" `dropPrefix` s }
| "-h=" `isPrefixOf` s = a0{ aHeight = read $ "-h=" `dropPrefix` s }
| otherwise = a0
dropPrefix :: String -> String -> String
dropPrefix p s = drop (length p) s
roundUp2 :: N -> N
roundUp2 n = head . dropWhile (< n) . iterate (2*) $ 1
main :: IO ()
main = do
args <- foldl combineArgs defaultArgs `fmap` unsafeInitGUIForThreadedRTS
let width = aWidth args
height = aHeight args
size = roundUp2 (width `max` height)
rng = ((0, 0), (height - 1, width - 1))
_ <- initGL
glconfig <- glConfigNew [ GLModeRGBA, GLModeDouble ]
canvas <- glDrawingAreaNew glconfig
widgetSetSizeRequest canvas width height
imgdata <- mallocBytes $ width * height * 3
pokeArray imgdata (replicate (height * width * 3) (255 :: B))
let output x y r g b = do
let p = imgdata `plusPtr` ((y * width + x) * 3)
pokeByteOff p 0 r
pokeByteOff p 1 g
pokeByteOff p 2 b
window <- windowNew
eventb <- eventBoxNew
set window [ containerBorderWidth := 0, containerChild := eventb,windowResizable := False ]
set eventb [ containerBorderWidth := 0, containerChild := canvas ]
mapM_ (flip forkOnIO $ fpu_fix_start nullPtr) [ 0 .. numCapabilities - 1 ]
stop0 <- renderer' rng output c0 zoom0
sR <- newIORef (c0, zoom0, stop0)
_ <- eventb `on` buttonPressEvent $ {-# SCC "cb/event" #-} tryEvent $ do
LeftButton <- eventButton
(x, y) <- eventCoordinates
liftIO $ do
(c, zoom, stop) <- readIORef sR
stop
pokeArray imgdata (replicate (height * width * 3) (255 :: B))
let c' = c + ((convert x :+ convert (-y)) - (fromIntegral width :+ fromIntegral (-height)) * (0.5 :+ 0)) * ((1/2^^zoom) :+ 0)
zoom' = zoom + 1
stop' <- renderer' rng output c' zoom'
writeIORef sR (c', zoom', stop')
print (c', zoom') -- FIXME replace with GUI widgets
_ <- onRealize canvas $ {-# SCC "cb/realize" #-}withGLDrawingArea canvas $ \_ -> do
GL.clearColor $= (GL.Color4 0.0 0.0 0.0 0.0)
GL.matrixMode $= GL.Projection
GL.loadIdentity
GL.ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0
GL.drawBuffer $= GL.BackBuffers
[tex] <- GL.genObjectNames 1
GL.texture GL.Texture2D $= GL.Enabled
GL.textureBinding GL.Texture2D $= Just tex
GL.texImage2D Nothing GL.NoProxy 0 GL.RGB' (GL.TextureSize2D (fromIntegral size) (fromIntegral size)) 0 (GL.PixelData GL.RGB GL.UnsignedByte nullPtr)
GL.textureFilter GL.Texture2D $= ((GL.Nearest, Nothing), GL.Nearest)
GL.textureWrapMode GL.Texture2D GL.S $= (GL.Repeated, GL.ClampToEdge)
GL.textureWrapMode GL.Texture2D GL.T $= (GL.Repeated, GL.ClampToEdge)
_ <- onExpose canvas $ {-# SCC "cb/expose" #-} \_ -> do
withGLDrawingArea canvas $ \glwindow -> do
let v :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> IO ()
v tx ty vx vy = GL.texCoord (GL.TexCoord2 tx ty) >> GL.vertex (GL.Vertex2 vx vy)
w = fromIntegral width
h = fromIntegral height
sx = fromIntegral width / fromIntegral size
sy = fromIntegral height / fromIntegral size
GL.clear [ GL.ColorBuffer ]
GL.texSubImage2D Nothing 0 (GL.TexturePosition2D 0 0) (GL.TextureSize2D w h) (GL.PixelData GL.RGB GL.UnsignedByte imgdata)
GL.renderPrimitive GL.Quads $ do
v 0 sy 0 0 >> v 0 0 0 1 >> v sx 0 1 1 >> v sx sy 1 0
glDrawableSwapBuffers glwindow
return True
_ <- onDestroy window mainQuit
_ <- timeoutAdd (widgetQueueDraw canvas >> return True) 200
widgetShowAll window
mainGUI
spreadX, spreadY :: [ N ]
spreadX = [ -workers, 0, workers ]
spreadY = [ -1, 0, 1 ]
workers :: N
workers = numCapabilities
limit :: N
limit = (2^(11::N)-1)
c0 :: Complex QuadDouble
c0 = 0
zoom0 :: N
zoom0 = 6
escapeR, escapeR2 :: R
escapeR = 65536
escapeR2 = escapeR * escapeR