“Mutable Image” 的 “Gloss render” 中存在内存泄漏。

huangapple go评论63阅读模式
英文:

Space Leak on Gloss render of Mutable Image

问题

I used the JuicyPixel library to generate a rendered image and the gloss library for a live preview.

The following Haskell code snippet appears to be causing a space leak:

viewportRenderer :: Viewport Picture
viewportRenderer = do
  eventText <- Color [rgb|#FFFFFF|] . scale 0.1 0.1 . Text . show . _lastEvent <$> get

  viewportLoc' <- gets _viewportLoc
  viewportScale' <- gets _viewportScale
  image <- gets _renderedImage >>= unsafeFreezeImage
  let viewport =
        Color [rgb|#323232|] $
          unV2 translate viewportLoc' $
            join scale viewportScale' $
              fromImageRGBA8 image
  return (Pictures [viewport, eventText])

Some additional context:

type RenderedImage = MutableImage RealWorld PixelRGBA8

data ViewportState = ViewPortState
  { _lastEvent :: Event
  , _viewportScale :: !Float
  , _viewportOrigin :: V2 Float
  , _viewportLoc :: V2 Float
  , _repeatActions :: [(Event -> Bool, StateT ViewportState IO ())] -- Actions that get repeated until (Event -> Bool) returns True
  , _renderedImage :: !RenderedImage
  }

makeLenses ''ViewportState

type Viewport a = StateT ViewportState IO a -- Uses lazy StateT

initialViewPortState :: RenderedImage -> ViewportState
-- ...

viewWindow :: RenderedImage -> IO ()
-- ...

renderer :: RenderedImage -> IO ()
-- ...

main :: IO ()
-- ...

The idea is to continually write to a MutableImage in the background using the renderer function, and then display the result in the viewportRenderer function. However, viewportRenderer is causing a space leak.

You've tried using IORef RenderedImage and MVar RenderedImage but didn't resolve the space leak. Profiling shows that createMutableImage is responsible for most allocations.

You're wondering if modifying strictness or using a different data structure might fix the issue.

Please let me know if you need specific advice or further assistance with this code.

英文:

I used the JuicyPixel library for generating a rendered image and the gloss library for a live preview.

The piece of code below causes a Space Leak


viewportRenderer :: Viewport Picture
viewportRenderer = do
  eventText <- Color [rgb|#FFFFFF|] . scale 0.1 0.1 . Text . show . _lastEvent <$> get

  viewportLoc' <- gets _viewportLoc
  viewportScale' <- gets _viewportScale
  image <- gets _renderedImage >>= unsafeFreezeImage
  let viewport =
        Color [rgb|#323232|] $
          unV2 translate viewportLoc' $
            join scale viewportScale' $
              fromImageRGBA8 image
  return (Pictures [viewport, eventText])

Some additional context:

type RenderedImage = MutableImage RealWorld PixelRGBA8
data ViewportState = ViewPortState
  { _lastEvent :: Event
  , _viewportScale :: !Float
  , _viewportOrigin :: V2 Float
  , _viewportLoc :: V2 Float
  , _repeatActions :: [(Event -> Bool, StateT ViewportState IO ())]
  -- ^ Actions that get repeated until (Event -> Bool) returns True
  , _renderedImage :: !RenderedImage
  }

makeLenses ''ViewportState

type Viewport a = StateT ViewportState IO a
-- Uses lazy StateT

initialViewPortState :: RenderedImage -> ViewportState
initialViewPortState image =
  ViewPortState
    { _lastEvent = EventResize (0, 0) -- Sentinel value
    , _viewportScale = 1
    , _viewportOrigin = 0
    , _viewportLoc = 0
    , _repeatActions = []
    , _renderedImage = image
    }
viewWindow :: RenderedImage -> IO ()
viewWindow !image = do
  playIO
    (InWindow "Reticule-Minor viewport" (400, 300) (100, 100))
    [rgb|#0B0B0B|]
    60
    (initialViewPortState image)
    (fmap fst . runStateT viewportRenderer)
    (\event -> fmap snd . runStateT (eventHandler event))
    (\t -> fmap snd . runStateT (timeHandler t))
renderer :: RenderedImage -> IO ()
renderer image = do
  forM_ [0..399] \x -> forM_ [0..299] \y -> do
    let r = floor @Float $ (fromIntegral x / 399) * 255
        g = floor @Float $ (fromIntegral y / 299) * 255
    writePixel image x y (PixelRGBA8 r g 255 255)
    threadDelay 10

main :: IO ()
main = do
  image <- createMutableImage 400 300 (PixelRGBA8 255 255 255 0) >>= newIORef
  _ <- forkIO $ renderer image
  V.viewWindow image

https://github.com/Perigord-Kleisli/reticule-minor
(Repo containing the whole codebase)


The idea is to have a MutableImage be continually written to by renderer forked in the background and to have viewportRenderer display the result every frame. Though as said, viewportRenderer causes a space leak. I'm wondering if this can be fixed by modifying Strictness or if I should probably utilize another Data Structure for passing the image.

I wouldnt really call it "best practice" but I don't really need to deal with having race conditions here.

  • I've experimented on using IORef RenderedImage and MVar RenderedImage though both of these didn't fix the space leak.

  • It was also unable to be optimized away by passing -O2 as a compile option.

  • I tried it with just generating a new image every frame and it didn't create a space leak. Unless I generate an image from the RenderedImage.

  • I've read the image in both freezeImage, unsafeFreezeImage though both caused space leaks. (unsafeFreezeImage was faster and produced the same results which is strange as the library docs mention that you shouldnt be able to use the MutableImage after passing it)

Edit: I did some profiling which resulted in the following results.
“Mutable Image” 的 “Gloss render” 中存在内存泄漏。

“Mutable Image” 的 “Gloss render” 中存在内存泄漏。

With the profiler output being:
https://pastebin.com/3YMFpAem

The results are admittedly quite surprising, with createMutableImage being responsible for most of the allocations.

答案1

得分: 4

已解决问题,显然是因为我连续使用的gloss-juicy函数每帧都缓存了图像。它的实现如下:

fromImageRGBA8 :: Image PixelRGBA8 -> Picture
fromImageRGBA8 (Image { imageWidth = w, imageHeight = h, imageData = id }) =
  bitmapOfForeignPtr w h
                     (BitmapFormat TopToBottom PxRGBA)
                     ptr True
    where (ptr, _, _) = unsafeToForeignPtr id

所以我只是复制了它,并将bitmapOfForeignPtrBool参数设置为False

英文:

Solved the issue, apparently it's because the gloss-juicy function I used continually cached the image each frame. It is implemented as:

fromImageRGBA8 :: Image PixelRGBA8 -> Picture
fromImageRGBA8 (Image { imageWidth = w, imageHeight = h, imageData = id }) =
  bitmapOfForeignPtr w h
                     (BitmapFormat TopToBottom PxRGBA)
                     ptr True
    where (ptr, _, _) = unsafeToForeignPtr id

So I just copied it and set the Bool argument to bitmapOfForeignPtr to False.

huangapple
  • 本文由 发表于 2023年5月14日 22:13:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76247925.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定