Merge branch 'master' into pr/1987
This commit is contained in:
@@ -24,6 +24,7 @@ on:
|
||||
env:
|
||||
DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60
|
||||
UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60
|
||||
CMAKE_ARGS: -DUSE_WERROR=ON -DBUILD_TESTS=ON
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -73,7 +74,8 @@ jobs:
|
||||
cmake .. -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \
|
||||
-DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}"
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
@@ -204,7 +206,8 @@ jobs:
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
run: |
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
@@ -356,7 +359,8 @@ jobs:
|
||||
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
|
||||
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||
project(olive-editor VERSION 0.2.0 LANGUAGES CXX)
|
||||
|
||||
option(BUILD_DOXYGEN "Build Doxygen documentation" OFF)
|
||||
option(BUILD_TESTS "Build unit tests" ON)
|
||||
option(USE_WERROR "Error on compile warning" ON)
|
||||
option(BUILD_TESTS "Build unit tests" OFF)
|
||||
option(USE_WERROR "Error on compile warning" OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
@@ -43,6 +43,12 @@ Decoder::Decoder() :
|
||||
UpdateLastAccessed();
|
||||
}
|
||||
|
||||
void Decoder::IncrementAccessTime(qint64 t)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
last_accessed_ += t;
|
||||
}
|
||||
|
||||
bool Decoder::Open(const CodecStream &stream)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -87,6 +87,8 @@ public:
|
||||
virtual bool SupportsVideo(){return false;}
|
||||
virtual bool SupportsAudio(){return false;}
|
||||
|
||||
void IncrementAccessTime(qint64 t);
|
||||
|
||||
class CodecStream
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -513,6 +513,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can
|
||||
|
||||
}
|
||||
|
||||
desc.SetStreamCount(fmt_ctx->nb_streams);
|
||||
|
||||
}
|
||||
|
||||
// Free all memory
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef FOOTAGEMETA_H
|
||||
#define FOOTAGEMETA_H
|
||||
|
||||
struct FootageData {
|
||||
struct StreamData {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // FOOTAGEMETA_H
|
||||
@@ -73,7 +73,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance
|
||||
|
||||
bool stream_enabled = true;
|
||||
|
||||
for (int i=0; in->seek_subimage(i, 0); i++) {
|
||||
int i;
|
||||
for (i=0; in->seek_subimage(i, 0); i++) {
|
||||
OIIO::ImageSpec spec = in->spec();
|
||||
|
||||
VideoParams video_params = GetVideoParamsFromImageSpec(spec);
|
||||
@@ -104,6 +105,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cance
|
||||
desc.AddVideoStream(video_params);
|
||||
}
|
||||
|
||||
desc.SetStreamCount(i);
|
||||
|
||||
// If we're here, we have a successful image open
|
||||
in->close();
|
||||
|
||||
|
||||
@@ -586,6 +586,8 @@ void Core::ImportTaskComplete(Task* task)
|
||||
}
|
||||
|
||||
undo_stack_.pushIfHasChildren(command);
|
||||
|
||||
main_window_->SelectFootage(import_task->GetImportedFootage());
|
||||
}
|
||||
|
||||
bool Core::ConfirmImageSequence(const QString& filename)
|
||||
|
||||
@@ -35,7 +35,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList<QString> &pix_f
|
||||
pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0);
|
||||
|
||||
yuv_color_range_combobox_ = new QComboBox();
|
||||
yuv_color_range_combobox_->addItems({tr("MPEG (16-235)"), tr("JPEG (0-255)")});
|
||||
yuv_color_range_combobox_->addItems({tr("Limited (16-235)"), tr("Full (0-255)")});
|
||||
pixel_layout->addWidget(yuv_color_range_combobox_, row, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,9 @@
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include "common/range.h"
|
||||
#include "core.h"
|
||||
#include "node/traverser.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString TransformDistortNode::kParentInput = QStringLiteral("parent_in");
|
||||
const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in");
|
||||
const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in");
|
||||
@@ -36,6 +33,8 @@ const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interp
|
||||
|
||||
TransformDistortNode::TransformDistortNode()
|
||||
{
|
||||
AddInput(kParentInput, NodeValue::kMatrix);
|
||||
|
||||
AddInput(kAutoscaleInput, NodeValue::kCombo, 0);
|
||||
|
||||
AddInput(kInterpolationInput, NodeValue::kCombo, 2);
|
||||
@@ -73,6 +72,7 @@ void TransformDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kParentInput, tr("Parent"));
|
||||
SetInputName(kAutoscaleInput, tr("Auto-Scale"));
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
SetInputName(kInterpolationInput, tr("Interpolation"));
|
||||
@@ -84,12 +84,12 @@ void TransformDistortNode::Retranslate()
|
||||
void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
// Generate matrix
|
||||
QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false);
|
||||
QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false, value[kParentInput].toMatrix());
|
||||
|
||||
// Pop texture
|
||||
NodeValue texture_meta = value[kTextureInput];
|
||||
|
||||
bool pushed_job = false;
|
||||
QVariant job_to_push;
|
||||
|
||||
// If we have a texture, generate a matrix and make it happen
|
||||
if (TexturePtr texture = texture_meta.toTexture()) {
|
||||
@@ -103,15 +103,17 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this));
|
||||
job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast<Texture::Interpolation>(value[kInterpolationInput].toInt()));
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
|
||||
pushed_job = true;
|
||||
job_to_push = QVariant::fromValue(job);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed_job) {
|
||||
table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this);
|
||||
|
||||
if (job_to_push.isNull()) {
|
||||
// Re-push whatever value we received
|
||||
table->Push(texture_meta);
|
||||
} else {
|
||||
table->Push(NodeValue::kTexture, job_to_push, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
|
||||
if (gizmo == anchor_gizmo_) {
|
||||
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false).toTransform().inverted();
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false, row[kParentInput].toMatrix()).toTransform().inverted();
|
||||
|
||||
} else if (IsAScaleGizmo(gizmo)) {
|
||||
|
||||
@@ -171,7 +173,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
}
|
||||
|
||||
// Store current matrix
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true).toTransform().inverted();
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true, row[kParentInput].toMatrix()).toTransform().inverted();
|
||||
|
||||
} else if (gizmo == rotation_gizmo_) {
|
||||
|
||||
@@ -356,7 +358,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
|
||||
// Fold values into a matrix for the rectangle
|
||||
QMatrix4x4 rectangle_matrix;
|
||||
rectangle_matrix.scale(sequence_half_res);
|
||||
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false),
|
||||
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()),
|
||||
sequence_res,
|
||||
tex_sz,
|
||||
tex_offset,
|
||||
@@ -376,7 +378,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
|
||||
// Draw anchor point
|
||||
QMatrix4x4 anchor_matrix;
|
||||
anchor_matrix.scale(sequence_half_res);
|
||||
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false),
|
||||
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()),
|
||||
sequence_res,
|
||||
tex_sz,
|
||||
tex_offset,
|
||||
@@ -402,7 +404,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
|
||||
QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const
|
||||
{
|
||||
if (TexturePtr texture = row[kTextureInput].toTexture()) {
|
||||
auto m = GenerateMatrix(row, false, false, false);
|
||||
auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix());
|
||||
return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform();
|
||||
}
|
||||
return super::GizmoTransformation(row, globals);
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override;
|
||||
|
||||
static const QString kParentInput;
|
||||
static const QString kTextureInput;
|
||||
static const QString kAutoscaleInput;
|
||||
static const QString kInterpolationInput;
|
||||
|
||||
@@ -90,11 +90,11 @@ void MatrixGenerator::Retranslate()
|
||||
void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
// Push matrix output
|
||||
QMatrix4x4 mat = GenerateMatrix(value, false, false, false);
|
||||
QMatrix4x4 mat = GenerateMatrix(value, false, false, false, QMatrix4x4());
|
||||
table->Push(NodeValue::kMatrix, mat, this);
|
||||
}
|
||||
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const
|
||||
{
|
||||
QVector2D anchor;
|
||||
QVector2D position;
|
||||
@@ -116,17 +116,17 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignor
|
||||
value[kRotationInput].toDouble(),
|
||||
scale,
|
||||
value[kUniformScaleInput].toBool(),
|
||||
anchor);
|
||||
anchor,
|
||||
mat);
|
||||
}
|
||||
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
|
||||
const float& rot,
|
||||
const QVector2D& scale,
|
||||
bool uniform_scale,
|
||||
const QVector2D& anchor)
|
||||
const QVector2D& anchor,
|
||||
QMatrix4x4 mat)
|
||||
{
|
||||
QMatrix4x4 mat;
|
||||
|
||||
// Position
|
||||
mat.translate(pos);
|
||||
|
||||
|
||||
@@ -53,12 +53,13 @@ public:
|
||||
static const QString kAnchorInput;
|
||||
|
||||
protected:
|
||||
QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const;
|
||||
QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const;
|
||||
static QMatrix4x4 GenerateMatrix(const QVector2D &pos,
|
||||
const float &rot,
|
||||
const QVector2D &scale,
|
||||
bool uniform_scale,
|
||||
const QVector2D &anchor);
|
||||
const QVector2D &anchor,
|
||||
QMatrix4x4 mat);
|
||||
|
||||
virtual void InputValueChangedEvent(const QString& input, int element) override;
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
return InputArraySize(kSubtitleParamsInput);
|
||||
}
|
||||
|
||||
int GetTotalStreamCount() const
|
||||
virtual int GetTotalStreamCount() const
|
||||
{
|
||||
return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount();
|
||||
}
|
||||
|
||||
@@ -73,6 +73,21 @@ bool Folder::ChildExistsWithName(const QString &s) const
|
||||
return ChildExistsWithNameInternal(this, s);
|
||||
}
|
||||
|
||||
bool Folder::HasChildRecursive(Node *child) const
|
||||
{
|
||||
for (Node *i : item_children_) {
|
||||
if (i == child) {
|
||||
return true;
|
||||
} else if (Folder *f = dynamic_cast<Folder*>(i)) {
|
||||
if (f->HasChildRecursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Folder::index_of_child_in_array(Node *item) const
|
||||
{
|
||||
int index_of_item = item_children_.indexOf(item);
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
|
||||
bool ChildExistsWithName(const QString& s) const;
|
||||
|
||||
bool HasChildRecursive(Node *child) const;
|
||||
|
||||
int item_child_count() const
|
||||
{
|
||||
return item_children_.size();
|
||||
|
||||
@@ -44,7 +44,8 @@ Footage::Footage(const QString &filename) :
|
||||
ViewerOutput(false, false),
|
||||
timestamp_(0),
|
||||
valid_(false),
|
||||
cancelled_(nullptr)
|
||||
cancelled_(nullptr),
|
||||
total_stream_count_(0)
|
||||
{
|
||||
SetCacheTextures(true);
|
||||
|
||||
@@ -125,6 +126,9 @@ void Footage::Clear()
|
||||
// Clear decoder link
|
||||
decoder_.clear();
|
||||
|
||||
// Clear total stream count
|
||||
total_stream_count_ = 0;
|
||||
|
||||
// Reset ready state
|
||||
valid_ = false;
|
||||
}
|
||||
@@ -497,6 +501,8 @@ void Footage::Reprobe()
|
||||
SetStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)), i);
|
||||
}
|
||||
|
||||
total_stream_count_ = footage_info.GetStreamCount();
|
||||
|
||||
SetValid();
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,8 @@ public:
|
||||
virtual qint64 creation_time() const override;
|
||||
virtual qint64 mod_time() const override;
|
||||
|
||||
virtual int GetTotalStreamCount() const override { return total_stream_count_; }
|
||||
|
||||
static const QString kFilenameInput;
|
||||
|
||||
protected:
|
||||
@@ -224,6 +226,8 @@ private:
|
||||
|
||||
CancelAtom *cancelled_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
private slots:
|
||||
void CheckFootage();
|
||||
|
||||
|
||||
@@ -43,9 +43,11 @@ bool FootageDescription::Load(const QString &filename)
|
||||
// Default to first version of metadata (which wasn't versioned at all)
|
||||
unsigned version = 1;
|
||||
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toUInt();
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toUInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +60,14 @@ bool FootageDescription::Load(const QString &filename)
|
||||
if (reader.name() == QStringLiteral("decoder")) {
|
||||
decoder_ = reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("streams")) {
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
total_stream_count_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("video")) {
|
||||
VideoParams vp;
|
||||
@@ -116,6 +126,8 @@ bool FootageDescription::Save(const QString &filename) const
|
||||
|
||||
writer.writeStartElement(QStringLiteral("streams"));
|
||||
|
||||
writer.writeAttribute(QStringLiteral("count"), QString::number(total_stream_count_));
|
||||
|
||||
foreach (const VideoParams& vp, video_streams_) {
|
||||
writer.writeStartElement(QStringLiteral("video"));
|
||||
vp.Save(&writer);
|
||||
|
||||
@@ -32,7 +32,8 @@ class FootageDescription
|
||||
{
|
||||
public:
|
||||
FootageDescription(const QString& decoder = QString()) :
|
||||
decoder_(decoder)
|
||||
decoder_(decoder),
|
||||
total_stream_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -118,6 +119,9 @@ public:
|
||||
return StreamIsVideo(index) || StreamIsAudio(index) || StreamIsSubtitle(index);
|
||||
}
|
||||
|
||||
int GetStreamCount() const { return total_stream_count_; }
|
||||
void SetStreamCount(int s) { total_stream_count_ = s; }
|
||||
|
||||
bool Load(const QString& filename);
|
||||
|
||||
bool Save(const QString& filename) const;
|
||||
@@ -138,7 +142,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr unsigned kFootageMetaVersion = 2;
|
||||
static constexpr unsigned kFootageMetaVersion = 4;
|
||||
|
||||
QString decoder_;
|
||||
|
||||
@@ -148,6 +152,8 @@ private:
|
||||
|
||||
QVector<SubtitleParams> subtitle_streams_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ public:
|
||||
|
||||
ProjectViewModel* model() const;
|
||||
|
||||
bool SelectItem(Node *n)
|
||||
bool SelectItem(Node *n, bool deselect_all_first = true)
|
||||
{
|
||||
return explorer_->SelectItem(n);
|
||||
return explorer_->SelectItem(n, deselect_all_first);
|
||||
}
|
||||
|
||||
virtual void SelectAll() override;
|
||||
|
||||
@@ -78,9 +78,9 @@ private:
|
||||
#define PRINT_GL_ERRORS ErrorPrinter __e(__FUNCTION__, functions_)
|
||||
|
||||
#define GL_PREAMBLE \
|
||||
QMutexLocker __l(&global_opengl_mutex);
|
||||
//QMutexLocker __l(&global_opengl_mutex);
|
||||
|
||||
QMutex global_opengl_mutex;
|
||||
//QMutex global_opengl_mutex;
|
||||
|
||||
OpenGLRenderer::OpenGLRenderer(QObject* parent) :
|
||||
Renderer(parent),
|
||||
@@ -107,7 +107,7 @@ void OpenGLRenderer::Init(QOpenGLContext *existing_ctx)
|
||||
|
||||
bool OpenGLRenderer::Init()
|
||||
{
|
||||
QMutexLocker locker(&global_opengl_mutex);
|
||||
GL_PREAMBLE;
|
||||
|
||||
if (context_) {
|
||||
qCritical() << "Can't initialize already initialized OpenGLRenderer";
|
||||
@@ -177,7 +177,7 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub
|
||||
GL_PREAMBLE;
|
||||
|
||||
if (texture) {
|
||||
AttachTextureAsDestination(texture);
|
||||
AttachTextureAsDestination(texture->id());
|
||||
}
|
||||
|
||||
ClearDestinationInternal(r, g, b, a);
|
||||
@@ -187,16 +187,11 @@ void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, doub
|
||||
}
|
||||
}
|
||||
|
||||
QVariant OpenGLRenderer::CreateNativeTexture2D(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize)
|
||||
QVariant OpenGLRenderer::CreateNativeTexture(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize)
|
||||
{
|
||||
GL_PREAMBLE;
|
||||
|
||||
return CreateNativeTexture2DInternal(width, height, format, channel_count, data, linesize);
|
||||
}
|
||||
|
||||
QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth, VideoParams::Format format, int channel_count, const void *data, int linesize)
|
||||
{
|
||||
GL_PREAMBLE;
|
||||
bool is_3d = depth > 1;
|
||||
|
||||
// Generate new texture
|
||||
GLuint texture;
|
||||
@@ -205,23 +200,32 @@ QVariant OpenGLRenderer::CreateNativeTexture3D(int width, int height, int depth,
|
||||
|
||||
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
|
||||
|
||||
GLenum target_current = is_3d ? GL_TEXTURE_BINDING_3D : GL_TEXTURE_BINDING_2D;
|
||||
GLenum target = is_3d ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
|
||||
GLint current_tex;
|
||||
functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, ¤t_tex);
|
||||
functions_->glGetIntegerv(target_current, ¤t_tex);
|
||||
|
||||
functions_->glBindTexture(GL_TEXTURE_3D, texture);
|
||||
functions_->glBindTexture(target, texture);
|
||||
|
||||
context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count),
|
||||
width, height, depth, 0, GetPixelFormat(channel_count),
|
||||
GetPixelType(format), data);
|
||||
if (is_3d) {
|
||||
context_->extraFunctions()->glTexImage3D(target, 0, GetInternalFormat(format, channel_count),
|
||||
width, height, depth, 0, GetPixelFormat(channel_count),
|
||||
GetPixelType(format), data);
|
||||
} else {
|
||||
functions_->glTexImage2D(target, 0, GetInternalFormat(format, channel_count),
|
||||
width, height, 0, GetPixelFormat(channel_count),
|
||||
GetPixelType(format), data);
|
||||
}
|
||||
|
||||
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
functions_->glBindTexture(GL_TEXTURE_3D, current_tex);
|
||||
functions_->glBindTexture(target, current_tex);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void OpenGLRenderer::AttachTextureAsDestination(Texture* texture)
|
||||
void OpenGLRenderer::AttachTextureAsDestination(const QVariant &texture)
|
||||
{
|
||||
PRINT_GL_ERRORS;
|
||||
|
||||
@@ -229,7 +233,7 @@ void OpenGLRenderer::AttachTextureAsDestination(Texture* texture)
|
||||
functions_->glFramebufferTexture2D(GL_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
texture->id().value<GLuint>(),
|
||||
texture.value<GLuint>(),
|
||||
0);
|
||||
}
|
||||
|
||||
@@ -287,15 +291,16 @@ void OpenGLRenderer::DestroyNativeShader(QVariant shader)
|
||||
functions_->glDeleteProgram(program);
|
||||
}
|
||||
|
||||
void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int linesize)
|
||||
void OpenGLRenderer::UploadToTexture(const QVariant &handle, const VideoParams &p, const void *data, int linesize)
|
||||
{
|
||||
GL_PREAMBLE;
|
||||
|
||||
GLuint t = texture->id().value<GLuint>();
|
||||
const VideoParams& p = texture->params();
|
||||
GLuint t = handle.value<GLuint>();
|
||||
|
||||
GLenum tex_type = texture->type() == Texture::k2D ? GL_TEXTURE_2D : GL_TEXTURE_3D;
|
||||
GLenum tex_binding = texture->type() == Texture::k2D ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D;
|
||||
bool is_3d = p.is_3d();
|
||||
|
||||
GLenum tex_type = !is_3d ? GL_TEXTURE_2D : GL_TEXTURE_3D;
|
||||
GLenum tex_binding = !is_3d ? GL_TEXTURE_BINDING_2D : GL_TEXTURE_BINDING_3D;
|
||||
|
||||
// Store currently bound texture so it can be restored later
|
||||
GLint current_tex;
|
||||
@@ -308,7 +313,7 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin
|
||||
{
|
||||
PRINT_GL_ERRORS;
|
||||
|
||||
if (texture->type() == Texture::k2D) {
|
||||
if (!is_3d) {
|
||||
functions_->glTexSubImage2D(tex_type, 0, 0, 0,
|
||||
p.effective_width(), p.effective_height(),
|
||||
GetPixelFormat(p.channel_count()), GetPixelType(p.format()),
|
||||
@@ -326,16 +331,14 @@ void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int lin
|
||||
functions_->glBindTexture(tex_type, current_tex);
|
||||
}
|
||||
|
||||
void OpenGLRenderer::DownloadFromTexture(Texture* texture, void *data, int linesize)
|
||||
void OpenGLRenderer::DownloadFromTexture(const QVariant &id, const VideoParams &p, void *data, int linesize)
|
||||
{
|
||||
GL_PREAMBLE;
|
||||
|
||||
const VideoParams& p = texture->params();
|
||||
|
||||
GLint current_tex;
|
||||
functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex);
|
||||
|
||||
AttachTextureAsDestination(texture);
|
||||
AttachTextureAsDestination(id);
|
||||
|
||||
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize);
|
||||
|
||||
@@ -366,7 +369,7 @@ void OpenGLRenderer::Flush()
|
||||
|
||||
Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
||||
{
|
||||
AttachTextureAsDestination(texture);
|
||||
AttachTextureAsDestination(texture->id());
|
||||
|
||||
QByteArray data(VideoParams::GetBytesPerPixel(texture->format(), texture->channel_count()), Qt::Uninitialized);
|
||||
|
||||
@@ -502,7 +505,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
|
||||
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + i);
|
||||
|
||||
GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
functions_->glBindTexture(target, tex_id);
|
||||
|
||||
if (tex_id) {
|
||||
@@ -584,11 +587,11 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
|
||||
TexturePtr output_tex, input_tex;
|
||||
if (real_iteration_count > 1) {
|
||||
// Create one texture to bounce off
|
||||
output_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params);
|
||||
output_tex = CreateTexture(destination_params);
|
||||
|
||||
if (real_iteration_count > 2) {
|
||||
// Create a second texture bounce off
|
||||
input_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params);
|
||||
input_tex = CreateTexture(destination_params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,7 +607,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
|
||||
// This is the last iteration, draw to the destination
|
||||
if (destination) {
|
||||
// If we have a destination texture, draw to it
|
||||
AttachTextureAsDestination(destination);
|
||||
AttachTextureAsDestination(destination->id());
|
||||
} else if (iteration > 0) {
|
||||
// Otherwise, if we were iterating before, detach texture now
|
||||
DetachTextureAsDestination();
|
||||
@@ -616,7 +619,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
|
||||
}
|
||||
} else {
|
||||
// Always draw to output_tex, which gets swapped with input_tex every iteration
|
||||
AttachTextureAsDestination(output_tex.get());
|
||||
AttachTextureAsDestination(output_tex->id());
|
||||
}
|
||||
|
||||
if (iteration > 0) {
|
||||
@@ -648,7 +651,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
|
||||
// Release any textures we bound before
|
||||
for (int i=textures_to_bind.size()-1; i>=0; i--) {
|
||||
TexturePtr texture = textures_to_bind.at(i).texture;
|
||||
GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
GLenum target = (texture && texture->params().is_3d()) ? GL_TEXTURE_3D : GL_TEXTURE_2D;
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + i);
|
||||
functions_->glBindTexture(target, 0);
|
||||
}
|
||||
@@ -788,38 +791,6 @@ void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, doub
|
||||
functions_->glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
QVariant OpenGLRenderer::CreateNativeTexture2DInternal(int width, int height, VideoParams::Format format, int channel_count, const void *data, int linesize)
|
||||
{
|
||||
GLuint texture;
|
||||
functions_->glGenTextures(1, &texture);
|
||||
texture_params_.insert(texture, {width, height, 1, format, channel_count});
|
||||
|
||||
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
|
||||
|
||||
GLint current_tex;
|
||||
functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_tex);
|
||||
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
{
|
||||
PRINT_GL_ERRORS;
|
||||
functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count),
|
||||
width, height, 0, GetPixelFormat(channel_count),
|
||||
GetPixelType(format), data);
|
||||
}
|
||||
|
||||
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, current_tex);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
QVariant OpenGLRenderer::CreateNativeTexture2DInternal(const VideoParams ¶ms, const void *data, int linesize)
|
||||
{
|
||||
return CreateNativeTexture2DInternal(params.effective_width(), params.effective_height(), params.format(), params.channel_count(), data, linesize);
|
||||
}
|
||||
|
||||
GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
|
||||
{
|
||||
static const QString shader_preamble =
|
||||
|
||||
@@ -47,37 +47,35 @@ public:
|
||||
|
||||
virtual void PostDestroy() override;
|
||||
|
||||
public slots:
|
||||
virtual void PostInit() override;
|
||||
|
||||
virtual void DestroyInternal() override;
|
||||
|
||||
virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) override;
|
||||
|
||||
virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override;
|
||||
virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override;
|
||||
|
||||
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||
|
||||
virtual QVariant CreateNativeShader(olive::ShaderCode code) override;
|
||||
|
||||
virtual void DestroyNativeShader(QVariant shader) override;
|
||||
|
||||
virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) override;
|
||||
virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) override;
|
||||
|
||||
virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) override;
|
||||
virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) override;
|
||||
|
||||
virtual void Flush() override;
|
||||
|
||||
virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) override;
|
||||
|
||||
protected slots:
|
||||
protected:
|
||||
virtual void Blit(QVariant shader,
|
||||
olive::ShaderJob job,
|
||||
olive::Texture* destination,
|
||||
olive::VideoParams destination_params,
|
||||
bool clear_destination) override;
|
||||
|
||||
virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) override;
|
||||
|
||||
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||
|
||||
virtual void DestroyInternal() override;
|
||||
|
||||
private:
|
||||
static GLint GetInternalFormat(VideoParams::Format format, int channel_layout);
|
||||
|
||||
@@ -85,7 +83,7 @@ private:
|
||||
|
||||
static GLenum GetPixelFormat(int channel_count);
|
||||
|
||||
void AttachTextureAsDestination(olive::Texture* texture);
|
||||
void AttachTextureAsDestination(const QVariant &texture);
|
||||
|
||||
void DetachTextureAsDestination();
|
||||
|
||||
@@ -93,9 +91,6 @@ private:
|
||||
|
||||
void ClearDestinationInternal(double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0);
|
||||
|
||||
QVariant CreateNativeTexture2DInternal(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0);
|
||||
QVariant CreateNativeTexture2DInternal(const VideoParams ¶ms, const void* data = nullptr, int linesize = 0);
|
||||
|
||||
GLuint CompileShader(GLenum type, const QString &code);
|
||||
|
||||
QOpenGLContext* context_;
|
||||
|
||||
@@ -61,7 +61,7 @@ PreviewAutoCacher::~PreviewAutoCacher()
|
||||
SetViewerNode(nullptr);
|
||||
}
|
||||
|
||||
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t)
|
||||
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
|
||||
{
|
||||
// If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now
|
||||
CancelQueuedSingleFrameRender();
|
||||
@@ -70,6 +70,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t)
|
||||
auto sfr = std::make_shared<RenderTicket>();
|
||||
sfr->Start();
|
||||
sfr->setProperty("time", QVariant::fromValue(t));
|
||||
sfr->setProperty("dry", dry);
|
||||
|
||||
// Queue it and try to render
|
||||
single_frame_render_ = sfr;
|
||||
@@ -566,7 +567,8 @@ void PreviewAutoCacher::TryRender()
|
||||
if (single_frame_render_) {
|
||||
// Check if already caching this
|
||||
RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value<rational>(),
|
||||
nullptr);
|
||||
nullptr,
|
||||
single_frame_render_->property("dry").toBool());
|
||||
video_immediate_passthroughs_[watcher].append(single_frame_render_);
|
||||
|
||||
single_frame_render_ = nullptr;
|
||||
@@ -583,7 +585,7 @@ void PreviewAutoCacher::TryRender()
|
||||
// We want this hash, if we're not already rendering, start render now
|
||||
if (!render_task) {
|
||||
// Don't render any hash more than once
|
||||
RenderFrame(t, viewer_node_->video_frame_cache());
|
||||
RenderFrame(t, viewer_node_->video_frame_cache(), false);
|
||||
}
|
||||
|
||||
emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size()));
|
||||
@@ -609,7 +611,7 @@ void PreviewAutoCacher::TryRender()
|
||||
}
|
||||
}
|
||||
|
||||
RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache)
|
||||
RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, FrameHashCache *cache, bool dry)
|
||||
{
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
@@ -623,7 +625,8 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational&
|
||||
time,
|
||||
RenderMode::kOffline,
|
||||
cache,
|
||||
RenderManager::kTexture));
|
||||
dry ? RenderManager::kNull : RenderManager::kTexture));
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
|
||||
virtual ~PreviewAutoCacher() override;
|
||||
|
||||
RenderTicketPtr GetSingleFrame(const rational& t);
|
||||
RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false);
|
||||
|
||||
RenderTicketPtr GetRangeOfAudio(TimeRange range);
|
||||
|
||||
@@ -98,10 +98,10 @@ signals:
|
||||
private:
|
||||
void TryRender();
|
||||
|
||||
RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache);
|
||||
RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache)
|
||||
RenderTicketWatcher *RenderFrame(Node *node, const rational &time, FrameHashCache *cache, bool dry);
|
||||
RenderTicketWatcher *RenderFrame(const rational &time, FrameHashCache *cache, bool dry)
|
||||
{
|
||||
return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache);
|
||||
return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, cache, dry);
|
||||
}
|
||||
|
||||
RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms);
|
||||
|
||||
+66
-26
@@ -20,36 +20,62 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "common/ocioutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
Renderer::Renderer(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, Texture::Type type, const void *data, int linesize)
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (type == Texture::k3D) {
|
||||
v = CreateNativeTexture3D(params.effective_width(), params.effective_height(),
|
||||
params.effective_depth(), params.format(), params.channel_count(), data, linesize);
|
||||
} else {
|
||||
v = CreateNativeTexture2D(params.effective_width(), params.effective_height(), params.format(),
|
||||
params.channel_count(), data, linesize);
|
||||
}
|
||||
|
||||
return CreateTextureFromNativeHandle(v, params, type);
|
||||
}
|
||||
|
||||
TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, int linesize)
|
||||
{
|
||||
return CreateTexture(params, Texture::k2D, data, linesize);
|
||||
QVariant v;
|
||||
|
||||
if (USE_TEXTURE_CACHE) {
|
||||
for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) {
|
||||
if (it->width == params.effective_width()
|
||||
&& it->height == params.effective_height()
|
||||
&& it->depth == params.effective_depth()
|
||||
&& it->format == params.format()
|
||||
&& it->channel_count == params.channel_count()) {
|
||||
v = it->handle;
|
||||
texture_cache_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (v.isNull()) {
|
||||
v = CreateNativeTexture(params.effective_width(), params.effective_height(), params.effective_depth(),
|
||||
params.format(), params.channel_count(), data, linesize);
|
||||
} else if (data) {
|
||||
UploadToTexture(v, params, data, linesize);
|
||||
} else {
|
||||
this->Flush();
|
||||
}
|
||||
|
||||
return CreateTextureFromNativeHandle(v, params);
|
||||
}
|
||||
|
||||
void Renderer::DestroyTexture(Texture *texture)
|
||||
{
|
||||
if (USE_TEXTURE_CACHE) {
|
||||
texture_cache_.push_back({texture->params().effective_width(),
|
||||
texture->params().effective_height(),
|
||||
texture->params().effective_depth(),
|
||||
texture->params().format(),
|
||||
texture->params().channel_count(),
|
||||
texture->id(),
|
||||
QDateTime::currentMSecsSinceEpoch()});
|
||||
|
||||
ClearOldTextures();
|
||||
} else {
|
||||
DestroyNativeTexture(texture->id());
|
||||
}
|
||||
}
|
||||
|
||||
TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms)
|
||||
@@ -95,16 +121,21 @@ void Renderer::Destroy()
|
||||
interlace_texture_.clear();
|
||||
}
|
||||
|
||||
for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); it++) {
|
||||
DestroyNativeTexture(it->handle);
|
||||
}
|
||||
texture_cache_.clear();
|
||||
|
||||
DestroyInternal();
|
||||
}
|
||||
|
||||
TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type)
|
||||
TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms)
|
||||
{
|
||||
if (v.isNull()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_shared<Texture>(this, v, params, type);
|
||||
return std::make_shared<Texture>(this, v, params);
|
||||
}
|
||||
|
||||
bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::ColorContext *ctx)
|
||||
@@ -175,8 +206,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col
|
||||
}
|
||||
|
||||
// Allocate 3D LUT
|
||||
color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount),
|
||||
Texture::k3D, values);
|
||||
color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), values);
|
||||
color_ctx.lut3d_textures[i].name = sampler_name;
|
||||
color_ctx.lut3d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear;
|
||||
}
|
||||
@@ -206,9 +236,7 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col
|
||||
}
|
||||
|
||||
// Allocate 1D LUT
|
||||
color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount),
|
||||
Texture::k2D,
|
||||
values);
|
||||
color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), values);
|
||||
color_ctx.lut1d_textures[i].name = sampler_name;
|
||||
color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear;
|
||||
}
|
||||
@@ -219,6 +247,18 @@ bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::Col
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::ClearOldTextures()
|
||||
{
|
||||
for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); ) {
|
||||
if (it->accessed < QDateTime::currentMSecsSinceEpoch() - MAX_TEXTURE_LIFE) {
|
||||
DestroyNativeTexture(it->handle);
|
||||
it = texture_cache_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *destination, const VideoParams ¶ms)
|
||||
{
|
||||
ColorContext color_ctx;
|
||||
|
||||
+29
-14
@@ -45,9 +45,10 @@ public:
|
||||
|
||||
virtual bool Init() = 0;
|
||||
|
||||
TexturePtr CreateTexture(const VideoParams& params, Texture::Type type, const void* data = nullptr, int linesize = 0);
|
||||
TexturePtr CreateTexture(const VideoParams& params, const void *data = nullptr, int linesize = 0);
|
||||
|
||||
void DestroyTexture(Texture *texture);
|
||||
|
||||
void BlitToTexture(QVariant shader,
|
||||
olive::ShaderJob job,
|
||||
olive::Texture* destination,
|
||||
@@ -82,39 +83,34 @@ public:
|
||||
|
||||
virtual void PostDestroy() = 0;
|
||||
|
||||
public slots:
|
||||
virtual void PostInit() = 0;
|
||||
|
||||
virtual void DestroyInternal() = 0;
|
||||
|
||||
virtual void ClearDestination(olive::Texture *texture = nullptr, double r = 0.0, double g = 0.0, double b = 0.0, double a = 0.0) = 0;
|
||||
|
||||
virtual QVariant CreateNativeTexture2D(int width, int height, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0;
|
||||
virtual QVariant CreateNativeTexture3D(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0;
|
||||
|
||||
virtual void DestroyNativeTexture(QVariant texture) = 0;
|
||||
|
||||
virtual QVariant CreateNativeShader(olive::ShaderCode code) = 0;
|
||||
|
||||
virtual void DestroyNativeShader(QVariant shader) = 0;
|
||||
|
||||
virtual void UploadToTexture(olive::Texture* texture, const void* data, int linesize) = 0;
|
||||
virtual void UploadToTexture(const QVariant &handle, const VideoParams ¶ms, const void* data, int linesize) = 0;
|
||||
|
||||
virtual void DownloadFromTexture(olive::Texture* texture, void* data, int linesize) = 0;
|
||||
virtual void DownloadFromTexture(const QVariant &handle, const VideoParams ¶ms, void* data, int linesize) = 0;
|
||||
|
||||
virtual void Flush() = 0;
|
||||
|
||||
virtual Color GetPixelFromTexture(olive::Texture *texture, const QPointF &pt) = 0;
|
||||
|
||||
protected slots:
|
||||
protected:
|
||||
virtual void Blit(QVariant shader,
|
||||
olive::ShaderJob job,
|
||||
olive::Texture* destination,
|
||||
olive::VideoParams destination_params,
|
||||
bool clear_destination) = 0;
|
||||
|
||||
protected:
|
||||
TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms, Texture::Type type = Texture::k2D);
|
||||
virtual QVariant CreateNativeTexture(int width, int height, int depth, olive::VideoParams::Format format, int channel_count, const void* data = nullptr, int linesize = 0) = 0;
|
||||
|
||||
virtual void DestroyNativeTexture(QVariant texture) = 0;
|
||||
|
||||
virtual void DestroyInternal() = 0;
|
||||
|
||||
private:
|
||||
struct ColorContext {
|
||||
@@ -130,10 +126,29 @@ private:
|
||||
|
||||
};
|
||||
|
||||
TexturePtr CreateTextureFromNativeHandle(const QVariant &v, const VideoParams ¶ms);
|
||||
|
||||
bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx);
|
||||
|
||||
void ClearOldTextures();
|
||||
|
||||
QHash<QString, ColorContext> color_cache_;
|
||||
|
||||
struct CachedTexture
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int depth;
|
||||
VideoParams::Format format;
|
||||
int channel_count;
|
||||
QVariant handle;
|
||||
qint64 accessed;
|
||||
};
|
||||
|
||||
static const int MAX_TEXTURE_LIFE = 5000;
|
||||
static const bool USE_TEXTURE_CACHE = true;
|
||||
std::list<CachedTexture> texture_cache_;
|
||||
|
||||
QMutex color_cache_mutex_;
|
||||
|
||||
QVariant default_shader_;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
namespace olive {
|
||||
|
||||
RenderManager* RenderManager::instance_ = nullptr;
|
||||
const rational RenderManager::kDryRunInterval = rational(10);
|
||||
|
||||
RenderManager::RenderManager(QObject *parent) :
|
||||
backend_(kOpenGL),
|
||||
@@ -52,9 +53,11 @@ RenderManager::RenderManager(QObject *parent) :
|
||||
|
||||
if (context_) {
|
||||
video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this);
|
||||
dry_run_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this);
|
||||
audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this);
|
||||
|
||||
video_thread_->start(QThread::IdlePriority);
|
||||
dry_run_thread_->start(QThread::IdlePriority);
|
||||
audio_thread_->start(QThread::IdlePriority);
|
||||
}
|
||||
|
||||
@@ -73,6 +76,9 @@ RenderManager::~RenderManager()
|
||||
video_thread_->quit();
|
||||
video_thread_->wait();
|
||||
|
||||
dry_run_thread_->quit();
|
||||
dry_run_thread_->wait();
|
||||
|
||||
context_->PostDestroy();
|
||||
delete context_;
|
||||
|
||||
@@ -129,7 +135,11 @@ RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manag
|
||||
ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid()));
|
||||
}
|
||||
|
||||
video_thread_->AddTicket(ticket);
|
||||
if (return_type == ReturnType::kNull) {
|
||||
dry_run_thread_->AddTicket(ticket);
|
||||
} else {
|
||||
video_thread_->AddTicket(ticket);
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
@@ -157,6 +167,8 @@ bool RenderManager::RemoveTicket(RenderTicketPtr ticket)
|
||||
return true;
|
||||
} else if (audio_thread_->RemoveTicket(ticket)) {
|
||||
return true;
|
||||
} else if (dry_run_thread_->RemoveTicket(ticket)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,9 +97,12 @@ public:
|
||||
|
||||
enum ReturnType {
|
||||
kTexture,
|
||||
kFrame
|
||||
kFrame,
|
||||
kNull
|
||||
};
|
||||
|
||||
static const rational kDryRunInterval;
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a frame at a given time
|
||||
*
|
||||
@@ -168,6 +171,7 @@ private:
|
||||
QTimer *decoder_clear_timer_;
|
||||
|
||||
RenderThread *video_thread_;
|
||||
RenderThread *dry_run_thread_;
|
||||
RenderThread *audio_thread_;
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -123,7 +123,7 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time
|
||||
|
||||
render_ctx_->Flush();
|
||||
|
||||
render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels());
|
||||
render_ctx_->DownloadFromTexture(texture->id(), texture->params(), frame->data(), frame->linesize_pixels());
|
||||
}
|
||||
|
||||
return frame;
|
||||
@@ -151,53 +151,57 @@ void RenderProcessor::Run()
|
||||
|
||||
TexturePtr texture = GenerateTexture(time, frame_length);
|
||||
|
||||
if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) {
|
||||
// Get next between frame and interlace it
|
||||
TexturePtr top = texture;
|
||||
TexturePtr bottom = GenerateTexture(time + frame_length, frame_length);
|
||||
|
||||
if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) {
|
||||
std::swap(top, bottom);
|
||||
}
|
||||
|
||||
texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams());
|
||||
}
|
||||
|
||||
if (HeardCancel()) {
|
||||
// Finish cancelled ticket with nothing since we can't guarantee the frame we generated
|
||||
// is actually "complete
|
||||
if (!render_ctx_) {
|
||||
ticket_->Finish();
|
||||
} else {
|
||||
RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt());
|
||||
if (GetCacheVideoParams().interlacing() != VideoParams::kInterlaceNone) {
|
||||
// Get next between frame and interlace it
|
||||
TexturePtr top = texture;
|
||||
TexturePtr bottom = GenerateTexture(time + frame_length, frame_length);
|
||||
|
||||
FramePtr frame;
|
||||
QString cache = ticket_->property("cache").toString();
|
||||
|
||||
if (return_type == RenderManager::kFrame || !cache.isEmpty()) {
|
||||
// Convert to CPU frame
|
||||
frame = GenerateFrame(texture, time);
|
||||
|
||||
// Save to cache if requested
|
||||
if (!cache.isEmpty()) {
|
||||
rational timebase = ticket_->property("cachetimebase").value<rational>();
|
||||
QUuid uuid = ticket_->property("cacheuuid").value<QUuid>();
|
||||
bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame);
|
||||
ticket_->setProperty("cached", cache_result);
|
||||
if (GetCacheVideoParams().interlacing() == VideoParams::kInterlacedBottomFirst) {
|
||||
std::swap(top, bottom);
|
||||
}
|
||||
|
||||
texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams());
|
||||
}
|
||||
|
||||
if (return_type == RenderManager::kTexture) {
|
||||
// Return GPU texture
|
||||
if (!texture) {
|
||||
texture = render_ctx_->CreateTexture(GetCacheVideoParams());
|
||||
render_ctx_->ClearDestination(texture.get());
|
||||
if (HeardCancel()) {
|
||||
// Finish cancelled ticket with nothing since we can't guarantee the frame we generated
|
||||
// is actually "complete
|
||||
ticket_->Finish();
|
||||
} else {
|
||||
RenderManager::ReturnType return_type = RenderManager::ReturnType(ticket_->property("return").toInt());
|
||||
|
||||
FramePtr frame;
|
||||
QString cache = ticket_->property("cache").toString();
|
||||
|
||||
if (return_type == RenderManager::kFrame || !cache.isEmpty()) {
|
||||
// Convert to CPU frame
|
||||
frame = GenerateFrame(texture, time);
|
||||
|
||||
// Save to cache if requested
|
||||
if (!cache.isEmpty()) {
|
||||
rational timebase = ticket_->property("cachetimebase").value<rational>();
|
||||
QUuid uuid = ticket_->property("cacheuuid").value<QUuid>();
|
||||
bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame);
|
||||
ticket_->setProperty("cached", cache_result);
|
||||
}
|
||||
}
|
||||
|
||||
render_ctx_->Flush();
|
||||
if (return_type == RenderManager::kTexture) {
|
||||
// Return GPU texture
|
||||
if (!texture) {
|
||||
texture = render_ctx_->CreateTexture(GetCacheVideoParams());
|
||||
render_ctx_->ClearDestination(texture.get());
|
||||
}
|
||||
|
||||
ticket_->Finish(QVariant::fromValue(texture));
|
||||
} else {
|
||||
ticket_->Finish(QVariant::fromValue(frame));
|
||||
render_ctx_->Flush();
|
||||
|
||||
ticket_->Finish(QVariant::fromValue(texture));
|
||||
} else {
|
||||
ticket_->Finish(QVariant::fromValue(frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -265,6 +269,11 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c
|
||||
<< "::" << stream.stream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!render_ctx_) {
|
||||
// Assume dry run and increment access time
|
||||
decoder.decoder->IncrementAccessTime(RenderManager::kDryRunInterval.toDouble() * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return decoder.decoder;
|
||||
@@ -404,10 +413,6 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
|
||||
|
||||
void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time)
|
||||
{
|
||||
if (!render_ctx_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
|
||||
// Video cannot contribute to audio, so we do nothing here
|
||||
return;
|
||||
@@ -440,21 +445,23 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ
|
||||
break;
|
||||
case VideoParams::kVideoTypeImageSequence:
|
||||
{
|
||||
// Since image sequences involve multiple files, we don't engage the decoder cache
|
||||
decoder = Decoder::CreateFromID(decoder_id);
|
||||
if (render_ctx_) {
|
||||
// Since image sequences involve multiple files, we don't engage the decoder cache
|
||||
decoder = Decoder::CreateFromID(decoder_id);
|
||||
|
||||
QString frame_filename;
|
||||
QString frame_filename;
|
||||
|
||||
int64_t frame_number = stream_data.get_time_in_timebase_units(input_time);
|
||||
frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number);
|
||||
int64_t frame_number = stream_data.get_time_in_timebase_units(input_time);
|
||||
frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number);
|
||||
|
||||
// Decoder will close automatically since it's a stream_ptr
|
||||
decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock()));
|
||||
// Decoder will close automatically since it's a stream_ptr
|
||||
decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder) {
|
||||
if (decoder && render_ctx_) {
|
||||
Decoder::RetrieveVideoParams p;
|
||||
p.divider = stream.video_params().divider();
|
||||
p.maximum_format = destination->format();
|
||||
|
||||
@@ -29,21 +29,21 @@ const Texture::Interpolation Texture::kDefaultInterpolation = Texture::kMipmappe
|
||||
Texture::~Texture()
|
||||
{
|
||||
if (renderer_) {
|
||||
renderer_->DestroyNativeTexture(id_);
|
||||
renderer_->DestroyTexture(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::Upload(void *data, int linesize)
|
||||
{
|
||||
if (renderer_) {
|
||||
renderer_->UploadToTexture(this, data, linesize);
|
||||
renderer_->UploadToTexture(this->id(), this->params(), data, linesize);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::Download(void *data, int linesize)
|
||||
{
|
||||
if (renderer_) {
|
||||
renderer_->DownloadFromTexture(this, data, linesize);
|
||||
renderer_->DownloadFromTexture(this->id(), this->params(), data, linesize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-17
@@ -32,11 +32,6 @@ class Renderer;
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
enum Type {
|
||||
k2D,
|
||||
k3D
|
||||
};
|
||||
|
||||
enum Interpolation {
|
||||
kNearest,
|
||||
kLinear,
|
||||
@@ -50,19 +45,17 @@ public:
|
||||
*/
|
||||
Texture(const VideoParams& param) :
|
||||
renderer_(nullptr),
|
||||
params_(param),
|
||||
type_(k2D)
|
||||
params_(param)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a real texture linked to a renderer backend
|
||||
*/
|
||||
Texture(Renderer* renderer, const QVariant& native, const VideoParams& param, Type type) :
|
||||
Texture(Renderer* renderer, const QVariant& native, const VideoParams& param) :
|
||||
renderer_(renderer),
|
||||
params_(param),
|
||||
id_(native),
|
||||
type_(type)
|
||||
id_(native)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -117,11 +110,6 @@ public:
|
||||
return params_.pixel_aspect_ratio();
|
||||
}
|
||||
|
||||
Type type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
Renderer* renderer() const
|
||||
{
|
||||
return renderer_;
|
||||
@@ -134,8 +122,6 @@ private:
|
||||
|
||||
QVariant id_;
|
||||
|
||||
Type type_;
|
||||
|
||||
};
|
||||
|
||||
using TexturePtr = std::shared_ptr<Texture>;
|
||||
|
||||
@@ -121,6 +121,8 @@ public:
|
||||
calculate_effective_size();
|
||||
}
|
||||
|
||||
bool is_3d() const { return depth_ > 1; }
|
||||
|
||||
const rational& time_base() const
|
||||
{
|
||||
return time_base_;
|
||||
|
||||
@@ -62,7 +62,11 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) :
|
||||
attached_renderer_ = new OpenGLRenderer(this);
|
||||
|
||||
// Create widget wrapper for OpenGL window
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
wrapper_ = QWidget::createWindowContainer(static_cast<ManagedDisplayWidgetOpenGL*>(inner_widget_));
|
||||
#else
|
||||
wrapper_ = inner_widget_;
|
||||
#endif
|
||||
layout->addWidget(wrapper_);
|
||||
} else {
|
||||
inner_widget_ = nullptr;
|
||||
|
||||
@@ -21,9 +21,15 @@
|
||||
#ifndef MANAGEDDISPLAYOBJECT_H
|
||||
#define MANAGEDDISPLAYOBJECT_H
|
||||
|
||||
#define USE_QOPENGLWINDOW
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLContext>
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
#include <QOpenGLWindow>
|
||||
#else
|
||||
#include <QOpenGLWidget>
|
||||
#endif
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "render/renderer.h"
|
||||
@@ -31,7 +37,12 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ManagedDisplayWidgetOpenGL : public QOpenGLWindow
|
||||
class ManagedDisplayWidgetOpenGL
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
: public QOpenGLWindow
|
||||
#else
|
||||
: public QOpenGLWidget
|
||||
#endif
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -175,7 +186,12 @@ protected:
|
||||
|
||||
void doneCurrent();
|
||||
|
||||
QWindow* inner_widget() const
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
QWindow*
|
||||
#else
|
||||
QWidget*
|
||||
#endif
|
||||
inner_widget() const
|
||||
{
|
||||
return inner_widget_;
|
||||
}
|
||||
@@ -225,7 +241,11 @@ private:
|
||||
/**
|
||||
* @brief Main drawing surface abstraction
|
||||
*/
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
QWindow* inner_widget_;
|
||||
#else
|
||||
QWidget* inner_widget_;
|
||||
#endif
|
||||
QWidget *wrapper_;
|
||||
|
||||
/**
|
||||
|
||||
@@ -653,9 +653,11 @@ void ProjectExplorer::DeleteSelected()
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectExplorer::SelectItem(Node *n)
|
||||
bool ProjectExplorer::SelectItem(Node *n, bool deselect_all_first)
|
||||
{
|
||||
DeselectAll();
|
||||
if (deselect_all_first) {
|
||||
DeselectAll();
|
||||
}
|
||||
|
||||
QModelIndex index = model_.CreateIndexFromItem(n);
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
|
||||
void DeleteSelected();
|
||||
|
||||
bool SelectItem(Node *n);
|
||||
bool SelectItem(Node *n, bool deselect_all_first = true);
|
||||
|
||||
public slots:
|
||||
void set_view_type(ProjectToolbar::ViewType type);
|
||||
|
||||
@@ -86,6 +86,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
|
||||
ruler_and_time_layout->addWidget(ruler());
|
||||
|
||||
ruler()->setFocusPolicy(Qt::TabFocus);
|
||||
QWidget::setTabOrder(ruler(), timecode_label_);
|
||||
|
||||
// Create list of TimelineViews - these MUST correspond to the ViewType enum
|
||||
|
||||
view_splitter_ = new QSplitter(Qt::Vertical);
|
||||
|
||||
@@ -63,8 +63,15 @@ void TrackRippleRemoveAreaCommand::prepare()
|
||||
|
||||
// If it's getting trimmed, determine if it's actually getting spliced
|
||||
if (first_block_is_out_trimmed && first_block_is_in_trimmed) {
|
||||
// This block is getting spliced, so we'll handle that later
|
||||
splice_split_command_ = new BlockSplitCommand(first_block, range_.in());
|
||||
if (dynamic_cast<GapBlock*>(first_block)) {
|
||||
// As a rule, we don't split gaps, so we just treat it as a trim of the range requested
|
||||
trim_out_ = {first_block,
|
||||
first_block->length(),
|
||||
first_block->length() - range_.length()};
|
||||
} else {
|
||||
// This block is getting spliced, so we'll handle that later
|
||||
splice_split_command_ = new BlockSplitCommand(first_block, range_.in());
|
||||
}
|
||||
} else {
|
||||
// It's just getting trimmed or removed, so we'll append that operation
|
||||
if (first_block_is_out_trimmed) {
|
||||
|
||||
@@ -560,6 +560,35 @@ void ViewerWidget::ShowSubtitleProperties()
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::DryRunFinished()
|
||||
{
|
||||
RenderTicketWatcher *w = static_cast<RenderTicketWatcher*>(sender());
|
||||
|
||||
if (dry_run_watchers_.contains(w)) {
|
||||
RequestNextDryRun();
|
||||
}
|
||||
|
||||
delete w;
|
||||
}
|
||||
|
||||
void ViewerWidget::RequestNextDryRun()
|
||||
{
|
||||
if (IsPlaying()) {
|
||||
rational next_time = Timecode::timestamp_to_time(dry_run_next_frame_, timebase());
|
||||
if (FrameExistsAtTime(next_time)) {
|
||||
if (next_time > GetTime() + RenderManager::kDryRunInterval) {
|
||||
QTimer::singleShot(timebase().toDouble() / playback_speed_, this, &ViewerWidget::RequestNextDryRun);
|
||||
} else {
|
||||
RenderTicketWatcher *watcher = new RenderTicketWatcher(this);
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished);
|
||||
watcher->SetTicket(auto_cacher_.GetSingleFrame(next_time, true));
|
||||
dry_run_next_frame_ += playback_speed_;
|
||||
dry_run_watchers_.append(watcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::CloseAudioProcessor()
|
||||
{
|
||||
audio_processor_.Close();
|
||||
@@ -828,6 +857,9 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
for (int i=0; i<prequeue_length_; i++) {
|
||||
RequestNextFrameForQueue();
|
||||
}
|
||||
|
||||
dry_run_next_frame_ = playback_queue_next_frame_;
|
||||
RequestNextDryRun();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +877,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
QueueNextAudioBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
// Force screen to stay awake
|
||||
PreventSleep(true);
|
||||
}
|
||||
@@ -893,6 +926,7 @@ void ViewerWidget::PauseInternal()
|
||||
|
||||
prequeuing_video_ = false;
|
||||
prequeuing_audio_ = 0;
|
||||
dry_run_watchers_.clear();
|
||||
|
||||
// Reset screen timeout timer
|
||||
PreventSleep(false);
|
||||
@@ -1048,7 +1082,7 @@ int ViewerWidget::DeterminePlaybackQueueSize()
|
||||
end_ts = 0;
|
||||
}
|
||||
|
||||
int remaining_frames = (end_ts - GetTimestamp()) / playback_speed_;
|
||||
int remaining_frames = (end_ts - GetTimestamp() - 1) / playback_speed_;
|
||||
|
||||
// Generate maximum queue
|
||||
int max_frames = qCeil(kVideoPlaybackInterval.toDouble() / timebase().toDouble());
|
||||
|
||||
@@ -256,6 +256,7 @@ private:
|
||||
QTimer playback_backup_timer_;
|
||||
|
||||
int64_t playback_queue_next_frame_;
|
||||
int64_t dry_run_next_frame_;
|
||||
QVector<ViewerDisplayWidget*> playback_devices_;
|
||||
|
||||
bool prequeuing_video_;
|
||||
@@ -294,6 +295,8 @@ private:
|
||||
|
||||
WaveformMode waveform_mode_;
|
||||
|
||||
QVector<RenderTicketWatcher*> dry_run_watchers_;
|
||||
|
||||
private slots:
|
||||
void PlaybackTimerUpdate();
|
||||
|
||||
@@ -354,6 +357,10 @@ private slots:
|
||||
|
||||
void ShowSubtitleProperties();
|
||||
|
||||
void DryRunFinished();
|
||||
|
||||
void RequestNextDryRun();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -456,84 +456,7 @@ void ViewerDisplayWidget::OnPaint()
|
||||
}
|
||||
|
||||
// Extraordinarily basic subtitle renderer. Hoping to swap this out with libass at some point.
|
||||
if (show_subtitles_ && subtitle_tracks_) {
|
||||
const QVector<Track*> &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks();
|
||||
|
||||
if (!subtitle_tracklist.empty()) {
|
||||
QPainterPath path;
|
||||
|
||||
QTransform transform = GenerateWorldTransform();
|
||||
QRect bounding_box = transform.mapRect(rect());
|
||||
|
||||
QFont f;
|
||||
qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt();
|
||||
{
|
||||
// Scale font size by transform
|
||||
QTransform display_transform = GenerateDisplayTransform();
|
||||
font_sz *= display_transform.m11();
|
||||
}
|
||||
f.setPointSizeF(font_sz);
|
||||
|
||||
QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString();
|
||||
if (!family.isEmpty()) {
|
||||
f.setFamily(family);
|
||||
}
|
||||
|
||||
f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt());
|
||||
|
||||
bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10);
|
||||
|
||||
QFontMetrics fm(f);
|
||||
|
||||
for (int j=subtitle_tracklist.size()-1; j>=0; j--) {
|
||||
Track *sub_track = subtitle_tracklist.at(j);
|
||||
if (!sub_track->IsMuted()) {
|
||||
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->BlockAtTime(time_))) {
|
||||
// Split into lines
|
||||
QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width());
|
||||
|
||||
for (int i=list.size()-1; i>=0; i--) {
|
||||
int w = QtUtils::QFontMetricsWidth(fm, list.at(i));
|
||||
path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool();
|
||||
|
||||
QPixmap *aa_pixmap;
|
||||
QPainter *text_painter;
|
||||
if (antialias) {
|
||||
// QPainter only supports anti-aliasing in software, so to achieve it, we draw to a
|
||||
// software buffer first and then draw that onto the hardware
|
||||
aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height());
|
||||
aa_pixmap->fill(Qt::transparent);
|
||||
text_painter = new QPainter(aa_pixmap);
|
||||
} else {
|
||||
// Just draw straight to the hardware
|
||||
text_painter = new QPainter(paint_device());
|
||||
|
||||
// Offset path by however much is necessary
|
||||
path.translate(bounding_box.x(), bounding_box.y());
|
||||
}
|
||||
|
||||
text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16));
|
||||
text_painter->setBrush(Qt::white);
|
||||
text_painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
text_painter->drawPath(path);
|
||||
|
||||
delete text_painter;
|
||||
|
||||
if (antialias) {
|
||||
// We just drew to a software buffer, now draw this image onto the hardware device
|
||||
QPainter p(paint_device());
|
||||
p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap);
|
||||
delete aa_pixmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawSubtitleTracks();
|
||||
|
||||
if (add_band_) {
|
||||
QPainter p(paint_device());
|
||||
@@ -974,6 +897,93 @@ void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e)
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::DrawSubtitleTracks()
|
||||
{
|
||||
if (!show_subtitles_ || !subtitle_tracks_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QVector<Track*> &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks();
|
||||
if (subtitle_tracklist.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scale font size by transform
|
||||
QTransform display_transform = GenerateDisplayTransform();
|
||||
qreal font_sz = OLIVE_CONFIG("DefaultSubtitleSize").toInt();
|
||||
font_sz *= display_transform.m11();
|
||||
if (qIsNaN(font_sz)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPainterPath path;
|
||||
|
||||
QTransform transform = GenerateWorldTransform();
|
||||
QRect bounding_box = transform.mapRect(rect());
|
||||
|
||||
QFont f;
|
||||
f.setPointSizeF(font_sz);
|
||||
|
||||
QString family = OLIVE_CONFIG("DefaultSubtitleFamily").toString();
|
||||
if (!family.isEmpty()) {
|
||||
f.setFamily(family);
|
||||
}
|
||||
|
||||
f.setWeight(OLIVE_CONFIG("DefaultSubtitleWeight").toInt());
|
||||
|
||||
bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10);
|
||||
|
||||
QFontMetrics fm(f);
|
||||
|
||||
for (int j=subtitle_tracklist.size()-1; j>=0; j--) {
|
||||
Track *sub_track = subtitle_tracklist.at(j);
|
||||
if (!sub_track->IsMuted()) {
|
||||
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->BlockAtTime(time_))) {
|
||||
// Split into lines
|
||||
QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width());
|
||||
|
||||
for (int i=list.size()-1; i>=0; i--) {
|
||||
int w = QtUtils::QFontMetricsWidth(fm, list.at(i));
|
||||
path.addText(bounding_box.width()/2 - w/2, bounding_box.height() - fm.height() * (list.size() - i) + fm.ascent(), f, list.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool antialias = OLIVE_CONFIG("AntialiasSubtitles").toBool();
|
||||
|
||||
QPixmap *aa_pixmap;
|
||||
QPainter *text_painter;
|
||||
if (antialias) {
|
||||
// QPainter only supports anti-aliasing in software, so to achieve it, we draw to a
|
||||
// software buffer first and then draw that onto the hardware
|
||||
aa_pixmap = new QPixmap(bounding_box.width(), bounding_box.height());
|
||||
aa_pixmap->fill(Qt::transparent);
|
||||
text_painter = new QPainter(aa_pixmap);
|
||||
} else {
|
||||
// Just draw straight to the hardware
|
||||
text_painter = new QPainter(paint_device());
|
||||
|
||||
// Offset path by however much is necessary
|
||||
path.translate(bounding_box.x(), bounding_box.y());
|
||||
}
|
||||
|
||||
text_painter->setPen(QPen(Qt::black, f.pointSizeF() / 16));
|
||||
text_painter->setBrush(Qt::white);
|
||||
text_painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
text_painter->drawPath(path);
|
||||
|
||||
delete text_painter;
|
||||
|
||||
if (antialias) {
|
||||
// We just drew to a software buffer, now draw this image onto the hardware device
|
||||
QPainter p(paint_device());
|
||||
p.drawPixmap(bounding_box.x(), bounding_box.y(), *aa_pixmap);
|
||||
delete aa_pixmap;
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::SetShowFPS(bool e)
|
||||
{
|
||||
show_fps_ = e;
|
||||
|
||||
@@ -263,6 +263,8 @@ private:
|
||||
|
||||
void EmitColorAtCursor(QMouseEvent* e);
|
||||
|
||||
void DrawSubtitleTracks();
|
||||
|
||||
/**
|
||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
||||
*/
|
||||
|
||||
@@ -410,6 +410,16 @@ void MainWindow::SetApplicationProgressValue(int value)
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::SelectFootage(const QVector<Footage *> &e)
|
||||
{
|
||||
for (ProjectPanel *p : project_panels_) {
|
||||
SelectFootageForProjectPanel(e, p);
|
||||
}
|
||||
for (ProjectPanel *p : folder_panels_) {
|
||||
SelectFootageForProjectPanel(e, p);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
// Try to close all projects (this will return false if the user chooses not to close)
|
||||
@@ -759,6 +769,16 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel)
|
||||
param_panel_->SetContexts(context);
|
||||
}
|
||||
|
||||
void MainWindow::SelectFootageForProjectPanel(const QVector<Footage *> &e, ProjectPanel *p)
|
||||
{
|
||||
p->DeselectAll();
|
||||
for (Footage *f : e) {
|
||||
if (p->get_root()->HasChildRecursive(f)) {
|
||||
p->SelectItem(f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::FocusedPanelChanged(PanelWidget *panel)
|
||||
{
|
||||
// Update audio monitor panel
|
||||
|
||||
@@ -92,6 +92,8 @@ public:
|
||||
*/
|
||||
void SetApplicationProgressValue(int value);
|
||||
|
||||
void SelectFootage(const QVector<Footage*> &e);
|
||||
|
||||
public slots:
|
||||
void ProjectOpen(Project *p);
|
||||
|
||||
@@ -142,6 +144,8 @@ private:
|
||||
|
||||
void UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel);
|
||||
|
||||
void SelectFootageForProjectPanel(const QVector<Footage*> &e, ProjectPanel *p);
|
||||
|
||||
QByteArray premaximized_state_;
|
||||
|
||||
// Standard panels
|
||||
|
||||
Reference in New Issue
Block a user