veandco / go-sdl2

SDL2 binding for Go
https://godoc.org/github.com/veandco/go-sdl2
BSD 3-Clause "New" or "Revised" License
2.17k stars 219 forks source link

No rendering when running on separate goroutine #467

Open bejaneps opened 4 years ago

bejaneps commented 4 years ago

So, basically I'm following justforfunc series, and started to build flappy gopher game. I've run into an issue that after running a renderer in a separate goroutine it doesn't copy texture to window.

Minimal reproduction

  1. Build game: go build *.go
  2. Run it: ./main
  3. Draws only Flappy Gopher title on window, no bird and background.

Goversion: 1.14.4 OS: Elementary OS Juno

Code main.go

package main

import (
    "context"
    "log"
    "os"
    "path/filepath"
    "runtime"
    "time"

    "github.com/pkg/errors"

    "github.com/veandco/go-sdl2/sdl"
    "github.com/veandco/go-sdl2/ttf"
)

var (
    pathToFonts     = filepath.Join("res", "fonts", "flappy.ttf")
    pathToBgImage   = filepath.Join("res", "images", "background.png")
    pathToBirdImage = filepath.Join("res", "images", "bird_frame_%d.png")
)

func main() {
    if err := run(); err != nil {
        log.Println(err)
        os.Exit(2)
    }
}

func run() error {
    var err error

    // initialize all dependencies for SDL
    err = sdl.Init(sdl.INIT_EVERYTHING)
    if err != nil {
        return errors.Errorf("couldn't init SDL: %v", err)
    }
    defer sdl.Quit()

    // initialize fonts that will be used
    err = ttf.Init()
    if err != nil {
        return errors.Errorf("couldn't init TTF: %v", err)
    }
    defer ttf.Quit()

    // create a window
    w, r, err := sdl.CreateWindowAndRenderer(800, 600, sdl.WINDOW_SHOWN)
    if err != nil {
        return errors.Errorf("couldn't create window: %v", err)
    }
    defer r.Destroy()
    defer w.Destroy()

    // draw title
    err = drawTitle(r, "Flappy Gopher")
    if err != nil {
        return errors.Errorf("couldn't draw title: %v", err)
    }

    sdl.Delay(1000)

    // initialize new scene
    scene, err := newScene(r)
    if err != nil {
        return errors.Errorf("couldn't create scene: %v", err)
    }
    defer scene.destroy()

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

        // problem code
    runtime.LockOSThread()
    errc := scene.run(ctx, r)
    for {
        select {
        case err := <-errc:
            return err
        case <-time.After(5 * time.Second):
            return nil
        }
    }
}

func drawTitle(r *sdl.Renderer, title string) error {
    r.Clear()

    // open and load font
    font, err := ttf.OpenFont(pathToFonts, 20)
    if err != nil {
        return errors.Errorf("couldn't open font: %v", err)
    }
    defer font.Close()

    // render title
    c := sdl.Color{R: 255, G: 100, B: 0, A: 255}
    surface, err := font.RenderUTF8Solid(title, c)
    if err != nil {
        return errors.Errorf("couldn't render title")
    }
    defer surface.Free()

    // create texture from rendered title
    texture, err := r.CreateTextureFromSurface(surface)
    if err != nil {
        return errors.Errorf("couldn't create font texture: %v", err)
    }
    defer texture.Destroy()

    err = r.Copy(texture, nil, nil)
    if err != nil {
        return errors.Errorf("coldn't copy font texture: %v", err)
    }

    r.Present()

    return nil
}

scene.go

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/pkg/errors"
    "github.com/veandco/go-sdl2/img"
    "github.com/veandco/go-sdl2/sdl"
)

type scene struct {
    time int

    bg    *sdl.Texture
    birds []*sdl.Texture
}

func newScene(r *sdl.Renderer) (*scene, error) {
    // open and load background image
    bg, err := img.LoadTexture(r, pathToBgImage)
    if err != nil {
        return nil, errors.Errorf("couldn't load background image: %v", err)
    }

    // open and load bird images
    var birds []*sdl.Texture
    for i := 1; i <= 4; i++ {
        path := fmt.Sprintf(pathToBirdImage, i)

        bird, err := img.LoadTexture(r, path)
        if err != nil {
            return nil, errors.Errorf("couldn't load bird image: %v", err)
        }

        birds = append(birds, bird)
    }

    return &scene{bg: bg, birds: birds}, nil
}

// problem method
func (s *scene) run(ctx context.Context, r *sdl.Renderer) <-chan error {
    errc := make(chan error)

    go func() {
        defer close(errc)
        tick := time.Tick(10 * time.Millisecond)
        for {
            select {
            case <-ctx.Done():
                return
            case <-tick:
                if err := s.paint(r); err != nil {
                    errc <- err
                }
            }

        }
    }()

    return errc
}

func (s *scene) paint(r *sdl.Renderer) error {
    s.time++
    r.Clear()

    // copy backround image
    err := r.Copy(s.bg, nil, nil)
    if err != nil {
        return errors.Errorf("couldn't copy background image: %v", err)
    }

    // place bird in the middle of window
    // and draw different bird images every second
    rect := &sdl.Rect{W: 50, H: 43, X: 10, Y: 300 - 43/2}
    i := s.time / 10 % len(s.birds)
    err = r.Copy(s.birds[i], nil, rect)
    if err != nil {
        return errors.Errorf("couldn't copy bird image: %v", err)
    }

    r.Present()

    return nil
}

func (s *scene) destroy() {
    s.bg.Destroy()

    for _, v := range s.birds {
        v.Destroy()
    }
}

Note: I tried to use directly scene.paint() method and it draws background and bird successfully, but when it's running inside scene.run() method, it basically shows black screen.

bejaneps commented 3 years ago

Up

veeableful commented 3 years ago

Hi @bejaneps, sorry for the wait. Could you post a minimal example that reproduces the issue? I don't have all the assets so it would be very helpful if you can share a tiny project that requires no assets and reproduces the same issue so I can see what's going on.

Noofbiz commented 3 years ago

I'm not sure if SDL supports using Renderer.Paint off the main thread. The only things I'm seeing that allow you to do so is via OpenGL contexts, not directly using the Renderer. Here and here are folks doing it with an OpenGL context, but I haven't seen anything using the built in Renderer