internal/graphicsdriver: refactoring: use image.Rectangle

This commit is contained in:
Hajime Hoshi 2023-04-28 00:00:51 +09:00
parent d2c991b774
commit e98acd3dc7
11 changed files with 92 additions and 117 deletions

View File

@ -16,6 +16,7 @@ package graphicscommand
import (
"fmt"
"image"
"math"
"strings"
@ -427,15 +428,12 @@ func (c *writePixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexO
type readPixelsCommand struct {
result []byte
img *Image
x int
y int
width int
height int
region image.Rectangle
}
// Exec executes a readPixelsCommand.
func (c *readPixelsCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
if err := c.img.image.ReadPixels(c.result, c.x, c.y, c.width, c.height); err != nil {
if err := c.img.image.ReadPixels(c.result, c.region); err != nil {
return err
}
return nil

View File

@ -163,14 +163,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, offsets [g
// ReadPixels reads the image's pixels.
// ReadPixels returns an error when an error happens in the graphics driver.
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, buf []byte, x, y, width, height int) error {
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, buf []byte, region image.Rectangle) error {
i.flushBufferedWritePixels()
c := &readPixelsCommand{
img: i,
x: x,
y: y,
width: width,
height: height,
region: region,
result: buf,
}
theCommandQueue.Enqueue(c)
@ -180,13 +177,10 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, buf []byte, x
return nil
}
func (i *Image) WritePixels(pixels []byte, x, y, width, height int) {
func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
i.bufferedWP = append(i.bufferedWP, &graphicsdriver.WritePixelsArgs{
Pixels: pixels,
X: x,
Y: y,
Width: width,
Height: height,
Region: region,
})
addImageWithBuffer(i)
}
@ -228,7 +222,7 @@ func (i *Image) dumpTo(w io.Writer, graphicsDriver graphicsdriver.Graphics, blac
}
pix := make([]byte, 4*i.width*i.height)
if err := i.ReadPixels(graphicsDriver, pix, 0, 0, i.width, i.height); err != nil {
if err := i.ReadPixels(graphicsDriver, pix, image.Rect(0, 0, i.width, i.height)); err != nil {
return err
}

View File

@ -16,6 +16,7 @@ package graphicscommand_test
import (
"fmt"
"image"
"image/color"
"testing"
@ -66,7 +67,7 @@ func TestClear(t *testing.T) {
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{src}, [graphics.ShaderImageCount - 1][2]float32{}, vs, is, graphicsdriver.BlendClear, dr, graphicsdriver.Region{}, nearestFilterShader, nil, false)
pix := make([]byte, 4*w*h)
if err := dst.ReadPixels(ui.GraphicsDriverForTesting(), pix, 0, 0, w, h); err != nil {
if err := dst.ReadPixels(ui.GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)); err != nil {
t.Fatal(err)
}
for j := 0; j < h/2; j++ {
@ -96,7 +97,7 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) {
}
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{clr}, [graphics.ShaderImageCount - 1][2]float32{}, vs, is, graphicsdriver.BlendClear, dr, graphicsdriver.Region{}, nearestFilterShader, nil, false)
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{src}, [graphics.ShaderImageCount - 1][2]float32{}, vs, is, graphicsdriver.BlendSourceOver, dr, graphicsdriver.Region{}, nearestFilterShader, nil, false)
dst.WritePixels(make([]byte, 4), 0, 0, 1, 1)
dst.WritePixels(make([]byte, 4), image.Rect(0, 0, 1, 1))
// TODO: Check the result.
}
@ -120,7 +121,7 @@ func TestShader(t *testing.T) {
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{}, [graphics.ShaderImageCount - 1][2]float32{}, vs, is, graphicsdriver.BlendSourceOver, dr, graphicsdriver.Region{}, s, nil, false)
pix := make([]byte, 4*w*h)
if err := dst.ReadPixels(g, pix, 0, 0, w, h); err != nil {
if err := dst.ReadPixels(g, pix, image.Rect(0, 0, w, h)); err != nil {
t.Fatal(err)
}
for j := 0; j < h; j++ {

View File

@ -16,6 +16,7 @@ package directx
import (
"fmt"
"image"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
@ -79,10 +80,10 @@ func (i *image11) IsInvalidated() bool {
return false
}
func (i *image11) ReadPixels(buf []byte, x, y, width, height int) error {
func (i *image11) ReadPixels(buf []byte, region image.Rectangle) error {
staging, err := i.graphics.device.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
Width: uint32(width),
Height: uint32(height),
Width: uint32(region.Dx()),
Height: uint32(region.Dy()),
MipLevels: 0,
ArraySize: 1,
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
@ -101,11 +102,11 @@ func (i *image11) ReadPixels(buf []byte, x, y, width, height int) error {
defer staging.Release()
i.graphics.deviceContext.CopySubresourceRegion(unsafe.Pointer(staging), 0, 0, 0, 0, unsafe.Pointer(i.texture), 0, &_D3D11_BOX{
left: uint32(x),
top: uint32(y),
left: uint32(region.Min.X),
top: uint32(region.Min.Y),
front: 0,
right: uint32(x + width),
bottom: uint32(y + height),
right: uint32(region.Max.X),
bottom: uint32(region.Max.Y),
back: 1,
})
@ -115,12 +116,12 @@ func (i *image11) ReadPixels(buf []byte, x, y, width, height int) error {
}
stride := int(mapped.RowPitch)
src := unsafe.Slice((*byte)(mapped.pData), stride*height)
if stride == 4*width {
src := unsafe.Slice((*byte)(mapped.pData), stride*region.Dy())
if stride == 4*region.Dx() {
copy(buf, src)
} else {
for j := 0; j < height; j++ {
copy(buf[j*4*width:(j+1)*4*width], src[j*stride:j*stride+4*width])
for j := 0; j < region.Dy(); j++ {
copy(buf[j*4*region.Dx():(j+1)*4*region.Dx()], src[j*stride:j*stride+4*region.Dx()])
}
}
@ -132,13 +133,13 @@ func (i *image11) ReadPixels(buf []byte, x, y, width, height int) error {
func (i *image11) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
for _, a := range args {
i.graphics.deviceContext.UpdateSubresource(unsafe.Pointer(i.texture), 0, &_D3D11_BOX{
left: uint32(a.X),
top: uint32(a.Y),
left: uint32(a.Region.Min.X),
top: uint32(a.Region.Min.Y),
front: 0,
right: uint32(a.X + a.Width),
bottom: uint32(a.Y + a.Height),
right: uint32(a.Region.Max.X),
bottom: uint32(a.Region.Max.Y),
back: 1,
}, unsafe.Pointer(&a.Pixels[0]), uint32(4*a.Width), 0)
}, unsafe.Pointer(&a.Pixels[0]), uint32(4*a.Region.Dx()), 0)
}
return nil
}

View File

@ -17,6 +17,7 @@ package directx
import (
"errors"
"fmt"
"image"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
@ -71,7 +72,7 @@ func (*image12) IsInvalidated() bool {
return false
}
func (i *image12) ReadPixels(buf []byte, x, y, width, height int) error {
func (i *image12) ReadPixels(buf []byte, region image.Rectangle) error {
if i.screen {
return errors.New("directx: Pixels cannot be called on the screen")
}
@ -83,8 +84,8 @@ func (i *image12) ReadPixels(buf []byte, x, y, width, height int) error {
desc := _D3D12_RESOURCE_DESC{
Dimension: _D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: uint64(width),
Height: uint32(height),
Width: uint64(region.Dx()),
Height: uint32(region.Dy()),
DepthOrArraySize: 1,
MipLevels: 0,
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
@ -126,11 +127,11 @@ func (i *image12) ReadPixels(buf []byte, x, y, width, height int) error {
i.graphics.needFlushCopyCommandList = true
i.graphics.copyCommandList.CopyTextureRegion_PlacedFootPrint_SubresourceIndex(
&dst, 0, 0, 0, &src, &_D3D12_BOX{
left: uint32(x),
top: uint32(y),
left: uint32(region.Min.X),
top: uint32(region.Min.Y),
front: 0,
right: uint32(x + width),
bottom: uint32(y + height),
right: uint32(region.Max.X),
bottom: uint32(region.Max.Y),
back: 1,
})
@ -139,8 +140,8 @@ func (i *image12) ReadPixels(buf []byte, x, y, width, height int) error {
}
dstBytes := unsafe.Slice((*byte)(unsafe.Pointer(m)), totalBytes)
for j := 0; j < height; j++ {
copy(buf[j*width*4:(j+1)*width*4], dstBytes[j*int(layouts.Footprint.RowPitch):])
for j := 0; j < region.Dy(); j++ {
copy(buf[j*region.Dx()*4:(j+1)*region.Dx()*4], dstBytes[j*int(layouts.Footprint.RowPitch):])
}
readingStagingBuffer.Unmap(0, nil)
@ -157,30 +158,16 @@ func (i *image12) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
return err
}
minX := i.width
minY := i.height
maxX := 0
maxY := 0
var region image.Rectangle
for _, a := range args {
if minX > a.X {
minX = a.X
}
if minY > a.Y {
minY = a.Y
}
if maxX < a.X+a.Width {
maxX = a.X + a.Width
}
if maxY < a.Y+a.Height {
maxY = a.Y + a.Height
}
region = region.Union(a.Region)
}
desc := _D3D12_RESOURCE_DESC{
Dimension: _D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: uint64(maxX - minX),
Height: uint32(maxY - minY),
Width: uint64(region.Dx()),
Height: uint32(region.Dy()),
DepthOrArraySize: 1,
MipLevels: 0,
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
@ -211,8 +198,8 @@ func (i *image12) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
srcBytes := unsafe.Slice((*byte)(unsafe.Pointer(m)), totalBytes)
for _, a := range args {
for j := 0; j < a.Height; j++ {
copy(srcBytes[((a.Y-minY)+j)*int(layouts.Footprint.RowPitch)+(a.X-minX)*4:], a.Pixels[j*a.Width*4:(j+1)*a.Width*4])
for j := 0; j < a.Region.Dy(); j++ {
copy(srcBytes[((a.Region.Min.Y-region.Min.Y)+j)*int(layouts.Footprint.RowPitch)+(a.Region.Min.X-region.Min.X)*4:], a.Pixels[j*a.Region.Dx()*4:(j+1)*a.Region.Dx()*4])
}
}
@ -228,12 +215,12 @@ func (i *image12) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
PlacedFootprint: layouts,
}
i.graphics.copyCommandList.CopyTextureRegion_SubresourceIndex_PlacedFootPrint(
&dst, uint32(a.X), uint32(a.Y), 0, &src, &_D3D12_BOX{
left: uint32(a.X - minX),
top: uint32(a.Y - minY),
&dst, uint32(a.Region.Min.X), uint32(a.Region.Min.Y), 0, &src, &_D3D12_BOX{
left: uint32(a.Region.Min.X - region.Min.X),
top: uint32(a.Region.Min.Y - region.Min.Y),
front: 0,
right: uint32(a.X - minX + a.Width),
bottom: uint32(a.Y - minY + a.Height),
right: uint32(a.Region.Max.X - region.Min.X),
bottom: uint32(a.Region.Max.Y - region.Min.Y),
back: 1,
})
}

View File

@ -15,6 +15,8 @@
package graphicsdriver
import (
"image"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
@ -65,7 +67,7 @@ type Image interface {
ID() ImageID
Dispose()
IsInvalidated() bool
ReadPixels(buf []byte, x, y, width, height int) error
ReadPixels(buf []byte, region image.Rectangle) error
WritePixels(args []*WritePixelsArgs) error
}
@ -73,10 +75,7 @@ type ImageID int
type WritePixelsArgs struct {
Pixels []byte
X int
Y int
Width int
Height int
Region image.Rectangle
}
type Shader interface {

View File

@ -16,6 +16,7 @@ package metal
import (
"fmt"
"image"
"math"
"runtime"
"sort"
@ -815,17 +816,17 @@ func (i *Image) syncTexture() {
cb.WaitUntilCompleted()
}
func (i *Image) ReadPixels(buf []byte, x, y, width, height int) error {
if got, want := len(buf), 4*width*height; got != want {
func (i *Image) ReadPixels(buf []byte, region image.Rectangle) error {
if got, want := len(buf), 4*region.Dx()*region.Dy(); got != want {
return fmt.Errorf("metal: len(buf) must be %d but %d at ReadPixels", want, got)
}
i.graphics.flushIfNeeded(false)
i.syncTexture()
i.texture.GetBytes(&buf[0], uintptr(4*width), mtl.Region{
Origin: mtl.Origin{X: x, Y: y},
Size: mtl.Size{Width: width, Height: height, Depth: 1},
i.texture.GetBytes(&buf[0], uintptr(4*region.Dx()), mtl.Region{
Origin: mtl.Origin{X: region.Min.X, Y: region.Min.Y},
Size: mtl.Size{Width: region.Dx(), Height: region.Dy(), Depth: 1},
}, 0)
return nil
}
@ -836,26 +837,10 @@ func (i *Image) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
g.flushRenderCommandEncoderIfNeeded()
// Calculate the smallest texture size to include all the values in args.
minX := math.MaxInt32
minY := math.MaxInt32
maxX := 0
maxY := 0
var region image.Rectangle
for _, a := range args {
if minX > a.X {
minX = a.X
}
if maxX < a.X+a.Width {
maxX = a.X + a.Width
}
if minY > a.Y {
minY = a.Y
}
if maxY < a.Y+a.Height {
maxY = a.Y + a.Height
}
region = region.Union(a.Region)
}
w := maxX - minX
h := maxY - minY
// Use a temporary texture to send pixels asynchronously, whichever the memory is shared (e.g., iOS) or
// managed (e.g., macOS). A temporary texture is needed since ReplaceRegion tries to sync the pixel
@ -864,8 +849,8 @@ func (i *Image) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
td := mtl.TextureDescriptor{
TextureType: mtl.TextureType2D,
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: w,
Height: h,
Width: region.Dx(),
Height: region.Dy(),
StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
}
@ -874,9 +859,9 @@ func (i *Image) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
for _, a := range args {
t.ReplaceRegion(mtl.Region{
Origin: mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0},
Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1},
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width)
Origin: mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0},
Size: mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1},
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Region.Dx())
}
if g.cb == (mtl.CommandBuffer{}) {
@ -884,9 +869,9 @@ func (i *Image) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
}
bce := g.cb.MakeBlitCommandEncoder()
for _, a := range args {
so := mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0}
ss := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}
do := mtl.Origin{X: a.X, Y: a.Y, Z: 0}
so := mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0}
ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1}
do := mtl.Origin{X: a.Region.Min.X, Y: a.Region.Min.Y, Z: 0}
bce.CopyFromTexture(t, 0, 0, so, ss, i.texture, 0, 0, do)
}
bce.EndEncoding()

View File

@ -17,6 +17,7 @@ package opengl
import (
"errors"
"fmt"
"image"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
@ -242,14 +243,18 @@ func (c *context) newTexture(width, height int) (textureNative, error) {
return textureNative(t), nil
}
func (c *context) framebufferPixels(buf []byte, f *framebuffer, x, y, width, height int) error {
if got, want := len(buf), 4*width*height; got != want {
func (c *context) framebufferPixels(buf []byte, f *framebuffer, region image.Rectangle) error {
if got, want := len(buf), 4*region.Dx()*region.Dy(); got != want {
return fmt.Errorf("opengl: len(buf) must be %d but was %d at framebufferPixels", got, want)
}
c.ctx.Flush()
c.bindFramebuffer(f.native)
c.ctx.ReadPixels(buf, int32(x), int32(y), int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE)
x := int32(region.Min.X)
y := int32(region.Min.Y)
width := int32(region.Dx())
height := int32(region.Dy())
c.ctx.ReadPixels(buf, x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE)
return nil
}

View File

@ -16,6 +16,7 @@ package opengl
import (
"errors"
"image"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
@ -71,11 +72,11 @@ func (i *Image) setViewport() error {
return nil
}
func (i *Image) ReadPixels(buf []byte, x, y, width, height int) error {
func (i *Image) ReadPixels(buf []byte, region image.Rectangle) error {
if err := i.ensureFramebuffer(); err != nil {
return err
}
if err := i.graphics.context.framebufferPixels(buf, i.framebuffer, x, y, width, height); err != nil {
if err := i.graphics.context.framebufferPixels(buf, i.framebuffer, region); err != nil {
return err
}
return nil
@ -147,7 +148,11 @@ func (i *Image) WritePixels(args []*graphicsdriver.WritePixelsArgs) error {
i.graphics.context.bindTexture(i.texture)
for _, a := range args {
i.graphics.context.ctx.TexSubImage2D(gl.TEXTURE_2D, 0, int32(a.X), int32(a.Y), int32(a.Width), int32(a.Height), gl.RGBA, gl.UNSIGNED_BYTE, a.Pixels)
x := int32(a.Region.Min.X)
y := int32(a.Region.Min.Y)
width := int32(a.Region.Dx())
height := int32(a.Region.Dy())
i.graphics.context.ctx.TexSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, a.Pixels)
}
return nil

View File

@ -282,12 +282,12 @@ func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
theImages.makeStaleIfDependingOn(i)
if pixels != nil {
i.image.WritePixels(pixels, region.Min.X, region.Min.Y, region.Dx(), region.Dy())
i.image.WritePixels(pixels, region)
} else {
// TODO: When pixels == nil, we don't have to care the pixel state there. In such cases, the image
// accepts only WritePixels and not Fill or DrawTriangles.
// TODO: Separate Image struct into two: images for WritePixels-only, and the others.
i.image.WritePixels(make([]byte, 4*region.Dx()*region.Dy()), region.Min.X, region.Min.Y, region.Dx(), region.Dy())
i.image.WritePixels(make([]byte, 4*region.Dx()*region.Dy()), region)
}
// Even if the image is already stale, call makeStale to extend the stale region.
@ -429,7 +429,7 @@ func (i *Image) readPixelsFromGPUIfNeeded(graphicsDriver graphicsdriver.Graphics
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {
if AlwaysReadPixelsFromGPU() {
if err := i.image.ReadPixels(graphicsDriver, pixels, region.Min.X, region.Min.Y, region.Dx(), region.Dy()); err != nil {
if err := i.image.ReadPixels(graphicsDriver, pixels, region); err != nil {
return err
}
return nil
@ -494,7 +494,7 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error
pix = make([]byte, 4*r.Dx()*r.Dy())
i.pixelsCache[r] = pix
}
if err := i.image.ReadPixels(graphicsDriver, pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
if err := i.image.ReadPixels(graphicsDriver, pix, r); err != nil {
return err
}
i.basePixels.AddOrReplace(pix, r)
@ -633,7 +633,7 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
pix = make([]byte, 4*r.Dx()*r.Dy())
i.pixelsCache[r] = pix
}
if err := gimg.ReadPixels(graphicsDriver, pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
if err := gimg.ReadPixels(graphicsDriver, pix, r); err != nil {
return err
}
i.basePixels.AddOrReplace(pix, r)

View File

@ -122,7 +122,7 @@ func (pr *pixelsRecords) readPixels(pixels []byte, region image.Rectangle, image
func (pr *pixelsRecords) apply(img *graphicscommand.Image) {
// TODO: Isn't this too heavy? Can we merge the operations?
for _, r := range pr.records {
img.WritePixels(r.pix, r.rect.Min.X, r.rect.Min.Y, r.rect.Dx(), r.rect.Dy())
img.WritePixels(r.pix, r.rect)
}
}