packages feed

glsl-0.0.1.0: src/Language/GLSL/Runtime/Eval.hs

{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE StrictData                 #-}
{-# LANGUAGE ViewPatterns               #-}
module Language.GLSL.Runtime.Eval where

import           Control.Lens                     ((^.))
import           Control.Monad                    (foldM, foldM_)
import           Control.Monad.Trans.State.Strict (evalStateT, get, modify')
import qualified Data.IntMap                      as M
import qualified Data.Text.Lazy                   as LT
import qualified Data.Text.Lazy.IO                as IO
import qualified Debug.Trace                      as Trace
import           Language.GLSL.AST
import           Language.GLSL.Decls              (addDecl, addDeclN, addDeclNE,
                                                   emptyDecls, getDeclNE,
                                                   toUniformId)
import           Language.GLSL.Parser             (parseShader)
import           Language.GLSL.PrettyPrint        (pp, ppExpr, ppGlobalDecl,
                                                   ppNameExpr, ppSwizzle,
                                                   ppVecIndex)
import qualified Language.GLSL.Runtime.PrimFuns   as PrimFuns
import           Language.GLSL.Runtime.Value      (Eval, EvalResult (..),
                                                   EvalState (..), Proc (..),
                                                   Value (..), defaultValue,
                                                   evalBinaryOp, evalCoerce,
                                                   evalUnaryOp, isNaNValue,
                                                   roundValue)
import           Linear                           (R1 (..), R2 (..), R3 (..),
                                                   R4 (..))


traceAssignments :: Bool
traceAssignments = False

trace :: String -> a -> a
trace = if traceAssignments then Trace.trace else const id


eval :: LT.Text -> Either String Value
eval code = do
  glsl <- parseShader code
  fromResult $ evalStateT (evalGLSL glsl) startState


startState :: EvalState
startState = EvalState
  { stProcs = M.empty
  , stMainProc = Nothing

  , globals = emptyDecls
  , gl_Position = Nothing
  }


evalGLSL :: GLSL () -> Eval Value
evalGLSL (GLSL _ d) = do
  mapM_ discoverTopDecl d
  evalMain
  -- maybe (FloatValue 0) id . gl_Position <$> get
  roundValue <$> getValue emptyLocals (NameExpr (Name NsOut (NameId 0)))

discoverTopDecl :: TopDecl () -> Eval ()
discoverTopDecl (LayoutDecl _ d) = discoverGlobalDecl d
discoverTopDecl (GlobalDecl d) = discoverGlobalDecl d
discoverTopDecl (ProcDecl ProcMain params body) =
  modify' $ \st -> st{stMainProc = Just $ Proc params body}
discoverTopDecl (ProcDecl (ProcName (NameId n)) params body) =
  modify' $ \st@EvalState{..} -> st{stProcs = M.insert n (Proc params body) stProcs}

discoverGlobalDecl :: GlobalDecl -> Eval ()
discoverGlobalDecl (GDecl GkUniform (TyStruct _ fields) (Name NsU n)) =
  modify' $ \st@EvalState{..} ->
    st{globals = foldr (\(ty, m) -> addDecl NsU (toUniformId (n, m)) (defaultValue ty)) globals fields}
discoverGlobalDecl (GDecl GkOut ty n) =
  modify' $ \st@EvalState{..} -> st{globals = addDeclN n (defaultValue ty) globals}
discoverGlobalDecl (GDecl GkIn ty n) =
  modify' $ \st@EvalState{..} -> st{globals = addDeclN n (defaultValue ty) globals}
discoverGlobalDecl d@(GDecl GkUniform _ _) =
  fail $ "unsupported uniform type in decl: " <> pp ppGlobalDecl d


evalMain :: Eval ()
evalMain = do
  Just mainProc <- stMainProc <$> get
  evalProc mainProc []
  return ()

newtype LocalState = LocalState
  { temps :: M.IntMap Value
  }

emptyLocals :: LocalState
emptyLocals = LocalState
  { temps = M.empty
  }

evalProc :: Proc -> [Value] -> Eval ()
evalProc (Proc _params ss) _args =
  foldM_ evalStmtAnnot emptyLocals ss

evalStmtAnnot :: LocalState -> StmtAnnot () -> Eval LocalState
evalStmtAnnot lst (SA () s) = evalStmt lst s

evalStmt :: LocalState -> Stmt () -> Eval LocalState
evalStmt lst (AssignStmt n e) = do
  v <- evalExpr lst e
  setValue lst (NameExpr n) v
evalStmt lst (DeclStmt d) = evalLocalDecl lst d
evalStmt lst (EmitStmt e) = evalEmit lst e
evalStmt lst (IfStmt cond thens elses) = do
  BoolValue v <- getValue lst (NameExpr (Name NsT cond))
  if v
    then foldM evalStmtAnnot lst thens
    else foldM evalStmtAnnot lst elses

evalEmit :: LocalState -> Emit -> Eval LocalState
evalEmit lst EmitFragDepth = return lst
evalEmit lst (EmitPosition e) = do
  v <- evalExpr lst e
  modify' $ \st -> st{gl_Position = Just v}
  return lst

evalLocalDecl :: LocalState -> LocalDecl -> Eval LocalState
evalLocalDecl lst (LDecl ty (NameExpr . Name NsT -> n) Nothing) =
  let v = defaultValue ty in
  setValue lst n v
evalLocalDecl lst (LDecl ty (NameExpr . Name NsT -> n) (Just e)) = do
  v <- evalExpr lst e >>= evalCoerce ty
  setValue lst n v

evalExpr :: LocalState -> Expr -> Eval Value
evalExpr lst = \case
  BinaryExpr l op r -> do
    lv <- evalExprAtom lst l
    rv <- evalExprAtom lst r
    return $ evalBinaryOp lv op rv
  UnaryExpr op e -> do
    v <- evalExprAtom lst e
    return $ evalUnaryOp op v
  AtomExpr e -> evalExprAtom lst e
  FunCallExpr fun args -> do
    vals <- mapM (evalExprAtom lst) args
    PrimFuns.eval fun vals
  e@TextureExpr{} ->
    fail $ "texture() not implemented: " <> pp ppExpr e

evalExprAtom :: LocalState -> ExprAtom -> Eval Value
evalExprAtom lst = \case
  LitFloatExpr _ f   -> return $ FloatValue f
  LitIntExpr _ i     -> return $ IntValue i
  IdentifierExpr n   -> getValue lst n
  SwizzleExpr n s    -> getValue lst (NameExpr (Name NsT n)) >>= evalVecIndex s
  VecIndexExpr n i   -> getValue lst n >>= evalVecIndex i
  MatIndexExpr n i j -> getValue lst n >>= evalMatIndex i j

evalVecIndex :: Swizzle -> Value -> Eval Value
evalVecIndex X (Vec2Value v) = return $ FloatValue $ v ^. _x
evalVecIndex Y (Vec2Value v) = return $ FloatValue $ v ^. _y
evalVecIndex X (Vec3Value v) = return $ FloatValue $ v ^. _x
evalVecIndex Y (Vec3Value v) = return $ FloatValue $ v ^. _y
evalVecIndex Z (Vec3Value v) = return $ FloatValue $ v ^. _z
evalVecIndex X (Vec4Value v) = return $ FloatValue $ v ^. _x
evalVecIndex Y (Vec4Value v) = return $ FloatValue $ v ^. _y
evalVecIndex Z (Vec4Value v) = return $ FloatValue $ v ^. _z
evalVecIndex W (Vec4Value v) = return $ FloatValue $ v ^. _w
evalVecIndex s v = fail $ "cannot access " <> pp ppSwizzle s <> " on " <> show v

evalMatIndex :: Swizzle -> Swizzle -> Value -> Eval Value
evalMatIndex i j (Mat4x4Value v) =
  return $ FloatValue $ v ^. (swizzle i . swizzle j)
  where
    swizzle X = _x
    swizzle Y = _y
    swizzle Z = _z
    swizzle W = _w
evalMatIndex i j v =
  fail $ "cannot access [" <> pp ppVecIndex i <> "][" <> pp ppVecIndex j <> "]"
      <> " on " <> show v


setValue :: LocalState -> NameExpr -> Value -> Eval LocalState
setValue lst@LocalState{..} n@(NameExpr (Name NsT (NameId nId))) v =
  trace (pp ppNameExpr n <> " = " <> show v) $
  if isNaNValue v
    then fail $ pp ppNameExpr n <> " = " <> show v
    else return lst{temps = M.insert nId v temps}
setValue lst n v = do
  modify' $ \st@EvalState{..} -> st{globals = addDeclNE n v globals}
  trace (pp ppNameExpr n <> " = " <> show v) $
    if isNaNValue v
      then fail $ pp ppNameExpr n <> " = " <> show v
      else return lst


getValue :: LocalState -> NameExpr -> Eval Value
getValue LocalState{..} (NameExpr (Name NsT (NameId n))) = do
  let Just v = M.lookup n temps
  return v
getValue _ n = do
  v <- getDeclNE n . globals <$> get
  case v of
    Nothing -> fail $ "undefined global: " <> pp ppNameExpr n
    Just ok -> return ok


main :: IO ()
main = do
  txt <- IO.readFile "../large-shaders/xax.vert"
  print $ eval txt