caramia-0.7.2.2: tests/buffer/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Main ( main ) where
import Foreign.C.String
import Data.Bits
import Control.Concurrent
import Control.Exception
import qualified Data.Set as S
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import Graphics.Caramia
import Graphics.Caramia.Prelude hiding ( init )
import Graphics.UI.SDL
import System.IO.Unsafe ( unsafePerformIO )
import Test.Framework
import Test.Framework.Providers.HUnit
import Test.HUnit hiding ( Test )
foreign import ccall unsafe "memset" c_memset
:: Ptr a -> CInt -> CSize -> IO (Ptr b)
sdlLock :: MVar ()
sdlLock = unsafePerformIO $ newMVar ()
{-# NOINLINE sdlLock #-}
setup :: IO a -> IO a
setup action = runInBoundThread $ withMVar sdlLock $ \_ -> withCString "buffer" $ \cstr -> do
_ <- init SDL_INIT_VIDEO
_ <- glSetAttribute SDL_GL_CONTEXT_MAJOR_VERSION 3
_ <- glSetAttribute SDL_GL_CONTEXT_MINOR_VERSION 3
_ <- glSetAttribute SDL_GL_CONTEXT_PROFILE_MASK SDL_GL_CONTEXT_PROFILE_CORE
_ <- glSetAttribute SDL_GL_CONTEXT_FLAGS SDL_GL_CONTEXT_DEBUG_FLAG
w <- createWindow cstr SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED
1500 1000
(SDL_WINDOW_OPENGL .|.
SDL_WINDOW_SHOWN)
ctx <- glCreateContext w
finally (giveContext action) $ do
glDeleteContext ctx
destroyWindow w
quit
main :: IO ()
main = defaultMain tests
tests :: [Test]
tests = [
testCase "Invalidation does not crash and burn" invalidateSomeBuffers
, testCase "I can create various types of buffers" createSomeBuffers
, testCase "I cannot map write-only buffers for reading" writeOnlyTest
, testCase "I cannot map read-only buffers for writing" readOnlyTest
, testCase "I cannot map no-access buffers for anything" noAccessTest
, testCase "I can map readwrite buffers for anything" readWriteTest
, testCase "What I write I can get back" writeReadBackTest
, testCase "Mapping with offset works" offsetMappingTest
, testCase "Mapping with unsynchronized flag set doesn't crash" unsyncTest
, testCase "Buffer copying works" copyBuffersTest
, testCase "Explicit flushing tentatively works" explicitFlushTest
, testCase ("I cannot invoke `explicitFlush` on a mapping without " <>
"`ExplicitFlush` flag.") incorrectExplicitFlushTest
, testCase "I cannot map with `ExplicitFlush` and no writing access."
noWriteExplicitFlushTest
, testCase "I cannot invoke `explicitFlush` without a mapping."
explicitFlushNoMapTest
]
explicitFlushNoMapTest :: IO ()
explicitFlushNoMapTest = setup $ do
buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
expectException $ explicitFlush buf 10 10
noWriteExplicitFlushTest :: IO ()
noWriteExplicitFlushTest = setup $ do
buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
expectException $ withMapping2 (S.singleton ExplicitFlush)
100 100 ReadAccess buf $ const $ return ()
expectException $ withMapping2 (S.singleton ExplicitFlush)
100 100 NoAccess buf $ const $ return ()
incorrectExplicitFlushTest :: IO ()
incorrectExplicitFlushTest = setup $ do
buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
withMapping 100 100 WriteAccess buf $ \_ -> do
expectException $ explicitFlush buf 10 10
explicitFlushTest :: IO ()
explicitFlushTest = setup $ do
buf <- newBufferFromList (take 10000 $ repeat (12 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
withMapping2 (S.singleton ExplicitFlush) 7800 1000 WriteAccess buf $ \ptr -> do
pokeElemOff (ptr :: Ptr Word8) 10 8
pokeElemOff (ptr :: Ptr Word8) 11 9
pokeElemOff (ptr :: Ptr Word8) 12 10
explicitFlush buf 8 3
withMapping 7800 100 ReadAccess buf $ \ptr -> do
let assM x y = do z <- peekElemOff (ptr :: Ptr Word8) x
assertEqual "explicitly flushed bytes are the same"
y z
assM 10 8
assM 11 9
assM 12 10
copyBuffersTest :: IO ()
copyBuffersTest = setup $ do
buf <- newBufferFromList (take 10000 $ repeat (77 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
buf2 <- newBufferFromList (take 20000 $ repeat (99 :: Word8))
(\old -> old { accessFlags = ReadWriteAccess })
buf3 <- newBufferFromList (replicate 300 111 ++ replicate 300 222 :: [Word8])
(\old -> old { accessFlags = ReadWriteAccess })
-- copy from another to another
copy buf 11 buf2 13 5
-- copying inside the same buffer
copy buf3 3 buf3 311 5
withMapping 0 100 ReadAccess buf $ \ptr -> do
let cptr = castPtr ptr :: Ptr Word8
ass = assertEqual "bytes copied look correct"
assM x off = do v <- peekElemOff cptr off
ass x v
assM 77 0
assM 77 1
assM 77 2
assM 77 3
assM 77 4
assM 77 10
assM 99 11
assM 99 12
assM 99 13
assM 99 14
assM 99 15
assM 77 16
withMapping 0 600 ReadAccess buf3 $ \ptr -> do
let cptr = castPtr ptr :: Ptr Word8
ass = assertEqual "bytes copied look correct"
assM x off = do v <- peekElemOff cptr off
ass x v
assM 111 0
assM 111 1
assM 111 2
assM 222 3
assM 222 4
assM 222 5
assM 222 6
assM 222 7
assM 111 8
assM 111 9
assM 111 10
offsetMappingTest :: IO ()
offsetMappingTest = setup $ do
buf <- newBufferFromList (take 10000 $ cycle [(0 :: Word8)..])
(\old -> old { accessFlags = ReadWriteAccess })
for_ offsets $ \off -> withMapping off 32 ReadAccess buf $ \ptr -> do
let cptr = castPtr ptr :: Ptr Word8
expect m v = assertEqual "a byte read from a buffer should be the same that was written"
(fromIntegral $ (off+m) `mod` 256)
v
v1 <- peekElemOff cptr 0
v2 <- peekElemOff cptr 1
v3 <- peekElemOff cptr 2
v4 <- peekElemOff cptr 3
expect 0 v1
expect 1 v2
expect 2 v3
expect 3 v4
where
offsets = [ 0, 16, 1024, 2048, 2040, 8000 ]
writeReadBackTest :: IO ()
writeReadBackTest = setup $ do
buf <- newBuffer defaultBufferCreation
{ size = 1000000
, accessFlags = ReadWriteAccess }
withMapping 0 1000000 WriteAccess buf $ \ptr -> do
void $ c_memset ptr 33 1000000
pokeElemOff (castPtr ptr :: Ptr Word8) 12371 177
withMapping 0 1000000 ReadAccess buf $ \ptr -> do
let cptr = castPtr ptr :: Ptr Word8
v <- peekElemOff cptr 12371
assertEqual "a byte read from a buffer should be the same that was written"
177
v
v_prec <- peekElemOff cptr 12370
v_succ <- peekElemOff cptr 12372
assertEqual "a byte read from a buffer should be the same that was written"
33
v_prec
assertEqual "a byte read from a buffer should be the same that was written"
33
v_succ
writeOnlyTest :: IO ()
writeOnlyTest = setup $ do
buf <- newBuffer defaultBufferCreation
{ size = 1024
, accessFlags = WriteAccess }
expectException $ bufferMap 0 1024 ReadAccess buf
expectException $ bufferMap 0 1024 ReadWriteAccess buf
withMapping 0 1024 WriteAccess buf (const $ return ())
readOnlyTest :: IO ()
readOnlyTest = setup $ do
buf <- newBuffer defaultBufferCreation
{ size = 1024
, accessFlags = ReadAccess }
expectException $ bufferMap 0 1024 WriteAccess buf
expectException $ bufferMap 0 1024 ReadWriteAccess buf
withMapping 0 1024 ReadAccess buf (const $ return ())
noAccessTest :: IO ()
noAccessTest = setup $ do
buf <- newBuffer defaultBufferCreation
{ size = 1024
, accessFlags = NoAccess }
for_ allAccessFlags $ \flags ->
expectException $ bufferMap 0 1024 flags buf
readWriteTest :: IO ()
readWriteTest = setup $ do
buf <- newBuffer defaultBufferCreation
{ size = 1024
, accessFlags = ReadWriteAccess }
for_ allAccessFlags $ \flags ->
when (flags /= NoAccess) $
withMapping 0 1024 flags buf (const $ return ())
expectException :: IO a -> IO ()
expectException action = do
result <- try action
case result of
Left (_ :: SomeException) -> return ()
Right _ -> error "expected exception"
withSomeBuffers :: (BufferCreation -> Buffer -> IO ()) -> IO ()
withSomeBuffers action =
for_ allAccessHints $ \hints ->
for_ allAccessFlags $ \flags ->
for_ sizes $ \size -> do
let c = defaultBufferCreation {
size = size
, initialData = Nothing
, accessFlags = flags
, accessHints = hints }
newBuffer c >>= action c
allocaBytes size $ \ptr -> do
let c' = c { initialData = Just ptr }
newBuffer c' >>= action c'
runPendingFinalizers
unsyncTest :: IO ()
unsyncTest = setup $ withSomeBuffers $ \cr buf -> do
when (accessFlags cr == WriteAccess ||
accessFlags cr == ReadWriteAccess) $
withMapping2 (S.singleton UnSynchronized)
0
(size cr)
WriteAccess
buf $ const $ return ()
invalidateSomeBuffers :: IO ()
invalidateSomeBuffers = setup $ withSomeBuffers $ \_ -> invalidateBuffer
createSomeBuffers :: IO ()
createSomeBuffers = setup $ withSomeBuffers (\_ _ -> return ())
sizes :: [Int]
sizes = [ 1, 2, 3, 4, 8, 11, 12, 512, 1024, 1023, 1025, 1000000 ]
allAccessFlags :: [AccessFlags]
allAccessFlags = [ReadAccess, WriteAccess, ReadWriteAccess, NoAccess]
allAccessHints :: [(AccessFrequency, AccessNature)]
allAccessHints = [ (f, n) | f <- [Stream, Static, Dynamic]
, n <- [Draw, Read, Copy] ]