mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
parent
1b011e3864
commit
5027bc1af5
73
image.go
73
image.go
@ -40,8 +40,6 @@ type Image struct {
|
||||
bounds image.Rectangle
|
||||
original *Image
|
||||
|
||||
pendingPixels []byte
|
||||
|
||||
filter Filter
|
||||
}
|
||||
|
||||
@ -92,8 +90,6 @@ func (i *Image) Fill(clr color.Color) error {
|
||||
panic("ebiten: render to a subimage is not implemented (Fill)")
|
||||
}
|
||||
|
||||
i.invalidatePendingPixels()
|
||||
|
||||
i.mipmap.fill(color.RGBAModel.Convert(clr).(color.RGBA))
|
||||
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)")
|
||||
}
|
||||
|
||||
img.resolvePendingPixels(true)
|
||||
i.resolvePendingPixels(false)
|
||||
|
||||
// Calculate vertices before locking because the user can do anything in
|
||||
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
||||
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)")
|
||||
}
|
||||
|
||||
img.resolvePendingPixels(true)
|
||||
i.resolvePendingPixels(false)
|
||||
|
||||
if len(indices)%3 != 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) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
// TODO: Use pending pixels
|
||||
i.resolvePendingPixels(true)
|
||||
r, g, b, a := i.mipmap.at(x, y)
|
||||
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 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.
|
||||
func (img *Image) Set(x, y int, clr color.Color) {
|
||||
img.copyCheck()
|
||||
if img.isDisposed() {
|
||||
func (i *Image) Set(x, y int, clr color.Color) {
|
||||
i.copyCheck()
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
if !image.Pt(x, y).In(img.Bounds()) {
|
||||
if !image.Pt(x, y).In(i.Bounds()) {
|
||||
return
|
||||
}
|
||||
if img.isSubImage() {
|
||||
img = img.original
|
||||
if i.isSubImage() {
|
||||
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()
|
||||
img.pendingPixels[4*(x+y*w)] = byte(r >> 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
|
||||
}
|
||||
i.mipmap.set(x, y, byte(r>>8), byte(g>>8), byte(b>>8), byte(a>>8))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
i.mipmap.dispose()
|
||||
i.invalidatePendingPixels()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -505,7 +449,6 @@ func (i *Image) ReplacePixels(p []byte) error {
|
||||
if i.isSubImage() {
|
||||
panic("ebiten: render to a subimage is not implemented (ReplacePixels)")
|
||||
}
|
||||
i.invalidatePendingPixels()
|
||||
s := i.Bounds().Size()
|
||||
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))
|
||||
|
@ -24,6 +24,10 @@ import (
|
||||
|
||||
type Image struct {
|
||||
img *shareable.Image
|
||||
width int
|
||||
height int
|
||||
|
||||
pendingPixels []byte
|
||||
}
|
||||
|
||||
func BeginFrame() error {
|
||||
@ -44,6 +48,8 @@ func NewImage(width, height int, volatile bool) *Image {
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
i.img = shareable.NewImage(width, height, volatile)
|
||||
i.width = width
|
||||
i.height = height
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return i
|
||||
@ -51,6 +57,8 @@ func NewImage(width, height int, volatile bool) *Image {
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.img = shareable.NewImage(width, height, volatile)
|
||||
i.width = width
|
||||
i.height = height
|
||||
return i
|
||||
}
|
||||
|
||||
@ -60,6 +68,8 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
i.img = shareable.NewScreenFramebufferImage(width, height)
|
||||
i.width = width
|
||||
i.height = height
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return i
|
||||
@ -67,17 +77,38 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.img = shareable.NewScreenFramebufferImage(width, height)
|
||||
i.width = width
|
||||
i.height = height
|
||||
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() {
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
i.img.MarkDisposed()
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
}
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.invalidatePendingPixels()
|
||||
}
|
||||
|
||||
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 {
|
||||
panic("buffered: the command queue is not available yet at At")
|
||||
}
|
||||
// TODO: Use pending pixels
|
||||
i.resolvePendingPixels(true)
|
||||
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 {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
@ -109,6 +179,7 @@ func (i *Image) Fill(clr color.RGBA) {
|
||||
}
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.invalidatePendingPixels()
|
||||
i.img.Fill(clr)
|
||||
}
|
||||
|
||||
@ -137,6 +208,7 @@ func (i *Image) ReplacePixels(pix []byte) {
|
||||
}
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.invalidatePendingPixels()
|
||||
i.img.ReplacePixels(pix)
|
||||
}
|
||||
|
||||
@ -155,5 +227,7 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
}
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
src.resolvePendingPixels(true)
|
||||
i.resolvePendingPixels(false)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
if det := geom.det(); det == 0 {
|
||||
return
|
||||
|
Loading…
Reference in New Issue
Block a user