Skip to content

Commit

Permalink
mipmap: Create mipmap package and bufferd.Image uses it
Browse files Browse the repository at this point in the history
Mipmap calculation must be executed after the main loop starts
because the graphics driver's HasHighPrecisionFloat is needed.
Then, operations on mipmap images must be called from images in
buffered package.

Updates #1044
  • Loading branch information
hajimehoshi committed Jan 7, 2020
1 parent 7f4a82d commit c99fd1a
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 100 deletions.
3 changes: 1 addition & 2 deletions export_test.go
Expand Up @@ -15,6 +15,5 @@
package ebiten

var (
CopyImage = copyImage
MipmapLevelForDownscale = mipmapLevelForDownscale
CopyImage = copyImage
)
71 changes: 48 additions & 23 deletions image.go
Expand Up @@ -19,6 +19,7 @@ import (
"image"
"image/color"

"github.com/hajimehoshi/ebiten/internal/buffered"
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/graphics"
)
Expand All @@ -33,9 +34,7 @@ type Image struct {
// See strings.Builder for similar examples.
addr *Image

// mipmap is a set of shareable.Image sorted by the order of mipmap level.
// The level 0 image is a regular image and higher-level images are used for mipmap.
mipmap *mipmap
buffered *buffered.Image

bounds image.Rectangle
original *Image
Expand All @@ -56,7 +55,7 @@ func (i *Image) Size() (width, height int) {
}

func (i *Image) isDisposed() bool {
return i.mipmap.isDisposed()
return i.buffered == nil
}

func (i *Image) isSubImage() bool {
Expand Down Expand Up @@ -90,7 +89,7 @@ func (i *Image) Fill(clr color.Color) error {
panic("ebiten: render to a subimage is not implemented (Fill)")
}

i.mipmap.fill(color.RGBAModel.Convert(clr).(color.RGBA))
i.buffered.Fill(color.RGBAModel.Convert(clr).(color.RGBA))
return nil
}

Expand Down Expand Up @@ -199,7 +198,8 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
filter = driver.Filter(img.filter)
}

i.mipmap.drawImage(img.mipmap, img.Bounds(), geom, options.ColorM.impl, mode, filter)
a, b, c, d, tx, ty := geom.elements()
i.buffered.DrawImage(img.buffered, img.Bounds(), a, b, c, d, tx, ty, options.ColorM.impl, mode, filter)
return nil
}

Expand Down Expand Up @@ -307,7 +307,32 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
filter = driver.Filter(img.filter)
}

i.mipmap.drawTriangles(img.mipmap, img.Bounds(), vertices, indices, options.ColorM.impl, mode, filter, driver.Address(options.Address))
b := img.Bounds()
bx0 := float32(b.Min.X)
by0 := float32(b.Min.Y)
bx1 := float32(b.Max.X)
by1 := float32(b.Max.Y)

// TODO: Should we use mipmap.verticesBackend?
vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
for i, v := range vertices {
vs[i*graphics.VertexFloatNum] = v.DstX
vs[i*graphics.VertexFloatNum+1] = v.DstY
vs[i*graphics.VertexFloatNum+2] = v.SrcX
vs[i*graphics.VertexFloatNum+3] = v.SrcY
vs[i*graphics.VertexFloatNum+4] = bx0
vs[i*graphics.VertexFloatNum+5] = by0
vs[i*graphics.VertexFloatNum+6] = bx1
vs[i*graphics.VertexFloatNum+7] = by1
vs[i*graphics.VertexFloatNum+8] = v.ColorR
vs[i*graphics.VertexFloatNum+9] = v.ColorG
vs[i*graphics.VertexFloatNum+10] = v.ColorB
vs[i*graphics.VertexFloatNum+11] = v.ColorA
}
is := make([]uint16, len(indices))
copy(is, indices)

i.buffered.DrawTriangles(img.buffered, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address))
}

// SubImage returns an image representing the portion of the image p visible through r. The returned value shares pixels with the original image.
Expand All @@ -324,8 +349,8 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
}

img := &Image{
mipmap: i.mipmap,
filter: i.filter,
buffered: i.buffered,
filter: i.filter,
}

// Keep the original image's reference not to dispose that by GC.
Expand All @@ -351,10 +376,6 @@ func (i *Image) Bounds() image.Rectangle {
if i.isDisposed() {
panic("ebiten: the image is already disposed")
}
if !i.isSubImage() {
w, h := i.mipmap.size()
return image.Rect(0, 0, w, h)
}
return i.bounds
}

Expand All @@ -380,7 +401,7 @@ func (i *Image) At(x, y int) color.Color {
if i.isSubImage() && !image.Pt(x, y).In(i.bounds) {
return color.RGBA{}
}
r, g, b, a := i.mipmap.at(x, y)
r, g, b, a := i.buffered.At(x, y)
return color.RGBA{r, g, b, a}
}

Expand All @@ -404,7 +425,7 @@ func (i *Image) Set(x, y int, clr color.Color) {
}

