mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
internal/atlas: introduce a managed byte slice pool
A managed byte slice from the new byte slice pool has a function to release and put it back to the pool explicitly, and this doesn't rely on GCs. Updates #1681 Closes #2804
This commit is contained in:
parent
34d577a5ff
commit
f269b61903
@ -482,39 +482,38 @@ func (i *Image) writePixels(pix []byte, region image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy pixels in the case when pix is modified before the graphics command is executed.
|
// Copy pixels in the case when pix is modified before the graphics command is executed.
|
||||||
// TODO: Create byte slices from a pool.
|
pix2 := graphics.NewManagedBytes(len(pix), func(bs []byte) {
|
||||||
pix2 := make([]byte, len(pix))
|
copy(bs, pix)
|
||||||
copy(pix2, pix)
|
})
|
||||||
i.backend.restorable.WritePixels(pix2, region)
|
i.backend.restorable.WritePixels(pix2, region)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Create byte slices from a pool.
|
|
||||||
pixb := make([]byte, 4*r.Dx()*r.Dy())
|
|
||||||
|
|
||||||
// Clear the edges. pixb might not be zero-cleared.
|
|
||||||
// TODO: These loops assume that paddingSize is 1.
|
// TODO: These loops assume that paddingSize is 1.
|
||||||
// TODO: Is clearing edges explicitly really needed?
|
// TODO: Is clearing edges explicitly really needed?
|
||||||
const paddingSize = 1
|
const paddingSize = 1
|
||||||
if paddingSize != i.paddingSize() {
|
if paddingSize != i.paddingSize() {
|
||||||
panic(fmt.Sprintf("atlas: writePixels assumes the padding is always 1 but the actual padding was %d", i.paddingSize()))
|
panic(fmt.Sprintf("atlas: writePixels assumes the padding is always 1 but the actual padding was %d", i.paddingSize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pixb := graphics.NewManagedBytes(4*r.Dx()*r.Dy(), func(bs []byte) {
|
||||||
|
// Clear the edges. bs might not be zero-cleared.
|
||||||
rowPixels := 4 * r.Dx()
|
rowPixels := 4 * r.Dx()
|
||||||
for i := 0; i < rowPixels; i++ {
|
for i := 0; i < rowPixels; i++ {
|
||||||
pixb[rowPixels*(r.Dy()-1)+i] = 0
|
bs[rowPixels*(r.Dy()-1)+i] = 0
|
||||||
}
|
}
|
||||||
for j := 1; j < r.Dy(); j++ {
|
for j := 1; j < r.Dy(); j++ {
|
||||||
pixb[rowPixels*j-4] = 0
|
bs[rowPixels*j-4] = 0
|
||||||
pixb[rowPixels*j-3] = 0
|
bs[rowPixels*j-3] = 0
|
||||||
pixb[rowPixels*j-2] = 0
|
bs[rowPixels*j-2] = 0
|
||||||
pixb[rowPixels*j-1] = 0
|
bs[rowPixels*j-1] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the content.
|
// Copy the content.
|
||||||
for j := 0; j < region.Dy(); j++ {
|
for j := 0; j < region.Dy(); j++ {
|
||||||
copy(pixb[4*j*r.Dx():], pix[4*j*region.Dx():4*(j+1)*region.Dx()])
|
copy(bs[4*j*r.Dx():], pix[4*j*region.Dx():4*(j+1)*region.Dx()])
|
||||||
}
|
}
|
||||||
|
})
|
||||||
i.backend.restorable.WritePixels(pixb, r)
|
i.backend.restorable.WritePixels(pixb, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
134
internal/graphics/bytes.go
Normal file
134
internal/graphics/bytes.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2023 The Ebitengine 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 graphics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManagedBytes is a managed byte slice.
|
||||||
|
// The internal byte alice are managed in a pool.
|
||||||
|
// ManagedBytes is useful when its lifetime is explicit, as the underlying byte slice can be reused for another ManagedBytes later.
|
||||||
|
// This can redduce allocations and GCs.
|
||||||
|
type ManagedBytes struct {
|
||||||
|
bytes []byte
|
||||||
|
pool *bytesPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the slice.
|
||||||
|
func (m *ManagedBytes) Len() int {
|
||||||
|
return len(m.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads the byte slice's content to dst.
|
||||||
|
func (m *ManagedBytes) Read(dst []byte, from, to int) {
|
||||||
|
copy(dst, m.bytes[from:to])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a new ManagedBytes with the same content.
|
||||||
|
func (m *ManagedBytes) Clone() *ManagedBytes {
|
||||||
|
return NewManagedBytes(len(m.bytes), func(bs []byte) {
|
||||||
|
copy(bs, m.bytes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAndRelease returns the raw byte slice and a finalizer.
|
||||||
|
// A finalizer should be called when you can ensure that the slice is no longer used,
|
||||||
|
// e.g. when a graphics command using this slice is sent and executed.
|
||||||
|
//
|
||||||
|
// After GetAndRelease is called, the underlying byte slice is no longer available.
|
||||||
|
func (m *ManagedBytes) GetAndRelease() ([]byte, func()) {
|
||||||
|
bs := m.bytes
|
||||||
|
m.bytes = nil
|
||||||
|
return bs, func() {
|
||||||
|
m.pool.put(bs)
|
||||||
|
runtime.SetFinalizer(m, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManagedBytes returns a managed byte slice initialized by the given constructor f.
|
||||||
|
//
|
||||||
|
// The byte slice is not zero-cleared at the constructor.
|
||||||
|
func NewManagedBytes(size int, f func([]byte)) *ManagedBytes {
|
||||||
|
bs := theBytesPool.get(size)
|
||||||
|
f(bs.bytes)
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesPool struct {
|
||||||
|
pool [][]byte
|
||||||
|
|
||||||
|
m sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var theBytesPool bytesPool
|
||||||
|
|
||||||
|
func (b *bytesPool) get(size int) *ManagedBytes {
|
||||||
|
bs := b.getFromCache(size)
|
||||||
|
if bs == nil {
|
||||||
|
bs = make([]byte, size)
|
||||||
|
}
|
||||||
|
m := &ManagedBytes{
|
||||||
|
bytes: bs,
|
||||||
|
pool: b,
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(m, func(m *ManagedBytes) {
|
||||||
|
b.put(m.bytes)
|
||||||
|
})
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bytesPool) getFromCache(size int) []byte {
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
|
||||||
|
for i, bs := range b.pool {
|
||||||
|
if cap(bs) < size {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(b.pool[i:], b.pool[i+1:])
|
||||||
|
b.pool[len(b.pool)-1] = nil
|
||||||
|
b.pool = b.pool[:len(b.pool)-1]
|
||||||
|
return bs[:size]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bytesPool) put(bs []byte) {
|
||||||
|
if len(bs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.m.Lock()
|
||||||
|
defer b.m.Unlock()
|
||||||
|
|
||||||
|
b.pool = append(b.pool, bs)
|
||||||
|
|
||||||
|
// GC the pool. The size limitation is arbitrary.
|
||||||
|
for len(b.pool) >= 32 || b.totalSize() >= 1024*1024*1024 {
|
||||||
|
b.pool = b.pool[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bytesPool) totalSize() int {
|
||||||
|
var s int
|
||||||
|
for _, bs := range b.pool {
|
||||||
|
s += len(bs)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
@ -16,6 +16,7 @@ package graphicscommand
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ import (
|
|||||||
type command interface {
|
type command interface {
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
|
|
||||||
Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error
|
Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error
|
||||||
NeedsSync() bool
|
NeedsSync() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ func (c *drawTrianglesCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the drawTrianglesCommand.
|
// Exec executes the drawTrianglesCommand.
|
||||||
func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *drawTrianglesCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
// TODO: Is it ok not to bind any framebuffer here?
|
// TODO: Is it ok not to bind any framebuffer here?
|
||||||
if len(c.dstRegions) == 0 {
|
if len(c.dstRegions) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -211,7 +212,12 @@ func mightOverlapDstRegions(vertices1, vertices2 []float32) bool {
|
|||||||
// writePixelsCommand represents a command to replace pixels of an image.
|
// writePixelsCommand represents a command to replace pixels of an image.
|
||||||
type writePixelsCommand struct {
|
type writePixelsCommand struct {
|
||||||
dst *Image
|
dst *Image
|
||||||
args []graphicsdriver.PixelsArgs
|
args []writePixelsCommandArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
type writePixelsCommandArgs struct {
|
||||||
|
pixels *graphics.ManagedBytes
|
||||||
|
region image.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *writePixelsCommand) String() string {
|
func (c *writePixelsCommand) String() string {
|
||||||
@ -219,11 +225,24 @@ func (c *writePixelsCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the writePixelsCommand.
|
// Exec executes the writePixelsCommand.
|
||||||
func (c *writePixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *writePixelsCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
if len(c.args) == 0 {
|
if len(c.args) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := c.dst.image.WritePixels(c.args); err != nil {
|
args := make([]graphicsdriver.PixelsArgs, 0, len(c.args))
|
||||||
|
for _, a := range c.args {
|
||||||
|
pix, f := a.pixels.GetAndRelease()
|
||||||
|
// A finalizer is executed when flushing the queue at the end of the frame.
|
||||||
|
// At the end of the frame, the last command is rendering triangles onto the screen,
|
||||||
|
// so the bytes are already sent to GPU and synced.
|
||||||
|
// TODO: This might be fragile. When is the better time to call finalizers by a command queue?
|
||||||
|
commandQueue.addFinalizer(f)
|
||||||
|
args = append(args, graphicsdriver.PixelsArgs{
|
||||||
|
Pixels: pix,
|
||||||
|
Region: a.region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := c.dst.image.WritePixels(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -239,7 +258,7 @@ type readPixelsCommand struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes a readPixelsCommand.
|
// Exec executes a readPixelsCommand.
|
||||||
func (c *readPixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *readPixelsCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
if err := c.img.image.ReadPixels(c.args); err != nil {
|
if err := c.img.image.ReadPixels(c.args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -264,7 +283,7 @@ func (c *disposeImageCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the disposeImageCommand.
|
// Exec executes the disposeImageCommand.
|
||||||
func (c *disposeImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *disposeImageCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
c.target.image.Dispose()
|
c.target.image.Dispose()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -283,7 +302,7 @@ func (c *disposeShaderCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the disposeShaderCommand.
|
// Exec executes the disposeShaderCommand.
|
||||||
func (c *disposeShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *disposeShaderCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
c.target.shader.Dispose()
|
c.target.shader.Dispose()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -305,7 +324,7 @@ func (c *newImageCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes a newImageCommand.
|
// Exec executes a newImageCommand.
|
||||||
func (c *newImageCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *newImageCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
var err error
|
var err error
|
||||||
if c.screen {
|
if c.screen {
|
||||||
c.result.image, err = graphicsDriver.NewScreenFramebufferImage(c.width, c.height)
|
c.result.image, err = graphicsDriver.NewScreenFramebufferImage(c.width, c.height)
|
||||||
@ -330,7 +349,7 @@ func (c *newShaderCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes a newShaderCommand.
|
// Exec executes a newShaderCommand.
|
||||||
func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *newShaderCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
s, err := graphicsDriver.NewShader(c.ir)
|
s, err := graphicsDriver.NewShader(c.ir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -352,7 +371,7 @@ func (c *isInvalidatedCommand) String() string {
|
|||||||
return fmt.Sprintf("is-invalidated: image: %d", c.image.id)
|
return fmt.Sprintf("is-invalidated: image: %d", c.image.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *isInvalidatedCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
func (c *isInvalidatedCommand) Exec(commandQueue *commandQueue, graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
|
||||||
c.result = c.image.image.IsInvalidated()
|
c.result = c.image.image.IsInvalidated()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,17 @@ type commandQueue struct {
|
|||||||
drawTrianglesCommandPool drawTrianglesCommandPool
|
drawTrianglesCommandPool drawTrianglesCommandPool
|
||||||
|
|
||||||
uint32sBuffer uint32sBuffer
|
uint32sBuffer uint32sBuffer
|
||||||
|
finalizers []func()
|
||||||
|
|
||||||
err atomic.Value
|
err atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addFinalizer adds a finalizer function to this queue.
|
||||||
|
// A finalizer is executed when the command queue is flushed at the end of the frame.
|
||||||
|
func (q *commandQueue) addFinalizer(f func()) {
|
||||||
|
q.finalizers = append(q.finalizers, f)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
|
func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
|
||||||
n := len(q.indices)
|
n := len(q.indices)
|
||||||
q.indices = append(q.indices, indices...)
|
q.indices = append(q.indices, indices...)
|
||||||
@ -220,6 +227,11 @@ func (q *commandQueue) flush(graphicsDriver graphicsdriver.Graphics, endFrame bo
|
|||||||
|
|
||||||
if endFrame {
|
if endFrame {
|
||||||
q.uint32sBuffer.reset()
|
q.uint32sBuffer.reset()
|
||||||
|
for i, f := range q.finalizers {
|
||||||
|
f()
|
||||||
|
q.finalizers[i] = nil
|
||||||
|
}
|
||||||
|
q.finalizers = q.finalizers[:0]
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -247,7 +259,7 @@ func (q *commandQueue) flush(graphicsDriver graphicsdriver.Graphics, endFrame bo
|
|||||||
}
|
}
|
||||||
indexOffset := 0
|
indexOffset := 0
|
||||||
for _, c := range cs[:nc] {
|
for _, c := range cs[:nc] {
|
||||||
if err := c.Exec(graphicsDriver, indexOffset); err != nil {
|
if err := c.Exec(q, graphicsDriver, indexOffset); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Logf(" %s\n", c)
|
logger.Logf(" %s\n", c)
|
||||||
|
@ -43,7 +43,7 @@ type Image struct {
|
|||||||
// have its graphicsdriver.Image.
|
// have its graphicsdriver.Image.
|
||||||
id int
|
id int
|
||||||
|
|
||||||
bufferedWritePixelsArgs []graphicsdriver.PixelsArgs
|
bufferedWritePixelsArgs []writePixelsCommandArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextID = 1
|
var nextID = 1
|
||||||
@ -78,6 +78,7 @@ func (i *Image) flushBufferedWritePixels() {
|
|||||||
if len(i.bufferedWritePixelsArgs) == 0 {
|
if len(i.bufferedWritePixelsArgs) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &writePixelsCommand{
|
c := &writePixelsCommand{
|
||||||
dst: i,
|
dst: i,
|
||||||
args: i.bufferedWritePixelsArgs,
|
args: i.bufferedWritePixelsArgs,
|
||||||
@ -159,10 +160,10 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, args []graphi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
|
func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangle) {
|
||||||
i.bufferedWritePixelsArgs = append(i.bufferedWritePixelsArgs, graphicsdriver.PixelsArgs{
|
i.bufferedWritePixelsArgs = append(i.bufferedWritePixelsArgs, writePixelsCommandArgs{
|
||||||
Pixels: pixels,
|
pixels: pixels,
|
||||||
Region: region,
|
region: region,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,12 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) {
|
|||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]image.Rectangle{}, nearestFilterShader, nil, false)
|
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]image.Rectangle{}, nearestFilterShader, nil, false)
|
||||||
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, nearestFilterShader, nil, false)
|
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, nearestFilterShader, nil, false)
|
||||||
dst.WritePixels(make([]byte, 4), image.Rect(0, 0, 1, 1))
|
bs := graphics.NewManagedBytes(4, func(bs []byte) {
|
||||||
|
for i := range bs {
|
||||||
|
bs[i] = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dst.WritePixels(bs, image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
// TODO: Check the result.
|
// TODO: Check the result.
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ func (p *Pixels) Apply(img *graphicscommand.Image) {
|
|||||||
p.pixelsRecords.apply(img)
|
p.pixelsRecords.apply(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pixels) AddOrReplace(pix []byte, region image.Rectangle) {
|
func (p *Pixels) AddOrReplace(pix *graphics.ManagedBytes, region image.Rectangle) {
|
||||||
if p.pixelsRecords == nil {
|
if p.pixelsRecords == nil {
|
||||||
p.pixelsRecords = &pixelsRecords{}
|
p.pixelsRecords = &pixelsRecords{}
|
||||||
}
|
}
|
||||||
@ -70,6 +70,13 @@ func (p *Pixels) AppendRegion(regions []image.Rectangle) []image.Rectangle {
|
|||||||
return p.pixelsRecords.appendRegions(regions)
|
return p.pixelsRecords.appendRegions(regions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pixels) Dispose() {
|
||||||
|
if p.pixelsRecords == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.pixelsRecords.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
// drawTrianglesHistoryItem is an item for history of draw-image commands.
|
// drawTrianglesHistoryItem is an item for history of draw-image commands.
|
||||||
type drawTrianglesHistoryItem struct {
|
type drawTrianglesHistoryItem struct {
|
||||||
images [graphics.ShaderImageCount]*Image
|
images [graphics.ShaderImageCount]*Image
|
||||||
@ -251,7 +258,7 @@ func (i *Image) needsRestoring() bool {
|
|||||||
// WritePixels replaces the image pixels with the given pixels slice.
|
// WritePixels replaces the image pixels with the given pixels slice.
|
||||||
//
|
//
|
||||||
// The specified region must not be overlapped with other regions by WritePixels.
|
// The specified region must not be overlapped with other regions by WritePixels.
|
||||||
func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
|
func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangle) {
|
||||||
if region.Dx() <= 0 || region.Dy() <= 0 {
|
if region.Dx() <= 0 || region.Dy() <= 0 {
|
||||||
panic("restorable: width/height must be positive")
|
panic("restorable: width/height must be positive")
|
||||||
}
|
}
|
||||||
@ -278,7 +285,8 @@ func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
|
|||||||
|
|
||||||
if region.Eq(image.Rect(0, 0, w, h)) {
|
if region.Eq(image.Rect(0, 0, w, h)) {
|
||||||
if pixels != nil {
|
if pixels != nil {
|
||||||
i.basePixels.AddOrReplace(pixels, image.Rect(0, 0, w, h))
|
// Clone a ManagedBytes as the package graphicscommand has a different lifetime management.
|
||||||
|
i.basePixels.AddOrReplace(pixels.Clone(), image.Rect(0, 0, w, h))
|
||||||
} else {
|
} else {
|
||||||
i.basePixels.Clear(image.Rect(0, 0, w, h))
|
i.basePixels.Clear(image.Rect(0, 0, w, h))
|
||||||
}
|
}
|
||||||
@ -295,7 +303,8 @@ func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pixels != nil {
|
if pixels != nil {
|
||||||
i.basePixels.AddOrReplace(pixels, region)
|
// Clone a ManagedBytes as the package graphicscommand has a different lifetime management.
|
||||||
|
i.basePixels.AddOrReplace(pixels.Clone(), region)
|
||||||
} else {
|
} else {
|
||||||
i.basePixels.Clear(region)
|
i.basePixels.Clear(region)
|
||||||
}
|
}
|
||||||
@ -483,7 +492,10 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
i.basePixels.AddOrReplace(a.Pixels, a.Region)
|
bs := graphics.NewManagedBytes(len(a.Pixels), func(bs []byte) {
|
||||||
|
copy(bs, a.Pixels)
|
||||||
|
})
|
||||||
|
i.basePixels.AddOrReplace(bs, a.Region)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.clearDrawTrianglesHistory()
|
i.clearDrawTrianglesHistory()
|
||||||
@ -563,6 +575,7 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
|
|||||||
// The screen image should also be recreated because framebuffer might
|
// The screen image should also be recreated because framebuffer might
|
||||||
// be changed.
|
// be changed.
|
||||||
i.image = graphicscommand.NewImage(w, h, true)
|
i.image = graphicscommand.NewImage(w, h, true)
|
||||||
|
i.basePixels.Dispose()
|
||||||
i.basePixels = Pixels{}
|
i.basePixels = Pixels{}
|
||||||
i.clearDrawTrianglesHistory()
|
i.clearDrawTrianglesHistory()
|
||||||
i.stale = false
|
i.stale = false
|
||||||
@ -633,7 +646,10 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
i.basePixels.AddOrReplace(a.Pixels, a.Region)
|
bs := graphics.NewManagedBytes(len(a.Pixels), func(bs []byte) {
|
||||||
|
copy(bs, a.Pixels)
|
||||||
|
})
|
||||||
|
i.basePixels.AddOrReplace(bs, a.Region)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,6 +667,7 @@ func (i *Image) Dispose() {
|
|||||||
theImages.remove(i)
|
theImages.remove(i)
|
||||||
i.image.Dispose()
|
i.image.Dispose()
|
||||||
i.image = nil
|
i.image = nil
|
||||||
|
i.basePixels.Dispose()
|
||||||
i.basePixels = Pixels{}
|
i.basePixels = Pixels{}
|
||||||
i.pixelsCache = nil
|
i.pixelsCache = nil
|
||||||
i.clearDrawTrianglesHistory()
|
i.clearDrawTrianglesHistory()
|
||||||
|
@ -37,6 +37,15 @@ func pixelsToColor(p *restorable.Pixels, i, j, imageWidth, imageHeight int) colo
|
|||||||
return color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]}
|
return color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bytesToManagedBytes(src []byte) *graphics.ManagedBytes {
|
||||||
|
if len(src) == 0 {
|
||||||
|
panic("restorable: len(src) must be > 0")
|
||||||
|
}
|
||||||
|
return graphics.NewManagedBytes(len(src), func(dst []byte) {
|
||||||
|
copy(dst, src)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func abs(x int) int {
|
func abs(x int) int {
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
return -x
|
return -x
|
||||||
@ -61,7 +70,7 @@ func TestRestore(t *testing.T) {
|
|||||||
defer img0.Dispose()
|
defer img0.Dispose()
|
||||||
|
|
||||||
clr0 := color.RGBA{A: 0xff}
|
clr0 := color.RGBA{A: 0xff}
|
||||||
img0.WritePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, image.Rect(0, 0, 1, 1))
|
img0.WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, 1, 1))
|
||||||
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -129,7 +138,7 @@ func TestRestoreChain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
clr := color.RGBA{A: 0xff}
|
clr := color.RGBA{A: 0xff}
|
||||||
imgs[0].WritePixels([]byte{clr.R, clr.G, clr.B, clr.A}, image.Rect(0, 0, 1, 1))
|
imgs[0].WritePixels(bytesToManagedBytes([]byte{clr.R, clr.G, clr.B, clr.A}), image.Rect(0, 0, 1, 1))
|
||||||
for i := 0; i < num-1; i++ {
|
for i := 0; i < num-1; i++ {
|
||||||
vs := quadVertices(1, 1, 0, 0)
|
vs := quadVertices(1, 1, 0, 0)
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
@ -169,11 +178,11 @@ func TestRestoreChain2(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
clr0 := color.RGBA{R: 0xff, A: 0xff}
|
clr0 := color.RGBA{R: 0xff, A: 0xff}
|
||||||
imgs[0].WritePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, image.Rect(0, 0, w, h))
|
imgs[0].WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, w, h))
|
||||||
clr7 := color.RGBA{G: 0xff, A: 0xff}
|
clr7 := color.RGBA{G: 0xff, A: 0xff}
|
||||||
imgs[7].WritePixels([]byte{clr7.R, clr7.G, clr7.B, clr7.A}, image.Rect(0, 0, w, h))
|
imgs[7].WritePixels(bytesToManagedBytes([]byte{clr7.R, clr7.G, clr7.B, clr7.A}), image.Rect(0, 0, w, h))
|
||||||
clr8 := color.RGBA{B: 0xff, A: 0xff}
|
clr8 := color.RGBA{B: 0xff, A: 0xff}
|
||||||
imgs[8].WritePixels([]byte{clr8.R, clr8.G, clr8.B, clr8.A}, image.Rect(0, 0, w, h))
|
imgs[8].WritePixels(bytesToManagedBytes([]byte{clr8.R, clr8.G, clr8.B, clr8.A}), image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
@ -218,12 +227,12 @@ func TestRestoreOverrideSource(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
clr0 := color.RGBA{A: 0xff}
|
clr0 := color.RGBA{A: 0xff}
|
||||||
clr1 := color.RGBA{B: 0x01, A: 0xff}
|
clr1 := color.RGBA{B: 0x01, A: 0xff}
|
||||||
img1.WritePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, image.Rect(0, 0, w, h))
|
img1.WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, w, h))
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
img2.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
img2.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
img3.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
img3.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
img0.WritePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, image.Rect(0, 0, w, h))
|
img0.WritePixels(bytesToManagedBytes([]byte{clr1.R, clr1.G, clr1.B, clr1.A}), image.Rect(0, 0, w, h))
|
||||||
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -390,7 +399,7 @@ func TestRestoreComplexGraph(t *testing.T) {
|
|||||||
func newImageFromImage(rgba *image.RGBA) *restorable.Image {
|
func newImageFromImage(rgba *image.RGBA) *restorable.Image {
|
||||||
s := rgba.Bounds().Size()
|
s := rgba.Bounds().Size()
|
||||||
img := restorable.NewImage(s.X, s.Y, restorable.ImageTypeRegular)
|
img := restorable.NewImage(s.X, s.Y, restorable.ImageTypeRegular)
|
||||||
img.WritePixels(rgba.Pix, image.Rect(0, 0, s.X, s.Y))
|
img.WritePixels(bytesToManagedBytes(rgba.Pix), image.Rect(0, 0, s.X, s.Y))
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +468,7 @@ func TestWritePixels(t *testing.T) {
|
|||||||
for i := range pix {
|
for i := range pix {
|
||||||
pix[i] = 0xff
|
pix[i] = 0xff
|
||||||
}
|
}
|
||||||
img.WritePixels(pix, image.Rect(5, 7, 9, 11))
|
img.WritePixels(bytesToManagedBytes(pix), image.Rect(5, 7, 9, 11))
|
||||||
// Check the region (5, 7)-(9, 11). Outside state is indeterminate.
|
// Check the region (5, 7)-(9, 11). Outside state is indeterminate.
|
||||||
pix = make([]byte, 4*4*4)
|
pix = make([]byte, 4*4*4)
|
||||||
for i := range pix {
|
for i := range pix {
|
||||||
@ -514,7 +523,7 @@ func TestDrawTrianglesAndWritePixels(t *testing.T) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, 2, 1)
|
dr := image.Rect(0, 0, 2, 1)
|
||||||
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
img1.WritePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, image.Rect(0, 0, 2, 1))
|
img1.WritePixels(bytesToManagedBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), image.Rect(0, 0, 2, 1))
|
||||||
|
|
||||||
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -580,7 +589,7 @@ func TestWritePixelsPart(t *testing.T) {
|
|||||||
|
|
||||||
img := restorable.NewImage(4, 4, restorable.ImageTypeRegular)
|
img := restorable.NewImage(4, 4, restorable.ImageTypeRegular)
|
||||||
// This doesn't make the image stale. Its base pixels are available.
|
// This doesn't make the image stale. Its base pixels are available.
|
||||||
img.WritePixels(pix, image.Rect(1, 1, 3, 3))
|
img.WritePixels(bytesToManagedBytes(pix), image.Rect(1, 1, 3, 3))
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
i int
|
i int
|
||||||
@ -655,14 +664,14 @@ func TestWritePixelsOnly(t *testing.T) {
|
|||||||
defer img1.Dispose()
|
defer img1.Dispose()
|
||||||
|
|
||||||
for i := 0; i < w*h; i += 5 {
|
for i := 0; i < w*h; i += 5 {
|
||||||
img0.WritePixels([]byte{1, 2, 3, 4}, image.Rect(i%w, i/w, i%w+1, i/w+1))
|
img0.WritePixels(bytesToManagedBytes([]byte{1, 2, 3, 4}), image.Rect(i%w, i/w, i%w+1, i/w+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
vs := quadVertices(1, 1, 0, 0)
|
vs := quadVertices(1, 1, 0, 0)
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, 1, 1)
|
dr := image.Rect(0, 0, 1, 1)
|
||||||
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
img0.WritePixels([]byte{5, 6, 7, 8}, image.Rect(0, 0, 1, 1))
|
img0.WritePixels(bytesToManagedBytes([]byte{5, 6, 7, 8}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
// BasePixelsForTesting is available without GPU accessing.
|
// BasePixelsForTesting is available without GPU accessing.
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
@ -704,14 +713,14 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
|
|||||||
src := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
src := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||||
|
|
||||||
// First, make sure that dst has pixels
|
// First, make sure that dst has pixels
|
||||||
dst.WritePixels(make([]byte, 4*w*h), image.Rect(0, 0, w, h))
|
dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
// Second, draw src to dst. If the implementation is correct, dst becomes stale.
|
// Second, draw src to dst. If the implementation is correct, dst becomes stale.
|
||||||
pix := make([]byte, 4*w*h)
|
pix := make([]byte, 4*w*h)
|
||||||
for i := range pix {
|
for i := range pix {
|
||||||
pix[i] = 0xff
|
pix[i] = 0xff
|
||||||
}
|
}
|
||||||
src.WritePixels(pix, image.Rect(0, 0, w, h))
|
src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h))
|
||||||
vs := quadVertices(1, 1, 0, 0)
|
vs := quadVertices(1, 1, 0, 0)
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
@ -740,7 +749,7 @@ func TestAllowWritePixelsAfterDrawTriangles(t *testing.T) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
dst.WritePixels(make([]byte, 4*w*h), image.Rect(0, 0, w, h))
|
dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h))
|
||||||
// WritePixels for a whole image doesn't panic.
|
// WritePixels for a whole image doesn't panic.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -753,13 +762,13 @@ func TestAllowWritePixelsForPartAfterDrawTriangles(t *testing.T) {
|
|||||||
for i := range pix {
|
for i := range pix {
|
||||||
pix[i] = 0xff
|
pix[i] = 0xff
|
||||||
}
|
}
|
||||||
src.WritePixels(pix, image.Rect(0, 0, w, h))
|
src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
vs := quadVertices(w, h, 0, 0)
|
vs := quadVertices(w, h, 0, 0)
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dr := image.Rect(0, 0, w, h)
|
dr := image.Rect(0, 0, w, h)
|
||||||
dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, false)
|
||||||
dst.WritePixels(make([]byte, 4*2*2), image.Rect(0, 0, 2, 2))
|
dst.WritePixels(bytesToManagedBytes(make([]byte, 4*2*2)), image.Rect(0, 0, 2, 2))
|
||||||
// WritePixels for a part of image doesn't panic.
|
// WritePixels for a part of image doesn't panic.
|
||||||
|
|
||||||
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil {
|
||||||
@ -806,7 +815,7 @@ func TestExtend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orig.WritePixels(pix, image.Rect(0, 0, w, h))
|
orig.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h))
|
||||||
extended := orig.Extend(w*2, h*2) // After this, orig is already disposed.
|
extended := orig.Extend(w*2, h*2) // After this, orig is already disposed.
|
||||||
|
|
||||||
result := make([]byte, 4*(w*2)*(h*2))
|
result := make([]byte, 4*(w*2)*(h*2))
|
||||||
@ -846,7 +855,7 @@ func TestDrawTrianglesAndExtend(t *testing.T) {
|
|||||||
pix[4*idx+3] = v
|
pix[4*idx+3] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
src.WritePixels(pix, image.Rect(0, 0, w, h))
|
src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
orig := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
orig := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||||
vs := quadVertices(w, h, 0, 0)
|
vs := quadVertices(w, h, 0, 0)
|
||||||
@ -876,13 +885,13 @@ func TestDrawTrianglesAndExtend(t *testing.T) {
|
|||||||
func TestClearPixels(t *testing.T) {
|
func TestClearPixels(t *testing.T) {
|
||||||
const w, h = 16, 16
|
const w, h = 16, 16
|
||||||
img := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
img := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||||
img.WritePixels(make([]byte, 4*4*4), image.Rect(0, 0, 4, 4))
|
img.WritePixels(bytesToManagedBytes(make([]byte, 4*4*4)), image.Rect(0, 0, 4, 4))
|
||||||
img.WritePixels(make([]byte, 4*4*4), image.Rect(4, 0, 8, 4))
|
img.WritePixels(bytesToManagedBytes(make([]byte, 4*4*4)), image.Rect(4, 0, 8, 4))
|
||||||
img.ClearPixels(image.Rect(0, 0, 4, 4))
|
img.ClearPixels(image.Rect(0, 0, 4, 4))
|
||||||
img.ClearPixels(image.Rect(4, 0, 8, 4))
|
img.ClearPixels(image.Rect(4, 0, 8, 4))
|
||||||
|
|
||||||
// After clearing, the regions will be available again.
|
// After clearing, the regions will be available again.
|
||||||
img.WritePixels(make([]byte, 4*8*4), image.Rect(0, 0, 8, 4))
|
img.WritePixels(bytesToManagedBytes(make([]byte, 4*8*4)), image.Rect(0, 0, 8, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMutateSlices(t *testing.T) {
|
func TestMutateSlices(t *testing.T) {
|
||||||
@ -896,7 +905,7 @@ func TestMutateSlices(t *testing.T) {
|
|||||||
pix[4*i+2] = byte(i)
|
pix[4*i+2] = byte(i)
|
||||||
pix[4*i+3] = 0xff
|
pix[4*i+3] = 0xff
|
||||||
}
|
}
|
||||||
src.WritePixels(pix, image.Rect(0, 0, w, h))
|
src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
vs := quadVertices(w, h, 0, 0)
|
vs := quadVertices(w, h, 0, 0)
|
||||||
is := make([]uint16, len(graphics.QuadIndices()))
|
is := make([]uint16, len(graphics.QuadIndices()))
|
||||||
@ -950,7 +959,7 @@ func TestOverlappedPixels(t *testing.T) {
|
|||||||
pix0[idx+3] = 0xff
|
pix0[idx+3] = 0xff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst.WritePixels(pix0, image.Rect(0, 0, 2, 2))
|
dst.WritePixels(bytesToManagedBytes(pix0), image.Rect(0, 0, 2, 2))
|
||||||
|
|
||||||
pix1 := make([]byte, 4*2*2)
|
pix1 := make([]byte, 4*2*2)
|
||||||
for j := 0; j < 2; j++ {
|
for j := 0; j < 2; j++ {
|
||||||
@ -962,7 +971,7 @@ func TestOverlappedPixels(t *testing.T) {
|
|||||||
pix1[idx+3] = 0xff
|
pix1[idx+3] = 0xff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst.WritePixels(pix1, image.Rect(1, 1, 3, 3))
|
dst.WritePixels(bytesToManagedBytes(pix1), image.Rect(1, 1, 3, 3))
|
||||||
|
|
||||||
wantColors := []color.RGBA{
|
wantColors := []color.RGBA{
|
||||||
{0xff, 0, 0, 0xff},
|
{0xff, 0, 0, 0xff},
|
||||||
@ -1032,7 +1041,7 @@ func TestOverlappedPixels(t *testing.T) {
|
|||||||
pix2[idx+3] = 0xff
|
pix2[idx+3] = 0xff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst.WritePixels(pix2, image.Rect(1, 1, 3, 3))
|
dst.WritePixels(bytesToManagedBytes(pix2), image.Rect(1, 1, 3, 3))
|
||||||
|
|
||||||
wantColors = []color.RGBA{
|
wantColors = []color.RGBA{
|
||||||
{0xff, 0, 0, 0xff},
|
{0xff, 0, 0, 0xff},
|
||||||
@ -1089,7 +1098,7 @@ func TestDrawTrianglesAndReadPixels(t *testing.T) {
|
|||||||
src := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
src := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||||
dst := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
dst := restorable.NewImage(w, h, restorable.ImageTypeRegular)
|
||||||
|
|
||||||
src.WritePixels([]byte{0x80, 0x80, 0x80, 0x80}, image.Rect(0, 0, 1, 1))
|
src.WritePixels(bytesToManagedBytes([]byte{0x80, 0x80, 0x80, 0x80}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
vs := quadVertices(w, h, 0, 0)
|
vs := quadVertices(w, h, 0, 0)
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
@ -1109,10 +1118,10 @@ func TestWritePixelsAndDrawTriangles(t *testing.T) {
|
|||||||
src := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
src := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
||||||
dst := restorable.NewImage(2, 1, restorable.ImageTypeRegular)
|
dst := restorable.NewImage(2, 1, restorable.ImageTypeRegular)
|
||||||
|
|
||||||
src.WritePixels([]byte{0x80, 0x80, 0x80, 0x80}, image.Rect(0, 0, 1, 1))
|
src.WritePixels(bytesToManagedBytes([]byte{0x80, 0x80, 0x80, 0x80}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
// Call WritePixels first.
|
// Call WritePixels first.
|
||||||
dst.WritePixels([]byte{0x40, 0x40, 0x40, 0x40}, image.Rect(0, 0, 1, 1))
|
dst.WritePixels(bytesToManagedBytes([]byte{0x40, 0x40, 0x40, 0x40}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
// Call DrawTriangles at a different region second.
|
// Call DrawTriangles at a different region second.
|
||||||
vs := quadVertices(1, 1, 1, 0)
|
vs := quadVertices(1, 1, 1, 0)
|
||||||
|
@ -18,12 +18,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pixelsRecord struct {
|
type pixelsRecord struct {
|
||||||
rect image.Rectangle
|
rect image.Rectangle
|
||||||
pix []byte
|
pix *graphics.ManagedBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pixelsRecord) readPixels(pixels []byte, region image.Rectangle, imageWidth, imageHeight int) {
|
func (p *pixelsRecord) readPixels(pixels []byte, region image.Rectangle, imageWidth, imageHeight int) {
|
||||||
@ -41,7 +42,7 @@ func (p *pixelsRecord) readPixels(pixels []byte, region image.Rectangle, imageWi
|
|||||||
for j := 0; j < r.Dy(); j++ {
|
for j := 0; j < r.Dy(); j++ {
|
||||||
dstX := 4 * ((dstBaseY+j)*region.Dx() + dstBaseX)
|
dstX := 4 * ((dstBaseY+j)*region.Dx() + dstBaseX)
|
||||||
srcX := 4 * ((srcBaseY+j)*p.rect.Dx() + srcBaseX)
|
srcX := 4 * ((srcBaseY+j)*p.rect.Dx() + srcBaseX)
|
||||||
copy(pixels[dstX:dstX+lineWidth], p.pix[srcX:srcX+lineWidth])
|
p.pix.Read(pixels[dstX:dstX+lineWidth], srcX, srcX+lineWidth)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for j := 0; j < r.Dy(); j++ {
|
for j := 0; j < r.Dy(); j++ {
|
||||||
@ -57,9 +58,9 @@ type pixelsRecords struct {
|
|||||||
records []*pixelsRecord
|
records []*pixelsRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *pixelsRecords) addOrReplace(pixels []byte, region image.Rectangle) {
|
func (pr *pixelsRecords) addOrReplace(pixels *graphics.ManagedBytes, region image.Rectangle) {
|
||||||
if len(pixels) != 4*region.Dx()*region.Dy() {
|
if pixels.Len() != 4*region.Dx()*region.Dy() {
|
||||||
msg := fmt.Sprintf("restorable: len(pixels) must be 4*%d*%d = %d but %d", region.Dx(), region.Dy(), 4*region.Dx()*region.Dy(), len(pixels))
|
msg := fmt.Sprintf("restorable: len(pixels) must be 4*%d*%d = %d but %d", region.Dx(), region.Dy(), 4*region.Dx()*region.Dy(), pixels.Len())
|
||||||
if pixels == nil {
|
if pixels == nil {
|
||||||
msg += " (nil)"
|
msg += " (nil)"
|
||||||
}
|
}
|
||||||
@ -128,7 +129,8 @@ func (pr *pixelsRecords) apply(img *graphicscommand.Image) {
|
|||||||
// TODO: Isn't this too heavy? Can we merge the operations?
|
// TODO: Isn't this too heavy? Can we merge the operations?
|
||||||
for _, r := range pr.records {
|
for _, r := range pr.records {
|
||||||
if r.pix != nil {
|
if r.pix != nil {
|
||||||
img.WritePixels(r.pix, r.rect)
|
// Clone a ManagedBytes as the package graphicscommand has a different lifetime management.
|
||||||
|
img.WritePixels(r.pix.Clone(), r.rect)
|
||||||
} else {
|
} else {
|
||||||
clearImage(img, r.rect)
|
clearImage(img, r.rect)
|
||||||
}
|
}
|
||||||
@ -144,3 +146,14 @@ func (pr *pixelsRecords) appendRegions(regions []image.Rectangle) []image.Rectan
|
|||||||
}
|
}
|
||||||
return regions
|
return regions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pr *pixelsRecords) dispose() {
|
||||||
|
for _, r := range pr.records {
|
||||||
|
if r.pix == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// As the package graphicscommands already has cloned ManagedBytes objects, it is OK to release it.
|
||||||
|
_, f := r.pix.GetAndRelease()
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -80,7 +80,7 @@ func TestShaderChain(t *testing.T) {
|
|||||||
imgs = append(imgs, img)
|
imgs = append(imgs, img)
|
||||||
}
|
}
|
||||||
|
|
||||||
imgs[0].WritePixels([]byte{0xff, 0, 0, 0xff}, image.Rect(0, 0, 1, 1))
|
imgs[0].WritePixels(bytesToManagedBytes([]byte{0xff, 0, 0, 0xff}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
s := restorable.NewShader(etesting.ShaderProgramImages(1))
|
s := restorable.NewShader(etesting.ShaderProgramImages(1))
|
||||||
for i := 0; i < num-1; i++ {
|
for i := 0; i < num-1; i++ {
|
||||||
@ -109,9 +109,9 @@ func TestShaderMultipleSources(t *testing.T) {
|
|||||||
for i := range srcs {
|
for i := range srcs {
|
||||||
srcs[i] = restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
srcs[i] = restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
||||||
}
|
}
|
||||||
srcs[0].WritePixels([]byte{0x40, 0, 0, 0xff}, image.Rect(0, 0, 1, 1))
|
srcs[0].WritePixels(bytesToManagedBytes([]byte{0x40, 0, 0, 0xff}), image.Rect(0, 0, 1, 1))
|
||||||
srcs[1].WritePixels([]byte{0, 0x80, 0, 0xff}, image.Rect(0, 0, 1, 1))
|
srcs[1].WritePixels(bytesToManagedBytes([]byte{0, 0x80, 0, 0xff}), image.Rect(0, 0, 1, 1))
|
||||||
srcs[2].WritePixels([]byte{0, 0, 0xc0, 0xff}, image.Rect(0, 0, 1, 1))
|
srcs[2].WritePixels(bytesToManagedBytes([]byte{0, 0, 0xc0, 0xff}), image.Rect(0, 0, 1, 1))
|
||||||
|
|
||||||
dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
||||||
|
|
||||||
@ -138,11 +138,11 @@ func TestShaderMultipleSources(t *testing.T) {
|
|||||||
|
|
||||||
func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
|
func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
|
||||||
src := restorable.NewImage(3, 1, restorable.ImageTypeRegular)
|
src := restorable.NewImage(3, 1, restorable.ImageTypeRegular)
|
||||||
src.WritePixels([]byte{
|
src.WritePixels(bytesToManagedBytes([]byte{
|
||||||
0x40, 0, 0, 0xff,
|
0x40, 0, 0, 0xff,
|
||||||
0, 0x80, 0, 0xff,
|
0, 0x80, 0, 0xff,
|
||||||
0, 0, 0xc0, 0xff,
|
0, 0, 0xc0, 0xff,
|
||||||
}, image.Rect(0, 0, 3, 1))
|
}), image.Rect(0, 0, 3, 1))
|
||||||
srcs := [graphics.ShaderImageCount]*restorable.Image{src, src, src}
|
srcs := [graphics.ShaderImageCount]*restorable.Image{src, src, src}
|
||||||
|
|
||||||
dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular)
|
||||||
|
Loading…
Reference in New Issue
Block a user