mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +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
|
||||
maxImageSize int
|
||||
drawCalled bool
|
||||
tmpTexture mtl.Texture
|
||||
|
||||
t *thread.Thread
|
||||
|
||||
@ -353,7 +353,7 @@ func (g *Graphics) Begin() {
|
||||
}
|
||||
|
||||
func (g *Graphics) End() {
|
||||
g.flush(false, true)
|
||||
g.flushIfNeeded(false, true)
|
||||
g.t.Call(func() error {
|
||||
g.screenDrawable = ca.MetalDrawable{}
|
||||
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 {
|
||||
if g.cb == (mtl.CommandBuffer{}) {
|
||||
return nil
|
||||
@ -639,8 +639,6 @@ func (g *Graphics) Draw(dstID, srcID driver.ImageID, indexLen int, indexOffset i
|
||||
dst := g.images[dstID]
|
||||
src := g.images[srcID]
|
||||
|
||||
g.drawCalled = true
|
||||
|
||||
if err := g.t.Call(func() error {
|
||||
g.view.update()
|
||||
|
||||
@ -841,7 +839,7 @@ func (i *Image) syncTexture() {
|
||||
|
||||
i.graphics.t.Call(func() error {
|
||||
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()
|
||||
@ -855,7 +853,7 @@ func (i *Image) syncTexture() {
|
||||
}
|
||||
|
||||
func (i *Image) Pixels() ([]byte, error) {
|
||||
i.graphics.flush(true, false)
|
||||
i.graphics.flushIfNeeded(true, false)
|
||||
i.syncTexture()
|
||||
|
||||
b := make([]byte, 4*i.width*i.height)
|
||||
@ -868,40 +866,62 @@ func (i *Image) Pixels() ([]byte, error) {
|
||||
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) {
|
||||
g := i.graphics
|
||||
if g.drawCalled {
|
||||
g.flush(true, false)
|
||||
g.drawCalled = false
|
||||
if i.needsToSyncBeforeReplaceRegion(args) {
|
||||
i.syncTexture()
|
||||
}
|
||||
g.flushIfNeeded(true, false)
|
||||
|
||||
// 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 {
|
||||
for _, a := range args {
|
||||
i.texture.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)
|
||||
}
|
||||
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 {
|
||||
i.texture.ReplaceRegion(mtl.Region{
|
||||
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()
|
||||
|
||||
// Read pixels from output texture into an image.
|
||||
img := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
|
||||
bytesPerRow := 4 * texture.Width
|
||||
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
|
||||
img := image.NewNRGBA(image.Rect(0, 0, texture.Width(), texture.Height()))
|
||||
bytesPerRow := 4 * texture.Width()
|
||||
region := mtl.RegionMake2D(0, 0, texture.Width(), texture.Height())
|
||||
texture.GetBytes(&img.Pix[0], uintptr(bytesPerRow), region, 0)
|
||||
|
||||
// Output image to stdout as grayscale ASCII art.
|
||||
|
@ -501,8 +501,6 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
|
||||
}
|
||||
return Texture{
|
||||
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))
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
type Texture struct {
|
||||
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.
|
||||
@ -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))
|
||||
}
|
||||
|
||||
// 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
|
||||
// that is accessible to the GPU.
|
||||
//
|
||||
@ -814,16 +822,8 @@ type Region struct {
|
||||
|
||||
func (r *Region) c() C.struct_Region {
|
||||
return C.struct_Region{
|
||||
Origin: C.struct_Origin{
|
||||
X: C.uint_t(r.Origin.X),
|
||||
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),
|
||||
},
|
||||
Origin: r.Origin.c(),
|
||||
Size: r.Size.c(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -833,12 +833,28 @@ func (r *Region) c() C.struct_Region {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
|
||||
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,
|
||||
// such as an image, texture, threadgroup, or grid.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
|
||||
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.
|
||||
//
|
||||
// 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 *texture, uint_t slice,
|
||||
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);
|
||||
|
||||
@ -171,6 +176,8 @@ void Texture_GetBytes(void *texture, void *pixelBytes, size_t bytesPerRow,
|
||||
struct Region region, uint_t level);
|
||||
void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
|
||||
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_Retain(void *buffer);
|
||||
|
@ -280,6 +280,26 @@ void BlitCommandEncoder_SynchronizeTexture(void *blitCommandEncoder,
|
||||
#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) {
|
||||
return [(id<MTLLibrary>)library
|
||||
newFunctionWithName:[NSString stringWithUTF8String:name]];
|
||||
@ -312,6 +332,10 @@ void Texture_ReplaceRegion(void *texture, struct Region region, uint_t level,
|
||||
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) {
|
||||
memcpy(((id<MTLBuffer>)buffer).contents, data, lengthInBytes);
|
||||
}
|
||||
|
@ -125,9 +125,9 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
cb.WaitUntilCompleted()
|
||||
|
||||
// Read pixels from output texture into an image.
|
||||
got := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
|
||||
bytesPerRow := 4 * texture.Width
|
||||
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
|
||||
got := image.NewNRGBA(image.Rect(0, 0, texture.Width(), texture.Height()))
|
||||
bytesPerRow := 4 * texture.Width()
|
||||
region := mtl.RegionMake2D(0, 0, texture.Width(), texture.Height())
|
||||
texture.GetBytes(&got.Pix[0], uintptr(bytesPerRow), region, 0)
|
||||
|
||||
// TODO: Embed this file?
|
||||
|
Loading…
Reference in New Issue
Block a user