packages feed

mischief-render-0.1.0.0: src/Mischief/Shader/Generation.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeAbstractions #-}

module Mischief.Shader.Generation where

import Control.Monad.State hiding (get)
import Data.Data hiding (cast)
import Data.Default
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics
import GHC.TypeLits (KnownNat, Nat, natVal)
import Mischief.Render.Core
import Mischief.Render.Texture
import Mischief.Shader.Functions
import Mischief.Shader.Internal.Bindings
import Mischief.Shader.Internal.Params
import Mischief.Shader.Internal.State
import Mischief.Shader.Types hiding ((<>))
import Unsafe.Coerce (unsafeCoerce)
import Prelude (String, map, pure, show, undefined, ($), (.), (<>))
import Prelude qualified

data Binds f = Binds
  { sampler :: Binding f Sampler,
    texture :: Binding f Texture
  }
  deriving (Generic, Bindable)

data VertexOutput f = VertexOutput
  { position :: BuiltIn f "position" (Vec 4 Float),
    uv :: Location f 0 (Vec 2 Float)
  }
  deriving (Generic, ShaderParam)

newtype FragmentOutput f = FragmentOutput (Location f 0 (Vec 4 Float))
  deriving stock (Generic)
  deriving anyclass (ShaderParam)

-- frag :: Binds -> BuiltIn "location" Vec2f -> Shader (Location 0 Vec4f)
-- frag b input = do
--   let result = sample (get b.texture) (get b.sampler) (get input)
--   pure (set result)

shaderToWGSL :: forall a m. (ShaderParam a) => Map TypeRep ErasedValue -> Shader m (a GPU) -> Text
shaderToWGSL dict (Shader s) = do
  let (a, ShaderState {ast}) = runState s ShaderState {counter = 0, ast = Empty, dict}
  let ast' = case (paramDefinition @a) of
        StructParam n _ -> do
          let Values vs = collectValues a
          let vst = map valueToWGSL vs
          Concat ast (Return (StructInit n vst))
        AnonParam _ _ -> do
          let Values [v] = collectValues a
          Concat ast (Return (unwrapValue v))
  stmtToWGSL ast'

unwrapValue :: forall a. Value -> Expr a
unwrapValue (Value (a :: Expr b)) = unsafeCoerce a

valueToWGSL :: Value -> Text
valueToWGSL (Value (a :: Expr a)) = exprToWGSL a

genFunction :: forall a m. (ShaderParam a) => Text -> Text -> Text -> Map TypeRep ErasedValue -> Shader m (a GPU) -> Text
genFunction name input output dict s = "fn " <> name <> "(" <> input <> ") -> " <> output <> " {\n" <> shaderToWGSL dict s <> "\n}"

genInput :: ParamDefinition -> Text
genInput (StructParam name _) = "input: " <> name
genInput (AnonParam n m) = n <> "input: " <> m

genOutput :: ParamDefinition -> Text
genOutput (StructParam name _) = name
genOutput (AnonParam n m) = n <> m

genShader :: forall m p a. (ShaderParam p, ShaderParam a) => Text -> [ErasedBinding] -> (p GPU -> Shader m (a GPU)) -> Text
genShader tag b s = genBindings 0 b <> genParams (Proxy @p) <> genParams (Proxy @a) <> "@" <> tag <> "\n" <> genFunction "main" (genInput $ paramDefinition @p) (genOutput $ paramDefinition @a) (genDict 0 b) (s dummyParam)

genDict :: Nat -> [ErasedBinding] -> Map TypeRep ErasedValue
genDict _ [] = Map.empty
genDict n (ErasedBinding _ m (_ :: Proxy b) : xs) =
  let (y, n') = dummyBinding @b n
   in Map.union (Map.singleton (typeRep m) (ErasedValue y)) (genDict n' xs)

genBindings :: Nat -> [ErasedBinding] -> Text
genBindings _ [] = ""
genBindings n (ErasedBinding _ _ (_ :: Proxy b) : xs) =
  let (Bindings x, n') = collectBindings n (Proxy @(b Internal))
   in T.concat (map genBinding x) <> "\n" <> genBindings n' xs

genBinding :: BindingData -> Text
genBinding NormalData {bType, index} = "@group(0) @binding(" <> T.pack (show index) <> ")\nvar b" <> T.pack (show index) <> " : " <> bType <> ";\n\n"
genBinding UniformData {structName, structFields, index} =
  "@group(0) @binding(" <> T.pack (show index) <> ")\nvar<uniform> b" <> T.pack (show index) <> " : " <> structName <> ";\n\n" <> genBuffer structName structFields

genBuffer :: Text -> [(Text, Text)] -> Text
genBuffer name fields =
  let fields' = map (\(x, y) -> "  " <> x <> ": " <> y) fields
   in "struct " <> name <> " {\n" <> T.intercalate ",\n" fields' <> "\n};\n\n"

genParams :: forall p. (ShaderParam p) => Proxy p -> Text
genParams _ =
  let definition = paramDefinition @p
   in case definition of
        StructParam name (Params params) -> "struct " <> name <> " {\n" <> T.concat (map genParam params) <> "};\n\n"
        _ -> ""

genParam :: ParamData -> Text
genParam ParamData {pType, index = BuiltInKind s} = "  @builtin(" <> s <> ") " <> s <> " : " <> pType <> ",\n"
genParam ParamData {pType, index = LocKind n} = "  @location(" <> T.pack (show n) <> ") l" <> T.pack (show n) <> " : " <> pType <> ",\n"

genShaderT :: forall m b a p. (Bindable b, ShaderParam p, ShaderParam a) => [ErasedBinding] -> (p GPU -> Shader m (a GPU)) -> String
genShaderT p = T.unpack . genShader "no_tag" p