packages feed

mischief-render-0.1.0.0: src/Mischief/Shader/Internal/Bindings.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-partial-fields #-}

module Mischief.Shader.Internal.Bindings where

import Data.Bifunctor qualified
import Data.Data
import Data.Default (Default (def))
import Data.Kind
import Data.Primitive.Ptr
import Data.Singletons (SingI)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Vector.Storable qualified as VS
import Foreign (Bits ((.|.)), with)
import Foreign.C.ConstPtr
import GHC.Generics (Generic (Rep, from), K1 (K1), M1 (M1), U1 (U1), V1 (..), to, (:*:) ((:*:)))
import GHC.TypeLits
import Mischief.ECS (System)
import Mischief.Render.Buffer (Buffer (..), BufferDescriptor (..))
import Mischief.Render.Core
import Mischief.Render.Texture
import Mischief.Shader.Internal.Buffers qualified as Buffers
import Mischief.Shader.Internal.Singletons
import Mischief.Shader.Internal.State
import Mischief.Shader.Types hiding ((<>))
import Mischief.WGPU
import Mischief.WGPU.Opaque (WGPUBindGroup, WGPUBindGroupLayout)
import Mischief.WGPU.Types.Enums
import Mischief.WGPU.Types.General

-- data Binding (index :: Nat) a where
--   Binding :: forall b index. Expr (AssociatedExpr b) -> Binding index b
--   BindEntry :: WGPUBindGroupEntry -> Binding index b

-- data Uniform (index :: Nat) a where
--   Uniform :: forall b index. WGPUBindGroupEntry -> Uniform index b

newtype UniformBinding a = UniformBinding a

newtype NormalBinding a = NormalBinding a

type family Binding f a where
  Binding Internal b = NormalBinding b
  Binding CPU Sampler = Sampler
  Binding GPU Sampler = Expr TSampler
  Binding CPU Texture = TextureView
  Binding GPU Texture = Expr TTexture

type family Uniform f a where
  Uniform Internal b = UniformBinding b
  Uniform CPU (a b) = Buffer a
  Uniform GPU (Expr a) = Expr a
  Uniform GPU (a GPU) = a GPU

-- Uniform CPU

-- type family ToGpu a where
--   T

type family AssociatedExpr a where
  AssociatedExpr (Expr a) = a
  AssociatedExpr Texture = TTexture
  AssociatedExpr Sampler = TSampler

-- instance (KnownNat n, SingI (AssociatedExpr a)) => Default (Binding n a) where
--   def = Binding $ BindingVar (natVal (Proxy @n))

data BindingData
  = NormalData
      { bType :: Text,
        index :: Integer
      }
  | UniformData
      { structName :: Text,
        structFields :: [(Text, Text)],
        index :: Integer
      }
  deriving (Show)

newtype Bindings = Bindings [BindingData] deriving newtype (Semigroup, Show)

class (Typeable a) => Bindable a where
  collectBindings :: Nat -> Proxy (a Internal) -> (Bindings, Nat)
  default collectBindings :: (GCollectBindings (Rep (a Internal))) => Nat -> Proxy (a Internal) -> (Bindings, Nat)
  collectBindings n _ = gCollectBindings (Proxy @(Rep (a Internal))) n

  collectLayouts :: Nat -> Proxy (a Internal) -> ([WGPUBindGroupLayoutEntry], Nat)
  default collectLayouts :: (GCollectBindings (Rep (a Internal))) => Nat -> Proxy (a Internal) -> ([WGPUBindGroupLayoutEntry], Nat)
  collectLayouts n _ = gCollectLayouts (Proxy @(Rep (a Internal))) n

  collectEntries :: Nat -> a CPU -> ([WGPUBindGroupEntry], Nat)
  default collectEntries :: (GCollectEntries (Rep (a CPU)) (Rep (a Internal)), Generic (a CPU)) => Nat -> a CPU -> ([WGPUBindGroupEntry], Nat)
  collectEntries n a = gCollectEntries @(Rep (a CPU)) @(Rep (a Internal)) (from a) n

  dummyBinding :: Nat -> (a GPU, Nat)
  default dummyBinding :: (Generic (a GPU), GDummyBinding (Rep (a Internal)) (Rep (a GPU))) => Nat -> (a GPU, Nat)
  dummyBinding n = Data.Bifunctor.first to $ gDummyBinding @(Rep (a Internal)) @(Rep (a GPU)) n

