1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

ComponentPeer::VBlankListener: Add timestamp parameter to the vblank callback

This commit is contained in:
attila 2024-10-03 10:58:51 +02:00
parent 4bc2952419
commit d9a3efd3cb
11 changed files with 88 additions and 33 deletions

View file

@ -1,5 +1,29 @@
# JUCE breaking changes
# develop
## Change
The signature of VBlankListener::onVBlank() was changed to
VBlankListener::onVBlank (double), with the addition of a timestamp parameter
that corresponds to the time at which the next frame will be displayed.
**Possible Issues**
Code that overrides VBlankListener::onVBlank() will fail to compile.
**Workaround**
Add a double parameter to the function overriding VBlankListener::onVBlank().
The behaviour will be unchanged if this new parameter is then ignored.
**Rationale**
A timestamp parameter has been missing from the VBlank callback since its
addition. The new parameter allows all VBlankListeners to synchronise the
content of their draw calls to the same frame timestamp.
# Version 8.0.2
## Change

View file

@ -1075,9 +1075,9 @@ public:
return areAnyWindowsInLiveResize();
}
void onVBlank()
void onVBlank (double timestampSec)
{
vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
callVBlankListeners (timestampSec);
setNeedsDisplayRectangles();
}
@ -1758,13 +1758,18 @@ private:
explicit AsyncRepainter (NSViewComponentPeer& o) : owner (o) {}
~AsyncRepainter() override { cancelPendingUpdate(); }
void markUpdated (const CGDirectDisplayID x)
void markUpdated (const CGDirectDisplayID x, double timestampSec)
{
{
const std::scoped_lock lock { mutex };
if (std::find (backgroundDisplays.cbegin(), backgroundDisplays.cend(), x) == backgroundDisplays.cend())
backgroundDisplays.push_back (x);
if (const auto it = std::find_if (backgroundVBlankEvents.cbegin(),
backgroundVBlankEvents.cend(),
[&x] (const auto& event) { return event.display == x; });
it == backgroundVBlankEvents.cend())
{
backgroundVBlankEvents.push_back ({ x, timestampSec });
}
}
triggerAsyncUpdate();
@ -1775,20 +1780,28 @@ private:
{
{
const std::scoped_lock lock { mutex };
mainThreadDisplays = backgroundDisplays;
backgroundDisplays.clear();
mainThreadVBlankEvents = backgroundVBlankEvents;
backgroundVBlankEvents.clear();
}
for (const auto& display : mainThreadDisplays)
for (const auto& event : mainThreadVBlankEvents)
{
if (auto* peerView = owner.view)
if (auto* peerWindow = [peerView window])
if (display == ScopedDisplayLink::getDisplayIdForScreen ([peerWindow screen]))
owner.onVBlank();
if (event.display == ScopedDisplayLink::getDisplayIdForScreen ([peerWindow screen]))
owner.onVBlank (event.timeSec);
}
}
struct VBlankEvent
{
CGDirectDisplayID display{};
double timeSec{};
};
NSViewComponentPeer& owner;
std::mutex mutex;
std::vector<CGDirectDisplayID> backgroundDisplays, mainThreadDisplays;
std::vector<VBlankEvent> backgroundVBlankEvents, mainThreadVBlankEvents;
};
AsyncRepainter asyncRepainter { *this };
@ -1801,7 +1814,10 @@ private:
{
sharedDisplayLinks->registerFactory ([this] (CGDirectDisplayID display)
{
return [this, display] { asyncRepainter.markUpdated (display); };
return [this, display] (double timestampSec)
{
asyncRepainter.markUpdated (display, timestampSec);
};
})
};

View file

