mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-29 08:15:18 -04:00
cocoa: Improvements to VSync implementation:
- Use futex instead of condition var - Prepare for adaptive sync support (#1553) - Support in tinydisplay
This commit is contained in:
parent
034f1ea358
commit
a52744a973
@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
#include "pandabase.h"
|
#include "pandabase.h"
|
||||||
#include "graphicsPipe.h"
|
#include "graphicsPipe.h"
|
||||||
|
#include "patomic.h"
|
||||||
|
|
||||||
#include <ApplicationServices/ApplicationServices.h>
|
#include <ApplicationServices/ApplicationServices.h>
|
||||||
|
#include <CoreVideo/CoreVideo.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This graphics pipe represents the base class for pipes that create
|
* This graphics pipe represents the base class for pipes that create
|
||||||
@ -28,17 +30,28 @@ public:
|
|||||||
CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
|
CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
|
||||||
virtual ~CocoaGraphicsPipe();
|
virtual ~CocoaGraphicsPipe();
|
||||||
|
|
||||||
INLINE CGDirectDisplayID get_display_id() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual PreferredWindowThread get_preferred_window_thread() const;
|
virtual PreferredWindowThread get_preferred_window_thread() const;
|
||||||
|
|
||||||
|
INLINE CGDirectDisplayID get_display_id() const;
|
||||||
|
|
||||||
|
bool init_vsync(uint32_t &counter);
|
||||||
|
void wait_vsync(uint32_t &counter, bool adaptive=false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static CVReturn display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
|
||||||
|
const CVTimeStamp *output_time,
|
||||||
|
CVOptionFlags flags_in, CVOptionFlags *flags_out,
|
||||||
|
void *context);
|
||||||
|
|
||||||
void load_display_information();
|
void load_display_information();
|
||||||
|
|
||||||
// This is the Quartz display identifier.
|
// This is the Quartz display identifier.
|
||||||
CGDirectDisplayID _display;
|
CGDirectDisplayID _display;
|
||||||
|
|
||||||
|
CVDisplayLinkRef _display_link = nullptr;
|
||||||
|
patomic<int> _last_wait_frame {0};
|
||||||
|
uint32_t _vsync_counter = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static TypeHandle get_class_type() {
|
static TypeHandle get_class_type() {
|
||||||
return _type_handle;
|
return _type_handle;
|
||||||
|
@ -76,6 +76,13 @@ CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
|
|||||||
cocoadisplay_cat.debug()
|
cocoadisplay_cat.debug()
|
||||||
<< "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
|
<< "Creating CocoaGraphicsPipe for display ID " << _display << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It takes a while to fire up the display link, so let's fire it up now if
|
||||||
|
// we expect to need VSync.
|
||||||
|
if (sync_video) {
|
||||||
|
uint32_t counter;
|
||||||
|
init_vsync(counter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,6 +178,14 @@ load_display_information() {
|
|||||||
*/
|
*/
|
||||||
CocoaGraphicsPipe::
|
CocoaGraphicsPipe::
|
||||||
~CocoaGraphicsPipe() {
|
~CocoaGraphicsPipe() {
|
||||||
|
if (_display_link != nil) {
|
||||||
|
CVDisplayLinkRelease(_display_link);
|
||||||
|
_display_link = nil;
|
||||||
|
|
||||||
|
// Unblock any threads that may be waiting on the VSync counter.
|
||||||
|
__atomic_fetch_add(&_vsync_counter, 1u, __ATOMIC_SEQ_CST);
|
||||||
|
patomic_notify_all(&_vsync_counter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,3 +199,112 @@ CocoaGraphicsPipe::get_preferred_window_thread() const {
|
|||||||
// only be called from the main thread!
|
// only be called from the main thread!
|
||||||
return PWT_app;
|
return PWT_app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures a CVDisplayLink is created, which tells us when the display will
|
||||||
|
* want a frame, to avoid tearing (vertical blanking interval).
|
||||||
|
* Initializes the counter with the value that can be passed to wait_vsync
|
||||||
|
* to wait for the next interval.
|
||||||
|
*/
|
||||||
|
bool CocoaGraphicsPipe::
|
||||||
|
init_vsync(uint32_t &counter) {
|
||||||
|
if (_display_link != nil) {
|
||||||
|
// Already set up.
|
||||||
|
__atomic_load(&_vsync_counter, &counter, __ATOMIC_SEQ_CST);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
counter = 0;
|
||||||
|
_vsync_counter = 0;
|
||||||
|
|
||||||
|
CVDisplayLinkRef display_link;
|
||||||
|
CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&display_link);
|
||||||
|
if (result != kCVReturnSuccess) {
|
||||||
|
cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
|
||||||
|
display_link = nil;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CVDisplayLinkSetCurrentCGDisplay(display_link, _display);
|
||||||
|
if (result != kCVReturnSuccess) {
|
||||||
|
cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
|
||||||
|
CVDisplayLinkRelease(display_link);
|
||||||
|
display_link = nil;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CVDisplayLinkSetOutputCallback(display_link, &display_link_cb, this);
|
||||||
|
if (result != kCVReturnSuccess) {
|
||||||
|
cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
|
||||||
|
CVDisplayLinkRelease(display_link);
|
||||||
|
display_link = nil;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CVDisplayLinkStart(display_link);
|
||||||
|
if (result != kCVReturnSuccess) {
|
||||||
|
cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
|
||||||
|
CVDisplayLinkRelease(display_link);
|
||||||
|
display_link = nil;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_display_link = display_link;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first time this method is called in a frame, waits for the vertical
|
||||||
|
* blanking interval. If init_vsync has not first been called, does nothing.
|
||||||
|
*
|
||||||
|
* The given counter will be updated with the vblank counter. If adaptive is
|
||||||
|
* true and the value differs from the current, no wait will occur.
|
||||||
|
*/
|
||||||
|
void CocoaGraphicsPipe::
|
||||||
|
wait_vsync(uint32_t &counter, bool adaptive) {
|
||||||
|
if (_display_link == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use direct atomic operations since we need this to be thread-safe even
|
||||||
|
// when compiling without thread support.
|
||||||
|
uint32_t current_count = __atomic_load_n(&_vsync_counter, __ATOMIC_SEQ_CST);
|
||||||
|
uint32_t diff = current_count - counter;
|
||||||
|
if (diff > 0) {
|
||||||
|
if (cocoadisplay_cat.is_spam()) {
|
||||||
|
cocoadisplay_cat.spam()
|
||||||
|
<< "Missed vertical blanking interval by " << diff << " frames.\n";
|
||||||
|
}
|
||||||
|
if (adaptive) {
|
||||||
|
counter = current_count;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only wait for the first window that gets flipped in a single frame,
|
||||||
|
// otherwise we end up halving our FPS when we have multiple windows!
|
||||||
|
int cur_frame = ClockObject::get_global_clock()->get_frame_count();
|
||||||
|
if (_last_wait_frame.exchange(cur_frame) == cur_frame) {
|
||||||
|
counter = current_count;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
patomic_wait(&_vsync_counter, current_count);
|
||||||
|
__atomic_load(&_vsync_counter, &counter, __ATOMIC_SEQ_CST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever a display wants a frame. The context argument contains the
|
||||||
|
* applicable CocoaGraphicsPipe.
|
||||||
|
*/
|
||||||
|
CVReturn CocoaGraphicsPipe::
|
||||||
|
display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
|
||||||
|
const CVTimeStamp *output_time, CVOptionFlags flags_in,
|
||||||
|
CVOptionFlags *flags_out, void *context) {
|
||||||
|
|
||||||
|
CocoaGraphicsPipe *pipe = (CocoaGraphicsPipe *)context;
|
||||||
|
__atomic_fetch_add(&pipe->_vsync_counter, 1u, __ATOMIC_SEQ_CST);
|
||||||
|
patomic_notify_all(&pipe->_vsync_counter);
|
||||||
|
|
||||||
|
return kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
#import <AppKit/NSOpenGL.h>
|
#import <AppKit/NSOpenGL.h>
|
||||||
#import <OpenGL/OpenGL.h>
|
#import <OpenGL/OpenGL.h>
|
||||||
#import <CoreVideo/CoreVideo.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
|
* A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
|
||||||
@ -39,7 +38,6 @@ public:
|
|||||||
CocoaGLGraphicsStateGuardian *share_with);
|
CocoaGLGraphicsStateGuardian *share_with);
|
||||||
|
|
||||||
virtual ~CocoaGLGraphicsStateGuardian();
|
virtual ~CocoaGLGraphicsStateGuardian();
|
||||||
bool setup_vsync();
|
|
||||||
|
|
||||||
INLINE void lock_context();
|
INLINE void lock_context();
|
||||||
INLINE void unlock_context();
|
INLINE void unlock_context();
|
||||||
@ -49,11 +47,6 @@ public:
|
|||||||
NSOpenGLPixelFormat *_format = nullptr;
|
NSOpenGLPixelFormat *_format = nullptr;
|
||||||
FrameBufferProperties _fbprops;
|
FrameBufferProperties _fbprops;
|
||||||
|
|
||||||
CVDisplayLinkRef _display_link = nullptr;
|
|
||||||
TrueMutexImpl _swap_lock;
|
|
||||||
TrueConditionVarImpl _swap_condition;
|
|
||||||
AtomicAdjust::Integer _last_wait_frame = 0;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void query_gl_version();
|
virtual void query_gl_version();
|
||||||
virtual void *do_get_extension_func(const char *name);
|
virtual void *do_get_extension_func(const char *name);
|
||||||
|
@ -28,21 +28,6 @@
|
|||||||
#define NSAppKitVersionNumber10_7 1138
|
#define NSAppKitVersionNumber10_7 1138
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever a display wants a frame. The context argument contains the
|
|
||||||
* applicable CocoaGLGraphicsStateGuardian.
|
|
||||||
*/
|
|
||||||
static CVReturn
|
|
||||||
display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
|
|
||||||
const CVTimeStamp* output_time, CVOptionFlags flags_in,
|
|
||||||
CVOptionFlags *flags_out, void *context) {
|
|
||||||
CocoaGLGraphicsStateGuardian *gsg = (CocoaGLGraphicsStateGuardian *)context;
|
|
||||||
gsg->_swap_lock.lock();
|
|
||||||
gsg->_swap_condition.notify();
|
|
||||||
gsg->_swap_lock.unlock();
|
|
||||||
return kCVReturnSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
|
TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,8 +36,7 @@ TypeHandle CocoaGLGraphicsStateGuardian::_type_handle;
|
|||||||
CocoaGLGraphicsStateGuardian::
|
CocoaGLGraphicsStateGuardian::
|
||||||
CocoaGLGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
|
CocoaGLGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
|
||||||
CocoaGLGraphicsStateGuardian *share_with) :
|
CocoaGLGraphicsStateGuardian *share_with) :
|
||||||
GLGraphicsStateGuardian(engine, pipe),
|
GLGraphicsStateGuardian(engine, pipe)
|
||||||
_swap_condition(_swap_lock)
|
|
||||||
{
|
{
|
||||||
_share_context = nil;
|
_share_context = nil;
|
||||||
_context = nil;
|
_context = nil;
|
||||||
@ -71,57 +55,12 @@ CocoaGLGraphicsStateGuardian::
|
|||||||
if (_format != nil) {
|
if (_format != nil) {
|
||||||
[_format release];
|
[_format release];
|
||||||
}
|
}
|
||||||
if (_display_link != nil) {
|
|
||||||
CVDisplayLinkRelease(_display_link);
|
|
||||||
_display_link = nil;
|
|
||||||
_swap_lock.lock();
|
|
||||||
_swap_condition.notify();
|
|
||||||
_swap_lock.unlock();
|
|
||||||
}
|
|
||||||
if (_context != nil) {
|
if (_context != nil) {
|
||||||
[_context clearDrawable];
|
[_context clearDrawable];
|
||||||
[_context release];
|
[_context release];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a CVDisplayLink, which tells us when the display the window is on
|
|
||||||
* will want a frame.
|
|
||||||
*/
|
|
||||||
bool CocoaGLGraphicsStateGuardian::
|
|
||||||
setup_vsync() {
|
|
||||||
if (_display_link != nil) {
|
|
||||||
// Already set up.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
|
|
||||||
if (result != kCVReturnSuccess) {
|
|
||||||
cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
|
|
||||||
if (result != kCVReturnSuccess) {
|
|
||||||
cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
|
|
||||||
if (result != kCVReturnSuccess) {
|
|
||||||
cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CVDisplayLinkStart(_display_link);
|
|
||||||
if (result != kCVReturnSuccess) {
|
|
||||||
cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the FrameBufferProperties to match the indicated config.
|
* Gets the FrameBufferProperties to match the indicated config.
|
||||||
*/
|
*/
|
||||||
|
@ -42,6 +42,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool _vsync_enabled = false;
|
bool _vsync_enabled = false;
|
||||||
|
uint32_t _vsync_counter = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static TypeHandle get_class_type() {
|
static TypeHandle get_class_type() {
|
||||||
|
@ -159,13 +159,16 @@ end_flip() {
|
|||||||
CocoaGLGraphicsStateGuardian *cocoagsg;
|
CocoaGLGraphicsStateGuardian *cocoagsg;
|
||||||
DCAST_INTO_V(cocoagsg, _gsg);
|
DCAST_INTO_V(cocoagsg, _gsg);
|
||||||
|
|
||||||
if (_vsync_enabled) {
|
if (sync_video) {
|
||||||
AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count();
|
CocoaGraphicsPipe *cocoapipe = (CocoaGraphicsPipe *)_pipe.p();
|
||||||
if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) {
|
if (!_vsync_enabled) {
|
||||||
cocoagsg->_swap_lock.lock();
|
// If this fails, we don't keep trying.
|
||||||
cocoagsg->_swap_condition.wait();
|
cocoapipe->init_vsync(_vsync_counter);
|
||||||
cocoagsg->_swap_lock.unlock();
|
_vsync_enabled = true;
|
||||||
}
|
}
|
||||||
|
cocoapipe->wait_vsync(_vsync_counter);
|
||||||
|
} else {
|
||||||
|
_vsync_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cocoagsg->lock_context();
|
cocoagsg->lock_context();
|
||||||
@ -237,8 +240,6 @@ open_window() {
|
|||||||
}
|
}
|
||||||
_fb_properties = cocoagsg->get_fb_properties();
|
_fb_properties = cocoagsg->get_fb_properties();
|
||||||
|
|
||||||
_vsync_enabled = sync_video && cocoagsg->setup_vsync();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,8 @@ private:
|
|||||||
|
|
||||||
small_vector<SwapBuffer, 2> _swap_chain;
|
small_vector<SwapBuffer, 2> _swap_chain;
|
||||||
int _swap_index = 0;
|
int _swap_index = 0;
|
||||||
|
uint32_t _vsync_counter = 0;
|
||||||
|
bool _vsync_enabled = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static TypeHandle get_class_type() {
|
static TypeHandle get_class_type() {
|
||||||
|
@ -114,6 +114,21 @@ end_flip() {
|
|||||||
if (_flip_ready) {
|
if (_flip_ready) {
|
||||||
do_present();
|
do_present();
|
||||||
_swap_index = (_swap_index + 1) % _swap_chain.size();
|
_swap_index = (_swap_index + 1) % _swap_chain.size();
|
||||||
|
|
||||||
|
// We don't really support proper VSync because we just update the backing
|
||||||
|
// store and let the OS update it when needed, but we still need to wait
|
||||||
|
// for VBlank so that the frame rate is appropriately limited.
|
||||||
|
if (sync_video) {
|
||||||
|
CocoaGraphicsPipe *cocoapipe = (CocoaGraphicsPipe *)_pipe.p();
|
||||||
|
if (!_vsync_enabled) {
|
||||||
|
// If this fails, we don't keep trying.
|
||||||
|
cocoapipe->init_vsync(_vsync_counter);
|
||||||
|
_vsync_enabled = true;
|
||||||
|
}
|
||||||
|
cocoapipe->wait_vsync(_vsync_counter);
|
||||||
|
} else {
|
||||||
|
_vsync_enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsWindow::end_flip();
|
GraphicsWindow::end_flip();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user