mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
parent
1b011e3864
commit
5027bc1af5
73
image.go
73
image.go
@ -40,8 +40,6 @@ type Image struct {
|
|||||||
bounds image.Rectangle
|
bounds image.Rectangle
|
||||||
original *Image
|
original *Image
|
||||||
|
|
||||||
pendingPixels []byte
|
|
||||||
|
|
||||||
filter Filter
|
filter Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +90,6 @@ func (i *Image) Fill(clr color.Color) error {
|
|||||||
panic("ebiten: render to a subimage is not implemented (Fill)")
|
panic("ebiten: render to a subimage is not implemented (Fill)")
|
||||||
}
|
}
|
||||||
|
|
||||||
i.invalidatePendingPixels()
|
|
||||||
|
|
||||||
i.mipmap.fill(color.RGBAModel.Convert(clr).(color.RGBA))
|
i.mipmap.fill(color.RGBAModel.Convert(clr).(color.RGBA))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -150,9 +146,6 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
|
|||||||
panic("ebiten: render to a subimage is not implemented (drawImage)")
|
panic("ebiten: render to a subimage is not implemented (drawImage)")
|
||||||
}
|
}
|
||||||
|
|
||||||
img.resolvePendingPixels(true)
|
|
||||||
i.resolvePendingPixels(false)
|
|
||||||
|
|
||||||
// Calculate vertices before locking because the user can do anything in
|
// Calculate vertices before locking because the user can do anything in
|
||||||
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
||||||
if options == nil {
|
if options == nil {
|
||||||
@ -293,9 +286,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
|||||||
panic("ebiten: render to a subimage is not implemented (DrawTriangles)")
|
panic("ebiten: render to a subimage is not implemented (DrawTriangles)")
|
||||||
}
|
}
|
||||||
|
|
||||||
img.resolvePendingPixels(true)
|
|
||||||
i.resolvePendingPixels(false)
|
|
||||||
|
|
||||||
if len(indices)%3 != 0 {
|
if len(indices)%3 != 0 {
|
||||||
panic("ebiten: len(indices) % 3 must be 0")
|
panic("ebiten: len(indices) % 3 must be 0")
|
||||||
}
|
}
|
||||||
@ -390,8 +380,6 @@ func (i *Image) At(x, y int) color.Color {
|
|||||||
if i.isSubImage() && !image.Pt(x, y).In(i.bounds) {
|
if i.isSubImage() && !image.Pt(x, y).In(i.bounds) {
|
||||||
return color.RGBA{}
|
return color.RGBA{}
|
||||||
}
|
}
|
||||||
// TODO: Use pending pixels
|
|
||||||
i.resolvePendingPixels(true)
|
|
||||||
r, g, b, a := i.mipmap.at(x, y)
|
r, g, b, a := i.mipmap.at(x, y)
|
||||||
return color.RGBA{r, g, b, a}
|
return color.RGBA{r, g, b, a}
|
||||||
}
|
}
|
||||||
@ -400,66 +388,23 @@ func (i *Image) At(x, y int) color.Color {
|
|||||||
//
|
//
|
||||||
// Set loads pixels from GPU to system memory if necessary, which means that Set can be slow.
|
// Set loads pixels from GPU to system memory if necessary, which means that Set can be slow.
|
||||||
//
|
//
|
||||||
// Set can't be called outside the main loop (ebiten.Run's updating function) starts.
|
// In the current implementation, successive calls of Set invokes loading pixels at most once, so this is efficient.
|
||||||
//
|
//
|
||||||
// If the image is disposed, Set does nothing.
|
// If the image is disposed, Set does nothing.
|
||||||
func (img *Image) Set(x, y int, clr color.Color) {
|
func (i *Image) Set(x, y int, clr color.Color) {
|
||||||
img.copyCheck()
|
i.copyCheck()
|
||||||
if img.isDisposed() {
|
if i.isDisposed() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !image.Pt(x, y).In(img.Bounds()) {
|
if !image.Pt(x, y).In(i.Bounds()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if img.isSubImage() {
|
if i.isSubImage() {
|
||||||
img = img.original
|
i = i.original
|
||||||
}
|
}
|
||||||
|
|
||||||
w, h := img.Size()
|
|
||||||
if img.pendingPixels == nil {
|
|
||||||
pix := make([]byte, 4*w*h)
|
|
||||||
idx := 0
|
|
||||||
for j := 0; j < h; j++ {
|
|
||||||
for i := 0; i < w; i++ {
|
|
||||||
r, g, b, a := img.mipmap.at(i, j)
|
|
||||||
pix[4*idx] = r
|
|
||||||
pix[4*idx+1] = g
|
|
||||||
pix[4*idx+2] = b
|
|
||||||
pix[4*idx+3] = a
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
img.pendingPixels = pix
|
|
||||||
}
|
|
||||||
r, g, b, a := clr.RGBA()
|
r, g, b, a := clr.RGBA()
|
||||||
img.pendingPixels[4*(x+y*w)] = byte(r >> 8)
|
i.mipmap.set(x, y, byte(r>>8), byte(g>>8), byte(b>>8), byte(a>>8))
|
||||||
img.pendingPixels[4*(x+y*w)+1] = byte(g >> 8)
|
|
||||||
img.pendingPixels[4*(x+y*w)+2] = byte(b >> 8)
|
|
||||||
img.pendingPixels[4*(x+y*w)+3] = byte(a >> 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) invalidatePendingPixels() {
|
|
||||||
if i.isSubImage() {
|
|
||||||
i.original.invalidatePendingPixels()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i.pendingPixels = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
|
|
||||||
if i.isSubImage() {
|
|
||||||
i.original.resolvePendingPixels(keepPendingPixels)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.pendingPixels == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i.mipmap.replacePixels(i.pendingPixels)
|
|
||||||
if !keepPendingPixels {
|
|
||||||
i.pendingPixels = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose disposes the image data. After disposing, most of image functions do nothing and returns meaningless values.
|
// Dispose disposes the image data. After disposing, most of image functions do nothing and returns meaningless values.
|
||||||
@ -480,7 +425,6 @@ func (i *Image) Dispose() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
i.mipmap.dispose()
|
i.mipmap.dispose()
|
||||||
i.invalidatePendingPixels()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +449,6 @@ func (i *Image) ReplacePixels(p []byte) error {
|
|||||||
if i.isSubImage() {
|
if i.isSubImage() {
|
||||||
panic("ebiten: render to a subimage is not implemented (ReplacePixels)")
|
panic("ebiten: render to a subimage is not implemented (ReplacePixels)")
|
||||||
}
|
}
|
||||||
i.invalidatePendingPixels()
|
|
||||||
s := i.Bounds().Size()
|
s := i.Bounds().Size()
|
||||||
if l := 4 * s.X * s.Y; len(p) != l {
|
if l := 4 * s.X * s.Y; len(p) != l {
|
||||||
panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l))
|
panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l))
|
||||||
|
@ -24,6 +24,10 @@ import (
|
|||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
img *shareable.Image
|
img *shareable.Image
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
|
||||||
|
pendingPixels []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeginFrame() error {
|
func BeginFrame() error {
|
||||||
@ -44,6 +48,8 @@ func NewImage(width, height int, volatile bool) *Image {
|
|||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() {
|
||||||
i.img = shareable.NewImage(width, height, volatile)
|
i.img = shareable.NewImage(width, height, volatile)
|
||||||
|
i.width = width
|
||||||
|
i.height = height
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return i
|
return i
|
||||||
@ -51,6 +57,8 @@ func NewImage(width, height int, volatile bool) *Image {
|
|||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
i.img = shareable.NewImage(width, height, volatile)
|
i.img = shareable.NewImage(width, height, volatile)
|
||||||
|
i.width = width
|
||||||
|
i.height = height
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +68,8 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() {
|
||||||
i.img = shareable.NewScreenFramebufferImage(width, height)
|
i.img = shareable.NewScreenFramebufferImage(width, height)
|
||||||
|
i.width = width
|
||||||
|
i.height = height
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return i
|
return i
|
||||||
@ -67,17 +77,38 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
i.img = shareable.NewScreenFramebufferImage(width, height)
|
i.img = shareable.NewScreenFramebufferImage(width, height)
|
||||||
|
i.width = width
|
||||||
|
i.height = height
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Image) invalidatePendingPixels() {
|
||||||
|
i.pendingPixels = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
|
||||||
|
if i.pendingPixels == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.img.ReplacePixels(i.pendingPixels)
|
||||||
|
if !keepPendingPixels {
|
||||||
|
i.pendingPixels = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Image) MarkDisposed() {
|
func (i *Image) MarkDisposed() {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() {
|
||||||
i.img.MarkDisposed()
|
i.img.MarkDisposed()
|
||||||
})
|
})
|
||||||
|
delayedCommandsM.Unlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
|
i.invalidatePendingPixels()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) At(x, y int) (r, g, b, a byte) {
|
func (i *Image) At(x, y int) (r, g, b, a byte) {
|
||||||
@ -86,9 +117,48 @@ func (i *Image) At(x, y int) (r, g, b, a byte) {
|
|||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
panic("buffered: the command queue is not available yet at At")
|
panic("buffered: the command queue is not available yet at At")
|
||||||
}
|
}
|
||||||
|
// TODO: Use pending pixels
|
||||||
|
i.resolvePendingPixels(true)
|
||||||
return i.img.At(x, y)
|
return i.img.At(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Image) Set(x, y int, r, g, b, a byte) {
|
||||||
|
delayedCommandsM.Lock()
|
||||||
|
if needsToDelayCommands {
|
||||||
|
delayedCommands = append(delayedCommands, func() {
|
||||||
|
i.set(x, y, r, g, b, a)
|
||||||
|
})
|
||||||
|
delayedCommandsM.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
|
i.set(x, y, r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) set(x, y int, r, g, b, a byte) {
|
||||||
|
w, h := img.width, img.height
|
||||||
|
if img.pendingPixels == nil {
|
||||||
|
pix := make([]byte, 4*w*h)
|
||||||
|
idx := 0
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
r, g, b, a := img.img.At(i, j)
|
||||||
|
pix[4*idx] = r
|
||||||
|
pix[4*idx+1] = g
|
||||||
|
pix[4*idx+2] = b
|
||||||
|
pix[4*idx+3] = a
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.pendingPixels = pix
|
||||||
|
}
|
||||||
|
img.pendingPixels[4*(x+y*w)] = r
|
||||||
|
img.pendingPixels[4*(x+y*w)+1] = g
|
||||||
|
img.pendingPixels[4*(x+y*w)+2] = b
|
||||||
|
img.pendingPixels[4*(x+y*w)+3] = a
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Image) Dump(name string) error {
|
func (i *Image) Dump(name string) error {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
defer delayedCommandsM.Unlock()
|
defer delayedCommandsM.Unlock()
|
||||||
@ -109,6 +179,7 @@ func (i *Image) Fill(clr color.RGBA) {
|
|||||||
}
|
}
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
|
i.invalidatePendingPixels()
|
||||||
i.img.Fill(clr)
|
i.img.Fill(clr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +208,7 @@ func (i *Image) ReplacePixels(pix []byte) {
|
|||||||
}
|
}
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
|
i.invalidatePendingPixels()
|
||||||
i.img.ReplacePixels(pix)
|
i.img.ReplacePixels(pix)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,5 +227,7 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
|||||||
}
|
}
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
|
|
||||||
|
src.resolvePendingPixels(true)
|
||||||
|
i.resolvePendingPixels(false)
|
||||||
i.img.DrawTriangles(src.img, vertices, indices, colorm, mode, filter, address)
|
i.img.DrawTriangles(src.img, vertices, indices, colorm, mode, filter, address)
|
||||||
}
|
}
|
||||||
|
72
internal/buffered/image_test.go
Normal file
72
internal/buffered/image_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2019 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package buffered_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainCh = make(chan func())
|
||||||
|
|
||||||
|
func runOnMainThread(f func()) {
|
||||||
|
ch := make(chan struct{})
|
||||||
|
mainCh <- func() {
|
||||||
|
f()
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
go func() {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-mainCh:
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetBeforeRun(t *testing.T) {
|
||||||
|
clr := color.RGBA{1, 2, 3, 4}
|
||||||
|
|
||||||
|
img, _ := ebiten.NewImage(16, 16, ebiten.FilterDefault)
|
||||||
|
img.Set(0, 0, clr)
|
||||||
|
|
||||||
|
want := clr
|
||||||
|
var got color.RGBA
|
||||||
|
|
||||||
|
runOnMainThread(func() {
|
||||||
|
quit := errors.New("quit")
|
||||||
|
if err := ebiten.Run(func(*ebiten.Image) error {
|
||||||
|
got = img.At(0, 0).(color.RGBA)
|
||||||
|
return quit
|
||||||
|
}, 320, 240, 1, ""); err != nil && err != quit {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
@ -77,6 +77,10 @@ func (m *mipmap) at(x, y int) (r, g, b, a byte) {
|
|||||||
return m.orig.At(x, y)
|
return m.orig.At(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mipmap) set(x, y int, r, g, b, a byte) {
|
||||||
|
m.orig.Set(x, y, r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
|
func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
|
||||||
if det := geom.det(); det == 0 {
|
if det := geom.det(); det == 0 {
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user