packages feed

mischief-render-0.1.0.0: src/Mischief/Render/Texture.hs

module Mischief.Render.Texture where

import Codec.Picture qualified as P
import Codec.Picture.Extra (scaleBilinear)
import Control.Applicative.Singletons
import Control.Monad.IO.Class
import Data.Default (Default (def))
import Data.Maybe.Singletons (SMaybe (SJust))
import Data.Singletons
import Data.Vector.Storable (Vector)
import Data.Vector.Storable qualified as VS
import Foreign (Bits ((.|.)), Ptr, Word32, Word8, castPtr, nullPtr, with)
import Foreign.C.ConstPtr (ConstPtr (..))
import GHC.TypeLits
import Mischief.Assets
import Mischief.Assets.Image
import Mischief.ECS
import Mischief.Render.Core
import Mischief.Render.TextureFormat
import Mischief.WGPU
import Mischief.WGPU.Opaque
import Mischief.WGPU.Types.Enums
import Mischief.WGPU.Types.General

data Texture = Texture {texture :: Ptr WGPUTexture, desc :: TextureDescriptor}

data SwappableTexture = SwappableTexture {currentTexture :: Ptr WGPUTexture, otherTexture :: Ptr WGPUTexture, desc :: TextureDescriptor}

extractTexture :: SwappableTexture -> Texture
extractTexture SwappableTexture {currentTexture, desc} = Texture currentTexture desc

swapTexture :: SwappableTexture -> (SwappableTexture, Texture)
swapTexture SwappableTexture {currentTexture, otherTexture, desc} = (SwappableTexture {currentTexture = otherTexture, otherTexture = currentTexture, desc}, Texture currentTexture desc)

newtype TextureView = TextureView (Ptr WGPUTextureView)

data TextureDescriptor = TextureDescriptor
  { width :: Int,
    height :: Int,
    depth :: Int,
    label :: String,
    mipLevels :: Int,
    samples :: Int,
    format :: TextureFormat,
    usages :: [TextureUsage]
  }
  deriving (Show)

instance Default TextureDescriptor where
  def = textureDescriptor

textureDescriptor :: TextureDescriptor
textureDescriptor =
  TextureDescriptor
    { width = 1,
      height = 1,
      depth = 1,
      label = "texture",
      mipLevels = 1,
      samples = 1,
      format = RGBA8UnormSrgb,
      usages = [TextureUsageTextureBinding, TextureUsageCopyDst, TextureUsageRenderAttachment]
    }

data TextureUsage
  = TextureUsageNone
  | TextureUsageCopySrc
  | TextureUsageCopyDst
  | TextureUsageTextureBinding
  | TextureUsageStorageBinding
  | TextureUsageRenderAttachment
  | TextureUsageTransientAttachment
  deriving (Show)

usageBits :: TextureUsage -> WGPUTextureUsage
usageBits TextureUsageNone = wGPUTextureUsage_None
usageBits TextureUsageCopySrc = wGPUTextureUsage_CopySrc
usageBits TextureUsageCopyDst = wGPUTextureUsage_CopyDst
usageBits TextureUsageTextureBinding = wGPUTextureUsage_TextureBinding
usageBits TextureUsageStorageBinding = wGPUTextureUsage_StorageBinding
usageBits TextureUsageRenderAttachment = wGPUTextureUsage_RenderAttachment
usageBits TextureUsageTransientAttachment = wGPUTextureUsage_TransientAttachment

processUsages :: [TextureUsage] -> WGPUTextureUsage
processUsages = foldr ((.|.) . usageBits) (WGPUTextureUsage (WGPUFlags 0))

createSwappableTexture :: RenderDevice -> TextureDescriptor -> IO SwappableTexture
createSwappableTexture device desc = do
  a <- createTexture' device desc
  b <- createTexture' device desc
  pure $ SwappableTexture a b desc

createTexture :: RenderDevice -> TextureDescriptor -> IO Texture
createTexture device desc = (`Texture` desc) <$> createTexture' device desc

createTexture' :: RenderDevice -> TextureDescriptor -> IO (Ptr WGPUTexture)
createTexture' (RenderDevice device) descriptor = liftIO $ do
  let TextureDescriptor {width, height, depth, label, mipLevels, samples, format, usages} = descriptor
  withWGPUString label $ \label -> do
    let desc =
          WGPUTextureDescriptor
            { size = WGPUExtent3D (fromIntegral width) (fromIntegral height) (fromIntegral depth),
              label,
              mipLevelCount = fromIntegral mipLevels,
              sampleCount = fromIntegral samples,
              dimension = wGPUTextureDimension_2D,
              format = toWGPUFormat format,
              usage = processUsages usages,
              nextInChain = nullPtr,
              viewFormatCount = 0,
              viewFormats = ConstPtr nullPtr
            }

    with desc (wgpuDeviceCreateTexture device . ConstPtr)

getDimension :: (Int, Int, Int) -> WGPUTextureDimension
getDimension (_, 1, 1) = wGPUTextureDimension_1D
getDimension (_, _, 1) = wGPUTextureDimension_2D
getDimension (_, _, _) = wGPUTextureDimension_3D

uploadImage :: RenderQueue -> Image -> Texture -> IO ()
uploadImage (RenderQueue queue) (Image image) (Texture {texture, desc = TextureDescriptor {width, height}}) = do
  let (P.Image w h bytes) = scaleBilinear width height (P.convertRGBA8 image)

  let extent = WGPUExtent3D (fromIntegral w) (fromIntegral h) 1
  let copyInfo =
        WGPUTexelCopyTextureInfo
          { texture,
            mipLevel = 0,
            origin = WGPUOrigin3D 0 0 0,
            aspect = wGPUTextureAspect_All
          }

  let layout =
        WGPUTexelCopyBufferLayout
          { offset = 0,
            bytesPerRow = 4 * fromIntegral w,
            rowsPerImage = fromIntegral h
          }

  VS.unsafeWith bytes $ \pixelPtr -> do
    with extent $ \extent -> do
      with copyInfo $ \copyInfo -> do
        with layout $ \layout -> do
          wgpuQueueWriteTexture queue (ConstPtr copyInfo) (ConstPtr $ castPtr pixelPtr) (fromIntegral $ VS.length bytes) (ConstPtr layout) (ConstPtr extent)

createTextureForImage :: RenderDevice -> Image -> IO Texture
createTextureForImage device (Image image) = do
  let (P.Image w h _) = P.convertRGBA8 image
  createTexture
    device
    TextureDescriptor
      { width = w,
        height = h,
        depth = 1,
        label = "texture",
        mipLevels = 1,
        samples = 1,
        format = RGBA8UnormSrgb,
        usages = [TextureUsageTextureBinding, TextureUsageCopyDst]
      }

createView :: Texture -> System TextureView
createView Texture {texture} = liftIO $ TextureView <$> wgpuTextureCreateView texture (ConstPtr nullPtr)