r, g, b, a := clr.RGBA()
i.mipmap.set(x, y, byte(r>>8), byte(g>>8), byte(b>>8), byte(a>>8))
i.buffered.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.
Expand All @@ -424,7 +445,8 @@ func (i *Image) Dispose() error {
if i.isSubImage() {
return nil
}
i.mipmap.dispose()
i.buffered.MarkDisposed()
i.buffered = nil
return nil
}

Expand Down Expand Up @@ -454,7 +476,7 @@ func (i *Image) ReplacePixels(p []byte) error {
panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l))
}

i.mipmap.replacePixels(p)
i.buffered.ReplacePixels(p)
return nil
}

Expand Down Expand Up @@ -508,8 +530,9 @@ func NewImage(width, height int, filter Filter) (*Image, error) {

func newImage(width, height int, filter Filter, volatile bool) *Image {
i := &Image{
mipmap: newMipmap(width, height, volatile),
filter: filter,
buffered: buffered.NewImage(width, height, volatile),
filter: filter,
bounds: image.Rect(0, 0, width, height),
}
i.addr = i
return i
Expand All @@ -529,8 +552,9 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
width, height := size.X, size.Y

i := &Image{
mipmap: newMipmap(width, height, false),
filter: filter,
buffered: buffered.NewImage(width, height, false),
filter: filter,
bounds: image.Rect(0, 0, width, height),
}
i.addr = i

Expand All @@ -540,8 +564,9 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {

func newScreenFramebufferImage(width, height int) *Image {
i := &Image{
mipmap: newScreenFramebufferMipmap(width, height),
filter: FilterDefault,
buffered: buffered.NewScreenFramebufferImage(width, height),
filter: FilterDefault,
bounds: image.Rect(0, 0, width, height),
}
i.addr = i
return i
Expand Down
2 changes: 1 addition & 1 deletion imagedumper_desktop.go
Expand Up @@ -52,7 +52,7 @@ func takeScreenshot(screen *Image) error {
return err
}

if err := screen.mipmap.dump(newname); err != nil {
if err := screen.buffered.Dump(newname); err != nil {
return err
}

Expand Down
46 changes: 38 additions & 8 deletions internal/buffered/image.go
Expand Up @@ -15,15 +15,16 @@
package buffered

import (
"image"
"image/color"

"github.com/hajimehoshi/ebiten/internal/affine"
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/shareable"
"github.com/hajimehoshi/ebiten/internal/mipmap"
)

type Image struct {
img *shareable.Image
img *mipmap.Mipmap
width int
height int

Expand All @@ -32,23 +33,23 @@ type Image struct {
}

func BeginFrame() error {
if err := shareable.BeginFrame(); err != nil {
if err := mipmap.BeginFrame(); err != nil {
return err
}
flushDelayedCommands()
return nil
}

func EndFrame() error {
return shareable.EndFrame()
return mipmap.EndFrame()
}

func NewImage(width, height int, volatile bool) *Image {
i := &Image{}
delayedCommandsM.Lock()
if needsToDelayCommands {
delayedCommands = append(delayedCommands, func() {
i.img = shareable.NewImage(width, height, volatile)
i.img = mipmap.New(width, height, volatile)
i.width = width
i.height = height
})
Expand All @@ -57,7 +58,7 @@ func NewImage(width, height int, volatile bool) *Image {
}
delayedCommandsM.Unlock()

i.img = shareable.NewImage(width, height, volatile)
i.img = mipmap.New(width, height, volatile)
i.width = width
i.height = height
return i
Expand All @@ -68,7 +69,7 @@ func NewScreenFramebufferImage(width, height int) *Image {
delayedCommandsM.Lock()
if needsToDelayCommands {
delayedCommands = append(delayedCommands, func() {
i.img = shareable.NewScreenFramebufferImage(width, height)
i.img = mipmap.NewScreenFramebufferMipmap(width, height)
i.width = width
i.height = height
})
Expand All @@ -77,7 +78,7 @@ func NewScreenFramebufferImage(width, height int) *Image {
}
delayedCommandsM.Unlock()

i.img = shareable.NewScreenFramebufferImage(width, height)
i.img = mipmap.NewScreenFramebufferMipmap(width, height)
i.width = width
i.height = height
return i
Expand Down Expand Up @@ -204,6 +205,35 @@ func (i *Image) ReplacePixels(pix []byte) {
i.img.ReplacePixels(pix)
}

func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty float32, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
if i == src {
panic("buffered: Image.DrawImage: src must be different from the receiver")
}

g := &mipmap.GeoM{
A: a,
B: b,
C: c,
D: d,
Tx: tx,
Ty: ty,
}

delayedCommandsM.Lock()
if needsToDelayCommands {
delayedCommands = append(delayedCommands, func() {
i.img.DrawImage(src.img, bounds, g, colorm, mode, filter)
})
delayedCommandsM.Unlock()
return
}
delayedCommandsM.Unlock()

src.resolvePendingPixels(true)
i.resolvePendingPixels(false)
i.img.DrawImage(src.img, bounds, g, colorm, mode, filter)
}

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")
Expand Down

0 comments on commit c99fd1a

Please sign in to comment.