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:
@@ -151,6 +151,19 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture,
|
||||
render_ctx_->DownloadFromTexture(texture->id(), texture->params(),
|
||||
frame->data(),
|
||||
frame->linesize_pixels());
|
||||
|
||||
// Diagnostic: check if downloaded frame is all black
|
||||
bool all_black = true;
|
||||
const uint8_t *pixels = reinterpret_cast<const uint8_t *>(frame->data());
|
||||
size_t total_bytes = frame->allocated_size();
|
||||
for (size_t i = 0; i < std::min(total_bytes, size_t(1024)); ++i) {
|
||||
if (pixels[i] != 0) {
|
||||
all_black = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "[RENDER] GenerateFrame DownloadFromTexture all_black="
|
||||
<< all_black << "total_bytes=" << total_bytes;
|
||||
}
|
||||
|
||||
return frame;
|
||||
@@ -196,6 +209,9 @@ void RenderProcessor::Run()
|
||||
}
|
||||
|
||||
TexturePtr texture = GenerateTexture(time, frame_length);
|
||||
qDebug() << "[RENDER] GenerateTexture time=" << time.toDouble()
|
||||
<< "tex_null=" << (texture == nullptr)
|
||||
<< "tex_dummy=" << (texture ? texture->IsDummy() : true);
|
||||
|
||||
if (!render_ctx_) {
|
||||
ticket_->Finish();
|
||||
@@ -219,6 +235,7 @@ void RenderProcessor::Run()
|
||||
if (HeardCancel()) {
|
||||
// Finish cancelled ticket with nothing since we can't guarantee the frame we generated
|
||||
// is actually "complete
|
||||
qDebug() << "[RENDER] HeardCancel, finishing empty";
|
||||
ticket_->Finish();
|
||||
} else {
|
||||
FramePtr frame;
|
||||
@@ -252,7 +269,7 @@ void RenderProcessor::Run()
|
||||
}
|
||||
|
||||
render_ctx_->Flush();
|
||||
|
||||
qDebug() << "[RENDER] Finishing with texture";
|
||||
ticket_->Finish(QVariant::fromValue(texture));
|
||||
} else {
|
||||
ticket_->Finish(QVariant::fromValue(frame));
|
||||
@@ -486,6 +503,9 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination,
|
||||
}
|
||||
|
||||
render_ctx_->BlitColorManaged(job, destination.get());
|
||||
// macOS TBDR: ensure tile writeback completes before the texture
|
||||
// is read back in a potentially different shared OpenGL context.
|
||||
render_ctx_->Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -711,6 +731,27 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob *val)
|
||||
{
|
||||
FramePtr frame = FrameHashCache::LoadCacheFrame(val->GetFilename());
|
||||
if (frame) {
|
||||
// Auto-detect and discard black/empty cached frames (macOS TBDR artifact)
|
||||
bool all_black = true;
|
||||
if (frame->data() && frame->allocated_size() > 0) {
|
||||
const uint8_t *pixels = reinterpret_cast<const uint8_t *>(frame->data());
|
||||
size_t alloc_size = static_cast<size_t>(frame->allocated_size());
|
||||
size_t check_bytes = std::min(alloc_size, size_t(4096));
|
||||
for (size_t i = 0; i < check_bytes; ++i) {
|
||||
if (pixels[i] != 0) {
|
||||
all_black = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (all_black) {
|
||||
qWarning() << "[CACHE] Discarding black cached frame:" << val->GetFilename()
|
||||
<< "time=" << frame->timestamp().toDouble()
|
||||
<< "size=" << frame->allocated_size();
|
||||
QFile::remove(val->GetFilename());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TexturePtr tex = CreateTexture(frame->video_params());
|
||||
if (tex) {
|
||||
tex->Upload(frame->data(), frame->linesize_pixels());
|
||||
|
||||
Reference in New Issue
Block a user