internal/graphicsdriver/metal: Make FPS stable by 'presentsWithTransaction'

Closes #1196
This commit is contained in:
Hajime Hoshi 2021-07-08 04:21:20 +09:00
parent ae0e30196b
commit 4573883b03
8 changed files with 57 additions and 2 deletions

View File

@ -147,6 +147,13 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
return MetalDrawable{md}, nil return MetalDrawable{md}, nil
} }
// PresentsWithTransaction returns a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
//
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
func (ml MetalLayer) PresentsWithTransaction() bool {
return C.MetalLayer_PresentsWithTransaction(ml.metalLayer) != 0
}
// SetFramebufferOnly sets a Boolean value that determines whether the layers textures are used only for rendering. // SetFramebufferOnly sets a Boolean value that determines whether the layers textures are used only for rendering.
// //
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly // https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
@ -175,3 +182,10 @@ func (md MetalDrawable) Drawable() unsafe.Pointer { return md.metalDrawable }
func (md MetalDrawable) Texture() mtl.Texture { func (md MetalDrawable) Texture() mtl.Texture {
return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable)) return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable))
} }
// Present presents the drawable onscreen as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present.
func (md MetalDrawable) Present() {
C.MetalDrawable_Present(md.metalDrawable)
}

View File

@ -31,5 +31,7 @@ void MetalLayer_SetDisplaySyncEnabled(void *metalLayer,
void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height); void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height);
void *MetalLayer_NextDrawable(void *metalLayer); void *MetalLayer_NextDrawable(void *metalLayer);
void MetalLayer_SetFramebufferOnly(void *metalLayer, uint8_t framebufferOnly); void MetalLayer_SetFramebufferOnly(void *metalLayer, uint8_t framebufferOnly);
uint8_t MetalLayer_PresentsWithTransaction(void *metalLayer);
void *MetalDrawable_Texture(void *drawable); void *MetalDrawable_Texture(void *drawable);
void MetalDrawable_Present(void *drawable);

View File

@ -85,6 +85,14 @@ void MetalLayer_SetDisplaySyncEnabled(void *metalLayer,
[((CAMetalLayer *)metalLayer) setDisplaySyncEnabled:displaySyncEnabled]; [((CAMetalLayer *)metalLayer) setDisplaySyncEnabled:displaySyncEnabled];
} }
#endif #endif
// setting presentsWithTransaction YES makes the FPS stable (#1196). We're not
// sure why...
if (displaySyncEnabled) {
[((CAMetalLayer *)metalLayer) setPresentsWithTransaction:YES];
} else {
[((CAMetalLayer *)metalLayer) setPresentsWithTransaction:NO];
}
} }
void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height) { void MetalLayer_SetDrawableSize(void *metalLayer, double width, double height) {
@ -99,6 +107,14 @@ void *MetalDrawable_Texture(void *metalDrawable) {
return ((id<CAMetalDrawable>)metalDrawable).texture; return ((id<CAMetalDrawable>)metalDrawable).texture;
} }
void MetalDrawable_Present(void *metalDrawable) {
[((id<CAMetalDrawable>)metalDrawable) present];
}
void MetalLayer_SetFramebufferOnly(void *metalLayer, uint8_t framebufferOnly) { void MetalLayer_SetFramebufferOnly(void *metalLayer, uint8_t framebufferOnly) {
[((CAMetalLayer *)metalLayer) setFramebufferOnly:framebufferOnly]; [((CAMetalLayer *)metalLayer) setFramebufferOnly:framebufferOnly];
} }
uint8_t MetalLayer_PresentsWithTransaction(void *metalLayer) {
return [((CAMetalLayer *)metalLayer) presentsWithTransaction];
}

View File

@ -467,10 +467,14 @@ func (g *Graphics) flushIfNeeded(present bool) {
} }
g.flushRenderCommandEncoderIfNeeded() g.flushRenderCommandEncoderIfNeeded()
if present && g.screenDrawable != (ca.MetalDrawable{}) { if !g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.PresentDrawable(g.screenDrawable) g.cb.PresentDrawable(g.screenDrawable)
} }
g.cb.Commit() g.cb.Commit()
if g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) {
g.cb.WaitUntilScheduled()
g.screenDrawable.Present()
}
for _, t := range g.tmpTextures { for _, t := range g.tmpTextures {
t.Release() t.Release()

View File

@ -670,6 +670,13 @@ func (cb CommandBuffer) WaitUntilCompleted() {
C.CommandBuffer_WaitUntilCompleted(cb.commandBuffer) C.CommandBuffer_WaitUntilCompleted(cb.commandBuffer)
} }
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled.
func (cb CommandBuffer) WaitUntilScheduled() {
C.CommandBuffer_WaitUntilScheduled(cb.commandBuffer)
}
// MakeRenderCommandEncoder creates an encoder object that can // MakeRenderCommandEncoder creates an encoder object that can
// encode graphics rendering commands into this command buffer. // encode graphics rendering commands into this command buffer.
// //

View File

@ -153,6 +153,7 @@ uint8_t CommandBuffer_Status(void *commandBuffer);
void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable); void CommandBuffer_PresentDrawable(void *commandBuffer, void *drawable);
void CommandBuffer_Commit(void *commandBuffer); void CommandBuffer_Commit(void *commandBuffer);
void CommandBuffer_WaitUntilCompleted(void *commandBuffer); void CommandBuffer_WaitUntilCompleted(void *commandBuffer);
void CommandBuffer_WaitUntilScheduled(void *commandBuffer);
void * void *
CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer, CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer,
struct RenderPassDescriptor descriptor); struct RenderPassDescriptor descriptor);

View File

@ -188,6 +188,10 @@ void CommandBuffer_WaitUntilCompleted(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer waitUntilCompleted]; [(id<MTLCommandBuffer>)commandBuffer waitUntilCompleted];
} }
void CommandBuffer_WaitUntilScheduled(void *commandBuffer) {
[(id<MTLCommandBuffer>)commandBuffer waitUntilScheduled];
}
void * void *
CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer, CommandBuffer_MakeRenderCommandEncoder(void *commandBuffer,
struct RenderPassDescriptor descriptor) { struct RenderPassDescriptor descriptor) {

View File

@ -69,7 +69,10 @@ func (v *view) reset() error {
// MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or // MTLPixelFormatBGRA8Unorm_sRGB, MTLPixelFormatRGBA16Float, MTLPixelFormatBGRA10_XR, or
// MTLPixelFormatBGRA10_XR_sRGB. // MTLPixelFormatBGRA10_XR_sRGB.
v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) v.ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
v.ml.SetMaximumDrawableCount(3)
// When presentsWithTransaction is YES and triple buffering is enabled, nextDrawing returns immediately once every two times.
// This makes FPS doubled. To avoid this, disable the triple buffering.
v.ml.SetMaximumDrawableCount(2)
// The vsync state might be reset. Set the state again (#1364). // The vsync state might be reset. Set the state again (#1364).
v.ml.SetDisplaySyncEnabled(v.vsync) v.ml.SetDisplaySyncEnabled(v.vsync)
@ -86,3 +89,7 @@ func (v *view) nextDrawable() ca.MetalDrawable {
} }
return d return d
} }
func (v *view) presentsWithTransaction() bool {
return v.ml.PresentsWithTransaction()
}