The export color transform was passed to RenderTask as a ColorProcessor, but the worker IPC render_frame message never carried it. The worker always set the ticket's coloroutput to null, so frames were returned in the project's reference space and encoded without the chosen output transform (e.g. Rec.709 / sRGB), causing the exported video to look wrongly tinted. Serialize the ColorTransform through the render_frame control message and reconstruct the ColorProcessor on the worker side before rendering. Also expose ColorTransform as a Qt metatype so it can be stored in a ticket QVariant.
157 lines
6.1 KiB
C++
157 lines
6.1 KiB
C++
/***
|
|
|
|
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 IPC_IPCMESSAGE_H
|
|
#define IPC_IPCMESSAGE_H
|
|
|
|
#include <cstdint>
|
|
#include <QByteArray>
|
|
#include <QJsonObject>
|
|
#include <QString>
|
|
#include <QVector>
|
|
|
|
class QIODevice;
|
|
|
|
namespace olive
|
|
{
|
|
namespace ipc
|
|
{
|
|
|
|
/**
|
|
* @brief Control-plane protocol exchanged over stdio between main and render worker.
|
|
*
|
|
* The wire format is NDJSON: one compact QJsonObject per line, terminated by '\n'. This is
|
|
* deliberately human-readable so the channel can be inspected live with `tee`/`cat` and test
|
|
* messages can be injected by hand. The stdio channel carries only low-frequency control traffic;
|
|
* bulk pixel data travels through the shared-memory FrameSlotPool, and the (potentially large)
|
|
* serialized node graph travels via a temporary file referenced by path.
|
|
*
|
|
* Every message object has a "type" string field. Directionality (M = main, W = worker):
|
|
* "handshake" M<->W Negotiate protocol version and announce shared-memory key/geometry.
|
|
* "load_graph" M ->W Path to a temporary file holding the serialized node graph.
|
|
* "render_frame" M ->W Request a frame: node uuid, time, video params.
|
|
* "frame_ready" W ->M A rendered frame is published; carries the output-slot index + ticket.
|
|
* "cancel" M ->W Abandon an in-flight ticket by id.
|
|
* "graph_update" M ->W (Reserved, Phase 6) Incremental graph mutation, mirrors ProjectCopier.
|
|
* "shutdown" M ->W Finish current work and exit cleanly.
|
|
* "error" W ->M Worker-side failure report (human-readable "message" field).
|
|
*/
|
|
namespace msgtype
|
|
{
|
|
constexpr const char *kHandshake = "handshake";
|
|
constexpr const char *kLoadGraph = "load_graph";
|
|
constexpr const char *kRenderFrame = "render_frame";
|
|
constexpr const char *kFrameReady = "frame_ready";
|
|
constexpr const char *kCancel = "cancel";
|
|
constexpr const char *kGraphUpdate = "graph_update";
|
|
constexpr const char *kShutdown = "shutdown";
|
|
constexpr const char *kError = "error";
|
|
} // namespace msgtype
|
|
|
|
/**
|
|
* @brief Write one NDJSON message line to `device`.
|
|
*
|
|
* Serializes `obj` to compact JSON, appends '\n', and writes the whole line in one call. Returns
|
|
* true only if the full line was written.
|
|
*/
|
|
bool WriteMessage(QIODevice *device, const QJsonObject &obj);
|
|
|
|
/**
|
|
* @brief Pull one complete NDJSON line out of `buffer` and parse it.
|
|
*
|
|
* If `buffer` contains at least one '\n', the leading line is removed, parsed as JSON, and returned
|
|
* via `out` (true). If no complete line is buffered yet, leaves `buffer` untouched and returns
|
|
* false. Malformed lines are skipped (removed) and reported via `*ok = false` so the reader can log
|
|
* and continue rather than wedge. Supports the typical "append bytes as they arrive, then drain
|
|
* complete lines" reader loop on a pipe.
|
|
*/
|
|
bool ReadMessage(QByteArray *buffer, QJsonObject *out, bool *ok = nullptr);
|
|
|
|
// ---- Typed message builders / parsers -------------------------------------------------------
|
|
//
|
|
// Thin helpers that construct or read the QJsonObject for each message type, keeping field names in
|
|
// one place so main and worker agree. Fields use plain JSON numbers/strings; 64-bit ids are stored
|
|
// as JSON numbers (doubles exactly represent integers up to 2^53, ample for our counters).
|
|
|
|
struct HandshakeMsg {
|
|
int protocol_version = 0;
|
|
QString shm_key; ///< Worker->main output shared-memory segment key.
|
|
QString input_shm_key; ///< Main->worker input shared-memory segment key (optional).
|
|
int input_slots = 0; ///< Number of main->worker input frame slots.
|
|
int output_slots = 0; ///< Number of worker->main output frame slots.
|
|
qint64 slot_data_bytes = 0; ///< Per-output-slot pixel block size.
|
|
qint64 input_slot_data_bytes = 0; ///< Per-input-slot pixel block size.
|
|
|
|
QJsonObject ToJson() const;
|
|
static bool FromJson(const QJsonObject &o, HandshakeMsg *out);
|
|
};
|
|
|
|
struct RenderFrameMsg {
|
|
qint64 ticket_id = 0; ///< Correlates this request with the eventual frame_ready.
|
|
QString node_uuid; ///< Output/viewer node to render, by stable uuid in the loaded graph.
|
|
qint64 time_num = 0;
|
|
qint64 time_den = 1;
|
|
int width = 0; ///< Forced output size (0 = use graph default).
|
|
int height = 0;
|
|
int format = -1; ///< Forced PixelFormat::Format (-1 = default/INVALID).
|
|
int channel_count = 0; ///< 0 = default.
|
|
int mode = 0; ///< RenderMode::Mode.
|
|
int input_slot = -1; ///< Optional main->worker decoded input slot for footage nodes.
|
|
QVector<int> input_slots; ///< Optional ordered decoded input slots for footage nodes.
|
|
|
|
// Output color transform to apply before returning the frame. When empty,
|
|
// the worker returns the image in the project's reference space.
|
|
bool has_color_transform = false;
|
|
bool color_is_display = false;
|
|
QString color_output;
|
|
QString color_view;
|
|
QString color_look;
|
|
|
|
QJsonObject ToJson() const;
|
|
static bool FromJson(const QJsonObject &o, RenderFrameMsg *out);
|
|
};
|
|
|
|
struct FrameReadyMsg {
|
|
qint64 ticket_id = 0;
|
|
int output_slot = 0; ///< Index into the worker->main output FrameSlotPool.
|
|
|
|
QJsonObject ToJson() const;
|
|
static bool FromJson(const QJsonObject &o, FrameReadyMsg *out);
|
|
};
|
|
|
|
struct CancelMsg {
|
|
qint64 ticket_id = 0;
|
|
|
|
QJsonObject ToJson() const;
|
|
static bool FromJson(const QJsonObject &o, CancelMsg *out);
|
|
};
|
|
|
|
struct LoadGraphMsg {
|
|
QString path; ///< Temporary file holding the serialized node graph.
|
|
|
|
QJsonObject ToJson() const;
|
|
static bool FromJson(const QJsonObject &o, LoadGraphMsg *out);
|
|
};
|
|
|
|
} // namespace ipc
|
|
} // namespace olive
|
|
|
|
#endif // IPC_IPCMESSAGE_H
|