@ -119,7 +119,7 @@ public:
return (CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue];
}
ScopedDisplayLink (NSScreen* screenIn, std::function<void()> onCallbackIn)
ScopedDisplayLink (NSScreen* screenIn, std::function<void (double)> onCallbackIn)
: displayId (getDisplayIdForScreen (screenIn)),
link ([display = displayId]
{
@ -133,12 +133,13 @@ public:
{
const auto callback = [] (CVDisplayLinkRef,
const CVTimeStamp*,
const CVTimeStamp*,
const CVTimeStamp* outputTime,
CVOptionFlags,
CVOptionFlags*,
void* context) -> int
{
static_cast<const ScopedDisplayLink*> (context)->onCallback();
const auto outputTimeSec = (double) outputTime->videoTime / (double) outputTime->videoTimeScale;
static_cast<const ScopedDisplayLink*> (context)->onCallback (outputTimeSec);
return kCVReturnSuccess;
};
@ -179,7 +180,7 @@ private:
CGDirectDisplayID displayId;
std::unique_ptr<std::remove_pointer_t<CVDisplayLinkRef>, DisplayLinkDestructor> link;
std::function<void()> onCallback;
std::function<void (double)> onCallback;
// Instances can't be copied or moved, because 'this' is passed as context to
// CVDisplayLinkSetOutputCallback
@ -202,7 +203,7 @@ public:
refreshScreens();
}
using RefreshCallback = std::function<void()>;
using RefreshCallback = std::function<void (double)>;
using Factory = std::function<RefreshCallback (CGDirectDisplayID)>;
/*
@ -293,10 +294,10 @@ private:
// This is the callback that will actually fire in response to this screen's display
// link callback.
result.emplace_back (screen, [cbs = std::move (callbacks)]
result.emplace_back (screen, [cbs = std::move (callbacks)] (double timestampSec)
{
for (const auto& callback : cbs)
callback();
callback (timestampSec);
});
}

View file

@ -420,7 +420,7 @@ public:
void setIcon (const Image& newIcon) override;
StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
void displayLinkCallback();
void displayLinkCallback (double timestampSec);
void drawRect (CGRect);
void drawRectWithContext (CGContextRef, CGRect);
@ -768,7 +768,7 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
- (void) displayLinkCallback: (CADisplayLink*) dl
{
if (owner != nullptr)
owner->displayLinkCallback();
owner->displayLinkCallback (dl.targetTimestamp);
}
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
@ -2160,9 +2160,9 @@ void UIViewComponentPeer::dismissPendingTextInput()
}
//==============================================================================
void UIViewComponentPeer::displayLinkCallback()
void UIViewComponentPeer::displayLinkCallback (double timestampSec)
{
vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
callVBlankListeners (timestampSec);
if (deferredRepaints.isEmpty())
return;

View file

@ -122,7 +122,7 @@ private:
if (output->WaitForVBlank() == S_OK)
{
if (const auto now = Time::getMillisecondCounterHiRes();
now - std::exchange (lastVBlankEvent, now) < 1.0)
now - lastVBlankEvent.exchange (now) < 1.0)
{
Thread::sleep (1);
}
@ -145,8 +145,10 @@ private:
void handleAsyncUpdate() override
{
const auto timestampSec = lastVBlankEvent / 1000.0;
for (auto& listener : listeners)
listener.get().onVBlank();
listener.get().onVBlank (timestampSec);
{
const std::scoped_lock lock { mutex };
@ -170,7 +172,7 @@ private:
exit,
};
double lastVBlankEvent = 0.0;
std::atomic<double> lastVBlankEvent{};
ThreadState threadState = ThreadState::paint;
std::condition_variable condvar;
std::mutex mutex;

View file

@ -1848,7 +1848,8 @@ public:
//==============================================================================
static void handleDoFrameCallback (JNIEnv*, AndroidComponentPeer& t, [[maybe_unused]] int64 frameTimeNanos)
{
t.vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
const auto timestampSec = (double) frameTimeNanos / (double) 1'000'000'000;
t.callVBlankListeners (timestampSec);
}
static void handlePaintCallback (JNIEnv* env, AndroidComponentPeer& t, jobject canvas, jobject paint)

View file

@ -573,7 +573,8 @@ private:
void onVBlank()
{
vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
const auto timestampSec = Time::getMillisecondCounterHiRes() / 1000.0;
callVBlankListeners (timestampSec);
if (repainter != nullptr)
repainter->dispatchDeferredRepaints();

View file

@ -1920,9 +1920,9 @@ public:
}
//==============================================================================
void onVBlank() override
void onVBlank (double timestampSec) override
{
vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
callVBlankListeners (timestampSec);
dispatchDeferredRepaints();
if (renderContext != nullptr)

View file

@ -617,6 +617,11 @@ void ComponentPeer::forceDisplayUpdate()
Desktop::getInstance().displays->refresh();
}
void ComponentPeer::callVBlankListeners (double timestampSec)
{
vBlankListeners.call ([timestampSec] (auto& l) { l.onVBlank (timestampSec); });
}
void ComponentPeer::globalFocusChanged ([[maybe_unused]] Component* comp)
{
refreshTextInputTarget();

View file

@ -516,8 +516,12 @@ public:
/** Destructor. */
virtual ~VBlankListener() = default;
/** Called on every vertical blank of the display to which the peer is associated. */
virtual void onVBlank() = 0;
/** Called on every vertical blank of the display to which the peer is associated.
The timestampSec parameter is a monotonically increasing value expressed in seconds
that corresponds to the time at which the next frame will be displayed.
*/
virtual void onVBlank (double timestampSec) = 0;
};
/** Adds a VBlankListener. */
@ -577,6 +581,7 @@ public:
protected:
//==============================================================================
static void forceDisplayUpdate();
void callVBlankListeners (double timestampSec);
Component& component;
const int styleFlags;

View file

@ -899,7 +899,7 @@ public:
{
connection.emplace (sharedDisplayLinks->registerFactory ([this] (CGDirectDisplayID display)
{
return [this, display]
return [this, display] (double)
{
if (display == lastDisplay)
triggerRepaint();