-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #44 from Anut-py/performance
Bunnymark example
- Loading branch information
Showing
28 changed files
with
1,135 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
packages: *.cabal | ||
|
||
optimization: 2 | ||
|
||
package h-raylib | ||
flags: +examples |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
{-# OPTIONS -Wall #-} | ||
{-# LANGUAGE TemplateHaskell #-} | ||
{-# LANGUAGE BangPatterns #-} | ||
|
||
-- Writing performant h-raylib code requires the use of pointers and other | ||
-- un-Haskelly functionality. Unfortunately, this cannot be avoided. | ||
|
||
module Main where | ||
|
||
import Control.Monad (forM_, unless, void, when) | ||
import Foreign | ||
( Ptr, | ||
Storable (alignment, peek, peekByteOff, poke, pokeByteOff, sizeOf), | ||
advancePtr, | ||
callocArray, | ||
free, | ||
malloc, | ||
nullPtr, | ||
peek, | ||
plusPtr, | ||
poke, | ||
) | ||
import Foreign.C (CFloat, withCString) | ||
import Raylib.Core | ||
( beginDrawing, | ||
c'getMouseX, | ||
c'getMouseY, | ||
c'getRandomValue, | ||
changeDirectory, | ||
clearBackground, | ||
endDrawing, | ||
getApplicationDirectory, | ||
getFrameTime, | ||
getScreenHeight, | ||
getScreenWidth, | ||
initWindow, | ||
isMouseButtonDown, | ||
setTargetFPS, | ||
windowShouldClose, | ||
) | ||
import Raylib.Core.Shapes (drawRectangle) | ||
import Raylib.Core.Text (drawFPS, drawText) | ||
import Raylib.Core.Textures (c'drawTexture, c'loadTexture, c'unloadTexture) | ||
import Raylib.Types (Color (Color), MouseButton (MouseButtonLeft), Texture (texture'height, texture'width)) | ||
import Raylib.Util (inGHCi, raylibApplication) | ||
import Raylib.Util.Colors (black, green, maroon, rayWhite) | ||
|
||
texPath :: String | ||
texPath = (if not inGHCi then "../../../../../../../../../../" else "./") ++ "examples/bunnymark/assets/wabbit_alpha.png" | ||
|
||
maxBunnies :: Int | ||
maxBunnies = 500000 -- 500K bunnies limit | ||
|
||
-- Strict fields | ||
data Bunny = Bunny | ||
{ px :: !Float, | ||
py :: !Float, | ||
sx :: !Float, | ||
sy :: !Float, | ||
color :: !(Ptr Color) -- Store colors as pointers to avoid reallocating memory each time | ||
} | ||
deriving (Show, Eq) | ||
|
||
cfs :: Int | ||
cfs = sizeOf (0 :: CFloat) | ||
|
||
nps :: Int | ||
nps = sizeOf nullPtr | ||
|
||
instance Storable Bunny where | ||
alignment _ = min cfs nps | ||
sizeOf _ = 4 * cfs + nps | ||
|
||
-- We do not peek/poke entire bunnies in this example, so the code below is never called | ||
-- (we advance the pointers using the `p'*` functions to directly access the fields) | ||
peek _p = do | ||
_px <- realToFrac <$> (peekByteOff _p (0 * cfs) :: IO CFloat) | ||
_py <- realToFrac <$> (peekByteOff _p (1 * cfs) :: IO CFloat) | ||
_sx <- realToFrac <$> (peekByteOff _p (2 * cfs) :: IO CFloat) | ||
_sy <- realToFrac <$> (peekByteOff _p (3 * cfs) :: IO CFloat) | ||
_color <- peekByteOff _p (4 * cfs) :: IO (Ptr Color) | ||
return $ Bunny {px = _px, py = _py, sx = _sx, sy = _sy, color = _color} | ||
poke _p (Bunny _px _py _sx _sy _color) = do | ||
pokeByteOff _p (0 * cfs) (realToFrac _px :: CFloat) | ||
pokeByteOff _p (1 * cfs) (realToFrac _py :: CFloat) | ||
pokeByteOff _p (2 * cfs) (realToFrac _sx :: CFloat) | ||
pokeByteOff _p (3 * cfs) (realToFrac _sy :: CFloat) | ||
pokeByteOff _p (4 * cfs) _color | ||
return () | ||
|
||
p'px :: Ptr Bunny -> Ptr CFloat | ||
p'px = (`plusPtr` (0 * cfs)) | ||
|
||
p'py :: Ptr Bunny -> Ptr CFloat | ||
p'py = (`plusPtr` (1 * cfs)) | ||
|
||
p'sx :: Ptr Bunny -> Ptr CFloat | ||
p'sx = (`plusPtr` (2 * cfs)) | ||
|
||
p'sy :: Ptr Bunny -> Ptr CFloat | ||
p'sy = (`plusPtr` (3 * cfs)) | ||
|
||
p'color :: Ptr Bunny -> Ptr (Ptr Color) | ||
p'color = (`plusPtr` (4 * cfs)) | ||
|
||
data AppState = AppState | ||
{ texBunny :: !(Ptr Texture), | ||
halfTexWidth :: !CFloat, | ||
halfTexHeight :: !CFloat, | ||
bunnies :: !(Ptr Bunny), -- Store the bunnies as a pointer because Haskell linked lists are extremely slow | ||
bunniesCount :: !Int | ||
} | ||
deriving (Show, Eq) | ||
|
||
startup :: IO AppState | ||
startup = do | ||
_ <- initWindow 800 450 "raylib [textures] example - bunnymark" | ||
setTargetFPS 60 | ||
unless inGHCi (void $ changeDirectory =<< getApplicationDirectory) | ||
texPtr <- withCString texPath c'loadTexture | ||
-- Use `peek` when you need to access the underlying fields | ||
|
||
-- If you have to do this often (e.g. every frame), use `plusPtr` | ||
-- to get a pointer to the exact field that you need | ||
|
||
-- For example, this could be rewritten as | ||
-- tWidth <- fromIntegral (peekByteOff texPtr 4 :: IO CInt) | ||
-- but since we are only doing this on startup, there is no need to optimize it | ||
tex <- peek texPtr | ||
bunniesPtr <- callocArray maxBunnies | ||
return | ||
( AppState | ||
{ texBunny = texPtr, | ||
bunnies = bunniesPtr, | ||
halfTexWidth = fromIntegral (texture'width tex) / 2, | ||
halfTexHeight = fromIntegral (texture'height tex) / 2, | ||
bunniesCount = 0 | ||
} | ||
) | ||
|
||
mainLoop :: AppState -> IO AppState | ||
mainLoop state = do | ||
screenWidth <- getScreenWidth | ||
screenHeight <- getScreenHeight | ||
|
||
beginDrawing | ||
clearBackground rayWhite | ||
forM_ | ||
[0 .. (bunniesCount state - 1)] | ||
( \(!i) -> | ||
do | ||
-- Advancing the array pointer to get a pointer to a bunny | ||
let bunny = advancePtr (bunnies state) i | ||
-- Advancing the bunny pointer to access the fields | ||
_px <- peek $ p'px bunny | ||
_py <- peek $ p'py bunny | ||
_color <- peek $ p'color bunny | ||
c'drawTexture (texBunny state) (floor _px) (floor _py) _color | ||
) | ||
drawRectangle 0 0 screenWidth 40 black | ||
drawText ("bunnies: " ++ show (bunniesCount state)) 120 10 20 green | ||
drawText ("batched draw calls: " ++ show (1 + (bunniesCount state `div` 8192))) 320 10 20 maroon | ||
drawFPS 10 10 | ||
endDrawing | ||
|
||
forM_ | ||
[0 .. (bunniesCount state - 1)] | ||
( \(!i) -> | ||
do | ||
-- Same thing as before, but reading (`peek`) _and_ writing (`poke`) | ||
let bunny = advancePtr (bunnies state) i | ||
_px <- peek $ p'px bunny | ||
_py <- peek $ p'py bunny | ||
_sx <- peek $ p'sx bunny | ||
_sy <- peek $ p'sy bunny | ||
_color <- peek $ p'color bunny | ||
let px' = _px + _sx | ||
py' = _py + _sy | ||
adjX = px' + halfTexWidth state | ||
adjY = py' + halfTexHeight state | ||
poke (p'px bunny) px' | ||
poke (p'py bunny) py' | ||
when (adjX > fromIntegral screenWidth || adjX < 0) $ poke (p'sx bunny) (-_sx) | ||
when (adjY > fromIntegral screenHeight || adjY < 40) $ poke (p'sy bunny) (-_sy) | ||
) | ||
|
||
do | ||
lDown <- isMouseButtonDown MouseButtonLeft | ||
if lDown | ||
then do | ||
frameTime <- getFrameTime | ||
let newBunnies = min (round (10000 * frameTime)) (maxBunnies - bunniesCount state) | ||
mx <- realToFrac <$> c'getMouseX | ||
my <- realToFrac <$> c'getMouseY | ||
forM_ | ||
[bunniesCount state .. (bunniesCount state + newBunnies - 1)] | ||
( \(!i) -> | ||
do | ||
-- Creating elements uses `poke`, just like writing | ||
let bunny = advancePtr (bunnies state) i | ||
xSpeed <- (/ 60) . fromIntegral <$> c'getRandomValue (-250) 250 | ||
ySpeed <- (/ 60) . fromIntegral <$> c'getRandomValue (-250) 250 | ||
r <- fromIntegral <$> c'getRandomValue 50 240 | ||
g <- fromIntegral <$> c'getRandomValue 80 240 | ||
b <- fromIntegral <$> c'getRandomValue 100 240 | ||
ptr <- malloc | ||
poke ptr (Color r g b 255) | ||
|
||
poke (p'px bunny) mx | ||
poke (p'py bunny) my | ||
poke (p'sx bunny) xSpeed | ||
poke (p'sy bunny) ySpeed | ||
poke (p'color bunny) ptr | ||
) | ||
return $ state {bunniesCount = bunniesCount state + newBunnies} | ||
else return state | ||
|
||
shouldClose :: AppState -> IO Bool | ||
shouldClose _ = windowShouldClose | ||
|
||
teardown :: AppState -> IO () | ||
teardown state = do | ||
-- Unload and free functions have to be manually called | ||
c'unloadTexture (texBunny state) | ||
free (texBunny state) | ||
forM_ [0 .. (bunniesCount state - 1)] (\(!i) -> let bunny = advancePtr (bunnies state) i in free =<< peek (p'color bunny)) | ||
free (bunnies state) | ||
|
||
$(raylibApplication 'startup 'mainLoop 'shouldClose 'teardown) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.