packages feed

glsl-0.0.1.0: src/Language/GLSL/BitCode.hs

{-# LANGUAGE BinaryLiterals        #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE ViewPatterns          #-}
{-# OPTIONS_GHC -Wno-missing-signatures #-}
module Language.GLSL.BitCode where

import           Data.Bits                   (shiftL, (.|.))
import qualified Data.Text.Lazy              as LT
import qualified Data.Text.Lazy.IO           as IO
import           Data.Word                   (Word64)
import           Debug.Trace                 (trace)
import           Language.GLSL.AST
import           Language.GLSL.ConstExpr     (ConstExprs)
import qualified Language.GLSL.ConstExpr     as ConstExpr
import           Language.GLSL.Internal.Bits (B (..), expand, flat, zero)
import           Language.GLSL.Parser        (parseShader)
import           Language.GLSL.PrettyPrint


assemble :: BitsStmt -> Word64
assemble bits = trace (show bits) $
  foldr (\(toInt -> a) b -> (a `shiftL` 1) .|. b) 0 $ flat bits

toInt :: B -> Word64
toInt O = 0
toInt I = 1

type BitsStmt = ((B,B), (BitsType, B, BitsExpr))
encodeStmt :: Maybe ConstExprs -> Stmt a -> [BitsStmt]
encodeStmt ce = \case
  AssignStmt _ e ->
    [(bitsAssignStmt, (zero, zero, encodeExpr ce e))]
  DeclStmt d ->
    [(bitsDeclStmt, encodeLocalDecl ce d)]
  EmitStmt e ->
    [(bitsEmitStmt, uncurry (zero,,) $ encodeEmit ce e)]
  IfStmt _ t e -> do
    [(bitsIfStmt, ((bitsThenStmt, zero), zero, zero))]
    ++ concatMap (encodeStmt ce . unAnnot) t
    ++ [(bitsIfStmt, ((bitsElseStmt, zero), zero, zero))]
    ++ concatMap (encodeStmt ce . unAnnot) e
    ++ [(bitsIfStmt, ((bitsEndifStmt, zero), zero, zero))]
  where
    -- 2 bits encode which statement this is.
    bitsAssignStmt     = (O,O)
    bitsDeclStmt       = (O,I)
    bitsEmitStmt       = (I,O)
    bitsIfStmt         = (I,I)

    -- For IfStmt, we need to know when "else" branch starts and when it's over.
    -- We encode that in the next 2 bits for the IfStmt encoding.
    bitsThenStmt       = (O,O)
    bitsElseStmt       = (O,I)
    bitsEndifStmt      = (I,O)

encodeLocalDecl :: Maybe ConstExprs -> LocalDecl -> (BitsType, B, BitsExpr)
encodeLocalDecl _ (LDecl ty _ Nothing) =
  -- 1 bit encodes whether the declaration has an initialiser.
  (encodeType ty, O, zero)
encodeLocalDecl ce (LDecl ty _ (Just e)) =
  (encodeType ty, I, encodeExpr ce e)

-- | 2 bits encode the type, then 2 bits encode the vec/mat size.
type BitsType = ((B,B), (B,B))
encodeType :: Type -> BitsType
encodeType = \case
  TyBool      -> (bitsTyBool, (O,O))
  TyFloat     -> (bitsTyFloat, (O,O))
  TyVec i     -> (bitsTyVec, encodeVecSize i i)
  TyMat i j   -> (bitsTyMat, encodeVecSize i j)
  TySampler2D -> error "no encoding for local sampler declarations"
  TyStruct{}  -> error "no encoding for local struct declarations"
  where
    -- 2 bits encode the type of a LocalDecl.
    bitsTyBool         = (O,O)
    bitsTyFloat        = (O,I)
    bitsTyVec          = (I,O)
    bitsTyMat          = (I,I)

encodeVecSize :: Int -> Int -> (B,B)
encodeVecSize i j | i /= j = error "no encoding for non-square matrices"
encodeVecSize i _ = case i of
  2 -> bitsVecSize2
  3 -> bitsVecSize3
  4 -> bitsVecSize4
  _ -> error $ "no encoding for vec/mat size: " <> show i
  where
    -- 2 bits encode the size of TyVec/TyMat. We only allow square matrices, so
    -- the size of mat2x2 has the same encoding as the size of vec2.
    bitsVecSize2       = (O,O)
    bitsVecSize3       = (O,I)
    bitsVecSize4       = (I,O)

encodeEmit :: Maybe ConstExprs -> Emit -> (B, BitsExpr)
encodeEmit _ EmitFragDepth     = (O,zero)
encodeEmit ce (EmitPosition e) = (I,encodeExpr ce e)

type BitsExpr = ((B,B), BitsFunName, BitsExprAtom, BitsExprAtom)
encodeExpr :: Maybe ConstExprs -> Expr -> BitsExpr
encodeExpr (Just ce) e | ConstExpr.isConstExpr ce e =
  -- Constant expressions are encoded as atom expressions with the "constant
  -- expression" operator 0b11.
  ((O,O), expand (I,I), zero, zero)
encodeExpr _ expr = case expr of
  UnaryExpr o e -> (bitsUnaryExpr, expand $ encodeUnaryOp o, encodeExprAtom e, zero)
  BinaryExpr l o r -> (bitsBinaryExpr, expand $ encodeBinaryOp o, encodeExprAtom l, encodeExprAtom r)
  TextureExpr t x y -> (bitsTextureExpr, zero, zero, zero)
  FunCallExpr f args -> uncurry (bitsFunCallExpr, encodeFunName f,,) $ encodeArgs f args
  AtomExpr e -> (bitsAtomExpr, expand bitsUOpIdentity, encodeExprAtom e, zero)
  where
    -- 2 bits encode the expression type.
    bitsUnaryExpr      = (O,O)
    bitsBinaryExpr     = (O,I)
    bitsFunCallExpr    = (I,O)
    bitsTextureExpr    = (I,I)
    -- AtomExpr is a encoded as UnaryExpr with a zero operator.
    bitsAtomExpr       = bitsUnaryExpr
    bitsUOpIdentity    = (O,O)

-- | Implements special encodings for functions with more than 2 args.

-- Because we can't encode all the args individually, we take shortcuts where we
-- know something about how a function tends to be called.
encodeArgs :: FunName -> [ExprAtom] -> (BitsExprAtom, BitsExprAtom)
encodeArgs PrimSmoothstep [a,b,c] | isLitExpr a && isLitExpr b =
  (encodeExprAtom c, zero)
encodeArgs PrimVec4 args@[a,_,_,d] | all isLitExpr args =
  (encodeExprAtom a, encodeExprAtom d)
encodeArgs PrimVec4 [a,b,c,d] | all isIdentifierExpr [a,b,c] && isLitExpr d =
  (encodeExprAtom a, encodeExprAtom d)
encodeArgs PrimVec4 args@[a,_,_,d] | all isIdentifierExpr args =
  (encodeExprAtom a, encodeExprAtom d)
encodeArgs PrimMat4x4 args@[a,_,_,d] | all isIdentifierExpr args =
  (encodeExprAtom a, encodeExprAtom d)

encodeArgs _ [] = (zero, zero)
encodeArgs _ [a] = (encodeExprAtom a, zero)
encodeArgs _ [a,b] = (encodeExprAtom a, encodeExprAtom b)
encodeArgs f args =
  error $ "unsupported argument list for '" <> pp ppFunName f <> "': "
        <> pps ", " ppExprAtom args

encodeUnaryOp :: UnaryOp -> (B,B)
encodeUnaryOp = \case
  UOpMinus -> bitsUOpMinus
  UOpNot   -> bitsUOpNot
  where
    -- 2 bits encode encode the unary operator. 0 is AtomExpr (no operator or
    -- unary "+", i.e. the identity operator).
    bitsUOpMinus       = (O,I)
    bitsUOpNot         = (I,O)

-- | 4 bits encode the binary operator.
type BitsBinaryOp = (B,B,B,B)
encodeBinaryOp :: BinaryOp -> BitsBinaryOp
encodeBinaryOp = \case
  BOpPlus  -> bitsBOpPlus
  BOpMinus -> bitsBOpMinus
  BOpMul   -> bitsBOpMul
  BOpDiv   -> bitsBOpDiv
  BOpGE    -> bitsBOpGE
  BOpGT    -> bitsBOpGT
  BOpLE    -> bitsBOpLE
  BOpLT    -> bitsBOpLT
  BOpAnd   -> bitsBOpAnd
  BOpOr    -> bitsBOpOr
  where
    bitsBOpPlus        = (O,O,O,O)
    bitsBOpMinus       = (O,O,O,I)
    bitsBOpMul         = (O,O,I,O)
    bitsBOpDiv         = (O,O,I,I)
    bitsBOpGE          = (O,I,O,O)
    bitsBOpGT          = (O,I,O,I)
    bitsBOpLE          = (O,I,I,O)
    bitsBOpLT          = (O,I,I,I)
    bitsBOpAnd         = (I,O,O,O)
    bitsBOpOr          = (I,O,O,I)

-- | 5 bits encode the function name for FunCallExpr.
type BitsFunName = (B,B,B,B,B)
encodeFunName :: FunName -> BitsFunName
encodeFunName = \case
  PrimAbs        -> bitsPrimAbs
  PrimAsin       -> bitsPrimAsin
  PrimAtan       -> bitsPrimAtan
  PrimCos        -> bitsPrimCos
  PrimCross      -> bitsPrimCross
  PrimDot        -> bitsPrimDot
  PrimFloor      -> bitsPrimFloor
  PrimFract      -> bitsPrimFract
  PrimLength     -> bitsPrimLength
  PrimMat3x3     -> bitsPrimMat3x3
  PrimMat4x4     -> bitsPrimMat4x4
  PrimMod        -> bitsPrimMod
  PrimNormalize  -> bitsPrimNormalize
  PrimPow        -> bitsPrimPow
  PrimSin        -> bitsPrimSin
  PrimSmoothstep -> bitsPrimSmoothstep
  PrimSqrt       -> bitsPrimSqrt
  PrimStep       -> bitsPrimStep
  PrimTan        -> bitsPrimTan
  PrimVec2       -> bitsPrimVec2
  PrimVec3       -> bitsPrimVec3
  PrimVec4       -> bitsPrimVec4
  where
    bitsPrimAbs        = (O,O,O,O,O)
    bitsPrimAsin       = (O,O,O,O,I)
    bitsPrimAtan       = (O,O,O,I,O)
    bitsPrimCos        = (O,O,O,I,I)
    bitsPrimCross      = (O,O,I,O,O)
    bitsPrimDot        = (O,O,I,O,I)
    bitsPrimFloor      = (O,O,I,I,O)
    bitsPrimFract      = (O,O,I,I,I)
    bitsPrimLength     = (O,I,O,O,O)
    bitsPrimMat3x3     = (O,I,O,O,I)
    bitsPrimMat4x4     = (O,I,O,I,O)
    bitsPrimMod        = (O,I,O,I,I)
    bitsPrimNormalize  = (O,I,I,O,O)
    bitsPrimPow        = (O,I,I,O,I)
    bitsPrimSin        = (O,I,I,I,O)
    bitsPrimSmoothstep = (O,I,I,I,I)
    bitsPrimSqrt       = (I,O,O,O,O)
    bitsPrimStep       = (I,O,O,O,I)
    bitsPrimTan        = (I,O,O,I,O)
    bitsPrimVec2       = (I,O,O,I,I)
    bitsPrimVec3       = (I,O,I,O,O)
    bitsPrimVec4       = (I,O,I,O,I)

-- | ExprAtom type is encoded in 3 bits.
encodeExprAtomType :: ExprAtom -> (B,B,B)
encodeExprAtomType = \case
  LitIntExpr{}     -> bitsLitExpr
  LitFloatExpr{}   -> bitsLitExpr
  IdentifierExpr{} -> bitsIdentifierExpr
  SwizzleExpr{}    -> bitsSwizzleExpr
  VecIndexExpr{}   -> bitsVecIndexExpr
  MatIndexExpr{}   -> bitsMatIndexExpr
  where
    -- Literals are encoded the same.
    bitsLitExpr        = (O,O,O)
    bitsIdentifierExpr = (O,O,I)
    bitsSwizzleExpr    = (O,I,O)
    bitsVecIndexExpr   = (O,I,I)
    bitsMatIndexExpr   = (I,O,O)

-- ExprAtom is encoded in 7 bits: 3 bits for type and 4 bits for arguments.
--
-- We only need the full 4 bits for MatIndexExpr arguments which can be any of
-- the 16 positions in the matrix indexing operation.
type BitsExprAtom = ((B,B,B), ((B,B), (B,B)))
encodeExprAtom :: ExprAtom -> BitsExprAtom
encodeExprAtom e = (encodeExprAtomType e, encodeOperand e)
  where
    encodeOperand (SwizzleExpr _ i)    = (encodeSwizzle i, (O,O))
    encodeOperand (VecIndexExpr _ i)   = (encodeSwizzle i, (O,O))
    encodeOperand (MatIndexExpr _ i j) = (encodeSwizzle i, encodeSwizzle j)
    encodeOperand _                    = ((O,O),(O,O))

-- Swizzle/VecIndex is encoded in 2 bits.
encodeSwizzle :: Swizzle -> (B,B)
encodeSwizzle = \case
  X -> bitsSwizzleX
  Y -> bitsSwizzleY
  Z -> bitsSwizzleZ
  W -> bitsSwizzleW
  where
    bitsSwizzleX       = (O,O)
    bitsSwizzleY       = (O,I)
    bitsSwizzleZ       = (I,O)
    bitsSwizzleW       = (I,I)


parse :: LT.Text -> Either String (GLSL ())
parse = parseShader

main :: IO ()
main = do
  putStrLn "Loading shader source..."
  -- inText <- IO.readFile "../large-shaders/lambdacnc.frag"
  -- inText <- IO.readFile "../large-shaders/lambdacnc.vert"
  -- inText <- IO.readFile "../large-shaders/lambdaray.frag"
  -- inText <- IO.readFile "../large-shaders/xax.frag"
  -- inText <- IO.readFile "../large-shaders/xax.vert"
  inText <- IO.readFile "../large-shaders/small.vert"
  putStrLn "Parsing shader source..."
  case parse inText of
    Left err -> writeFile "../opt.glsl" $ "// Error\n" <> err
    Right (GLSL _ (reverse -> (ProcDecl _ _ ss):_)) ->
      mapM_ (print . encodeStmt Nothing . unAnnot) ss
    Right _ ->
      return ()