elm-community / webgl

Moved to elm-explorations/webgl
https://package.elm-lang.org/packages/elm-explorations/webgl/latest
BSD 3-Clause "New" or "Revised" License
130 stars 18 forks source link

Memory leak when creating a mesh in update/view #46

Closed w0rm closed 5 years ago

w0rm commented 7 years ago

This was discovered in the https://github.com/francisdb/glmorph demo by @francisdb. The current implementation caches the attributes for each mesh. This means that every newly created mesh will be added to the cache, and if this is done in AnimationFrame subscription, the cache may grow pretty fast.

This is the smallest example to reproduce this issue:

module Main exposing (main)

import AnimationFrame
import Html exposing (Html)
import Math.Vector3 as Vec3 exposing (vec3, Vec3)
import WebGL exposing (Mesh, Shader)

main : Program Never () ()
main =
    Html.program
        { init = ( (), Cmd.none )
        , view = view
        , subscriptions = always (AnimationFrame.diffs (always ()))
        , update = \_ _ -> ( (), Cmd.none )
        }

view : () -> Html msg
view _ =
    WebGL.toHtml
        []
        [ WebGL.entity
            vertexShader
            fragmentShader
            (mesh ())
            {}
        ]

type alias Vertex =
    { position : Vec3
    }

triangles : List ( Vertex, Vertex, Vertex )
triangles =
    List.range 0 10
        |> List.map
            (\_ ->
                ( { position = vec3 0 0 0 }
                , { position = vec3 1 1 0 }
                , { position = vec3 1 -1 0 }
                )
            )

mesh : () -> Mesh Vertex
mesh _ =
    WebGL.triangles triangles

vertexShader : Shader Vertex {} {}
vertexShader =
    [glsl|
        attribute vec3 position;
        void main () {
            gl_Position = vec4(position, 1.0);
        }
    |]

fragmentShader : Shader {} {} {}
fragmentShader =
    [glsl|
        void main () {
            gl_FragColor = vec4(0, 0, 0, 1.0);
        }
    |]

The heap snapshot shows the growing amount of WebGLBuffer objects:

start end
w0rm commented 6 years ago

A list of possible use cases for updating a mesh on GPU:

  1. Animation with multiple frames (e.g. morphing a bunny into a cube), where each frame is a mesh, and there are many frames, so that all of them don’t fit in the GPU memory
  2. A free drawing app, where you have to “stream the mesh” as you draw
  3. 2D games with a lot of sprites, where changing the attributes on GPU is faster than calling drawElements many times (which is bound by ~500 elements), especially on smartphones. Pixi js does this optimisation
  4. Particle animation with complex physics, that is easier to do on CPU
w0rm commented 6 years ago

In the meantime I updated the docs to not generate mesh in view.

w0rm commented 5 years ago

Should be fixed by https://github.com/elm-explorations/webgl/pull/9

w0rm commented 5 years ago

According to https://stackoverflow.com/questions/31250052/are-webgltextures-garbage-collected this is not fully fixed. This approach should be fine for occasional changes but not for constantly creating new meshes/textures on every frame.