mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
graphics: Add buffered package
Moved the command queue to the package.
This commit is contained in:
parent
0a872b342a
commit
79b32c7601
112
image.go
112
image.go
@ -18,63 +18,11 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/driver"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||
)
|
||||
|
||||
var (
|
||||
// imageQueue represents a queue for image operations that are ordered before the game starts (BeginFrame).
|
||||
// Before the game starts, the package shareable doesn't determine the minimum/maximum texture sizes (#879).
|
||||
// Instead of accessing the package shareable, defer the image operations until the game starts (#921).
|
||||
imageQueue []func()
|
||||
imageQueueM sync.Mutex
|
||||
needsEnqueueImageOps = true
|
||||
)
|
||||
|
||||
func checkNeedsEnqueueImageOp(location string) {
|
||||
imageQueueM.Lock()
|
||||
defer imageQueueM.Unlock()
|
||||
|
||||
if needsEnqueueImageOps {
|
||||
panic(fmt.Sprintf("ebiten: %s is not available before the game starts", location))
|
||||
}
|
||||
}
|
||||
|
||||
func enqueueImageOpIfNeeded(f func() func()) bool {
|
||||
imageQueueM.Lock()
|
||||
defer imageQueueM.Unlock()
|
||||
|
||||
if !needsEnqueueImageOps {
|
||||
return false
|
||||
}
|
||||
imageQueue = append(imageQueue, f())
|
||||
return true
|
||||
}
|
||||
|
||||
func flushImageOpsIfNeeded() {
|
||||
imageQueueM.Lock()
|
||||
|
||||
if !needsEnqueueImageOps {
|
||||
if len(imageQueue) > 0 {
|
||||
panic("ebiten: len(imageQueue) must be 0 after the game starts")
|
||||
}
|
||||
imageQueueM.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Set this flag false first, or the image operations will be queued again.
|
||||
needsEnqueueImageOps = false
|
||||
imageQueueM.Unlock()
|
||||
|
||||
// As a new item will not be enqueued any longer, mutex does not have to, or should not be used.
|
||||
for _, f := range imageQueue {
|
||||
f()
|
||||
}
|
||||
imageQueue = nil
|
||||
}
|
||||
|
||||
// Image represents a rectangle set of pixels.
|
||||
// The pixel format is alpha-premultiplied RGBA.
|
||||
// Image implements image.Image and draw.Image.
|
||||
@ -135,15 +83,6 @@ func (i *Image) Clear() error {
|
||||
func (i *Image) Fill(clr color.Color) error {
|
||||
i.copyCheck()
|
||||
|
||||
rgba := color.RGBAModel.Convert(clr).(color.RGBA)
|
||||
if enqueueImageOpIfNeeded(func() func() {
|
||||
return func() {
|
||||
i.Fill(rgba)
|
||||
}
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -155,7 +94,7 @@ func (i *Image) Fill(clr color.Color) error {
|
||||
|
||||
i.resolvePendingPixels(false)
|
||||
|
||||
i.mipmap.fill(rgba)
|
||||
i.mipmap.fill(color.RGBAModel.Convert(clr).(color.RGBA))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -199,15 +138,6 @@ func (i *Image) Fill(clr color.Color) error {
|
||||
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
|
||||
i.copyCheck()
|
||||
|
||||
if enqueueImageOpIfNeeded(func() func() {
|
||||
op := *options
|
||||
return func() {
|
||||
i.DrawImage(img, &op)
|
||||
}
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if img.isDisposed() {
|
||||
panic("ebiten: the given image to DrawImage must not be disposed")
|
||||
}
|
||||
@ -355,19 +285,6 @@ const MaxIndicesNum = graphics.IndicesNum
|
||||
func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) {
|
||||
i.copyCheck()
|
||||
|
||||
if enqueueImageOpIfNeeded(func() func() {
|
||||
vs := make([]Vertex, len(vertices))
|
||||
copy(vs, vertices)
|
||||
is := make([]uint16, len(indices))
|
||||
copy(is, indices)
|
||||
op := *options
|
||||
return func() {
|
||||
i.DrawTriangles(vs, is, img, &op)
|
||||
}
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
@ -467,8 +384,6 @@ func (i *Image) ColorModel() color.Model {
|
||||
//
|
||||
// At can't be called outside the main loop (ebiten.Run's updating function) starts (as of version 1.4.0-alpha).
|
||||
func (i *Image) At(x, y int) color.Color {
|
||||
checkNeedsEnqueueImageOp("(*Image).At")
|
||||
|
||||
if i.isDisposed() {
|
||||
return color.RGBA{}
|
||||
}
|
||||
@ -488,8 +403,6 @@ func (i *Image) At(x, y int) color.Color {
|
||||
//
|
||||
// If the image is disposed, Set does nothing.
|
||||
func (img *Image) Set(x, y int, clr color.Color) {
|
||||
checkNeedsEnqueueImageOp("(*Image).Set")
|
||||
|
||||
img.copyCheck()
|
||||
if img.isDisposed() {
|
||||
return
|
||||
@ -554,14 +467,6 @@ func (i *Image) resolvePendingPixels(draw bool) {
|
||||
func (i *Image) Dispose() error {
|
||||
i.copyCheck()
|
||||
|
||||
if enqueueImageOpIfNeeded(func() func() {
|
||||
return func() {
|
||||
i.Dispose()
|
||||
}
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -587,16 +492,6 @@ func (i *Image) Dispose() error {
|
||||
func (i *Image) ReplacePixels(p []byte) error {
|
||||
i.copyCheck()
|
||||
|
||||
if enqueueImageOpIfNeeded(func() func() {
|
||||
px := make([]byte, len(p))
|
||||
copy(px, p)
|
||||
return func() {
|
||||
i.ReplacePixels(px)
|
||||
}
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -609,7 +504,10 @@ func (i *Image) ReplacePixels(p []byte) error {
|
||||
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))
|
||||
}
|
||||
i.mipmap.replacePixels(p)
|
||||
|
||||
px := make([]byte, len(p))
|
||||
copy(px, p)
|
||||
i.mipmap.replacePixels(px)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
64
internal/buffered/command.go
Normal file
64
internal/buffered/command.go
Normal file
@ -0,0 +1,64 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type command struct {
|
||||
f func()
|
||||
}
|
||||
|
||||
var (
|
||||
delayedCommandsFlushable bool
|
||||
|
||||
// delayedCommands represents a queue for image operations that are ordered before the game starts
|
||||
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
||||
// sizes (#879).
|
||||
//
|
||||
// TODO: Flush the commands only when necessary (#921).
|
||||
delayedCommands []*command
|
||||
delayedCommandsM sync.Mutex
|
||||
)
|
||||
|
||||
func makeDelayedCommandFlushable() {
|
||||
delayedCommandsM.Lock()
|
||||
delayedCommandsFlushable = true
|
||||
delayedCommandsM.Unlock()
|
||||
}
|
||||
|
||||
func enqueueDelayedCommand(f func()) {
|
||||
delayedCommandsM.Lock()
|
||||
delayedCommands = append(delayedCommands, &command{
|
||||
f: f,
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
}
|
||||
|
||||
func flushDelayedCommands() bool {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
|
||||
if !delayedCommandsFlushable {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range delayedCommands {
|
||||
c.f()
|
||||
}
|
||||
delayedCommands = nil
|
||||
return true
|
||||
}
|
106
internal/buffered/image.go
Normal file
106
internal/buffered/image.go
Normal file
@ -0,0 +1,106 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/affine"
|
||||
"github.com/hajimehoshi/ebiten/internal/driver"
|
||||
"github.com/hajimehoshi/ebiten/internal/shareable"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
img *shareable.Image
|
||||
}
|
||||
|
||||
func BeginFrame() error {
|
||||
if err := shareable.BeginFrame(); err != nil {
|
||||
return err
|
||||
}
|
||||
makeDelayedCommandFlushable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EndFrame() error {
|
||||
if !flushDelayedCommands() {
|
||||
panic("buffered: the command queue must be available at EndFrame")
|
||||
}
|
||||
return shareable.EndFrame()
|
||||
}
|
||||
|
||||
func NewImage(width, height int, volatile bool) *Image {
|
||||
i := &Image{}
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img = shareable.NewImage(width, height, volatile)
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
func NewScreenFramebufferImage(width, height int) *Image {
|
||||
i := &Image{}
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img = shareable.NewScreenFramebufferImage(width, height)
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Image) MarkDisposed() {
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img.MarkDisposed()
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Image) At(x, y int) (r, g, b, a byte) {
|
||||
if !flushDelayedCommands() {
|
||||
panic("buffered: the command queue is not available yet at At")
|
||||
}
|
||||
return i.img.At(x, y)
|
||||
}
|
||||
|
||||
func (i *Image) Dump(name string) error {
|
||||
if !flushDelayedCommands() {
|
||||
panic("buffered: the command queue is not available yet at Dump")
|
||||
}
|
||||
return i.img.Dump(name)
|
||||
}
|
||||
|
||||
func (i *Image) Fill(clr color.RGBA) {
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img.Fill(clr)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Image) ClearFramebuffer() {
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img.ClearFramebuffer()
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Image) ReplacePixels(pix []byte) {
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img.ReplacePixels(pix)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) {
|
||||
if i == src {
|
||||
panic("buffered: Image.DrawTriangles: src must be different from the receiver")
|
||||
}
|
||||
enqueueDelayedCommand(func() {
|
||||
i.img.DrawTriangles(src.img, vertices, indices, colorm, mode, filter, address)
|
||||
})
|
||||
}
|
24
mipmap.go
24
mipmap.go
@ -21,18 +21,18 @@ import (
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/affine"
|
||||
"github.com/hajimehoshi/ebiten/internal/buffered"
|
||||
"github.com/hajimehoshi/ebiten/internal/driver"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/internal/shareable"
|
||||
)
|
||||
|
||||
type levelToImage map[int]*shareable.Image
|
||||
type levelToImage map[int]*buffered.Image
|
||||
|
||||
type mipmap struct {
|
||||
width int
|
||||
height int
|
||||
volatile bool
|
||||
orig *shareable.Image
|
||||
orig *buffered.Image
|
||||
imgs map[image.Rectangle]levelToImage
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ func newMipmap(width, height int, volatile bool) *mipmap {
|
||||
width: width,
|
||||
height: height,
|
||||
volatile: volatile,
|
||||
orig: shareable.NewImage(width, height, volatile),
|
||||
orig: buffered.NewImage(width, height, volatile),
|
||||
imgs: map[image.Rectangle]levelToImage{},
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ func newScreenFramebufferMipmap(width, height int) *mipmap {
|
||||
return &mipmap{
|
||||
width: width,
|
||||
height: height,
|
||||
orig: shareable.NewScreenFramebufferImage(width, height),
|
||||
orig: buffered.NewScreenFramebufferImage(width, height),
|
||||
imgs: map[image.Rectangle]levelToImage{},
|
||||
}
|
||||
}
|
||||
@ -126,7 +126,7 @@ func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colo
|
||||
vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca)
|
||||
is := graphics.QuadIndices()
|
||||
m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero)
|
||||
} else if shared := src.level(bounds, level); shared != nil {
|
||||
} else if buf := src.level(bounds, level); buf != nil {
|
||||
w, h := sizeForLevel(bounds.Dx(), bounds.Dy(), level)
|
||||
s := pow2(level)
|
||||
a *= s
|
||||
@ -135,7 +135,7 @@ func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colo
|
||||
d *= s
|
||||
vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca)
|
||||
is := graphics.QuadIndices()
|
||||
m.orig.DrawTriangles(shared, vs, is, colorm, mode, filter, driver.AddressClampToZero)
|
||||
m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero)
|
||||
}
|
||||
m.disposeMipmaps()
|
||||
}
|
||||
@ -164,11 +164,13 @@ func (m *mipmap) drawTriangles(src *mipmap, bounds image.Rectangle, vertices []V
|
||||
vs[i*graphics.VertexFloatNum+10] = v.ColorB
|
||||
vs[i*graphics.VertexFloatNum+11] = v.ColorA
|
||||
}
|
||||
m.orig.DrawTriangles(src.orig, vs, indices, colorm, mode, filter, address)
|
||||
is := make([]uint16, len(indices))
|
||||
copy(is, indices)
|
||||
m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, address)
|
||||
m.disposeMipmaps()
|
||||
}
|
||||
|
||||
func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image {
|
||||
func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image {
|
||||
if level == 0 {
|
||||
panic("ebiten: level must be non-zero at level")
|
||||
}
|
||||
@ -186,7 +188,7 @@ func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image {
|
||||
return img
|
||||
}
|
||||
|
||||
var src *shareable.Image
|
||||
var src *buffered.Image
|
||||
var vs []float32
|
||||
var filter driver.Filter
|
||||
switch {
|
||||
@ -226,7 +228,7 @@ func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image {
|
||||
imgs[level] = nil
|
||||
return nil
|
||||
}
|
||||
s := shareable.NewImage(w2, h2, m.volatile)
|
||||
s := buffered.NewImage(w2, h2, m.volatile)
|
||||
s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero)
|
||||
imgs[level] = s
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/buffered"
|
||||
"github.com/hajimehoshi/ebiten/internal/clock"
|
||||
"github.com/hajimehoshi/ebiten/internal/driver"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
|
||||
@ -77,13 +78,10 @@ func (c *uiContext) Update(afterFrameUpdate func()) error {
|
||||
|
||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
||||
|
||||
if err := shareable.BeginFrame(); err != nil {
|
||||
if err := buffered.BeginFrame(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Images are available after shareable is initialized.
|
||||
flushImageOpsIfNeeded()
|
||||
|
||||
for i := 0; i < updateCount; i++ {
|
||||
c.offscreen.Clear()
|
||||
// Mipmap images should be disposed by fill.
|
||||
@ -129,7 +127,7 @@ func (c *uiContext) Update(afterFrameUpdate func()) error {
|
||||
}
|
||||
_ = c.screen.DrawImage(c.offscreen, op)
|
||||
|
||||
if err := shareable.EndFrame(); err != nil {
|
||||
if err := buffered.EndFrame(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
Loading…
Reference in New Issue
Block a user