packing: Add Extend

This commit is contained in:
Hajime Hoshi 2018-03-07 02:18:08 +09:00
parent 8ac2ebde1e
commit 1dd32066d2
3 changed files with 250 additions and 83 deletions

View File

@ -16,16 +16,20 @@
package packing
import (
"errors"
"github.com/hajimehoshi/ebiten/internal/sync"
)
const (
MaxSize = 2048
minSize = 1
initSize = 1024
MaxSize = 4096
minSize = 1
)
type Page struct {
root *Node
size int
m sync.Mutex
}
@ -138,15 +142,30 @@ func (p *Page) alloc(n *Node, width, height int) *Node {
return nil
}
func (p *Page) ensureSize() {
if p.size == 0 {
p.size = initSize
}
}
func (p *Page) Size() int {
p.m.Lock()
p.ensureSize()
s := p.size
p.m.Unlock()
return s
}
func (p *Page) Alloc(width, height int) *Node {
p.m.Lock()
if width <= 0 || height <= 0 {
panic("bsp: width and height must > 0")
}
p.ensureSize()
if p.root == nil {
p.root = &Node{
width: MaxSize,
height: MaxSize,
width: p.size,
height: p.size,
}
}
if width < minSize {
@ -183,3 +202,93 @@ func (p *Page) free(node *Node) {
p.free(node.parent)
}
}
func walk(n *Node, f func(n *Node) error) error {
if err := f(n); err != nil {
return err
}
if n.child0 != nil {
if err := walk(n.child0, f); err != nil {
return err
}
}
if n.child1 != nil {
if err := walk(n.child1, f); err != nil {
return err
}
}
return nil
}
func (p *Page) Extend() bool {
p.m.Lock()
defer p.m.Unlock()
p.ensureSize()
if p.size >= MaxSize {
return false
}
newSize := p.size * 2
edgeNodes := []*Node{}
abort := errors.New("abort")
aborted := false
_ = walk(p.root, func(n *Node) error {
if n.x+n.width < p.size && n.y+n.height < p.size {
return nil
}
if n.used {
aborted = true
return abort
}
edgeNodes = append(edgeNodes, n)
return nil
})
if aborted {
leftUpper := p.root
leftLower := &Node{
x: 0,
y: p.size,
width: p.size,
height: newSize - p.size,
}
left := &Node{
x: 0,
y: 0,
width: p.size,
height: p.size,
child0: leftUpper,
child1: leftLower,
}
leftUpper.parent = left
leftLower.parent = left
right := &Node{
x: p.size,
y: 0,
width: newSize - p.size,
height: newSize,
}
p.root = &Node{
x: 0,
y: 0,
width: newSize,
height: newSize,
child0: left,
child1: right,
}
left.parent = p.root
right.parent = p.root
} else {
for _, n := range edgeNodes {
if n.x+n.width == p.size {
n.width += newSize - p.size
}
if n.y+n.height == p.size {
n.height += newSize - p.size
}
}
}
p.size = newSize
return true
}

View File

@ -52,7 +52,7 @@ func TestPage(t *testing.T) {
{0, 0, 0},
{0, 0, 2},
{0, 0, 4},
{MaxSize, MaxSize, -1},
{1024, 1024, -1},
},
Out: []*Rect{
{0, 0, 100, 100},
@ -67,108 +67,108 @@ func TestPage(t *testing.T) {
nil,
nil,
nil,
{0, 0, MaxSize, MaxSize},
{0, 0, 1024, 1024},
},
},
{
In: []Op{
{200, 400, -1},
{MaxSize, MaxSize, -1},
{200, 400, -1},
{100, 100, -1},
{400, 400, -1},
{MaxSize, MaxSize, -1},
{1000, 1000, -1},
{1200, 1200, -1},
{100, 200, -1},
{1024, 1024, -1},
{100, 200, -1},
{50, 50, -1},
{200, 200, -1},
{1024, 1024, -1},
{500, 500, -1},
{600, 600, -1},
{100, 100, -1},
{0, 0, 2},
{200, 400, -1},
{100, 200, -1},
},
Out: []*Rect{
{0, 0, 200, 400},
{0, 0, 100, 200},
nil,
{0, 400, 200, 400},
{0, 800, 100, 100},
{200, 0, 400, 400},
{0, 200, 100, 200},
{0, 400, 50, 50},
{100, 0, 200, 200},
nil,
{200, 400, 1000, 1000},
{100, 200, 500, 500},
nil,
{0, 900, 200, 200},
{0, 450, 100, 100},
nil,
{0, 400, 200, 400},
{0, 200, 100, 200},
},
},
{
In: []Op{
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{512, 512, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{256, 256, -1},
{512, 512, -1},
{256, 256, -1},
},
Out: []*Rect{
{0, 0, 512, 512},
{0, 512, 512, 512},
{0, 1024, 512, 512},
{0, 1536, 512, 512},
{0, 0, 256, 256},
{0, 256, 256, 256},
{0, 512, 256, 256},
{0, 768, 256, 256},
{512, 0, 512, 512},
{1024, 0, 512, 512},
{1536, 0, 512, 512},
{512, 512, 512, 512},
{256, 0, 256, 256},
{512, 0, 256, 256},
{768, 0, 256, 256},
{256, 256, 256, 256},
{512, 1024, 512, 512},
{512, 1536, 512, 512},
{1024, 512, 512, 512},
{1536, 512, 512, 512},
{256, 512, 256, 256},
{256, 768, 256, 256},
{512, 256, 256, 256},
{768, 256, 256, 256},
{1024, 1024, 512, 512},
{1024, 1536, 512, 512},
{1536, 1024, 512, 512},
{1536, 1536, 512, 512},
{512, 512, 256, 256},
{512, 768, 256, 256},
{768, 512, 256, 256},
{768, 768, 256, 256},
nil,
},
},
{
In: []Op{
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{600, 600, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
{300, 300, -1},
},
Out: []*Rect{
{0, 0, 600, 600},
{0, 600, 600, 600},
{0, 1200, 600, 600},
{600, 0, 600, 600},
{1200, 0, 600, 600},
{600, 600, 600, 600},
{600, 1200, 600, 600},
{1200, 600, 600, 600},
{1200, 1200, 600, 600},
{0, 0, 300, 300},
{0, 300, 300, 300},
{0, 600, 300, 300},
{300, 0, 300, 300},
{600, 0, 300, 300},
{300, 300, 300, 300},
{300, 600, 300, 300},
{600, 300, 300, 300},
{600, 600, 300, 300},
nil,
},
},
@ -206,3 +206,40 @@ func TestPage(t *testing.T) {
}
}
}
func TestExtend(t *testing.T) {
p := &Page{}
s := p.Size()
p.Alloc(s/2, s/2)
p.Extend()
if p.Alloc(s*3/2, s*2) == nil {
t.Fail()
}
if p.Alloc(s/2, s*3/2) == nil {
t.Fail()
}
if p.Alloc(1, 1) != nil {
t.Fail()
}
}
func TestExtend2(t *testing.T) {
p := &Page{}
s := p.Size()
p.Alloc(s/2, s/2)
n1 := p.Alloc(s/2, s/2)
n2 := p.Alloc(s/2, s/2)
p.Alloc(s/2, s/2)
p.Free(n1)
p.Free(n2)
p.Extend()
if p.Alloc(s, s*2) == nil {
t.Fail()
}
if p.Alloc(s, s) == nil {
t.Fail()
}
if p.Alloc(s, s) != nil {
t.Fail()
}
}

View File

@ -15,6 +15,8 @@
package ebiten
import (
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/packing"
"github.com/hajimehoshi/ebiten/internal/restorable"
"github.com/hajimehoshi/ebiten/internal/sync"
@ -71,22 +73,41 @@ func newSharedImagePart(width, height int) *sharedImagePart {
return nil
}
for _, s := range theSharedImages {
if n := s.page.Alloc(width, height); n != nil {
return &sharedImagePart{
sharedImage: s,
node: n,
for {
if n := s.page.Alloc(width, height); n != nil {
return &sharedImagePart{
sharedImage: s,
node: n,
}
}
if !s.page.Extend() {
break
}
newSharedImage := restorable.NewImage(s.page.Size(), s.page.Size(), false)
newSharedImage.DrawImage(s.restorable, 0, 0, s.page.Size(), s.page.Size(), nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
s.restorable.Dispose()
s.restorable = newSharedImage
}
}
s := &sharedImage{
restorable: restorable.NewImage(packing.MaxSize, packing.MaxSize, false),
}
theSharedImages = append(theSharedImages, s)
n := s.page.Alloc(width, height)
s := &sharedImage{}
var n *packing.Node
for {
n = s.page.Alloc(width, height)
if n != nil {
break
}
if !s.page.Extend() {
break
}
}
if n == nil {
panic("not reached")
}
s.restorable = restorable.NewImage(s.page.Size(), s.page.Size(), false)
theSharedImages = append(theSharedImages, s)
return &sharedImagePart{
sharedImage: s,
node: n,