packages feed

fir-0.1.0.0: src/FIR/Prim/Image.hs

{-# LANGUAGE AllowAmbiguousTypes    #-}
{-# LANGUAGE ConstraintKinds        #-}
{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE DerivingStrategies     #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE GADTs                  #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE PolyKinds              #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE TypeApplications       #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators          #-}
{-# LANGUAGE UndecidableInstances   #-}

{-|
Module: FIR.Prim.Image

This module defines the 'Image' datatype,
which is a void datatype used to manipulate image handles.

This datatype is annotated at the type-level with various properties
describing the image, such as the format of the image (e.g. RGBA8, depth image, etc).
-}

module FIR.Prim.Image
  ( -- * Images
    -- ** Image properties
    ImageCoordinateKind(..)
  , ImageProperties(Properties)
  -- ** Opaque image data type
  , Image
  -- ** Type reflection of image properties
  -- $reflection
  , ImageAndCoordinate(..)
  , knownImage, knownImageCoordinateKind

  -- * Image operands
  , OperandName(..), ImageOperands
  -- ** Specific data type for gathering operations
  , Gather(..), GatherInfo(..)

  -- * Helper type families for checking validity of image operands
  , CanAddProj, CanAddDref, BasicDim, NotCubeDim
  , NoMS, NoDuplicate, CanMultiSample
  , NoLODOps, UsesAffineCoords
  , WhichGather
  )
  where

-- base
import Data.Int
  ( Int32 )
import Data.Kind
  ( Type )
import Data.Type.Bool
  ( If )
import GHC.TypeNats
  ( Nat )

-- fir
import Data.Type.Known
  ( Demotable(Demote), Known(known), knownValue )
import Data.Type.List
  ( Elem )
import FIR.Prim.Array
  ( Array )
import {-# SOURCE #-} FIR.Prim.Types
  ( ScalarTy, scalarTy )
import FIR.Validation.Images
  ( MatchesFormat, BasicDim, NotCubeDim
  , CanAddProj, CanAddDref
  , UsesAffineCoords
  , NoDuplicate
  , NoMS, CanMultiSample
  , NoLODOps
  )
import Math.Linear
  ( V )
import SPIRV.Image
  ( Arrayness(..)
  , Dimensionality(..)
  , HasDepth(..)
  , ImageUsage(..)
  , ImageFormat(..)
  , MultiSampling(..)
  , Operand
  )
import qualified SPIRV.Image    as SPIRV
  ( Image(..) )

--------------------------------------------------

-- | Properties of an image, used as a data kind
-- to describe the properties of an image at the type-level.
--
-- This allows type-checking that the image properties
-- comply with the @SPIR-V@ specification, as well
-- as check that operations done with the image are valid.
data ImageProperties where
  Properties
    :: ImageCoordinateKind     -- ^ The kind of coordinates this image allows (integral/floating point).
    -> Type                    -- ^ Texel component type.
    -> Dimensionality          -- ^ Dimensionality of the image (1D, 2D, 3D, cubemap, ...).
    -> Maybe HasDepth          -- ^ Whether the image is a depth image.
    -> Arrayness               -- ^ Whether the image has an extra array component.
    -> MultiSampling           -- ^ Whether the image is multisampled.
    -> ImageUsage              -- ^ Is the image sampled or a storage image?
    -> Maybe (ImageFormat Nat) -- ^ 'SPIRV.Image.ImageFormat' of the image.
    -> ImageProperties

-- | The kind of coordinates that an image format supports.
data ImageCoordinateKind
  = IntegralCoordinates      -- ^ Access image texels using integral coordinates.
  | FloatingPointCoordinates -- ^ Access image texels using floating point coordinates.

instance Demotable ImageCoordinateKind where
  type Demote ImageCoordinateKind = ImageCoordinateKind
instance Known ImageCoordinateKind IntegralCoordinates where
  known = IntegralCoordinates
instance Known ImageCoordinateKind FloatingPointCoordinates where
  known = FloatingPointCoordinates

-- | Abstract handle to an image.
data Image (props :: ImageProperties)

-- newtype to retain injectivity of 'Demote' type family
newtype ImageAndCoordinate
  = ImageAndCoordinate (SPIRV.Image, ImageCoordinateKind)

instance Demotable ImageProperties where
  type Demote ImageProperties = ImageAndCoordinate

instance ( Known ImageCoordinateKind       coordKind
         , ScalarTy                        texelComp
         , Known Dimensionality            dimensionality
         , Known (Maybe HasDepth)          hasDepth
         , Known Arrayness                 arrayness
         , Known MultiSampling             multiSampling
         , Known ImageUsage                imageUsage
         , Known (Maybe (ImageFormat Nat)) imageFormat
         , MatchesFormat imageFormat texelComp
         )
  => Known ImageProperties
      ( 'Properties
           coordKind texelComp
           dimensionality hasDepth
           arrayness multiSampling
           imageUsage imageFormat
      )
  where
  known
    = ImageAndCoordinate
        ( SPIRV.Image
            { SPIRV.texelComponent   = scalarTy   @texelComp
            , SPIRV.dimensionality   = knownValue @dimensionality
            , SPIRV.hasDepth         = knownValue @hasDepth
            , SPIRV.arrayness        = knownValue @arrayness
            , SPIRV.multiSampling    = knownValue @multiSampling
            , SPIRV.imageUsage = Just (knownValue @imageUsage)
            , SPIRV.imageFormat      = knownValue @imageFormat
            }
        , knownValue @coordKind
        )


-- $reflection
--
-- As image properties are provided at the type level,
-- we use the 'Data.Type.Known.Known' mechanism to reflect
-- this type-level information to the value level.

-- | Provided image properties at the type-level,
-- return the kind of coordinates that the image uses.
knownImageCoordinateKind
  :: forall props. Known ImageProperties props
  => ImageCoordinateKind
knownImageCoordinateKind
  = case knownValue @props of
      ImageAndCoordinate (_, coordKind) -> coordKind

-- | Return the 'SPIRV.Image.Image' type with the given properties.
knownImage :: forall props. Known ImageProperties props => SPIRV.Image
knownImage
  = case knownValue @props of
      ImageAndCoordinate (img, _) -> img

--------------------------------------------------
-- Image operands

-- | Names of image operands.
--
-- These are used to keep track at the type level of the operands
-- which have been provided.
--
-- This is necessary so that we can type check their usage,
-- and know which types are being used in conjunction with the image.
--
-- For instance, if a depth-comparison is being performed,
-- the return of an image sampling operation is a scalar,
-- whereas otherwise it is a 4-vector.
data OperandName
  = DepthComparison  -- ^ Whether a depth-comparison is being performed.
  | ProjectiveCoords -- ^ Whether to use projective coordinates.
  | BaseOperand SPIRV.Image.Operand -- A @SPIR-V@ image "SPIRV.Image.Operand".
  deriving stock ( Show, Eq, Ord )

-- | == Image operands.
--
-- As there is no support for images in the evaluator, this datatype holds no information.
--
-- However, values of type @Code (ImageOperands props ops)@
-- can be constructed using the constructors of the AST,
-- and these contain information necessary for image sample/read/write operations.
--
-- === Overview
-- Image operands behave like a linked list of operands, with a /twist/:
--
--   - the type of an image operand in the list can depend
--     on which operands appear further down in the list.
--
-- Consider for instance the image operands:
--
--    @ Grad grad $ Proj $ NilOps @
--
-- These can loosely be thought of as a list of operands
--
--    @ Grad grad : Proj : [] @
--
-- This means we are specifying explicit derivatives,
-- and using projective coordinates.
--
-- However, the fact that we are using projective coordinates
-- means that the explicit derivatives require an extra
-- component (the projective coordinate).
--
-- In this way, the type of the head of the list
-- depends on the operands appearing in the tail.
data ImageOperands
       ( props :: ImageProperties )
       ( ops   :: [OperandName]   )
  = SomeImageOperands
    deriving stock ( Eq, Ord, Show, Bounded, Enum )

-----------------------------------------------------------
-- data type for gather image operand

data Gather
  = ComponentGather
  | DrefGather
  deriving stock ( Show, Eq, Ord, Bounded, Enum )

-- | Information hat needs to be provided to the 'Gather' image operand.
--
-- This consists of 4 offsets, together with the information
-- of which image component (0,1,2,3) to gather
-- in the case that a depth test is /not/ being performed.
--
-- Instantiated with val ~ AST Word32 (left as a parameter to avoid cylic module dependencies).
data GatherInfo val (gather :: Gather) where
  ComponentWithOffsets
    :: val -> Array 4 (V 2 Int32) -> GatherInfo val ComponentGather
  DepthWithOffsets
    ::        Array 4 (V 2 Int32) -> GatherInfo val DrefGather

-- Computes whether a component index needs to be provided to the
-- 'Gather' image operand.
type family WhichGather (ops :: [OperandName]) :: Gather where
  WhichGather ops
    = If
        ( DepthComparison `Elem` ops  )
        DrefGather
        ComponentGather