fir-0.1.0.0: src/CodeGen/Images.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module: CodeGen.Images
Code generation for image sampling, reading and writing.
-}
module CodeGen.Images
( imageTexel, writeTexel )
where
-- base
import Control.Arrow
( first, (&&&) )
import Data.Bits
( Bits((.|.), shiftR, testBit) )
import Data.List
( insertBy )
import Data.Ord
( comparing )
import Data.Proxy
( Proxy )
import Data.Word
( Word32 )
-- lens
import Control.Lens
( view )
-- mtl
import Control.Monad.Except
( throwError )
-- text-short
import Data.Text.Short
( ShortText )
import qualified Data.Text.Short as ShortText
( pack )
-- fir
import CodeGen.Application
( ASTs(..), Application(..) )
import CodeGen.Binary
( instruction )
import CodeGen.Composite
( vectorSwizzle )
import {-# SOURCE #-} CodeGen.CodeGen
( CodeGen(codeGenArgs), codeGen )
import CodeGen.IDs
( bindingID, constID, typeID )
import CodeGen.Instruction
( ID, TyID, Instruction(..), Pointer(..)
, Args(Arg,EndArgs), toArgs
)
import CodeGen.Monad
( CGMonad, MonadFresh(fresh) )
import CodeGen.Pointers
( load )
import CodeGen.State
( _backend, requireCapability, requireCapabilities )
import Data.Type.Known
( Known, knownValue )
import FIR.AST
( AST, Code
, pattern NilOps, pattern Proj, pattern Dref, pattern Bias
, pattern LOD, pattern MinLOD, pattern Grad
, pattern ConstOffsetBy, pattern OffsetBy
, pattern Gather, pattern SampleNo
, ImgOpsF
)
import FIR.AST.Images
( ImgQueryF(..) )
import FIR.Prim.Image
( ImageProperties, ImageAndCoordinate(..)
, ImageCoordinateKind(..)
, ImageOperands, OperandName
, GatherInfo(..)
, knownImage
)
import FIR.Prim.Types
( primTy )
import SPIRV.Image
( DepthTesting(..), Projection(..) )
import qualified SPIRV.Capability as SPIRV
( pattern ComputeKernel, pattern ImageQuery )
import qualified SPIRV.Image as SPIRV
hiding ( Image(Image) )
import qualified SPIRV.Image
( Image(Image) )
import qualified SPIRV.Operation as SPIRV
( Operation )
import qualified SPIRV.Operation as SPIRV.Op
import qualified SPIRV.PrimTy as SPIRV
import qualified SPIRV.PrimTy
( PrimTy(Image, SampledImage) )
import qualified SPIRV.Requirements as SPIRV
( lodCapabilities
, gatherCapabilities
, unknownFormatReadCapabilities
, unknownFormatWriteCapabilities
)
import qualified SPIRV.Stage as SPIRV
( Backend(OpenCL, Vulkan) )
--------------------------------------------------------------------------
-- image sample/read/write
imageTexel
:: forall
( props :: ImageProperties )
( ops :: [OperandName] )
. ( CodeGen AST, Known ImageProperties props )
=> (ID, SPIRV.PrimTy)
-> Code (ImageOperands props ops)
-> ID
-> (TyID, SPIRV.PrimTy)
-> CGMonad (ID, SPIRV.PrimTy)
imageTexel (imgID, imgTy) ops coords (imgTexelTyID, imgTexelTy)
= do
( bm, operandsArgs ) <- operandsToArgs ops
let lod = lodMethod bm
proj = projection bm
dtest = depthTesting bm
gathering = gather bm
ImageAndCoordinate
( SPIRV.Image.Image
{ SPIRV.dimensionality = dim
, SPIRV.imageFormat = mbFmt
}
, coordCompKind
) = knownValue @props
v <- fresh
let
-- Image reads, (non-depth-comparison) sampling and gathering return
-- four components: narrow the result to the requested texel type.
texelInstruction :: Bool -> SPIRV.Operation -> Args -> CGMonad (ID, SPIRV.PrimTy)
texelInstruction fourComponents op texelArgs = case imgTexelTy of
SPIRV.Scalar s
| fourComponents
-> narrow ( SPIRV.Vector 4 ( SPIRV.Scalar s ) ) [0]
SPIRV.Vector n c
| fourComponents, n < 4
-> narrow ( SPIRV.Vector 4 c ) [ 0 .. n - 1 ]
_ -> do
instruction ( Instruction { operation = op, resTy = Just imgTexelTyID, resID = Just v, args = texelArgs } )
pure ( v, imgTexelTy )
where
narrow vec4Ty components = do
vec4TyID <- typeID vec4Ty
instruction ( Instruction { operation = op, resTy = Just vec4TyID, resID = Just v, args = texelArgs } )
vectorSwizzle ( v, vec4Ty ) components
res <- case coordCompKind of
-- must be using a sampler (accessing with floating-point coordinates)
FloatingPointCoordinates
-> do sampledImgID
<- case imgTy of
SPIRV.PrimTy.SampledImage _
-> pure imgID
SPIRV.PrimTy.Image _
-> throwError
( "codeGen: cannot sample image with floating-point coordinates: \
\no sampler provided."
)
_ -> throwError
( "codeGen: image sampling operation provided non-image \
\of type " <> ShortText.pack ( show imgTy )
)
if gathering
then texelInstruction True ( gatherOperation dtest )
( Arg sampledImgID $ Arg coords $ operandsArgs )
else texelInstruction ( dtest == NoDepthTest ) ( sampleOperation lod dtest proj )
( Arg sampledImgID $ Arg coords $ operandsArgs )
-- reading from image directly (using integral coordinates)
IntegralCoordinates
-> do plainImgID
<- case imgTy of
SPIRV.PrimTy.Image _
-> pure imgID
SPIRV.PrimTy.SampledImage plainImg
-> fst <$> removeSampler (imgID, plainImg)
_ -> throwError
( "codeGen: image read operation provided non-image \
\of type " <> ShortText.pack ( show imgTy )
)
res <- texelInstruction True SPIRV.Op.ImageRead
( Arg plainImgID $ Arg coords $ operandsArgs )
requireCapabilities ( SPIRV.unknownFormatReadCapabilities dim mbFmt )
pure res
-- declare capabilities
requireOperandCapabilities bm
pure res
writeTexel
:: forall props ops
. ( CodeGen AST, Known ImageProperties props )
=> (ID, SPIRV.PrimTy)
-> Code (ImageOperands props ops)
-> ID
-> ID
-> CGMonad ()
writeTexel (imgID, imgTy) ops coords texel
= do
let SPIRV.Image.Image
{ SPIRV.imageFormat = mbFmt
} = knownImage @props
( bm, operandsArgs ) <- operandsToArgs ops
plainImgID
<- case imgTy of
SPIRV.PrimTy.Image _
-> pure imgID
SPIRV.PrimTy.SampledImage plainImg
-> fst <$> removeSampler (imgID, plainImg)
_ -> throwError
( "codeGen: image write operation provided non-image \
\of type " <> ShortText.pack ( show imgTy )
)
instruction
Instruction
{ operation = SPIRV.Op.ImageWrite
, resTy = Nothing
, resID = Nothing
, args = Arg plainImgID
$ Arg coords
$ Arg texel
$ operandsArgs
}
-- declare capabilities
requireOperandCapabilities bm
requireCapabilities ( SPIRV.unknownFormatWriteCapabilities mbFmt )
-- return the result ID
pure ()
--------------------------------------------------------------------------
-- add/remove sampler
{-
addSampler :: (ID, SPIRV.Image.Image) -> ID -> CGMonad (ID, SPIRV.PrimTy)
addSampler (imgID, imgTy) samplerID
= do
let sampledImgTy = SPIRV.PrimTy.SampledImage imgTy
sampledImgTyID <- typeID sampledImgTy
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.SampledImage
, resTy = Just sampledImgTyID
, resID = Just v
, args = Arg imgID
$ Arg samplerID EndArgs
}
pure (v, sampledImgTy)
-}
removeSampler :: CodeGen AST => (ID, SPIRV.Image.Image) -> CGMonad (ID, SPIRV.PrimTy)
removeSampler (imgID, imgTy)
= do
let plainImgTy = SPIRV.PrimTy.Image imgTy
plainImgTyID <- typeID plainImgTy
v <- fresh
instruction
Instruction
{ operation = SPIRV.Op.Image
, resTy = Just plainImgTyID
, resID = Just v
, args = Arg imgID EndArgs
}
pure (v, plainImgTy)
--------------------------------------------------------------------------
-- sampling: find which sampling operation to use
data LODMethod
= ImplicitLOD
| ExplicitLOD
deriving stock ( Show, Eq, Ord, Enum, Bounded )
lodMethod :: Word32 -> LODMethod
lodMethod bm
| bm `testBit` 9 || bm `testBit` 10 = ExplicitLOD --'LOD' or 'Grad' operand provided
| otherwise = ImplicitLOD
depthTesting :: Word32 -> DepthTesting
depthTesting bm
| bm `testBit` 0 = DepthTest
| otherwise = NoDepthTest
projection :: Word32 -> Projection
projection bm
| bm `testBit` 1 = Projective
| otherwise = Affine
gather :: Word32 -> Bool
gather = (`testBit` 13)
sampleOperation :: LODMethod -> DepthTesting -> Projection -> SPIRV.Operation
sampleOperation ImplicitLOD NoDepthTest Affine = SPIRV.Op.ImageSampleImplicitLod
sampleOperation ExplicitLOD NoDepthTest Affine = SPIRV.Op.ImageSampleExplicitLod
sampleOperation ImplicitLOD DepthTest Affine = SPIRV.Op.ImageSampleDrefImplicitLod
sampleOperation ExplicitLOD DepthTest Affine = SPIRV.Op.ImageSampleDrefExplicitLod
sampleOperation ImplicitLOD NoDepthTest Projective = SPIRV.Op.ImageSampleProjImplicitLod
sampleOperation ExplicitLOD NoDepthTest Projective = SPIRV.Op.ImageSampleProjExplicitLod
sampleOperation ImplicitLOD DepthTest Projective = SPIRV.Op.ImageSampleProjDrefImplicitLod
sampleOperation ExplicitLOD DepthTest Projective = SPIRV.Op.ImageSampleProjDrefExplicitLod
gatherOperation :: DepthTesting -> SPIRV.Operation
gatherOperation NoDepthTest = SPIRV.Op.ImageGather
gatherOperation DepthTest = SPIRV.Op.ImageDrefGather
--------------------------------------------------------------------------
-- capabilities
-- | Declare the capabilities required by the image operands
-- given by the bitmask.
requireOperandCapabilities :: Word32 -> CGMonad ()
requireOperandCapabilities bm
= do
bk <- view _backend
requireCapabilities ( SPIRV.lodCapabilities bk bm )
requireCapabilities ( SPIRV.gatherCapabilities bm )
--------------------------------------------------------------------------
-- dealing with operands
instance CodeGen (ImgOpsF ast) where
codeGenArgs _ = throwError "Unexpected image operand."
operands :: CodeGen AST => Code (ImageOperands props ops) -> CGMonad (Word32, [(ID, Word32)])
operands NilOps = pure ( 0x0000, [] )
operands (Proj ops) = first ( 0x0002 .|. ) <$> operands ops
operands (Dref dref ops)
= do
i <- fst <$> codeGen dref
(bm, nxt) <- operands ops
let opBit = 0x0001
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands (Bias bias ops)
= do
i <- fst <$> codeGen bias
(bm, nxt) <- operands ops
let opBit = 0x0100
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands (LOD lod ops)
= do
i <- fst <$> codeGen lod
(bm, nxt) <- operands ops
let opBit = 0x0200
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands (Grad (gx,gy) ops)
= do
i1 <- fst <$> codeGen gx
i2 <- fst <$> codeGen gy
(bm, nxt) <- operands ops
let opBit = 0x0400
pure ( opBit .|. bm
, stableSplice ([i1,i2], opBit) nxt )
operands (ConstOffsetBy off ops)
= do
i <- constID off
(bm, nxt) <- operands ops
let opBit = 0x0800
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands (OffsetBy off ops)
= do
offID <- fst <$> codeGen off
(bm, nxt) <- operands ops
let opBit = 0x1000
pure ( opBit .|. bm
, insertBy (comparing snd) (offID, opBit) nxt
)
operands (Gather (ComponentWithOffsets comp offs) ops)
= do
compID <- fst <$> codeGen comp
offsID <- constID offs
(bm, nxt) <- operands ops
let opBit = 0x2000
pure ( opBit .|. bm
, insertBy (comparing snd) (compID, 0x0001) -- says "put me first", see 'opArgs'
. insertBy (comparing snd) (offsID, opBit )
$ nxt
)
operands (Gather (DepthWithOffsets offs) ops)
= do
offsID <- constID offs
(bm, nxt) <- operands ops
let opBit = 0x2000
pure ( opBit .|. bm
, insertBy (comparing snd) (offsID, opBit) nxt
)
operands (SampleNo no ops)
= do
i <- fst <$> codeGen no
(bm, nxt) <- operands ops
let opBit = 0x4000
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands (MinLOD lod ops)
= do
i <- fst <$> codeGen lod
(bm, nxt) <- operands ops
let opBit = 0x8000
pure ( opBit .|. bm
, insertBy (comparing snd) (i, opBit) nxt
)
operands ops
= throwError
$ "'operands': image operands not of the expected form\n"
<> ShortText.pack (show ops)
opArgs :: Word32 -> [(ID, Word32)] -> Args
opArgs _ [] = EndArgs
opArgs bm ((putMeFirst, 0x0001) : ops)
-- special case for an argument that needs to be put first
-- must go before everything else (including bitmask)
= Arg putMeFirst (opArgs bm ops)
opArgs bm ops
= Arg
( shiftR bm 8 ) -- bitmask first, with bottom byte removed
( toArgs (map fst ops) ) -- then the IDs in order
operandsToArgs :: CodeGen AST => Code (ImageOperands props ops) -> CGMonad (Word32, Args)
operandsToArgs ops = (fst &&& uncurry opArgs) <$> operands ops
stableSplice :: Ord b => ( [a], b ) -> [ (a,b) ] -> [ (a,b) ]
stableSplice ( as, bref ) []
= map ( , bref ) as
stableSplice l1@( as, bref ) l2@( (a, b) : nxt )
| b > bref = map ( , bref ) as ++ l2
| otherwise = ( a, b ) : stableSplice l1 nxt
--------------------------------------------------------------------------
-- image query operations
instance CodeGen AST => CodeGen (ImgQueryF AST) where
codeGenArgs (Applied imgQueryOp as) = do
bk <- view _backend
case bk of
SPIRV.OpenCL -> case imgQueryOp of
QueryLODF {} -> pure ()
_ -> requireCapability SPIRV.ComputeKernel
SPIRV.Vulkan -> requireCapability SPIRV.ImageQuery
let
imgName :: ShortText
resultType :: SPIRV.PrimTy
( imgName, resultType ) = imageQueryImageNameAndResultType imgQueryOp
resTyID <- typeID resultType
( bdID, bindingTy ) <- bindingID imgName
( valID, valTy ) <- case bindingTy of
SPIRV.Pointer ptrTy
-> load imgName (Pointer bdID ptrTy)
_ -> throwError
( "codeGen: internal error, image query operation on non-pointer named "
<> imgName <> " of type " <> ShortText.pack ( show bindingTy )
)
-- LOD query operation needs a sampled image.
-- Other operations use a plain image.
imgID <- case valTy of
SPIRV.SampledImage imgTy
| QueryLODF {} <- imgQueryOp
-> pure valID
| otherwise
-> fst <$> removeSampler ( valID, imgTy )
SPIRV.Image {}
| QueryLODF {} <- imgQueryOp
-> throwError
( "codeGen: cannot query LOD of image named " <> imgName <> ":\n\
\no sampler provided."
)
| otherwise
-> pure valID
nonImgTy
-> throwError
( "codeGen: image query operation on non-image named " <> imgName <> " of type " <> ShortText.pack ( show nonImgTy ) )
v <- fresh
let
emitInstruction :: CGMonad ()
emitInstruction = case imgQueryOp of
QuerySizeF {} | NilAST <- as ->
instruction
Instruction
{ operation = SPIRV.Op.ImageQuerySize
, resTy = Just resTyID
, resID = Just v
, args = Arg imgID EndArgs
}
QuerySizeLODF {} | (lod `ConsAST` NilAST) <- as -> do
lodID <- fst <$> codeGen lod
instruction
Instruction
{ operation = SPIRV.Op.ImageQuerySizeLod
, resTy = Just resTyID
, resID = Just v
, args = Arg imgID
$ Arg lodID
$ EndArgs
}
QueryLODF {} | (coords `ConsAST` NilAST) <- as -> do
coordsID <- fst <$> codeGen coords
instruction
Instruction
{ operation = SPIRV.Op.ImageQueryLod
, resTy = Just resTyID
, resID = Just v
, args = Arg imgID
$ Arg coordsID
$ EndArgs
}
QueryLevelsF {} | NilAST <- as ->
instruction
Instruction
{ operation = SPIRV.Op.ImageQueryLevels
, resTy = Just resTyID
, resID = Just v
, args = Arg imgID EndArgs
}
QuerySamplesF {} | NilAST <- as ->
instruction
Instruction
{ operation = SPIRV.Op.ImageQuerySamples
, resTy = Just resTyID
, resID = Just v
, args = Arg imgID EndArgs
}
emitInstruction
pure ( v, resultType )
imageQueryImageNameAndResultType :: ImgQueryF ast a -> ( ShortText, SPIRV.PrimTy )
imageQueryImageNameAndResultType = \ case
QuerySizeF ( _ :: Proxy imgName ) ( _ :: Proxy resTy ) -> ( knownValue @imgName, primTy @resTy )
QuerySizeLODF ( _ :: Proxy imgName ) ( _ :: Proxy resTy ) -> ( knownValue @imgName, primTy @resTy )
QueryLODF ( _ :: Proxy imgName ) ( _ :: Proxy resTy ) -> ( knownValue @imgName, primTy @resTy )
QueryLevelsF ( _ :: Proxy imgName ) ( _ :: Proxy resTy ) -> ( knownValue @imgName, primTy @resTy )
QuerySamplesF ( _ :: Proxy imgName ) ( _ :: Proxy resTy ) -> ( knownValue @imgName, primTy @resTy )