packages feed

immutaball-core-0.1.0.4.1: Immutaball/Share/Video/LowLevel.hs

{-# OPTIONS_GHC -fno-warn-tabs #-}  -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

-- Video/LowLevel.hs.

{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | gpuEncodeArray and reverseRowsImage.
module Immutaball.Share.Video.LowLevel
	(
		gpuEncodeArray,
		reverseRowsImage,
		reverseRowsImageBuilderRows,
		reverseRowsImageBuilderBytes,
		reverseRowsImageLowLevel
	) where

-- Prelude imports.
import Prelude ()
import Immutaball.Prelude

-- base imports.
import Control.Arrow
import Control.Monad
import qualified Data.Array.Unsafe
import Data.Function hiding (id, (.))
import Data.Int
import Data.Word
import Foreign.C.Types
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable

-- external imports.
import Data.Array.IArray
import Data.Array.Storable
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BB
--import qualified Data.ByteString.Builder.Extra as BB
import qualified Data.ByteString.Lazy as BL

-- internal (local) imports.
import Immutaball.Share.ImmutaballIO.GLIO
import Immutaball.Share.Math
import Immutaball.Share.Utils

-- Low-level imports.
import qualified Data.ByteString.Unsafe as UnsafeBS
import System.IO.Unsafe (unsafePerformIO)

gpuEncodeArray :: forall a. (Storable a) => Array Int32 a -> GLData
gpuEncodeArray array_ = unsafePerformIO $ do
	(storableArray :: StorableArray Int32 a) <- Data.Array.Unsafe.unsafeThaw array_
	bs <- withStorableArray storableArray $ \(ptr :: Ptr a) -> do
		let (bsPtr :: Ptr CChar) = castPtr ptr
		let (numElems :: Int) = rangeSize . bounds $ array_
		let (bsLen :: Int) = numElems * sizeOf ((array_ ! 0)  :: a)
		bs <- BS.packCStringLen (bsPtr, bsLen)
		return bs
	let data_ = bsToGLData bs
	return $ data_

reverseRowsImage :: (WidthHeightI, BS.ByteString) -> BS.ByteString
--reverseRowsImage = reverseRowsImageLowLevel
--reverseRowsImage = reverseRowsImageBuilderBytes
reverseRowsImage = reverseRowsImageBuilderRows

-- This is much better.  And safe!
reverseRowsImageBuilderRows :: (WidthHeightI, BS.ByteString) -> BS.ByteString
reverseRowsImageBuilderRows ((w, h), image)
	| BS.length image <= 0 = image
	| otherwise = BL.toStrict . BB.toLazyByteString $
		flip fix 0 $ \withRow row ->
			if' (row >= h') mempty $
			(BB.byteString . BS.take (w'*4) . BS.drop (((h'-1)-row)*w'*4) $ image) <> (withRow (row+1))
	where
		w', h' :: Int
		(w', h') = join (***) fromIntegral (w, h)

-- This is still noticeably slower than reverseRowsImageLowLevel.
reverseRowsImageBuilderBytes :: (WidthHeightI, BS.ByteString) -> BS.ByteString
reverseRowsImageBuilderBytes ((w, h), image)
	| BS.length image <= 0 = image
	| otherwise = BL.toStrict . BB.toLazyByteString $
	-- | otherwise = BL.toStrict . BB.toLazyByteStringWith (BB.safeStrategy chunkSize chunkSize) BL.empty $
		flip fix 0 $ \withRow row ->
			if' (row >= h') mempty $
			flip fix 0 $ \withCol col ->
				if' (col >= w') (withRow (row+1)) $
				flip fix 0 $ \withComponent component ->
					if' (component >= 4) (withCol (col+1)) $
					let idx = ((h'-1)-row)*w'*4 + col*4 + component in
					if' (idx >= BS.length image) mempty $
					--(BB.word8 $ image `UnsafeBS.unsafeIndex` idx) <> (withComponent (component+1))  -- This doesn't seem to be a major improvement on the core performance issue.  Keep it to the safe API.
					(BB.word8 $ image `BS.index` idx) <> (withComponent (component+1))
	where
		w', h' :: Int
		(w', h') = join (***) fromIntegral (w, h)
		--chunkSize :: Int
		--chunkSize = 2^(20 :: Int)

-- | The old version was really slow.
--
-- Use unsafe lower-level functions to manually implement a faster version,
-- given the limitations of the safe bytestring interface provided along with
-- the low-level implementation of it.
--
-- But now we have a safe _and_ faster version that uses bytestring builders; see 'reverseRowsImageBuilderRows'.
reverseRowsImageLowLevel :: (WidthHeightI, BS.ByteString) -> BS.ByteString
reverseRowsImageLowLevel ((w, h), image)
	| BS.length image <= 0 = image
	| otherwise = unsafePerformIO $ do
		-- Unsafe optimization to avoid a copy: use unsafeUseAsCStringLen with the promise we won't mutate the data.
		--useAsCStringLen image $ \(ptr, len) -> do
		UnsafeBS.unsafeUseAsCStringLen image $ \(ptr, len_) -> do
			let len = max 1 $ len_
			let (imageBufferI :: Ptr Word8) = castPtr ptr
			(imageBufferO :: Ptr Word8) <- mallocArray len
			flip fix 0 $ \withRow rowI -> let rowOffsetI = rowI * 4*w' in if' (rowOffsetI >= len_) (return ()) $ do
				let rowO = (h' - 1) - rowI; rowOffsetO = rowO * 4*w'
				flip fix 0 $ \withCol colI -> let colOffsetI = 4*colI in if' (rowOffsetI + colOffsetI + 3 >= len_) (return ()) $ do
					if' (colI >= w') (withRow (rowI+1)) $ do
						let colO = colI; colOffsetO = 4*colO
						peek (imageBufferI `plusPtr'` (rowOffsetI + colOffsetI + 0)) >>= poke (imageBufferO `plusPtr'` (rowOffsetO + colOffsetO + 0))
						peek (imageBufferI `plusPtr'` (rowOffsetI + colOffsetI + 1)) >>= poke (imageBufferO `plusPtr'` (rowOffsetO + colOffsetO + 1))
						peek (imageBufferI `plusPtr'` (rowOffsetI + colOffsetI + 2)) >>= poke (imageBufferO `plusPtr'` (rowOffsetO + colOffsetO + 2))
						peek (imageBufferI `plusPtr'` (rowOffsetI + colOffsetI + 3)) >>= poke (imageBufferO `plusPtr'` (rowOffsetO + colOffsetO + 3))
						withCol (colI+1)
			UnsafeBS.unsafePackMallocCStringLen $ (castPtr imageBufferO, len)
	where
		w', h' :: Int
		(w', h') = join (***) fromIntegral (w, h)
		plusPtr' :: Ptr a -> Int -> Ptr a
		plusPtr' = plusPtr