packages feed

wgpu-hs-0.4.0.0: src-internal/WGPU/Internal/Buffer.hs

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module WGPU.Internal.Buffer
  ( -- * Types
    Buffer (..),
    BufferUsage (..),
    BufferDescriptor (..),

    -- * Functions
    createBuffer,
    createBufferInit,
  )
where

import Control.Monad.Cont (ContT (ContT))
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Bits ((.|.))
import Data.Default (Default, def)
import Data.Text (Text)
import Data.Word (Word32, Word64)
import Foreign
  ( Ptr,
    castPtr,
    copyBytes,
    nullPtr,
  )
import WGPU.Internal.Device (Device, deviceInst, wgpuDevice)
import WGPU.Internal.Instance (Instance, wgpuHsInstance)
import WGPU.Internal.Memory
  ( ByteSize,
    ReadableMemoryBuffer,
    ToRaw,
    evalContT,
    raw,
    rawPtr,
    readableMemoryBufferSize,
    showWithPtr,
    toCSize,
    unByteSize,
    withReadablePtr,
  )
import qualified WGPU.Raw.Generated.Enum.WGPUBufferUsage as WGPUBufferUsage
import qualified WGPU.Raw.Generated.Fun as RawFun
import qualified WGPU.Raw.Generated.Fun as WGPU
import WGPU.Raw.Generated.Struct.WGPUBufferDescriptor (WGPUBufferDescriptor)
import qualified WGPU.Raw.Generated.Struct.WGPUBufferDescriptor as WGPUBufferDescriptor
import WGPU.Raw.Types (WGPUBuffer (WGPUBuffer))

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

-- | Handle to a buffer.
data Buffer = Buffer
  { bufferInst :: !Instance,
    bufferDevice :: !Device,
    wgpuBuffer :: !WGPUBuffer
  }

instance Show Buffer where
  show b =
    let Buffer _ _ (WGPUBuffer ptr) = b
     in showWithPtr "Buffer" ptr

instance Eq Buffer where
  (==) b1 b2 =
    let Buffer _ _ (WGPUBuffer b1_ptr) = b1
        Buffer _ _ (WGPUBuffer b2_ptr) = b2
     in b1_ptr == b2_ptr

instance ToRaw Buffer WGPUBuffer where
  raw = pure . wgpuBuffer

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

-- | Different ways you can use a buffer.
data BufferUsage = BufferUsage
  { -- | Allow a buffer to be mapped for reading.
    bufMapRead :: !Bool,
    -- | Allow a buffer to be mapped for writing.
    bufMapWrite :: !Bool,
    -- | Allow a buffer to be a source buffer for a copy operation.
    bufCopySrc :: !Bool,
    -- | Allow a buffer to be a destination buffer for a copy operation.
    bufCopyDst :: !Bool,
    -- | Allow a buffer to be the index buffer in a draw operation.
    bufIndex :: !Bool,
    -- | Allow a buffer to be the vertex buffer in a draw operation.
    bufVertex :: !Bool,
    -- | Allow a buffer to be a uniform binding in a bind group.
    bufUniform :: !Bool,
    -- | Allow a buffer to be a storage binding in a bind group.
    bufStorage :: !Bool,
    -- | Allow a buffer to be the indirect buffer in an indirect draw call.
    bufIndirect :: !Bool
  }
  deriving (Eq, Show)

instance ToRaw BufferUsage Word32 where
  raw BufferUsage {..} =
    pure $
      (if bufMapRead then WGPUBufferUsage.MapRead else 0)
        .|. (if bufMapWrite then WGPUBufferUsage.MapWrite else 0)
        .|. (if bufCopySrc then WGPUBufferUsage.CopySrc else 0)
        .|. (if bufCopyDst then WGPUBufferUsage.CopyDst else 0)
        .|. (if bufIndex then WGPUBufferUsage.Index else 0)
        .|. (if bufVertex then WGPUBufferUsage.Vertex else 0)
        .|. (if bufUniform then WGPUBufferUsage.Uniform else 0)
        .|. (if bufStorage then WGPUBufferUsage.Storage else 0)
        .|. (if bufIndirect then WGPUBufferUsage.Indirect else 0)

