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.
279 lines
5.9 KiB
C++
279 lines
5.9 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2022 Olive Team
|
|
Modifications Copyright (C) 2025 mikesolar
|
|
|
|
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 RENDERBACKEND_H
|
|
#define RENDERBACKEND_H
|
|
|
|
#include <QtConcurrent/QtConcurrent>
|
|
|
|
#include "config/config.h"
|
|
#include "colorprocessorcache.h"
|
|
#include "dialog/rendercancel/rendercancel.h"
|
|
#include "node/output/viewer/viewer.h"
|
|
#include "node/project.h"
|
|
#include "node/traverser.h"
|
|
#include "render/previewautocacher.h"
|
|
#include "render/renderer.h"
|
|
#include "render/colortransform.h"
|
|
#include "render/renderticket.h"
|
|
#include "rendercache.h"
|
|
|
|
namespace olive
|
|
{
|
|
|
|
class RenderThread : public QThread {
|
|
Q_OBJECT
|
|
public:
|
|
RenderThread(Renderer *renderer, DecoderCache *decoder_cache,
|
|
ShaderCache *shader_cache, QObject *parent = nullptr);
|
|
|
|
void AddTicket(RenderTicketPtr ticket);
|
|
|
|
bool RemoveTicket(RenderTicketPtr ticket);
|
|
|
|
void quit();
|
|
|
|
protected:
|
|
virtual void run() override;
|
|
|
|
private:
|
|
QMutex mutex_;
|
|
|
|
QWaitCondition wait_;
|
|
|
|
std::list<RenderTicketPtr> queue_;
|
|
|
|
bool cancelled_;
|
|
|
|
Renderer *context_;
|
|
|
|
DecoderCache *decoder_cache_;
|
|
|
|
ShaderCache *shader_cache_;
|
|
};
|
|
|
|
class RenderWorkerPool;
|
|
|
|
class RenderManager : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
enum Backend {
|
|
/// Graphics acceleration provided by OpenGL
|
|
kOpenGL,
|
|
|
|
/// Vulkan requested by the user. Falls back to OpenGL until VulkanRenderer is implemented.
|
|
kVulkan,
|
|
|
|
/// Video frames are rendered by an external olive-render-worker process.
|
|
kMultiProcess,
|
|
|
|
/// No graphics rendering - used to test core threading logic
|
|
kDummy
|
|
};
|
|
|
|
static void CreateInstance()
|
|
{
|
|
instance_ = new RenderManager();
|
|
}
|
|
|
|
static void DestroyInstance()
|
|
{
|
|
delete instance_;
|
|
instance_ = nullptr;
|
|
}
|
|
|
|
static RenderManager *instance()
|
|
{
|
|
return instance_;
|
|
}
|
|
|
|
enum ReturnType { kTexture, kFrame, kNull };
|
|
|
|
struct RenderVideoParams {
|
|
RenderVideoParams(Node *n, const VideoParams &vparam,
|
|
const AudioParams &aparam, const rational &t,
|
|
ColorManager *colorman, RenderMode::Mode m)
|
|
{
|
|
node = n;
|
|
video_params = vparam;
|
|
audio_params = aparam;
|
|
time = t;
|
|
color_manager = colorman;
|
|
use_cache = false;
|
|
return_type = kFrame;
|
|
force_format = PixelFormat::INVALID;
|
|
force_color_output = nullptr;
|
|
force_color_transform = ColorTransform();
|
|
force_size = QSize(0, 0);
|
|
force_channel_count = 0;
|
|
mode = m;
|
|
multicam = nullptr;
|
|
}
|
|
|
|
void AddCache(FrameHashCache *cache)
|
|
{
|
|
cache_dir = cache->GetCacheDirectory();
|
|
cache_timebase = cache->GetTimebase();
|
|
cache_id = cache->GetUuid().toString();
|
|
}
|
|
|
|
Node *node;
|
|
VideoParams video_params;
|
|
AudioParams audio_params;
|
|
rational time;
|
|
ColorManager *color_manager;
|
|
bool use_cache;
|
|
ReturnType return_type;
|
|
RenderMode::Mode mode;
|
|
MultiCamNode *multicam;
|
|
|
|
QString cache_dir;
|
|
rational cache_timebase;
|
|
QString cache_id;
|
|
|
|
QSize force_size;
|
|
int force_channel_count;
|
|
QMatrix4x4 force_matrix;
|
|
PixelFormat force_format;
|
|
ColorProcessorPtr force_color_output;
|
|
ColorTransform force_color_transform;
|
|
};
|
|
|
|
static const rational kDryRunInterval;
|
|
|
|
/**
|
|
* @brief Asynchronously generate a frame at a given time
|
|
*
|
|
* The ticket from this function will return a FramePtr - the rendered frame in reference color
|
|
* space.
|
|
*
|
|
* This function is thread-safe.
|
|
*/
|
|
RenderTicketPtr RenderFrame(const RenderVideoParams ¶ms);
|
|
|
|
struct RenderAudioParams {
|
|
RenderAudioParams(Node *n, const TimeRange &time,
|
|
const AudioParams &aparam, RenderMode::Mode m)
|
|
{
|
|
node = n;
|
|
range = time;
|
|
audio_params = aparam;
|
|
generate_waveforms = false;
|
|
clamp = true;
|
|
mode = m;
|
|
}
|
|
|
|
Node *node;
|
|
TimeRange range;
|
|
AudioParams audio_params;
|
|
bool generate_waveforms;
|
|
bool clamp;
|
|
RenderMode::Mode mode;
|
|
};
|
|
|
|
/**
|
|
* @brief Asynchronously generate a chunk of audio
|
|
*
|
|
* The ticket from this function will return a SampleBufferPtr - the rendered audio.
|
|
*
|
|
* This function is thread-safe.
|
|
*/
|
|
RenderTicketPtr RenderAudio(const RenderAudioParams ¶ms);
|
|
|
|
bool RemoveTicket(RenderTicketPtr ticket);
|
|
|
|
enum TicketType { kTypeVideo, kTypeAudio };
|
|
|
|
Backend backend() const
|
|
{
|
|
return backend_;
|
|
}
|
|
|
|
Backend requested_backend() const
|
|
{
|
|
return requested_backend_;
|
|
}
|
|
|
|
static Backend BackendFromString(const QString &backend);
|
|
static QString BackendToString(Backend backend);
|
|
|
|
PreviewAutoCacher *GetCacher() const
|
|
{
|
|
return auto_cacher_;
|
|
}
|
|
|
|
void SetProject(Project *p)
|
|
{
|
|
auto_cacher_->SetProject(p);
|
|
}
|
|
|
|
public slots:
|
|
void SetAggressiveGarbageCollection(bool enabled);
|
|
|
|
signals:
|
|
|
|
private:
|
|
RenderManager(QObject *parent = nullptr);
|
|
|
|
virtual ~RenderManager() override;
|
|
|
|
RenderThread *CreateThread(Renderer *renderer = nullptr);
|
|
|
|
static RenderManager *instance_;
|
|
|
|
Renderer *context_;
|
|
|
|
Backend backend_;
|
|
Backend requested_backend_;
|
|
|
|
DecoderCache *decoder_cache_;
|
|
|
|
ShaderCache *shader_cache_;
|
|
|
|
static constexpr auto kDecoderMaximumInactivityAggressive = 1000;
|
|
static constexpr auto kDecoderMaximumInactivity = 5000;
|
|
|
|
int aggressive_gc_;
|
|
|
|
QTimer *decoder_clear_timer_;
|
|
|
|
RenderThread *dry_run_thread_;
|
|
RenderThread *audio_thread_;
|
|
|
|
std::vector<RenderThread *> waveform_threads_;
|
|
size_t last_waveform_thread_;
|
|
|
|
std::list<RenderThread *> render_threads_;
|
|
|
|
PreviewAutoCacher *auto_cacher_;
|
|
|
|
RenderWorkerPool *worker_pool_;
|
|
|
|
private slots:
|
|
void ClearOldDecoders();
|
|
};
|
|
|
|
}
|
|
|
|
Q_DECLARE_METATYPE(olive::RenderManager::TicketType)
|
|
|
|
#endif // RENDERBACKEND_H
|