packages feed

lambdacube-compiler-0.5.0.0: backendtest/TestServer.hs

{-# LANGUAGE OverloadedStrings, LambdaCase, RecordWildCards #-}
import Control.Monad
import Control.Concurrent
import Control.Exception (finally)
import Data.Aeson
import Foreign
import Codec.Picture as Juicy
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (unpack)
import qualified Data.Vector as V
import qualified Data.Vector.Storable as SV
import qualified Network.WebSockets as WS
import qualified Data.Map as Map
import Text.Printf
import System.FilePath
import System.Directory
import System.Process

import TestData
import LambdaCube.Linear
import LambdaCube.IR
import LambdaCube.PipelineSchema
import LambdaCube.PipelineSchemaUtil
import LambdaCube.Mesh

import qualified EditorExamplesTest

main :: IO ()
main = do
  putStrLn "listening"
  WS.runServer "192.168.0.12" 9160 application

application pending = do
  conn <- WS.acceptRequest pending
  WS.forkPingThread conn 30
  let disconnect = return ()
      one = 1 :: Int
  flip finally disconnect $ do
    -- receive client info
    decodeStrict <$> WS.receiveData conn >>= \case
      Nothing -> fail "invalid client info"
      Just ci@ClientInfo{..} -> print ci
    -- send pipeline
    (testName,renderJob@RenderJob{..}) <- EditorExamplesTest.getRenderJob -- TODO
    WS.sendTextData conn . encode $ renderJob
    -- get render result: pipeline x scene x frame
    res <- forM pipelines $ \PipelineInfo{..} -> do
      forM (zip [one..] $ V.toList scenes) $ \(sIdx,Scene{..}) ->
        forM [one..length frames] $ \fIdx -> do
          let name = "backend-test-data/" ++ testName ++ "/result/" ++ takeBaseName pipelineName ++ "_scn" ++ printf "%02d" sIdx ++ "_" ++ printf "%02d" fIdx ++ ".png"
          decodeStrict <$> WS.receiveData conn >>= \case
            Nothing -> fail $ name ++ " - invalid RenderJobResult"
            Just (RenderJobError e) -> fail $ name ++ " - render error:\n" ++ e -- TODO: test failed
            Just (RenderJobResult FrameResult{..}) -> do
              createDirectoryIfMissing True (takeDirectory name)
              compareOrSaveImage name =<< toImage frImageWidth frImageHeight . either error id . B64.decode =<< WS.receiveData conn
              --putStrLn $ name ++ "\t" ++ unwords (map showTime . V.toList $ frRenderTimes)
    let differ = or $ concat $ fmap concat res
    putStrLn $ "render job: " ++ if differ then "FAIL" else "OK"
    forever $ threadDelay 1000000

compareOrSaveImage name img@(Image w h pixels) = do
  doesFileExist name >>= \case
    False -> do
      putStrLn $ "new image: " ++ name
      savePngImage name (ImageRGBA8 img)
      return False
    True -> do
      Right (ImageRGBA8 (Image origW origH origPixels)) <- readImage name
      let diffPixels a b = SV.sum $ SV.zipWith (\x y -> (fromIntegral x - fromIntegral y)^2) a b :: Float
          diff = diffPixels pixels origPixels
          threshold = 0
          differ = w /= origW || h /= origH || diff > threshold
      case differ of
        True -> do
          putStrLn $ name ++ " - differ!!! " ++ show diff
          let mismatchImage = dropExtension name ++ "_mismatch.png"
              diffImage = dropExtension name ++ "_diff.png"
          putStrLn $ "save difference: " ++ diffImage
          savePngImage mismatchImage (ImageRGBA8 img)
          (exitCode,out,err) <- readProcessWithExitCode "compare" ["-compose","src",name,mismatchImage,diffImage] ""
          --let res = read . head . words $ out :: Float
          print (out,err)
        False -> putStrLn $ name ++ " OK"
      return differ

toImage :: Int -> Int -> BS.ByteString -> IO (Image PixelRGBA8)
toImage w h buf = do
    fp <- mallocForeignPtrBytes (4*w*h)
    withForeignPtr fp $ \dst -> BS.useAsCStringLen buf $ \(src,i) -> copyBytes dst (castPtr src) i
    return $ Image w h $ SV.unsafeFromForeignPtr fp 0 (w*h)

showTime delta
    | t > 1e-1  = printf "%.3fs" t
    | t > 1e-3  = printf "%.1fms" (t/1e-3)
    | otherwise = printf "%.0fus" (t/1e-6)
  where
    t = realToFrac delta :: Double

{-
  data sets:
    hello
    editor
-}
{-
  features to test:
    blending
    depth test
    culling
    texturing
      uniform texture
      render texture
    multi draw calls into the same framebuffer
-}
-- TODO
{-
  how to pair pipelines with predefined data
  basically: storage - pipelines
    render job:
      gpu data
        scene <--> storage
          frame
-}
{-
  initial test cases:
    - hello - done
    - editor exercises
      TODO
        create storage
        collect pipelines
    - create render job list
-}