packages feed

nano-ui-sdl-0.1.0.1: lib/NanoUI/Sdl/Image.hs

module NanoUI.Sdl.Image
  ( ImageAtlas
  , newImageAtlas
  , destroyImageAtlas
  , syncImageAtlas
  , lookupImage
  )
where

import Control.Exception (mask_)
import Control.Monad (when)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Word (Word8)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr, castPtr, nullPtr)
import NanoUI.Testing (Context, atlasSnapshot, atlasTextureId)
import SDL3.Sys.Bindgen.Blendmode (sDL_BLENDMODE_BLEND)
import SDL3.Sys.Bindgen.Pixels (data SDL_PIXELFORMAT_RGBA32)
import SDL3.Sys.Bindgen.Render (SDL_Renderer, SDL_Texture, data SDL_TEXTUREACCESS_STATIC)
import SDL3.Sys.Bindgen.Runtime.PtrConst qualified as PtrConst
import SDL3.Sys.Render (createTextureSafe, destroyTexture, setTextureBlendMode, updateTextureSafe)

-- A texture and its metadata have one lifetime; publish them together.
newtype ImageAtlas = ImageAtlas (IORef (Maybe AtlasTexture))

data AtlasTexture = AtlasTexture
  { atTexture :: !(Ptr SDL_Texture)
  , atGeneration :: !Int
  }

newImageAtlas :: IO ImageAtlas
newImageAtlas = ImageAtlas <$> newIORef Nothing

destroyImageAtlas :: ImageAtlas -> IO ()
destroyImageAtlas (ImageAtlas ref) = mask_ $ do
  old <- readIORef ref
  writeIORef ref Nothing
  mapM_ (destroyTexture . atTexture) old

syncImageAtlas :: Ptr SDL_Renderer -> ImageAtlas -> Context -> IO ()
syncImageAtlas ren atlas@(ImageAtlas ref) ctx = do
  snap <- atlasSnapshot ctx
  case snap of
    Nothing -> pure ()
    Just (w, h, pixels, gen) -> do
      old <- readIORef ref
      when (Just gen /= fmap atGeneration old) $
        uploadAtlas ren atlas w h pixels gen

uploadAtlas ::
  Ptr SDL_Renderer -> ImageAtlas -> Int -> Int -> ForeignPtr Word8 -> Int -> IO ()
uploadAtlas ren (ImageAtlas ref) w h pixels gen = mask_ $
  withForeignPtr pixels $ \ptr -> do
    tex <- createTextureSafe ren SDL_PIXELFORMAT_RGBA32 SDL_TEXTUREACCESS_STATIC (fromIntegral w) (fromIntegral h)
    ok <-
      if tex == nullPtr
        then pure False
        else do
          _ <- setTextureBlendMode tex (fromIntegral sDL_BLENDMODE_BLEND)
          uploaded <- updateTextureSafe tex (PtrConst.unsafeFromPtr nullPtr) (PtrConst.unsafeFromPtr (castPtr ptr)) (fromIntegral (w * 4))
          if uploaded then pure True else destroyTexture tex >> pure False
    when ok $ do
      old <- readIORef ref
      writeIORef ref (Just (AtlasTexture tex gen))
      mapM_ (destroyTexture . atTexture) old

lookupImage :: ImageAtlas -> Int -> IO (Maybe (Ptr SDL_Texture))
lookupImage (ImageAtlas ref) tid
  | tid == atlasTextureId = fmap atTexture <$> readIORef ref
  | otherwise = pure Nothing