instance Default BufferUsage where
  def =
    BufferUsage
      { bufMapRead = False,
        bufMapWrite = False,
        bufCopySrc = False,
        bufCopyDst = False,
        bufIndex = False,
        bufVertex = False,
        bufUniform = False,
        bufStorage = False,
        bufIndirect = False
      }

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

-- | Describes a 'Buffer'.
data BufferDescriptor = BufferDescriptor
  { -- | Debugging label for the buffer.
    bufferLabel :: !Text,
    -- | Size of the buffer, in bytes.
    bufferSize :: !ByteSize,
    -- | Usage(s) of the buffer.
    bufferUsage :: !BufferUsage,
    -- | Is the buffer mapped to host memory at creation? If this is set to
    -- 'True', then the buffer may be more easily populated with data
    -- initially. See 'createBufferInit' for a way to create a buffer and
    -- initialize it with data in one step.
    mappedAtCreation :: Bool
  }
  deriving (Eq, Show)

instance ToRaw BufferDescriptor WGPUBufferDescriptor where
  raw BufferDescriptor {..} = do
    label_ptr <- rawPtr bufferLabel
    n_usage <- raw bufferUsage
    n_mappedAtCreation <- raw mappedAtCreation
    pure
      WGPUBufferDescriptor.WGPUBufferDescriptor
        { nextInChain = nullPtr,
          label = label_ptr,
          usage = n_usage,
          size = unByteSize bufferSize,
          mappedAtCreation = n_mappedAtCreation
        }

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

-- | Create a 'Buffer'.
createBuffer :: MonadIO m => Device -> BufferDescriptor -> m Buffer
createBuffer device bufferDescriptor = liftIO . evalContT $ do
  let inst = deviceInst device
  bufferDescriptor_ptr <- rawPtr bufferDescriptor
  Buffer inst device
    <$> RawFun.wgpuDeviceCreateBuffer
      (wgpuHsInstance inst)
      (wgpuDevice device)
      bufferDescriptor_ptr

-- | Create a 'Buffer' with data to initialize it.
createBufferInit ::
  forall a m.
  (MonadIO m, ReadableMemoryBuffer a) =>
  -- | Device for which to create the buffer.
  Device ->
  -- | Debugging label for the buffer.
  Text ->
  -- | Usage for the buffer.
  BufferUsage ->
  -- | Data to initialize the buffer with.
  a ->
  -- | Buffer created with the specified data.
  m Buffer
createBufferInit device label bufferUsage content = liftIO . evalContT $ do
  -- Convert the foreign pointer to a raw pointer.
  contentPtr <- ContT $ withReadablePtr content
  let contentSz :: ByteSize
      contentSz = readableMemoryBufferSize content

  -- Create the buffer, marking it as "mappedAtCreation", so that its memory
  -- is mapped to host memory.
  let bufferDescriptor :: BufferDescriptor
      bufferDescriptor =
        BufferDescriptor
          { bufferLabel = label,
            bufferSize = contentSz,
            bufferUsage = bufferUsage,
            mappedAtCreation = True
          }
  buffer <- createBuffer device bufferDescriptor

  -- Find the pointer to the mapped region of the buffer.
  bufferPtr <- bufferGetMappedRange buffer 0 contentSz

  -- Copy the supplied content to the buffer
  liftIO $ copyBytes bufferPtr (castPtr contentPtr) (fromIntegral contentSz)

  -- Un-map the buffer and return it
  bufferUnmap buffer
  pure buffer

-- | Return a pointer to a region of host memory that has been mapped to a
-- buffer.
bufferGetMappedRange :: MonadIO m => Buffer -> Word64 -> ByteSize -> m (Ptr ())
bufferGetMappedRange buffer byteOffset byteLength = do
  let inst = bufferInst buffer
  WGPU.wgpuBufferGetMappedRange
    (wgpuHsInstance inst)
    (wgpuBuffer buffer)
    (fromIntegral byteOffset)
    (toCSize byteLength)
{-# INLINEABLE bufferGetMappedRange #-}

-- | Unmap a buffer that was previously mapped into host memory.
bufferUnmap :: MonadIO m => Buffer -> m ()
bufferUnmap buffer = do
  let inst = bufferInst buffer
  WGPU.wgpuBufferUnmap
    (wgpuHsInstance inst)
    (wgpuBuffer buffer)
{-# INLINEABLE bufferUnmap #-}