data ErasedBinding where
  ErasedBinding :: forall b (a :: Type) m. (Typeable m, Bindable b, Typeable b, Typeable a) => {materialType :: m, material :: Proxy a, binding :: Proxy b} -> ErasedBinding

instance Eq ErasedBinding where
  ErasedBinding _ a _ == ErasedBinding _ b _ = typeRep a == typeRep b

instance Ord ErasedBinding where
  compare (ErasedBinding _ a _) (ErasedBinding _ b _) = compare (typeRep a) (typeRep b)

data ErasedBindingValue where
  ErasedBindingValue :: (Bindable b, Typeable b) => b CPU -> ErasedBindingValue

class CollectBindings a where
  collectBinding :: Proxy a -> Nat -> BindingData
  collectLayout :: Proxy a -> Nat -> WGPUBindGroupLayoutEntry

class GCollectBindings f where
  gCollectBindings :: Proxy f -> Nat -> (Bindings, Nat)
  gCollectLayouts :: Proxy f -> Nat -> ([WGPUBindGroupLayoutEntry], Nat)

instance GCollectBindings V1 where
  gCollectBindings _ n = (Bindings [], n)
  gCollectLayouts _ n = ([], n)

instance GCollectBindings U1 where
  gCollectBindings _ n = (Bindings [], n)
  gCollectLayouts _ n = ([], n)

