graphics: Make Image functions concurrent safe (#201)

This commit is contained in:
Hajime Hoshi 2016-04-07 03:49:11 +09:00
parent bcf406f058
commit c3d8cf2366
2 changed files with 60 additions and 29 deletions

View File

@ -76,8 +76,20 @@ func (c *graphicsContext) setSize(screenWidth, screenHeight, screenScale int) er
if err != nil { if err != nil {
return err return err
} }
screen := &Image{framebuffer: screenF, texture: texture} w, h := screenF.Size()
c.defaultRenderTarget = &Image{framebuffer: f, texture: nil} screen := &Image{
framebuffer: screenF,
texture: texture,
width: w,
height: h,
}
w, h = f.Size()
c.defaultRenderTarget = &Image{
framebuffer: f,
texture: nil,
width: w,
height: h,
}
c.defaultRenderTarget.Clear() c.defaultRenderTarget.Clear()
c.screen = screen c.screen = screen
c.screenScale = screenScale c.screenScale = screenScale

View File

@ -26,7 +26,7 @@ import (
"github.com/hajimehoshi/ebiten/internal/graphics/opengl" "github.com/hajimehoshi/ebiten/internal/graphics/opengl"
) )
var imageM sync.RWMutex var imageM sync.Mutex
// Image represents an image. // Image represents an image.
// The pixel format is alpha-premultiplied. // The pixel format is alpha-premultiplied.
@ -43,24 +43,30 @@ type Image struct {
// Size returns the size of the image. // Size returns the size of the image.
func (i *Image) Size() (width, height int) { func (i *Image) Size() (width, height int) {
if i.width == 0 {
i.width, i.height = i.framebuffer.Size()
}
return i.width, i.height return i.width, i.height
} }
// Clear resets the pixels of the image into 0. // Clear resets the pixels of the image into 0.
func (i *Image) Clear() (err error) { func (i *Image) Clear() (err error) {
if i.isDisposed() { imageM.Lock()
return errors.New("image is already disposed") defer imageM.Unlock()
} return i.clear()
return i.Fill(color.Transparent) }
func (i *Image) clear() (err error) {
return i.fill(color.Transparent)
} }
// Fill fills the image with a solid color. // Fill fills the image with a solid color.
func (i *Image) Fill(clr color.Color) (err error) { func (i *Image) Fill(clr color.Color) (err error) {
imageM.Lock()
defer imageM.Unlock()
return i.fill(clr)
}
func (i *Image) fill(clr color.Color) (err error) {
if i.isDisposed() { if i.isDisposed() {
return errors.New("image is already disposed") return errors.New("ebiten: image is already disposed")
} }
i.pixels = nil i.pixels = nil
return i.framebuffer.Fill(glContext, clr) return i.framebuffer.Fill(glContext, clr)
@ -82,11 +88,13 @@ func (i *Image) Fill(clr color.Color) (err error) {
// Be careful that this method is potentially slow. // Be careful that this method is potentially slow.
// It would be better if you could call this method fewer times. // It would be better if you could call this method fewer times.
func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) { func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
imageM.Lock()
defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return errors.New("image is already disposed") return errors.New("ebiten: image is already disposed")
} }
if i == image { if i == image {
return errors.New("Image.DrawImage: image should be different from the receiver") return errors.New("ebiten: Image.DrawImage: image should be different from the receiver")
} }
i.pixels = nil i.pixels = nil
if options == nil { if options == nil {
@ -99,20 +107,17 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
if dparts != nil { if dparts != nil {
parts = imageParts(dparts) parts = imageParts(dparts)
} else { } else {
w, h := image.Size() parts = &wholeImage{image.width, image.height}
parts = &wholeImage{w, h}
} }
} }
w, h := image.Size() quads := &textureQuads{parts: parts, width: image.width, height: image.height}
quads := &textureQuads{parts: parts, width: w, height: h}
m := opengl.CompositeMode(options.CompositeMode) m := opengl.CompositeMode(options.CompositeMode)
return i.framebuffer.DrawTexture(glContext, image.texture, quads, &options.GeoM, &options.ColorM, m) return i.framebuffer.DrawTexture(glContext, image.texture, quads, &options.GeoM, &options.ColorM, m)
} }
// Bounds returns the bounds of the image. // Bounds returns the bounds of the image.
func (i *Image) Bounds() image.Rectangle { func (i *Image) Bounds() image.Rectangle {
w, h := i.Size() return image.Rect(0, 0, i.width, i.height)
return image.Rect(0, 0, w, h)
} }
// ColorModel returns the color model of the image. // ColorModel returns the color model of the image.
@ -124,6 +129,9 @@ func (i *Image) ColorModel() color.Model {
// //
// This method loads pixels from VRAM to system memory if necessary. // This method loads pixels from VRAM to system memory if necessary.
func (i *Image) At(x, y int) color.Color { func (i *Image) At(x, y int) color.Color {
// TODO: What if At is called internaly (like from image parts?)
imageM.Lock()
defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return color.Transparent return color.Transparent
} }
@ -134,8 +142,7 @@ func (i *Image) At(x, y int) color.Color {
panic(err) panic(err)
} }
} }
w, _ := i.Size() w := int(graphics.NextPowerOf2Int32(int32(i.width)))
w = int(graphics.NextPowerOf2Int32(int32(w)))
idx := 4*x + 4*y*w idx := 4*x + 4*y*w
r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3] r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3]
return color.RGBA{r, g, b, a} return color.RGBA{r, g, b, a}
@ -146,8 +153,10 @@ func (i *Image) At(x, y int) color.Color {
// //
// The behavior of any functions for a disposed image is undefined. // The behavior of any functions for a disposed image is undefined.
func (i *Image) Dispose() error { func (i *Image) Dispose() error {
imageM.Lock()
defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return errors.New("image is already disposed") return errors.New("ebiten: image is already disposed")
} }
if i.framebuffer != nil { if i.framebuffer != nil {
i.framebuffer.Dispose(glContext) i.framebuffer.Dispose(glContext)
@ -178,14 +187,13 @@ func (i *Image) ReplacePixels(p []uint8) error {
imageM.Lock() imageM.Lock()
defer imageM.Unlock() defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return errors.New("image is already disposed") return errors.New("ebiten: image is already disposed")
} }
// Don't set i.pixels here because i.pixels is used not every time. // Don't set i.pixels here because i.pixels is used not every time.
i.pixels = nil i.pixels = nil
w, h := i.Size() l := 4 * i.width * i.height
l := 4 * w * h
if len(p) != l { if len(p) != l {
return errors.New(fmt.Sprintf("p's length must be %d", l)) return fmt.Errorf("ebiten: p's length must be %d", l)
} }
return i.framebuffer.ReplacePixels(glContext, i.texture, p) return i.framebuffer.ReplacePixels(glContext, i.texture, p)
} }
@ -222,9 +230,14 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
// TODO: texture should be removed here? // TODO: texture should be removed here?
return nil, err return nil, err
} }
img := &Image{framebuffer: framebuffer, texture: texture} img := &Image{
framebuffer: framebuffer,
texture: texture,
width: width,
height: height,
}
runtime.SetFinalizer(img, (*Image).Dispose) runtime.SetFinalizer(img, (*Image).Dispose)
if err := img.Clear(); err != nil { if err := img.clear(); err != nil {
return nil, err return nil, err
} }
return img, nil return img, nil
@ -251,7 +264,13 @@ func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
// TODO: texture should be removed here? // TODO: texture should be removed here?
return nil, err return nil, err
} }
eimg := &Image{framebuffer: framebuffer, texture: texture} w, h := framebuffer.Size()
eimg := &Image{
framebuffer: framebuffer,
texture: texture,
width: w,
height: h,
}
runtime.SetFinalizer(eimg, (*Image).Dispose) runtime.SetFinalizer(eimg, (*Image).Dispose)
return eimg, nil return eimg, nil
} }