Recently I attempted to create a video player in Go. I decoded video playback with Reisen and tried to play each frame using sprite.Set() and then sprite.Draw() but I encountered an issue with RAM consumption. Pixel saves each picture the sprite receives through sprite.Set(). When we play a video, there gonna be a lot of different pictures going to the sprite. But there's no need for them after they were played on the screen.
That's why I think there's no need to make all the sprites able to cache incoming pictures. I implemented Cached parameter for Drawer and SetCached() method for Sprite so the user can disable picture caching for the sprite for a period of time or forever.
Here's a snippet of code that produces random images and passes them through a channel to the renderer for them to be displayed on the screen. Launch the terminal, execute top command, then run the code and see how the application process depletes your RAM.
But if you add a videoSprite.SetCached(false) line right after the sprite creation fragment, the RAM consumption disappears and the amount of memory taken by the application stay approximately the same. This addition is really useful.
Recently I attempted to create a video player in Go. I decoded video playback with Reisen and tried to play each frame using
sprite.Set()
and thensprite.Draw()
but I encountered an issue with RAM consumption. Pixel saves each picture the sprite receives throughsprite.Set()
. When we play a video, there gonna be a lot of different pictures going to the sprite. But there's no need for them after they were played on the screen.That's why I think there's no need to make all the sprites able to cache incoming pictures. I implemented
Cached
parameter forDrawer
andSetCached()
method forSprite
so the user can disable picture caching for the sprite for a period of time or forever.Here's a snippet of code that produces random images and passes them through a channel to the renderer for them to be displayed on the screen. Launch the terminal, execute
top
command, then run the code and see how the application process depletes your RAM.Source code
```go package main import ( "crypto/rand" "fmt" "image" "image/color" "runtime" "time" "github.com/faiface/pixel" "github.com/faiface/pixel/pixelgl" colors "golang.org/x/image/colornames" ) const ( width = 1280 height = 720 frameBufferSize = 128 ) func pixToPictureData(pixels []byte, width, height int) *pixel.PictureData { picData := pixel.MakePictureData(pixel. R(0, 0, float64(width), float64(height))) for y := height - 1; y >= 0; y-- { for x := 0; x < width; x++ { picData.Pix[(height-y-1)*width+x].R = pixels[y*width*4+x*4+0] picData.Pix[(height-y-1)*width+x].G = pixels[y*width*4+x*4+1] picData.Pix[(height-y-1)*width+x].B = pixels[y*width*4+x*4+2] picData.Pix[(height-y-1)*width+x].A = pixels[y*width*4+x*4+3] } } return picData } func readVideoFrames( filename string, ) ( <-chan *pixel.PictureData, chan error, error, ) { frameBuffer := make(chan *pixel.PictureData, frameBufferSize) errs := make(chan error) go func(frameBuffer chan *pixel.PictureData, errs chan error) { for { upLeft := image.Point{0, 0} lowRight := image.Point{width, height} img := image.NewNRGBA(image. Rectangle{upLeft, lowRight}) for i := 0; i < width; i++ { for j := 0; j < height; j++ { buf := make([]byte, 4) _, err := rand.Read(buf) if err != nil { go func(err error) { errs <- err }(err) } pixelColor := color.RGBA{ R: buf[0], G: buf[1], B: buf[2], A: buf[3], } img.Set(i, j, pixelColor) } } pic := pixToPictureData(img.Pix, width, height) frameBuffer <- pic } }(frameBuffer, errs) return frameBuffer, errs, nil } func run() { fname := "demo.mp4" // Create a new window. cfg := pixelgl.WindowConfig{ Title: "Video demo", Bounds: pixel.R(0, 0, width, height), } win, err := pixelgl.NewWindow(cfg) handleError(err) videoFPS := 30 handleError(err) spf := 1.0 / float64(videoFPS) frameDuration, err := time. ParseDuration(fmt.Sprintf("%fs", spf)) handleError(err) frameBuffer, errs, err := readVideoFrames( fname) handleError(err) ticker := time.Tick(frameDuration) tr := pixel.IM.Moved(pixel.V(width/2, height/2)) videoSprite := pixel.NewSprite(nil, pixel.R(0, 0, width, height)) // Setup metrics. last := time.Now() fps := 0 perSecond := time.Tick(time.Second) i := 0 k := 0 var ram runtime.MemStats for !win.Closed() { deltaTime := time.Since(last).Seconds() last = time.Now() select { case err, ok := <-errs: if ok { fmt.Println( "error occurred while reading video frames:", err) } default: } select { case <-ticker: frame, ok := <-frameBuffer if ok { videoSprite.Set(frame, frame.Rect) i++ k++ } default: } win.Clear(colors.White) videoSprite.Draw(win, tr) win.Update() fps++ select { case <-perSecond: runtime.ReadMemStats(&ram) win.SetTitle(fmt.Sprintf("%s | FPS: %d | dt: %f | Frames: %d | Video FPS: %d | RAM: %d", cfg.Title, fps, deltaTime, i, k, ram.Alloc)) fps = 0 k = 0 default: } } } func main() { pixelgl.Run(run) } func handleError(err error) { if err != nil { panic(err) } } ```But if you add a
videoSprite.SetCached(false)
line right after the sprite creation fragment, the RAM consumption disappears and the amount of memory taken by the application stay approximately the same. This addition is really useful.