instance (GCollectBindings f, GCollectBindings g) => GCollectBindings (f :*: g) where
  gCollectBindings _ n = do
    let (a, n') = gCollectBindings (Proxy @f) n
    let (b, n'') = gCollectBindings (Proxy @g) n'
    (a <> b, n'')
  gCollectLayouts _ n = do
    let (a, n') = gCollectLayouts (Proxy @f) n
    let (b, n'') = gCollectLayouts (Proxy @g) n'
    (a <> b, n'')

instance (CollectBindings c) => GCollectBindings (K1 i c) where
  gCollectBindings _ n = (Bindings [collectBinding (Proxy @c) n], n + 1)
  gCollectLayouts _ n = ([collectLayout (Proxy @c) n], n + 1)

instance (GCollectBindings f) => GCollectBindings (M1 i t f) where
  gCollectBindings _ = gCollectBindings (Proxy @f)
  gCollectLayouts _ = gCollectLayouts (Proxy @f)

instance CollectBindings (NormalBinding Sampler) where
  collectBinding _ n = NormalData {bType = T.pack "sampler", index = fromIntegral n}
  collectLayout _ n =
    WGPUBindGroupLayoutEntry
      { nextInChain = nullPtr,
        binding = fromIntegral n,
        visibility = wGPUShaderStage_Fragment .|. wGPUShaderStage_Vertex,
        bindingArraySize = 0,
        buffer = unusedBufferLayout,
        storageTexture = unusedStorageTextureLayout,
        texture = unusedTextureLayout,
        sampler =
          WGPUSamplerBindingLayout
            { _type = wGPUSamplerBindingType_Filtering,
              nextInChain = nullPtr
            }
      }

instance CollectBindings (NormalBinding Texture) where
  collectBinding _ n = NormalData {bType = T.pack "texture_2d<f32>", index = fromIntegral n}
  collectLayout _ n =
    WGPUBindGroupLayoutEntry
      { nextInChain = nullPtr,
        binding = fromIntegral n,
        visibility = wGPUShaderStage_Fragment .|. wGPUShaderStage_Vertex,
        bindingArraySize = 0,
        buffer = unusedBufferLayout,
        sampler = unusedSamplerLayout,
        storageTexture = unusedStorageTextureLayout,
        texture =
          WGPUTextureBindingLayout
            { sampleType = wGPUTextureSampleType_Float,
              multisampled = wgpuFalse,
              viewDimension = wGPUTextureViewDimension_2D,
              nextInChain = nullPtr
            }
      }

instance (Buffers.Bufferable a, Typeable a) => CollectBindings (UniformBinding (a Internal)) where
  collectBinding _ n =
    UniformData
      { structName = T.pack $ show $ typeRep (Proxy @a),
        structFields = Buffers.getFields @a,
        index = fromIntegral n
      }

  collectLayout _ n =
    WGPUBindGroupLayoutEntry
      { nextInChain = nullPtr,
        binding = fromIntegral n,
        visibility = wGPUShaderStage_Fragment .|. wGPUShaderStage_Vertex,
        bindingArraySize = 0,
        buffer =
          WGPUBufferBindingLayout
            { nextInChain = nullPtr,
              _type = wGPUBufferBindingType_Uniform,
              hasDynamicOffset = wgpuFalse,
              minBindingSize = fromIntegral $ Buffers.getSize @a
            },
        sampler = unusedSamplerLayout,
        storageTexture = unusedStorageTextureLayout,
        texture = unusedTextureLayout
      }

class CollectEntry a n where
  collectEntry :: a -> Nat -> WGPUBindGroupEntry

class GCollectEntries f g where
  gCollectEntries :: f p -> Nat -> ([WGPUBindGroupEntry], Nat)

instance GCollectEntries V1 V1 where
  gCollectEntries _ n = ([], n)

instance GCollectEntries U1 U1 where
  gCollectEntries _ n = ([], n)

instance (GCollectEntries f1 g1, GCollectEntries f2 g2) => GCollectEntries (f1 :*: f2) (g1 :*: g2) where
  gCollectEntries (f :*: g) n = do
    let (a, n') = gCollectEntries @f1 @g1 f n
    let (b, n'') = gCollectEntries @f2 @g2 g n'
    (a <> b, n'')

instance (CollectEntry c n) => GCollectEntries (K1 i c) (K1 i n) where
  gCollectEntries (K1 a) n = ([collectEntry @c @n a n], n + 1)

instance (GCollectEntries f n) => GCollectEntries (M1 i t f) (M1 i t n) where
  gCollectEntries (M1 a) = gCollectEntries @f @n a

instance CollectEntry TextureView (NormalBinding b) where
  collectEntry (TextureView textureView) n =
    WGPUBindGroupEntry
      { binding = fromIntegral n,
        textureView,
        size = 0,
        offset = 0,
        sampler = nullPtr,
        buffer = nullPtr,
        nextInChain = nullPtr
      }

instance CollectEntry Sampler (NormalBinding b) where
  collectEntry (Sampler sampler) n =
    WGPUBindGroupEntry
      { binding = fromIntegral n,
        textureView = nullPtr,
        size = 0,
        offset = 0,
        sampler,
        buffer = nullPtr,
        nextInChain = nullPtr
      }

instance CollectEntry (Buffer a) (UniformBinding b) where
  collectEntry Buffer {buffer, desc = BufferDescriptor {size}} n =
    WGPUBindGroupEntry
      { binding = fromIntegral n,
        textureView = nullPtr,
        size = fromIntegral size,
        offset = 0,
        sampler = nullPtr,
        buffer,
        nextInChain = nullPtr
      }

class DummyBinding a b where
  dummyBinding' :: Nat -> b

class GDummyBinding f g where
  gDummyBinding :: Nat -> (g p, Nat)

instance GDummyBinding V1 V1 where
  gDummyBinding = undefined

instance GDummyBinding U1 U1 where
  gDummyBinding n = (U1, n)

instance (GDummyBinding f1 g1, GDummyBinding f2 g2) => GDummyBinding (f1 :*: f2) (g1 :*: g2) where
  gDummyBinding n = do
    let (a, n') = gDummyBinding @f1 @g1 n
    let (b, n'') = gDummyBinding @f2 @g2 n'
    (a :*: b, n'')

instance (DummyBinding a b) => GDummyBinding (K1 i a) (K1 i b) where
  gDummyBinding n = let a = dummyBinding' @a @b n in (K1 a, n + 1)

instance (GDummyBinding a b) => GDummyBinding (M1 i t a) (M1 i t b) where
  gDummyBinding n = let (a, n') = gDummyBinding @a @b n in (M1 a, n')

instance (SingI b) => DummyBinding (NormalBinding a) (Expr b) where
  dummyBinding' n = BindingVar (fromIntegral n)

instance (Buffers.Bufferable a) => DummyBinding (UniformBinding (a Internal)) (a GPU) where
  dummyBinding' n = Buffers.dummyBuffer @a (fromIntegral n)

-- instance (KnownNat n) => Bindable (UniformBinding n Sampler) where
--   collectBindings _ = Bindings [BindingData {bType = T.pack "sampler", index = natVal (Proxy @n)}]
--   collectLayouts _ =
--     [ WGPUBindGroupLayoutEntry
--         { nextInChain = nullPtr,
--           binding = fromInteger (natVal (Proxy @n)),
--           visibility = wGPUShaderStage_Fragment,
--           bindingArraySize = 0,
--           buffer = unusedBufferLayout,
--           storageTexture = unusedStorageTextureLayout,
--           texture = unusedTextureLayout,
--           sampler =
--             WGPUSamplerBindingLayout
--               { _type = wGPUSamplerBindingType_Filtering,
--                 nextInChain = nullPtr
--               }
--         }
--     ]

--   collectEntries (UniformBinding a) = [bind a]

-- -- instance (KnownNat n, SingI b, f Internal ~ UniformBinding n a', f GPU ~ Expr b) => ToDummy f where
-- --   dummyB = BindingVar (natVal (Proxy @n))

-- instance (KnownNat n) => Bindable (UniformBinding n Texture) where
--   collectBindings _ = Bindings [BindingData {bType = T.pack "texture_2d<f32>", index = natVal (Proxy @n)}]
--   collectLayouts _ =
--     [ WGPUBindGroupLayoutEntry
--         { nextInChain = nullPtr,
--           binding = fromInteger (natVal (Proxy @n)),
--           visibility = wGPUShaderStage_Fragment,
--           bindingArraySize = 0,
--           buffer = unusedBufferLayout,
--           sampler = unusedSamplerLayout,
--           storageTexture = unusedStorageTextureLayout,
--           texture =
--             WGPUTextureBindingLayout
--               { sampleType = wGPUTextureSampleType_Float,
--                 multisampled = wgpuFalse,
--                 viewDimension = wGPUTextureViewDimension_2D,
--                 nextInChain = nullPtr
--               }
--         }
--     ]

--   collectEntries (UniformBinding a) = [bind a]

-- -- dummyB = Binding $ BindingVar (natVal (Proxy @n))

-- instance Bindable ()

-- instance (KnownNat n, SingI (Primitive a)) => Bindable (UniformBinding n (Expr (Primitive a))) where
--   collectBindings _ = Bindings [BindingData {bType = typeToWGSL (undefined :: Expr (Primitive a)), index = natVal (Proxy @n)}]
--   collectLayouts _ = undefined

--   collectEntries (UniformBinding a) = [bind a]

-- -- dummyB = Binding $ BindingVar (natVal (Proxy @n))

-- instance (KnownNat n, SingI (VectorType m a)) => Bindable (UniformBinding n (Expr (VectorType m a))) where
--   collectBindings _ = Bindings [BindingData {bType = typeToWGSL (undefined :: Expr (VectorType m a)), index = natVal (Proxy @n)}]
--   collectLayouts _ = undefined

--   collectEntries (UniformBinding a) = [bind a]

-- -- dummyB = Binding $ BindingVar (natVal (Proxy @n))

-- instance (KnownNat n, SingI (ArrayType m a)) => Bindable (UniformBinding n (Expr (ArrayType m a))) where
--   collectBindings _ = Bindings [BindingData {bType = typeToWGSL (undefined :: Expr (ArrayType m a)), index = natVal (Proxy @n)}]
--   collectLayouts _ = undefined

--   collectEntries (UniformBinding a) = [bind a]

-- -- dummyB = Binding $ BindingVar (natVal (Proxy @n))

collectAllLayouts :: Nat -> [ErasedBinding] -> [WGPUBindGroupLayoutEntry]
collectAllLayouts _ [] = []
collectAllLayouts n (ErasedBinding _ (_ :: Proxy m) (_ :: Proxy b) : xs) =
  let (x, n') = collectLayouts n (Proxy @(b Internal))
   in x ++ collectAllLayouts n' xs

createBindLayout :: [ErasedBinding] -> RenderDevice -> IO BindLayout
createBindLayout b (RenderDevice device) = do
  let layouts = VS.fromList $ collectAllLayouts 0 b

  withWGPUString "bind group layout" $ \label -> do
    x <- VS.unsafeWith layouts $ \entries -> do
      let desc =
            WGPUBindGroupLayoutDescriptor
              { entryCount = fromIntegral $ VS.length layouts,
                entries = ConstPtr entries,
                label,
                nextInChain = nullPtr
              }
      with desc $ \desc -> wgpuDeviceCreateBindGroupLayout device (ConstPtr desc)

    pure (BindLayout x)

collectAllEntries :: Nat -> [ErasedBindingValue] -> [WGPUBindGroupEntry]
collectAllEntries _ [] = []
collectAllEntries n (ErasedBindingValue b : xs) =
  let (x, n') = collectEntries n b
   in x ++ collectAllEntries n' xs

createBindGroup :: RenderDevice -> BindLayout -> [ErasedBindingValue] -> IO (Ptr WGPUBindGroup)
createBindGroup (RenderDevice device) (BindLayout bindGroupLayout) b = do
  let entries = VS.fromList $ collectAllEntries 0 b

  withWGPUString "bind group" $ \label -> do
    VS.unsafeWith entries $ \entries' -> do
      let desc =
            WGPUBindGroupDescriptor
              { layout = bindGroupLayout,
                entryCount = fromIntegral $ VS.length entries,
                label = label,
                nextInChain = nullPtr,
                entries = ConstPtr entries'
              }

      with desc $ \desc -> wgpuDeviceCreateBindGroup device (ConstPtr desc)

-- class (Bindable link) => ConvertBindings link where
--   type CpuBind link
--   type GpuBind link
--   toLink :: CpuBind link -> link
--   mkGpu :: GpuBind link

-- bind :: a -> WGPUBindGroupEntry
-- bind = undefined

-- instance (KnownNat n) => ConvertBindings (UniformBinding (n :: Nat) Texture) where
--   type CpuBind (UniformBinding (n :: Nat) Texture) = TextureView
--   type GpuBind (UniformBinding (n :: Nat) Texture) = Expr TTexture
--   toLink (TextureView view) =
--     Uniform
--       WGPUBindGroupEntry
--         { binding = fromInteger (natVal (Proxy @n)),
--           textureView = view,
--           size = 0,
--           offset = 0,
--           sampler = nullPtr,
--           buffer = nullPtr,
--           nextInChain = nullPtr
--         }

--   mkGpu = BindingVar (natVal (Proxy @n))

-- instance (KnownNat n) => ConvertBindings (Uniform (n :: Nat) Sampler) where
--   type CpuBind (Uniform (n :: Nat) Sampler) = Sampler
--   type GpuBind (Uniform (n :: Nat) Sampler) = Expr TSampler
--   toLink (Sampler sampler) =
--     Uniform
--       WGPUBindGroupEntry
--         { binding = fromInteger (natVal (Proxy @n)),
--           textureView = nullPtr,
--           size = 0,
--           offset = 0,
--           sampler = sampler,
--           buffer = nullPtr,
--           nextInChain = nullPtr
--         }

--   mkGpu = BindingVar (natVal (Proxy @n))

-- class GMkDummy i o where
--   gMkDummy :: o

-- instance GMkDummy (K1 a b c) (K1 a b c) where