Wire render worker pool into render manager
This commit is contained in:
@@ -144,6 +144,8 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"),
|
||||
NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("RenderProcessIsolationEnabled"),
|
||||
NodeValue::kBoolean, false);
|
||||
|
||||
SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt,
|
||||
Timeline::kThumbnailInOut);
|
||||
|
||||
@@ -54,6 +54,8 @@ set(OLIVE_SOURCES
|
||||
render/renderjobtracker.h
|
||||
render/rendermanager.cpp
|
||||
render/rendermanager.h
|
||||
render/renderworkerpool.cpp
|
||||
render/renderworkerpool.h
|
||||
render/rendermodes.h
|
||||
render/renderprocessor.cpp
|
||||
render/renderprocessor.h
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "core.h"
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
#include "renderprocessor.h"
|
||||
#include "renderworkerpool.h"
|
||||
#include "task/conform/conform.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
@@ -42,6 +43,7 @@ const rational RenderManager::kDryRunInterval = rational(10);
|
||||
RenderManager::RenderManager(QObject *parent)
|
||||
: backend_(kOpenGL)
|
||||
, aggressive_gc_(0)
|
||||
, worker_pool_(nullptr)
|
||||
{
|
||||
if (backend_ == kOpenGL) {
|
||||
context_ = new OpenGLRenderer();
|
||||
@@ -64,6 +66,12 @@ RenderManager::RenderManager(QObject *parent)
|
||||
}
|
||||
|
||||
auto_cacher_ = new PreviewAutoCacher(this);
|
||||
|
||||
if (OLIVE_CONFIG("RenderProcessIsolationEnabled").toBool()) {
|
||||
worker_pool_ = new RenderWorkerPool(this);
|
||||
worker_pool_->start(QThread::NormalPriority);
|
||||
backend_ = kMultiProcess;
|
||||
}
|
||||
}
|
||||
|
||||
decoder_clear_timer_ = new QTimer(this);
|
||||
@@ -76,6 +84,12 @@ RenderManager::RenderManager(QObject *parent)
|
||||
RenderManager::~RenderManager()
|
||||
{
|
||||
if (context_) {
|
||||
if (worker_pool_) {
|
||||
worker_pool_->Shutdown();
|
||||
delete worker_pool_;
|
||||
worker_pool_ = nullptr;
|
||||
}
|
||||
|
||||
delete shader_cache_;
|
||||
delete decoder_cache_;
|
||||
|
||||
@@ -126,6 +140,11 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms)
|
||||
ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id));
|
||||
ticket->setProperty("multicam", QtUtils::PtrToValue(params.multicam));
|
||||
|
||||
if (worker_pool_ && params.return_type == ReturnType::kFrame &&
|
||||
worker_pool_->SubmitFrame(ticket, params)) {
|
||||
return ticket;
|
||||
}
|
||||
|
||||
if (params.return_type == ReturnType::kNull) {
|
||||
dry_run_thread_->AddTicket(ticket);
|
||||
} else {
|
||||
|
||||
@@ -69,6 +69,8 @@ private:
|
||||
ShaderCache *shader_cache_;
|
||||
};
|
||||
|
||||
class RenderWorkerPool;
|
||||
|
||||
class RenderManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -76,6 +78,9 @@ public:
|
||||
/// Graphics acceleration provided by OpenGL
|
||||
kOpenGL,
|
||||
|
||||
/// Video frames are rendered by an external olive-render-worker process.
|
||||
kMultiProcess,
|
||||
|
||||
/// No graphics rendering - used to test core threading logic
|
||||
kDummy
|
||||
};
|
||||
@@ -246,6 +251,8 @@ private:
|
||||
|
||||
PreviewAutoCacher *auto_cacher_;
|
||||
|
||||
RenderWorkerPool *worker_pool_;
|
||||
|
||||
private slots:
|
||||
void ClearOldDecoders();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderworkerpool.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryFile>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "common/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int kProtocolVersion = 1;
|
||||
|
||||
QString WorkerProgramPath()
|
||||
{
|
||||
const QString dir = QCoreApplication::applicationDirPath();
|
||||
#if defined(Q_OS_WIN)
|
||||
return QDir(dir).filePath(QStringLiteral("olive-render-worker.exe"));
|
||||
#else
|
||||
return QDir(dir).filePath(QStringLiteral("olive-render-worker"));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WriteControlMessage(QProcess *process, const QJsonObject &obj)
|
||||
{
|
||||
const QByteArray line = QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
|
||||
return process->write(line) == line.size() && process->waitForBytesWritten(5000);
|
||||
}
|
||||
|
||||
bool ReadControlMessage(QProcess *process, QJsonObject *out, QString *error,
|
||||
int timeout_ms = 10000)
|
||||
{
|
||||
if (!process->waitForReadyRead(timeout_ms)) {
|
||||
if (error) {
|
||||
*error = QStringLiteral("timeout waiting for worker response");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
while (process->canReadLine()) {
|
||||
const QByteArray line = process->readLine().trimmed();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonParseError parse_error;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(line, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
if (error) {
|
||||
*error = QStringLiteral("worker emitted malformed control JSON");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = doc.object();
|
||||
if (out->value(QStringLiteral("type")).toString() ==
|
||||
QLatin1String(ipc::msgtype::kError)) {
|
||||
if (error) {
|
||||
*error = out->value(QStringLiteral("message")).toString();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = QStringLiteral("worker response did not contain a full line");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderWorkerPool::RenderWorkerPool(QObject *parent)
|
||||
: QThread(parent)
|
||||
{
|
||||
}
|
||||
|
||||
RenderWorkerPool::~RenderWorkerPool()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool RenderWorkerPool::SubmitFrame(RenderTicketPtr ticket,
|
||||
const RenderManager::RenderVideoParams ¶ms)
|
||||
{
|
||||
Job job(ticket, params);
|
||||
if (!PrepareJob(ticket, params, &job)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ticket->moveToThread(this);
|
||||
|
||||
QMutexLocker locker(&mutex_);
|
||||
queue_.push_back(job);
|
||||
wait_.wakeOne();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderWorkerPool::Shutdown()
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
stopping_ = true;
|
||||
wait_.wakeOne();
|
||||
}
|
||||
|
||||
if (isRunning()) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWorkerPool::run()
|
||||
{
|
||||
while (true) {
|
||||
mutex_.lock();
|
||||
while (queue_.empty() && !stopping_) {
|
||||
wait_.wait(&mutex_);
|
||||
}
|
||||
if (stopping_ && queue_.empty()) {
|
||||
mutex_.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
Job job = queue_.front();
|
||||
queue_.pop_front();
|
||||
mutex_.unlock();
|
||||
|
||||
ProcessJob(job);
|
||||
CleanupGraphFile(job.graph_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderWorkerPool::PrepareJob(RenderTicketPtr ticket,
|
||||
const RenderManager::RenderVideoParams ¶ms,
|
||||
Job *job)
|
||||
{
|
||||
if (!IsSupported(params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Project *project = Project::GetProjectFromObject(params.node);
|
||||
if (!project) {
|
||||
qWarning() << "RenderWorkerPool could not resolve project for render node";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString graph_path;
|
||||
if (!WriteGraphSnapshot(project, &graph_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
job->ticket = ticket;
|
||||
job->params = params;
|
||||
job->graph_path = graph_path;
|
||||
job->node_token = QString::number(reinterpret_cast<quintptr>(params.node));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderWorkerPool::WriteGraphSnapshot(Project *project, QString *path)
|
||||
{
|
||||
QTemporaryFile file(QDir::temp().filePath(QStringLiteral("oak-render-graph-XXXXXX.ove")));
|
||||
file.setAutoRemove(false);
|
||||
if (!file.open()) {
|
||||
qWarning() << "RenderWorkerPool failed to create graph snapshot temp file"
|
||||
<< file.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamWriter writer(&file);
|
||||
ProjectSerializer::SaveData data(ProjectSerializer::kProject, project, file.fileName());
|
||||
const ProjectSerializer::Result result = ProjectSerializer::Save(&writer, data);
|
||||
file.close();
|
||||
|
||||
if (result.code() != ProjectSerializer::kSuccess || writer.hasError()) {
|
||||
qWarning() << "RenderWorkerPool failed to serialize graph snapshot"
|
||||
<< result.GetDetails();
|
||||
QFile::remove(file.fileName());
|
||||
return false;
|
||||
}
|
||||
|
||||
*path = file.fileName();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderWorkerPool::IsSupported(const RenderManager::RenderVideoParams ¶ms) const
|
||||
{
|
||||
return params.node && params.return_type == RenderManager::kFrame &&
|
||||
params.video_params.is_valid();
|
||||
}
|
||||
|
||||
void RenderWorkerPool::ProcessJob(const Job &job)
|
||||
{
|
||||
job.ticket->Start();
|
||||
if (job.ticket->IsCancelled()) {
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
const int linesize = Frame::generate_linesize_bytes(
|
||||
kMaxWidth, PixelFormat::F32, VideoParams::kRGBAChannelCount);
|
||||
const size_t slot_bytes = size_t(linesize) * kMaxHeight;
|
||||
const size_t region_bytes = ipc::FrameSlotPool::BytesNeeded(kOutputSlots, slot_bytes);
|
||||
const QString shm_key =
|
||||
ipc::SharedMemoryRegion::MakeKey(QCoreApplication::applicationPid(),
|
||||
int(reinterpret_cast<quintptr>(job.ticket.get()) & 0xFFFF));
|
||||
|
||||
ipc::SharedMemoryRegion region;
|
||||
if (!region.Open(shm_key, region_bytes, ipc::SharedMemoryRegion::kCreate)) {
|
||||
qWarning() << "RenderWorkerPool failed to create shared memory"
|
||||
<< region.error();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
ipc::FrameSlotPool output_pool =
|
||||
ipc::FrameSlotPool::Create(region.data(), kOutputSlots, slot_bytes);
|
||||
|
||||
QProcess worker;
|
||||
worker.setProgram(WorkerProgramPath());
|
||||
worker.start();
|
||||
if (!worker.waitForStarted(10000)) {
|
||||
qWarning() << "RenderWorkerPool failed to start worker"
|
||||
<< worker.errorString();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
QJsonObject response;
|
||||
if (!ReadControlMessage(&worker, &response, &error)) {
|
||||
qWarning() << "RenderWorkerPool did not receive startup handshake"
|
||||
<< error << worker.readAllStandardError();
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ipc::HandshakeMsg handshake;
|
||||
handshake.protocol_version = kProtocolVersion;
|
||||
handshake.shm_key = shm_key;
|
||||
handshake.input_slots = 0;
|
||||
handshake.output_slots = int(kOutputSlots);
|
||||
handshake.slot_data_bytes = qint64(slot_bytes);
|
||||
if (!WriteControlMessage(&worker, handshake.ToJson())) {
|
||||
qWarning() << "RenderWorkerPool failed to send shared-memory handshake";
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ipc::LoadGraphMsg load;
|
||||
load.path = job.graph_path;
|
||||
if (!WriteControlMessage(&worker, load.ToJson()) ||
|
||||
!ReadControlMessage(&worker, &response, &error)) {
|
||||
qWarning() << "RenderWorkerPool failed to load graph in worker"
|
||||
<< error << worker.readAllStandardError();
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ipc::RenderFrameMsg render;
|
||||
render.ticket_id = qint64(reinterpret_cast<quintptr>(job.ticket.get()));
|
||||
render.node_uuid = job.node_token;
|
||||
render.time_num = job.params.time.numerator();
|
||||
render.time_den = job.params.time.denominator();
|
||||
render.width = job.params.force_size.width();
|
||||
render.height = job.params.force_size.height();
|
||||
render.format = int(job.params.force_format);
|
||||
render.channel_count = job.params.force_channel_count;
|
||||
render.mode = int(job.params.mode);
|
||||
|
||||
if (!WriteControlMessage(&worker, render.ToJson())) {
|
||||
qWarning() << "RenderWorkerPool failed to send render_frame";
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ipc::FrameReadyMsg ready;
|
||||
while (true) {
|
||||
if (!ReadControlMessage(&worker, &response, &error, 30000)) {
|
||||
qWarning() << "RenderWorkerPool failed waiting for frame_ready"
|
||||
<< error << worker.readAllStandardError();
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
job.ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ipc::FrameReadyMsg::FromJson(response, &ready)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FinishWithFrame(job.ticket, output_pool, uint32_t(ready.output_slot));
|
||||
|
||||
QJsonObject shutdown;
|
||||
shutdown[QStringLiteral("type")] = ipc::msgtype::kShutdown;
|
||||
WriteControlMessage(&worker, shutdown);
|
||||
worker.closeWriteChannel();
|
||||
if (!worker.waitForFinished(5000)) {
|
||||
worker.kill();
|
||||
worker.waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWorkerPool::FinishWithFrame(RenderTicketPtr ticket,
|
||||
const ipc::FrameSlotPool &pool,
|
||||
uint32_t slot)
|
||||
{
|
||||
const ipc::FrameSlotMeta *meta = pool.Meta(slot);
|
||||
if (!meta || meta->data_size <= 0 ||
|
||||
meta->data_size > int(pool.slot_data_bytes())) {
|
||||
ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
VideoParams params(meta->width, meta->height,
|
||||
PixelFormat::Format(meta->format),
|
||||
meta->channel_count);
|
||||
FramePtr frame = Frame::Create();
|
||||
frame->set_timestamp(rational(int(meta->time_num), int(meta->time_den)));
|
||||
frame->set_video_params(params);
|
||||
if (!frame->allocate() || frame->allocated_size() < meta->data_size) {
|
||||
ticket->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(frame->data(), pool.SlotData(slot), size_t(meta->data_size));
|
||||
ticket->Finish(QVariant::fromValue(frame));
|
||||
}
|
||||
|
||||
void RenderWorkerPool::CleanupGraphFile(const QString &path)
|
||||
{
|
||||
if (!path.isEmpty()) {
|
||||
QFile::remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,89 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERWORKERPOOL_H
|
||||
#define RENDERWORKERPOOL_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
#include <deque>
|
||||
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "render/ipc/frameslotpool.h"
|
||||
#include "render/ipc/ipcmessage.h"
|
||||
#include "render/ipc/sharedmemoryregion.h"
|
||||
#include "render/rendermanager.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class RenderWorkerPool : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RenderWorkerPool(QObject *parent = nullptr);
|
||||
~RenderWorkerPool() override;
|
||||
|
||||
bool SubmitFrame(RenderTicketPtr ticket,
|
||||
const RenderManager::RenderVideoParams ¶ms);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
struct Job {
|
||||
Job(RenderTicketPtr t, const RenderManager::RenderVideoParams &p)
|
||||
: ticket(t)
|
||||
, params(p)
|
||||
{
|
||||
}
|
||||
|
||||
RenderTicketPtr ticket;
|
||||
RenderManager::RenderVideoParams params;
|
||||
QString graph_path;
|
||||
QString node_token;
|
||||
};
|
||||
|
||||
bool PrepareJob(RenderTicketPtr ticket,
|
||||
const RenderManager::RenderVideoParams ¶ms,
|
||||
Job *job);
|
||||
bool WriteGraphSnapshot(Project *project, QString *path);
|
||||
bool IsSupported(const RenderManager::RenderVideoParams ¶ms) const;
|
||||
|
||||
void ProcessJob(const Job &job);
|
||||
void FinishWithFrame(RenderTicketPtr ticket, const ipc::FrameSlotPool &pool,
|
||||
uint32_t slot);
|
||||
void CleanupGraphFile(const QString &path);
|
||||
|
||||
QMutex mutex_;
|
||||
QWaitCondition wait_;
|
||||
std::deque<Job> queue_;
|
||||
bool stopping_ = false;
|
||||
|
||||
static constexpr uint32_t kOutputSlots = 2;
|
||||
static constexpr int kMaxWidth = 4096;
|
||||
static constexpr int kMaxHeight = 2160;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RENDERWORKERPOOL_H
|
||||
@@ -180,15 +180,26 @@ compact `QJsonObject`,`\n` 结尾。仅承载低频控制流量(握手、提
|
||||
`frame_ready`;输出 slot 元数据为 `id=1001, 64x64, fmt=3, channels=4, bytes=65536`,
|
||||
前 4KB 像素存在非零数据。
|
||||
|
||||
### 阶段 3:主进程 WorkerPool + 调度器接线
|
||||
### 阶段 3:主进程 WorkerPool + 调度器接线(单 worker MVP 已接入)
|
||||
|
||||
- 新增 `app/render/renderworkerpool.{h,cpp}`:
|
||||
- `QProcess` 启动 N 个 `olive-render-worker`,建立各自 SHM 段 + stdio 管道。
|
||||
- 维护 worker 忙闲状态,按最少负载派发。
|
||||
- `SubmitFrame(RenderTicketPtr)`:编码 `render_frame` 派发 → worker 回 `frame_ready` 后从输出 slot 拷出 `FramePtr` → `ticket->Finish(...)`。**对上层透明**,`RenderTicketWatcher`/`Viewer` 无感知。
|
||||
- `RenderManager` 增加 `kMultiProcess` backend 分支(与 `kOpenGL` 并存),多进程模式下 `RenderFrame()` 走 `RenderWorkerPool`。
|
||||
- config 开关控制启用,默认仍走进程内 `kOpenGL`。
|
||||
- 验证:开关打开后 Viewer 正常播放纯生成内容;关掉回退旧路径无差异。
|
||||
- ✅ 新增 `app/render/renderworkerpool.{h,cpp}`:
|
||||
- 当前 MVP 用后台 `QThread` 持有任务队列,每个任务启动 1 个 `olive-render-worker`,
|
||||
建立输出 SHM 段 + stdio 管道。
|
||||
- `SubmitFrame(RenderTicketPtr, RenderVideoParams)`:写全量图快照临时文件 →
|
||||
发送 `handshake` / `load_graph` / `render_frame` → worker 回 `frame_ready` 后从输出 slot
|
||||
拷出 `FramePtr` → `ticket->Finish(...)`。对上层 `RenderTicketWatcher`/`Viewer` 保持透明。
|
||||
- 当前仅支持普通视频 `ReturnType::kFrame`;素材输入仍按阶段 4 处理,失败或不支持时回退旧路径。
|
||||
- ✅ `RenderManager` 增加 `kMultiProcess` backend 分支(与 `kOpenGL` 并存),开关开启且
|
||||
WorkerPool 接受任务时 `RenderFrame()` 走 `RenderWorkerPool`。
|
||||
- ✅ `Config` 增加 `RenderProcessIsolationEnabled`,默认 `false`,默认仍走进程内 `kOpenGL`。
|
||||
- 待补:常驻 N worker、忙闲/负载派发、崩溃重启与重派、Viewer 开关实测。
|
||||
|
||||
**验证结果**:
|
||||
- `cmake --build build --target olive-render-worker olive-editor -j22` 通过。
|
||||
- `QT_QPA_PLATFORM=offscreen build/tests/gtest/olive-gtest --gtest_filter='SpscRingBuffer*:*FrameSlotPool*:*IpcMessage*:*ProjectSerializer*' --gtest_brief=1`
|
||||
通过,11 个测试全部通过。
|
||||
- 非沙箱环境 `printf '{"type":"shutdown"}\n' | build/app/olive-render-worker` 通过,输出合法
|
||||
handshake。
|
||||
|
||||
### 阶段 4:素材输入解耦(关键重构)
|
||||
|
||||
@@ -224,7 +235,7 @@ compact `QJsonObject`,`\n` 结尾。仅承载低频控制流量(握手、提
|
||||
| `app/render/ipc/CMakeLists.txt` | 0 | ✅ |
|
||||
| `tests/gtest/render_ipc_test.cpp` | 0 | ✅ |
|
||||
| `app/render/worker/workermain.cpp` | 1/2 | ✅ 基础主循环 |
|
||||
| `app/render/renderworkerpool.{h,cpp}` | 3 | 待办 |
|
||||
| `app/render/renderworkerpool.{h,cpp}` | 3 | ✅ 单 worker MVP |
|
||||
|
||||
**修改**
|
||||
|
||||
@@ -234,9 +245,9 @@ compact `QJsonObject`,`\n` 结尾。仅承载低频控制流量(握手、提
|
||||
| `tests/gtest/CMakeLists.txt`(注册 ipc 测试) | 0 | ✅ |
|
||||
| `app/CMakeLists.txt`(新增 `olive-render-worker` target) | 1 | ✅ |
|
||||
| `app/node/project/serializer/serializer*.{h,cpp}`(暴露加载映射供 worker 查节点) | 2 | ✅ |
|
||||
| `app/render/rendermanager.{h,cpp}`(`kMultiProcess` 分支 + WorkerPool 接线) | 3 | 待办 |
|
||||
| `app/render/rendermanager.{h,cpp}`(`kMultiProcess` 分支 + WorkerPool 接线) | 3 | ✅ 单 worker MVP |
|
||||
| `app/render/renderprocessor.cpp`(`ProcessVideoFootage` 改取输入 slot) | 4 | 待办 |
|
||||
| `app/config/config.h`(多进程开关) | 3 | 待办 |
|
||||
| `app/config/config.cpp`(多进程开关默认值) | 3 | ✅ 默认关闭 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user