fix(renderer): macOS TBDR cross-context sync and OFX instance thread-safety
This commit fixes crashes (SIGSEGV in CImg blur_bilateral) and black-frame corruption artifacts during playback and scrubbing on macOS Apple Silicon. Root cause analysis: 1. macOS uses Tile-Based Deferred Rendering (TBDR). glFlush() does not guarantee tile memory writeback, causing glReadPixels to read incomplete tiles (black/corrupted frames) and cache them to disk. 2. Olive uses multiple shared OpenGL contexts (RenderProcessor contexts vs. thread-local PluginRenderer context). glFinish() only waits for the current context, not the shared context that produced the texture. CPU readback in PluginRenderer could read partially-rendered tiles. 3. OlivePluginInstance and OliveClipInstance are not thread-safe. Concurrent RenderProcessors could corrupt internal QMap/images_ and params_ via setInputTexture/renderAction races. Fixes: - OpenGLRenderer::Flush() on macOS now uses glFinish() unconditionally. - OpenGLRenderer::DownloadFromTexture() and OpenGLRenderer::Blit() insert glFinish() before readback/detach to ensure tile writeback completes. - PluginRenderer::RenderPlugin() now acquires a per-instance mutex to serialize concurrent OFX render calls. - Before CPU readback in PluginRenderer, flush the renderer that originally produced each input texture, ensuring cross-context synchronization. - RenderProcessor::ProcessVideoFootage() flushes after BlitColorManaged. - Add black-frame detection in ProcessVideoCacheJob() to auto-purge TBDR- corrupted cache files. - Add diagnostic qDebug() logging in viewer, decoder, renderer, and plugin paths to aid future debugging.
This commit is contained in:
@@ -205,6 +205,9 @@ void ViewerWidget::TimeChangedEvent(const rational &time)
|
||||
|
||||
if (GetConnectedNode() && last_time_ != time) {
|
||||
if (!IsPlaying()) {
|
||||
qDebug() << "[VIEWER] TimeChanged seeking to" << time.toDouble()
|
||||
<< "frame_exists=" << FrameExistsAtTime(time)
|
||||
<< "might_be_still=" << ViewerMightBeAStill();
|
||||
UpdateTextureFromNode();
|
||||
|
||||
PushScrubbedAudio();
|
||||
@@ -1445,16 +1448,30 @@ void ViewerWidget::WindowAboutToClose()
|
||||
void ViewerWidget::RendererGeneratedFrame()
|
||||
{
|
||||
RenderTicketWatcher *ticket = static_cast<RenderTicketWatcher *>(sender());
|
||||
rational t = ticket->property("time").value<rational>();
|
||||
bool has_result = ticket->HasResult();
|
||||
qDebug() << "[VIEWER] RendererGeneratedFrame time=" << t.toDouble()
|
||||
<< "has_result=" << has_result
|
||||
<< "nonqueue_size=" << nonqueue_watchers_.size();
|
||||
|
||||
if (ticket->HasResult()) {
|
||||
if (nonqueue_watchers_.contains(ticket)) {
|
||||
while (!nonqueue_watchers_.isEmpty()) {
|
||||
// Pop frames that are "old"
|
||||
if (nonqueue_watchers_.takeFirst() == ticket) {
|
||||
break;
|
||||
}
|
||||
if (nonqueue_watchers_.contains(ticket)) {
|
||||
while (!nonqueue_watchers_.isEmpty()) {
|
||||
// Pop frames that are "old"
|
||||
if (nonqueue_watchers_.takeFirst() == ticket) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ticket->HasResult()) {
|
||||
QVariant v = ticket->Get();
|
||||
bool is_tex = v.canConvert<TexturePtr>();
|
||||
bool is_frame = v.canConvert<FramePtr>();
|
||||
TexturePtr tex = v.value<TexturePtr>();
|
||||
qDebug() << "[VIEWER] SetDisplayImage time=" << t.toDouble()
|
||||
<< "is_texture=" << is_tex
|
||||
<< "is_frame=" << is_frame
|
||||
<< "tex_null=" << (tex == nullptr)
|
||||
<< "tex_dummy=" << (tex ? tex->IsDummy() : true);
|
||||
SetDisplayImage(ticket->GetTicket());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user