mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
graphicsdriver/metal: Avoid synching texture data at ReplacePixels
Instead, use a temporary texture, transfer texture data to it, and copy the data on GPU side.
This commit is contained in:
parent
383ff1e6a7
commit
ebd0e11c0d
@ -326,7 +326,7 @@ type Graphics struct {
|
|||||||
|
|
||||||
transparent bool
|
transparent bool
|
||||||
maxImageSize int
|
maxImageSize int
|
||||||
drawCalled bool
|
tmpTexture mtl.Texture
|
||||||
|
|
||||||
t *thread.Thread
|
t *thread.Thread
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ func (g *Graphics) Begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) End() {
|
func (g *Graphics) End() {
|
||||||
g.flush(false, true)
|
g.flushIfNeeded(false, true)
|
||||||
g.t.Call(func() error {
|
g.t.Call(func() error {
|
||||||
g.screenDrawable = ca.MetalDrawable{}
|
g.screenDrawable = ca.MetalDrawable{}
|
||||||
C.releaseAutoreleasePool(g.pool)
|
C.releaseAutoreleasePool(g.pool)
|
||||||
@ -390,7 +390,7 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint16) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) flush(wait bool, present bool) {
|
func (g *Graphics) flushIfNeeded(wait bool, present bool) {
|
||||||
g.t.Call(func() error {
|
g.t.Call(func() error {
|
||||||
if g.cb == (mtl.CommandBuffer{}) {
|
if g.cb == (mtl.CommandBuffer{}) {
|
||||||
return nil
|
return nil
|
||||||
@ -639,8 +639,6 @@ func (g *Graphics) Draw(dstID, srcID driver.ImageID, indexLen int, indexOffset i
|
|||||||
dst := g.images[dstID]
|
dst := g.images[dstID]
|
||||||
src := g.images[srcID]
|
src := g.images[srcID]
|
||||||
|
|
||||||
g.drawCalled = true
|
|
||||||
|
|
||||||
if err := g.t.Call(func() error {
|
if err := g.t.Call(func() error {
|
||||||
g.view.update()
|
g.view.update()
|
||||||
|
|
||||||
@ -841,7 +839,7 @@ func (i *Image) syncTexture() {
|
|||||||
|
|
||||||
i.graphics.t.Call(func() error {
|
i.graphics.t.Call(func() error {
|
||||||
if i.graphics.cb != (mtl.CommandBuffer{}) {
|
if i.graphics.cb != (mtl.CommandBuffer{}) {
|
||||||
panic("metal: command buffer must be empty at syncTexture: flush is not called yet?")
|
panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
|
||||||
}
|
}
|
||||||
|
|
||||||
cb := i.graphics.cq.MakeCommandBuffer()
|
cb := i.graphics.cq.MakeCommandBuffer()
|
||||||
@ -855,7 +853,7 @@ func (i *Image) syncTexture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Pixels() ([]byte, error) {
|
func (i *Image) Pixels() ([]byte, error) {
|
||||||
i.graphics.flush(true, false)
|
i.graphics.flushIfNeeded(true, false)
|
||||||
i.syncTexture()
|
i.syncTexture()
|
||||||
|
|
||||||
b := make([]byte, 4*i.width*i.height)
|
b := make([]byte, 4*i.width*i.height)
|
||||||
@ -868,33 +866,12 @@ func (i *Image) Pixels() ([]byte, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// needsToSyncBeforeReplaceRegion reports whether syncTexture is needed before calling ReplaceRegion.
|
|
||||||
//
|
|
||||||
// Before a part region is updated, the texture data needs to be loaded to CPU. Otherwise, the remaining region will
|
|
||||||
// be uninitialized (#1213).
|
|
||||||
func (i *Image) needsToSyncBeforeReplaceRegion(args []*driver.ReplacePixelsArgs) bool {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if a := args[0]; a.X == 0 && a.Y == 0 && a.Width == i.width && a.Height == i.height {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
|
func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
|
||||||
g := i.graphics
|
g := i.graphics
|
||||||
if g.drawCalled {
|
g.flushIfNeeded(true, false)
|
||||||
g.flush(true, false)
|
|
||||||
g.drawCalled = false
|
|
||||||
if i.needsToSyncBeforeReplaceRegion(args) {
|
|
||||||
i.syncTexture()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// If the memory is shared (e.g., iOS), texture data doen't have to be synced. Send the data directly.
|
||||||
|
if storageMode == mtl.StorageModeShared {
|
||||||
g.t.Call(func() error {
|
g.t.Call(func() error {
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
i.texture.ReplaceRegion(mtl.Region{
|
i.texture.ReplaceRegion(mtl.Region{
|
||||||
@ -904,4 +881,47 @@ func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the memory is managed (e.g., macOS), texture data cannot be sent to the destination directly because
|
||||||
|
// this requires synchronizing data between CPU and GPU. As synchronizing is inefficient, let's send the
|
||||||
|
// data to a temporary texture once, and then copy it in GPU.
|
||||||
|
g.t.Call(func() error {
|
||||||
|
w, h := i.texture.Width(), i.texture.Height()
|
||||||
|
if g.tmpTexture == (mtl.Texture{}) || w > g.tmpTexture.Width() || h > g.tmpTexture.Height() {
|
||||||
|
if g.tmpTexture != (mtl.Texture{}) {
|
||||||
|
g.tmpTexture.Release()
|
||||||
|
}
|
||||||
|
td := mtl.TextureDescriptor{
|
||||||
|
TextureType: mtl.TextureType2D,
|
||||||
|
PixelFormat: mtl.PixelFormatRGBA8UNorm,
|
||||||
|
Width: w,
|
||||||
|
Height: h,
|
||||||
|
StorageMode: storageMode,
|
||||||
|
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
||||||
|
}
|
||||||
|
g.tmpTexture = g.view.getMTLDevice().MakeTexture(td)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range args {
|
||||||
|
g.tmpTexture.ReplaceRegion(mtl.Region{
|
||||||
|
Origin: mtl.Origin{X: a.X, Y: a.Y, Z: 0},
|
||||||
|
Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1},
|
||||||
|
}, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.cb == (mtl.CommandBuffer{}) {
|
||||||
|
g.cb = i.graphics.cq.MakeCommandBuffer()
|
||||||
|
}
|
||||||
|
bce := g.cb.MakeBlitCommandEncoder()
|
||||||
|
for _, a := range args {
|
||||||
|
o := mtl.Origin{X: a.X, Y: a.Y, Z: 0}
|
||||||
|
s := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}
|
||||||
|
bce.CopyFromTexture(g.tmpTexture, 0, 0, o, s, i.texture, 0, 0, o)
|
||||||
|
}
|
||||||
|
bce.EndEncoding()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -182,9 +182,9 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
|||||||
cb.WaitUntilCompleted()
|
cb.WaitUntilCompleted()
|
||||||
|
|
||||||
// Read pixels from output texture into an image.
|
// Read pixels from output texture into an image.
|
||||||
img := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
|
img := image.NewNRGBA(image.Rect(0, 0, texture.Width(), texture.Height()))
|
||||||
bytesPerRow := 4 * texture.Width
|
bytesPerRow := 4 * texture.Width()
|
||||||
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
|
region := mtl.RegionMake2D(0, 0, texture.Width(), texture.Height())
|
||||||
texture.GetBytes(&img.Pix[0], uintptr(bytesPerRow), region, 0)
|
texture.GetBytes(&img.Pix[0], uintptr(bytesPerRow), region, 0)
|
||||||
|
|
||||||
// Output image to stdout as grayscale ASCII art.
|
// Output image to stdout as grayscale ASCII art.
|
||||||
|
@ -501,8 +501,6 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
|
|||||||
}
|
}
|
||||||
return Texture{
|
return Texture{
|
||||||
texture: C.Device_MakeTexture(d.device, descriptor),
|
texture: C.Device_MakeTexture(d.device, descriptor),
|
||||||
Width: td.Width, // TODO: Fetch dimensions of actually created texture.
|
|
||||||
Height: td.Height, // TODO: Fetch dimensions of actually created texture.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,6 +698,10 @@ func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, lev
|
|||||||
C.BlitCommandEncoder_SynchronizeTexture(bce.commandEncoder, texture.texture, C.uint_t(slice), C.uint_t(level))
|
C.BlitCommandEncoder_SynchronizeTexture(bce.commandEncoder, texture.texture, C.uint_t(slice), C.uint_t(level))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) {
|
||||||
|
C.BlitCommandEncoder_CopyFromTexture(bce.commandEncoder, sourceTexture.texture, C.uint_t(sourceSlice), C.uint_t(sourceLevel), sourceOrigin.c(), sourceSize.c(), destinationTexture.texture, C.uint_t(destinationSlice), C.uint_t(destinationLevel), destinationOrigin.c())
|
||||||
|
}
|
||||||
|
|
||||||
// Library is a collection of compiled graphics or compute functions.
|
// Library is a collection of compiled graphics or compute functions.
|
||||||
//
|
//
|
||||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
|
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
|
||||||
@ -725,14 +727,6 @@ func (l Library) MakeFunction(name string) (Function, error) {
|
|||||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
|
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
|
||||||
type Texture struct {
|
type Texture struct {
|
||||||
texture unsafe.Pointer
|
texture unsafe.Pointer
|
||||||
|
|
||||||
// TODO: Change these fields into methods.
|
|
||||||
|
|
||||||
// Width is the width of the texture image for the base level mipmap, in pixels.
|
|
||||||
Width int
|
|
||||||
|
|
||||||
// Height is the height of the texture image for the base level mipmap, in pixels.
|
|
||||||
Height int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer.
|
// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer.
|
||||||
@ -764,6 +758,20 @@ func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Point
|
|||||||
C.Texture_ReplaceRegion(t.texture, r, C.uint_t(level), pixelBytes, C.uint_t(bytesPerRow))
|
C.Texture_ReplaceRegion(t.texture, r, C.uint_t(level), pixelBytes, C.uint_t(bytesPerRow))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Width is the width of the texture image for the base level mipmap, in pixels.
|
||||||
|
//
|
||||||
|
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width
|
||||||
|
func (t Texture) Width() int {
|
||||||
|
return int(C.Texture_Width(t.texture))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height is the height of the texture image for the base level mipmap, in pixels.
|
||||||
|
//
|
||||||
|
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height
|
||||||
|
func (t Texture) Height() int {
|
||||||
|
return int(C.Texture_Height(t.texture))
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer is a memory allocation for storing unformatted data
|
// Buffer is a memory allocation for storing unformatted data
|
||||||
// that is accessible to the GPU.
|
// that is accessible to the GPU.
|
||||||
//
|
//
|
||||||
@ -814,16 +822,8 @@ type Region struct {
|
|||||||
|
|
||||||
func (r *Region) c() C.struct_Region {
|
func (r *Region) c() C.struct_Region {
|
||||||
return C.struct_Region{
|
return C.struct_Region{
|
||||||
Origin: C.struct_Origin{
|
Origin: r.Origin.c(),
|
||||||
X: C.uint_t(r.Origin.X),
|
Size: r.Size.c(),
|
||||||
Y: C.uint_t(r.Origin.Y),
|
|
||||||
Z: C.uint_t(r.Origin.Z),
|
|
||||||
},
|
|
||||||
Size: C.struct_Size{
|
|
||||||
Width: C.uint_t(r.Size.Width),
|
|
||||||
Height: C.uint_t(r.Size.Height),
|
|
||||||
Depth: C.uint_t(r.Size.Depth),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,12 +833,28 @@ func (r *Region) c() C.struct_Region {
|
|||||||
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
|
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
|
||||||
type Origin struct{ X, Y, Z int }
|
type Origin struct{ X, Y, Z int }
|
||||||
|
|
||||||
|
func (o *Origin) c() C.struct_Origin {
|
||||||
|
return C.struct_Origin{
|
||||||
|
X: C.uint_t(o.X),
|
||||||
|
Y: C.uint_t(o.Y),
|
||||||
|
Z: C.uint_t(o.Z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Size represents the set of dimensions that declare the size of an object,
|
// Size represents the set of dimensions that declare the size of an object,
|
||||||
// such as an image, texture, threadgroup, or grid.
|
// such as an image, texture, threadgroup, or grid.
|
||||||
//
|
//
|
||||||
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
|
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
|
||||||
type Size struct{ Width, Height, Depth int }
|
type Size struct{ Width, Height, Depth int }
|
||||||
|
|
||||||
|
func (s *Size) c() C.struct_Size {
|
||||||
|
return C.struct_Size{
|
||||||
|
Width: C.uint_t(s.Width),
|
||||||
|
Height: C.uint_t(s.Height),
|
||||||
|
Depth: C.uint_t(s.Depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RegionMake2D returns a 2D, rectangular region for image or texture data.
|
// RegionMake2D returns a 2D, rectangular region for image or texture data.
|
||||||
//
|
//
|
||||||
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
|
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
|
||||||
|
@ -163,6 +163,11 @@ void BlitCommandEncoder_Synchronize(void *blitCommandEncoder, void *resource);
|
|||||||
void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
|
void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
|
||||||
void *texture, uint_t slice,
|
void *texture, uint_t slice,
|
||||||
uint_t level);
|
uint_t level);
|
||||||
|
void BlitCommandEncoder_CopyFromTexture(
|
||||||
|
void *blitCommandEncoder, void *sourceTexture, uint_t sourceSlice,
|
||||||
|
uint_t sourceLevel, struct Origin sourceOrigin, struct Size sourceSize,
|
||||||
|
void *destinationTexture, uint_t destinationSlice, uint_t destinationLevel,
|
||||||
|
struct Origin destinationOrigin);
|
||||||
|
|
||||||
void *Library_MakeFunction(void *library, const char *name);
|
void *Library_MakeFunction(void *library, const char *name);
|
||||||
|
|
||||||
@ -171,6 +176,8 @@ void Texture_GetBytes(void *texture, void *pixelBytes, size_t bytesPerRow,
|
|||||||
struct Region region, uint_t level);
|
struct Region region, uint_t level);
|
||||||
void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
|
void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
|
||||||
void *pixelBytes, uint_t bytesPerRow);
|
void *pixelBytes, uint_t bytesPerRow);
|
||||||
|
int Texture_Width(void *texture);
|
||||||
|
int Texture_Height(void *texture);
|
||||||
|
|
||||||
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes);
|
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes);
|
||||||
void Buffer_Retain(void *buffer);
|
void Buffer_Retain(void *buffer);
|
||||||
|
@ -280,6 +280,26 @@ void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlitCommandEncoder_CopyFromTexture(
|
||||||
|
void *blitCommandEncoder, void *sourceTexture, uint_t sourceSlice,
|
||||||
|
uint_t sourceLevel, struct Origin sourceOrigin, struct Size sourceSize,
|
||||||
|
void *destinationTexture, uint_t destinationSlice, uint_t destinationLevel,
|
||||||
|
struct Origin destinationOrigin) {
|
||||||
|
[(id<MTLBlitCommandEncoder>)blitCommandEncoder
|
||||||
|
copyFromTexture:(id<MTLTexture>)sourceTexture
|
||||||
|
sourceSlice:(NSUInteger)sourceSlice
|
||||||
|
sourceLevel:(NSUInteger)sourceLevel
|
||||||
|
sourceOrigin:(MTLOrigin){sourceOrigin.X, sourceOrigin.Y,
|
||||||
|
sourceOrigin.Z}
|
||||||
|
sourceSize:(MTLSize){sourceSize.Width, sourceSize.Height,
|
||||||
|
sourceSize.Depth}
|
||||||
|
toTexture:(id<MTLTexture>)destinationTexture
|
||||||
|
destinationSlice:(NSUInteger)destinationSlice
|
||||||
|
destinationLevel:(NSUInteger)destinationLevel
|
||||||
|
destinationOrigin:(MTLOrigin){destinationOrigin.X, destinationOrigin.Y,
|
||||||
|
destinationOrigin.Z}];
|
||||||
|
}
|
||||||
|
|
||||||
void *Library_MakeFunction(void *library, const char *name) {
|
void *Library_MakeFunction(void *library, const char *name) {
|
||||||
return [(id<MTLLibrary>)library
|
return [(id<MTLLibrary>)library
|
||||||
newFunctionWithName:[NSString stringWithUTF8String:name]];
|
newFunctionWithName:[NSString stringWithUTF8String:name]];
|
||||||
@ -312,6 +332,10 @@ void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
|
|||||||
bytesPerRow:(NSUInteger)bytesPerRow];
|
bytesPerRow:(NSUInteger)bytesPerRow];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Texture_Width(void *texture) { return [(id<MTLTexture>)texture width]; }
|
||||||
|
|
||||||
|
int Texture_Height(void *texture) { return [(id<MTLTexture>)texture height]; }
|
||||||
|
|
||||||
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes) {
|
void Buffer_CopyToContents(void *buffer, void *data, size_t lengthInBytes) {
|
||||||
memcpy(((id<MTLBuffer>)buffer).contents, data, lengthInBytes);
|
memcpy(((id<MTLBuffer>)buffer).contents, data, lengthInBytes);
|
||||||
}
|
}
|
||||||
|
@ -125,9 +125,9 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
|||||||
cb.WaitUntilCompleted()
|
cb.WaitUntilCompleted()
|
||||||
|
|
||||||
// Read pixels from output texture into an image.
|
// Read pixels from output texture into an image.
|
||||||
got := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
|
got := image.NewNRGBA(image.Rect(0, 0, texture.Width(), texture.Height()))
|
||||||
bytesPerRow := 4 * texture.Width
|
bytesPerRow := 4 * texture.Width()
|
||||||
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
|
region := mtl.RegionMake2D(0, 0, texture.Width(), texture.Height())
|
||||||
texture.GetBytes(&got.Pix[0], uintptr(bytesPerRow), region, 0)
|
texture.GetBytes(&got.Pix[0], uintptr(bytesPerRow), region, 0)
|
||||||
|
|
||||||
// TODO: Embed this file?
|
// TODO: Embed this file?
|
||||||
|
Loading…
Reference in New Issue
Block a user