Files
oak-editor/app/render/opengl/openglrenderer.cpp
T
itsmattkc 3aafb2466d Squashed commit of the following:
commit f05b5b3ff8286f6252c2a3f3c8c367f8a5b821b5
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 13:05:25 2022 -0700

    final consistency modifications

commit 4f27718d46de9660822eb197b0e900be947ec515
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 11:40:58 2022 -0700

    opengl: don't set texture options for null texture

commit bb54cfcf4e219e1cbf1ecc6b5552cca75e6e1573
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 11:40:29 2022 -0700

    rename inputs

commit e5ac8fffa2ac15f5549b6da5f42b05659c6d020f
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 10:37:23 2022 -0700

    change alpha association formula

commit dee09a88f17552e3f98be8940966822e45d6df4f
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 10:36:58 2022 -0700

    glsl: use constants to improve readability

commit aedc75ebee11b61198f29c464501a2ddb0506129
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 10:36:26 2022 -0700

    changed id capitalization

commit 470b2bb85b51c4a57c109d3fb230088a81024277
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 10:35:53 2022 -0700

    node: update categories

commit c78dacad82051dfbb3e40c92cfaf456dbff2de0d
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 09:22:57 2022 -0700

    updated shaders to es 3

commit 1e7544be93e55916c94cd91629ec5eef728bfad8
Merge: 1bbb8d7f9 25d019609
Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com>
Date:   Tue Apr 12 09:02:32 2022 -0700

    Merge branch 'master' into pr/1818

commit 1bbb8d7f9acd75cb4498733f105fbe0487c0db9c
Author: brimson <40442071+brimson@users.noreply.github.com>
Date:   Tue Jan 18 18:36:24 2022 -0700

    Simplify colordifferencekey.frag

    Avoiding extra MOV instruction

commit c7cf1021ce5a3d090833e94e4bc212cc51a61a2a
Author: brimson <40442071+brimson@users.noreply.github.com>
Date:   Tue Jan 18 11:43:02 2022 -0700

    Revert switch-case changes

    Thanks Simran, MattKC, and Wilshaw for the clarifications

commit 882ffeede01b81f0cb4dfd885ba05bf66119ac7e
Author: brimson <40442071+brimson@users.noreply.github.com>
Date:   Tue Jan 18 00:06:31 2022 -0700

    Optimize despill shader

    Turn previous averaging from (1 MAD + 1 MUL) to (1 DP2), reuse the result to prevent recalculation.

commit 0c93f519977fa34840ab03d51addcf2456f931d8
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sat Jan 8 17:23:49 2022 +0000

    Cleanup

commit 222560ce1a4666899b1111854d4d549dd7ba1357
Merge: 96e21c2b4 d3ffeaa35
Author: ThomasWilshaw <thomaswilshaw@gmail.com>
Date:   Tue Jan 4 23:50:39 2022 +0000

    Merge branch 'master' into color_key

commit 96e21c2b43db3bf1051aca696970509292159548
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Tue Jan 4 23:45:26 2022 +0000

    Rename and reoganise keying nodes

commit 6d42ed23a69ebdb1f5b399957f0b37434d0519dd
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Tue Jan 4 23:04:50 2022 +0000

    Move despill code to it's own node

commit e97ab2e16fd1d4e2f2ff145b8c023ff0e1ee451e
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Mon Jan 3 21:39:48 2022 +0000

    Add option for blue screens

commit 2346f44e5af48bb9547c77c4c08ee7abfcb09393
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Mon Jan 3 21:14:55 2022 +0000

    Optimise node slightly

commit 446522edcf63525fafe7a97cf6d59b7ea0385460
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Mon Jan 3 21:10:03 2022 +0000

    Fix GL ES compile error

commit a765ce6c6f3e0c0e996f08e2a3ef1101818098e8
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 18:27:24 2022 +0000

    Cleanup

commit d268e0645df34aa306dc391d1dc10bf335524f94
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 18:03:26 2022 +0000

    Fix core matte and change Darks start value

commit 30b024dc5f59dd60ffc5012eee7a79201ed4f9c3
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 17:26:39 2022 +0000

    Change matte controls and cleanup

commit b742a9ba7ed651785a0781289a4c03fd4fb72e03
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 16:31:41 2022 +0000

    Add a core and garbage matte

commit f814d036b61b8de090df4c1f080c124b98b432d6
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 03:31:20 2022 +0000

    Improve basic keyer

commit 4995ec3b9b92c9b4cb0dc31a247d585b2a47e408
Author: Thomas Wilshaw <thomaswilshaw@gmail.com>
Date:   Sun Jan 2 00:23:18 2022 +0000

    Start simple color key node
2022-04-12 13:05:45 -07:00

954 lines
28 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 Olive 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 "openglrenderer.h"
#include <QDateTime>
#include <QDebug>
#include <QOpenGLExtraFunctions>
namespace olive {
const int OpenGLRenderer::kTextureCacheMaxSize = 5000;
const QVector<GLfloat> blit_vertices = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
const QVector<GLfloat> blit_texcoords = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
class ErrorPrinter {
public:
ErrorPrinter(const char* name, QOpenGLFunctions* f)
{
GLuint err = f->glGetError();
if (err > 0)
qDebug() << name << "entered with" << err;
name_ = name;
functions_ = f;
}
~ErrorPrinter()
{
GLuint err = functions_->glGetError();
if (err > 0)
qDebug() << name_ << "exited with" << err;
}
private:
const char* name_;
QOpenGLFunctions* functions_;
};
#define PRINT_GL_ERRORS ErrorPrinter __e(__FUNCTION__, functions_)
#define GL_PREAMBLE \
QMutexLocker __l(&global_opengl_mutex);
QMutex global_opengl_mutex;
OpenGLRenderer::OpenGLRenderer(QObject* parent) :
Renderer(parent),
cache_timer_(this),
context_(nullptr),
framebuffer_(0)
{
cache_timer_.setInterval(kTextureCacheMaxSize);
connect(&cache_timer_, &QTimer::timeout, this, &OpenGLRenderer::GarbageCollectTextureCache);
}
OpenGLRenderer::~OpenGLRenderer()
{
Destroy();
PostDestroy();
}
void OpenGLRenderer::Init(QOpenGLContext *existing_ctx)
{
if (context_) {
qCritical() << "Can't initialize already initialized OpenGLRenderer";
return;
}
context_ = existing_ctx;
}
bool OpenGLRenderer::Init()
{
QMutexLocker locker(&global_opengl_mutex);
if (context_) {
qCritical() << "Can't initialize already initialized OpenGLRenderer";
return false;
}
surface_.create();
context_ = new QOpenGLContext(this);
context_->setShareContext(QOpenGLContext::globalShareContext());
if (!context_->create()) {
qCritical() << "Failed to create OpenGL context";
return false;
}
context_->moveToThread(this->thread());
return true;
}
void OpenGLRenderer::PostDestroy()
{
// Destroy surface if we created it
if (surface_.isValid()) {
surface_.destroy();
}
}
void OpenGLRenderer::PostInit()
{
GL_PREAMBLE;
// Make context current on that surface
if (context_->parent() == this && !context_->makeCurrent(&surface_)) {
qCritical() << "Failed to makeCurrent() on offscreen surface in thread" << thread();
return;
}
functions_ = context_->functions();
// Store OpenGL functions instance
functions_->glBlendFunc(GL_ONE, GL_ZERO);
// Set up framebuffer used for various things
functions_->glGenFramebuffers(1, &framebuffer_);
cache_timer_.start();
}
void OpenGLRenderer::DestroyInternal()
{
if (context_) {
GL_PREAMBLE;
// Delete framebuffer
functions_->glDeleteFramebuffers(1, &framebuffer_);
framebuffer_ = 0;
for (auto it=texture_cache_.cbegin(); it!=texture_cache_.cend(); it++) {
functions_->glDeleteTextures(1, &it->texture);
}
texture_cache_.clear();
// Delete context if it belongs to us
if (context_->parent() == this) {
delete context_;
}
context_ = nullptr;
}
cache_timer_.stop();
}
void OpenGLRenderer::ClearDestination(Texture *texture, double r, double g, double b, double a)
{
GL_PREAMBLE;
if (texture) {
AttachTextureAsDestination(texture);
}
ClearDestinationInternal(r, g, b, a);
if (texture) {
DetachTextureAsDestination();
}
}
QVariant OpenGLRenderer::CreateNativeTexture2D(int width, int height, 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;
GLuint texture = GetCachedTexture(width, height, depth, format, channel_count);
// If no texture in cache, generate new texture
bool new_tex = (texture == 0);
if (new_tex) {
functions_->glGenTextures(1, &texture);
texture_params_.insert(texture, {width, height, depth, format, channel_count});
}
if (new_tex || data) {
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
GLint current_tex;
functions_->glGetIntegerv(GL_TEXTURE_BINDING_3D, &current_tex);
functions_->glBindTexture(GL_TEXTURE_3D, texture);
if (new_tex) {
context_->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GetInternalFormat(format, channel_count),
width, height, depth, 0, GetPixelFormat(channel_count),
GetPixelType(format), data);
} else {
context_->extraFunctions()->glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0,
width, height, depth,
GetPixelFormat(channel_count), GetPixelType(format),
data);
}
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
functions_->glBindTexture(GL_TEXTURE_3D, current_tex);
}
return texture;
}
void OpenGLRenderer::AttachTextureAsDestination(Texture* texture)
{
PRINT_GL_ERRORS;
functions_->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
functions_->glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
texture->id().value<GLuint>(),
0);
}
void OpenGLRenderer::DetachTextureAsDestination()
{
functions_->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void OpenGLRenderer::DestroyNativeTexture(QVariant texture)
{
GLuint t = texture.value<GLuint>();
if (t > 0) {
TextureCacheKey key = texture_params_.value(t);
TextureCacheEntry entry = {key, t, QDateTime::currentMSecsSinceEpoch()};
texture_cache_.append(entry);
}
}
QVariant OpenGLRenderer::CreateNativeShader(ShaderCode code)
{
GL_PREAMBLE;
PRINT_GL_ERRORS;
GLuint vert = CompileShader(GL_VERTEX_SHADER, code.vert_code());
GLuint frag = CompileShader(GL_FRAGMENT_SHADER, code.frag_code());
GLuint program = 0;
if (frag && vert) {
program = functions_->glCreateProgram();
functions_->glAttachShader(program, frag);
functions_->glAttachShader(program, vert);
functions_->glLinkProgram(program);
GLint success;
functions_->glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
qWarning() << "Failed to link OpenGL shader program";
functions_->glDeleteProgram(program);
program = 0;
}
}
functions_->glDeleteShader(frag);
functions_->glDeleteShader(vert);
return program;
}
void OpenGLRenderer::DestroyNativeShader(QVariant shader)
{
GL_PREAMBLE;
GLuint program = shader.value<GLuint>();
functions_->glDeleteProgram(program);
}
void OpenGLRenderer::UploadToTexture(Texture *texture, const void *data, int linesize)
{
GL_PREAMBLE;
GLuint t = texture->id().value<GLuint>();
const VideoParams& p = texture->params();
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;
// Store currently bound texture so it can be restored later
GLint current_tex;
functions_->glGetIntegerv(tex_binding, &current_tex);
functions_->glBindTexture(tex_type, t);
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
{
PRINT_GL_ERRORS;
if (texture->type() == Texture::k2D) {
functions_->glTexSubImage2D(tex_type, 0, 0, 0,
p.effective_width(), p.effective_height(),
GetPixelFormat(p.channel_count()), GetPixelType(p.format()),
data);
} else {
context_->extraFunctions()->glTexSubImage3D(tex_type, 0, 0, 0, 0,
p.effective_width(), p.effective_height(), p.effective_depth(),
GetPixelFormat(p.channel_count()), GetPixelType(p.format()),
data);
}
}
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
functions_->glBindTexture(tex_type, current_tex);
}
void OpenGLRenderer::DownloadFromTexture(Texture* texture, void *data, int linesize)
{
GL_PREAMBLE;
const VideoParams& p = texture->params();
GLint current_tex;
functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_tex);
AttachTextureAsDestination(texture);
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize);
{
PRINT_GL_ERRORS;
functions_->glReadPixels(0,
0,
p.effective_width(),
p.effective_height(),
GetPixelFormat(p.channel_count()),
GetPixelType(p.format()),
data);
}
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
DetachTextureAsDestination();
functions_->glBindTexture(GL_TEXTURE_2D, current_tex);
}
void OpenGLRenderer::Flush()
{
GL_PREAMBLE;
functions_->glFinish();
}
Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
{
AttachTextureAsDestination(texture);
QByteArray data(VideoParams::GetBytesPerPixel(texture->format(), texture->channel_count()), Qt::Uninitialized);
functions_->glReadPixels(pt.x(), pt.y(), 1, 1, GetPixelFormat(texture->channel_count()), GetPixelType(texture->format()), data.data());
Color c = Color::fromData(data.data(), texture->format(), texture->channel_count());
if (texture->channel_count() == VideoParams::kRGBChannelCount) {
// No alpha channel, set to 1.0
c.set_alpha(1.0);
}
DetachTextureAsDestination();
return c;
}
struct TextureToBind {
TexturePtr texture;
Texture::Interpolation interpolation;
};
void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, VideoParams destination_params, bool clear_destination)
{
GL_PREAMBLE;
// If this node is iterative, we'll pick up which input here
QString iterative_name;
GLuint iterative_input = 0;
QVector<TextureToBind> textures_to_bind;
GLuint shader = s.value<GLuint>();
functions_->glUseProgram(shader);
for (auto it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) {
// See if the shader has takes this parameter as an input
GLint variable_location = functions_->glGetUniformLocation(shader, it.key().toUtf8().constData());
if (variable_location == -1) {
continue;
}
// This variable is used in the shader, let's set it
const NodeValue& value = it.value();
if (value.array()) {
continue;
}
switch (value.type()) {
case NodeValue::kInt:
// kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to
// over/underflows if the number is large enough, but the likelihood of that is quite low.
functions_->glUniform1i(variable_location, value.data().toInt());
break;
case NodeValue::kFloat:
// kFloat technically specifies a double but as above, OpenGL doesn't support those.
functions_->glUniform1f(variable_location, value.data().toFloat());
break;
case NodeValue::kVec2:
{
QVector2D v = value.data().value<QVector2D>();
functions_->glUniform2fv(variable_location, 1, reinterpret_cast<const GLfloat*>(&v));
break;
}
case NodeValue::kVec3:
{
QVector3D v = value.data().value<QVector3D>();
functions_->glUniform3fv(variable_location, 1, reinterpret_cast<const GLfloat*>(&v));
break;
}
case NodeValue::kVec4:
{
QVector4D v = value.data().value<QVector4D>();
functions_->glUniform4fv(variable_location, 1, reinterpret_cast<const GLfloat*>(&v));
break;
}
case NodeValue::kMatrix:
functions_->glUniformMatrix4fv(variable_location, 1, false, value.data().value<QMatrix4x4>().constData());
break;
case NodeValue::kCombo:
functions_->glUniform1i(variable_location, value.data().value<int>());
break;
case NodeValue::kColor:
{
Color color = value.data().value<Color>();
functions_->glUniform4f(variable_location, color.red(), color.green(), color.blue(), color.alpha());
break;
}
case NodeValue::kBoolean:
functions_->glUniform1i(variable_location, value.data().toBool());
break;
case NodeValue::kTexture:
{
TexturePtr texture = value.data().value<TexturePtr>();
// Set value to bound texture
functions_->glUniform1i(variable_location, textures_to_bind.size());
// If this texture binding is the iterative input, set it here
if (it.key() == job.GetIterativeInput()) {
iterative_input = textures_to_bind.size();
iterative_name = it.key();
}
textures_to_bind.append({texture, job.GetInterpolation(it.key())});
// Set enable flag if shader wants it
GLuint tex_id = texture ? texture->id().value<GLuint>() : 0;
int enable_param_location = functions_->glGetUniformLocation(shader, QStringLiteral("%1_enabled").arg(it.key()).toUtf8().constData());
if (enable_param_location > -1) {
functions_->glUniform1i(enable_param_location, tex_id > 0);
}
break;
}
case NodeValue::kSamples:
case NodeValue::kText:
case NodeValue::kRational:
case NodeValue::kFont:
case NodeValue::kFile:
case NodeValue::kVideoParams:
case NodeValue::kAudioParams:
case NodeValue::kShaderJob:
case NodeValue::kSampleJob:
case NodeValue::kGenerateJob:
case NodeValue::kFootageJob:
case NodeValue::kBezier:
case NodeValue::kNone:
case NodeValue::kDataTypeCount:
break;
}
}
// Bind all textures
for (int i=0; i<textures_to_bind.size(); i++) {
const TextureToBind& t = textures_to_bind.at(i);
TexturePtr texture = t.texture;
GLuint tex_id = texture ? texture->id().value<GLuint>() : 0;
functions_->glActiveTexture(GL_TEXTURE0 + i);
GLenum target = (texture && texture->type() == Texture::k3D) ? GL_TEXTURE_3D : GL_TEXTURE_2D;
functions_->glBindTexture(target, tex_id);
if (tex_id) {
PrepareInputTexture(target, t.interpolation);
if (texture->channel_count() == 1 && destination_params.channel_count() != 1) {
// Interpret this texture as a grayscale texture
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
functions_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
}
}
// Ensure matrix is set, at least to identity
GLint mvpmat_location = functions_->glGetUniformLocation(shader, "ove_mvpmat");
if (mvpmat_location > -1) {
functions_->glUniformMatrix4fv(mvpmat_location, 1, false, job.GetValue(QStringLiteral("ove_mvpmat")).data().value<QMatrix4x4>().constData());
}
// Set the viewport to the "physical" resolution of the destination
functions_->glViewport(0, 0,
destination_params.effective_width(),
destination_params.effective_height());
// Bind vertex array object
QOpenGLVertexArrayObject vao_;
vao_.create();
vao_.bind();
// Set buffers
QOpenGLBuffer vert_vbo_;
vert_vbo_.create();
vert_vbo_.bind();
// If the job has vertex coordinate overrides use them instead of the defaults.
if (!job.GetVertexCoordinates().isEmpty()) {
Q_ASSERT(job.GetVertexCoordinates().size() == 18);
vert_vbo_.allocate(job.GetVertexCoordinates().constData(), job.GetVertexCoordinates().size() * sizeof(float));
} else {
vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat));
}
vert_vbo_.release();
QOpenGLBuffer frag_vbo_;
frag_vbo_.create();
frag_vbo_.bind();
frag_vbo_.allocate(blit_texcoords.constData(), blit_texcoords.size() * sizeof(GLfloat));
frag_vbo_.release();
GLint vertex_location = functions_->glGetAttribLocation(shader, "a_position");
if (vertex_location != -1) {
vert_vbo_.bind();
functions_->glEnableVertexAttribArray(vertex_location);
functions_->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
vert_vbo_.release();
}
GLint tex_location = functions_->glGetAttribLocation(shader, "a_texcoord");
if (tex_location != -1) {
frag_vbo_.bind();
functions_->glEnableVertexAttribArray(tex_location);
functions_->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
frag_vbo_.release();
}
// Some shaders optimize through multiple iterations which requires ping-ponging textures
// - If there are only two iterations, we can just create one backend texture and then the
// destination can be the second
// - If there are more than two iterations, we need to ping pong back and forth between two
// textures. We can still use the destination as the last iteration, but we'll need textures
// for the iterative process.
int real_iteration_count;
if (job.GetIterationCount() > 1 && !job.GetIterativeInput().isEmpty()) {
real_iteration_count = job.GetIterationCount();
} else {
real_iteration_count = 1;
}
TexturePtr output_tex, input_tex;
if (real_iteration_count > 1) {
// Create one texture to bounce off
output_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params);
if (real_iteration_count > 2) {
// Create a second texture bounce off
input_tex = CreateTextureFromNativeHandle(CreateNativeTexture2DInternal(destination_params), destination_params);
}
}
GLint iteration_location = functions_->glGetUniformLocation(shader, "ove_iteration");
for (int iteration=0; iteration<real_iteration_count; iteration++) {
// Set iteration number
if (iteration_location > -1) {
functions_->glUniform1i(iteration_location, iteration);
}
// Replace iterative input
if (iteration == real_iteration_count-1) {
// This is the last iteration, draw to the destination
if (destination) {
// If we have a destination texture, draw to it
AttachTextureAsDestination(destination);
} else if (iteration > 0) {
// Otherwise, if we were iterating before, detach texture now
DetachTextureAsDestination();
}
// Clear the destination if the caller requested it
if (clear_destination) {
ClearDestinationInternal();
}
} else {
// Always draw to output_tex, which gets swapped with input_tex every iteration
AttachTextureAsDestination(output_tex.get());
}
if (iteration > 0) {
// If this is not the first iteration, replace the iterative texture with the one we
// last drew
functions_->glActiveTexture(GL_TEXTURE0 + iterative_input);
functions_->glBindTexture(GL_TEXTURE_2D, input_tex->id().value<GLuint>());
// At this time, we only support iterating 2D textures
PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_name));
}
// Swap so that the next iteration, the texture we draw now will be the input texture next
std::swap(output_tex, input_tex);
// Blit this texture through this shader
{
PRINT_GL_ERRORS;
functions_->glDrawArrays(GL_TRIANGLES, 0, blit_vertices.size() / 3);
}
}
if (destination) {
// Reset framebuffer to default if we were drawing to a texture
DetachTextureAsDestination();
}
// 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;
functions_->glActiveTexture(GL_TEXTURE0 + i);
functions_->glBindTexture(target, 0);
}
// Release shader
functions_->glUseProgram(0);
// Release vertex array object
frag_vbo_.destroy();
vert_vbo_.destroy();
vao_.release();
vao_.destroy();
}
GLint OpenGLRenderer::GetInternalFormat(VideoParams::Format format, int channel_layout)
{
switch (format) {
case VideoParams::kFormatUnsigned8:
switch (channel_layout) {
case 1:
return GL_R8;
case 2:
return GL_RG8;
case 3:
return GL_RGB8;
case 4:
return GL_RGBA8;
}
break;
case VideoParams::kFormatUnsigned16:
switch (channel_layout) {
case 1:
return GL_R16;
case 2:
return GL_RG16;
case 3:
return GL_RGB16;
case 4:
return GL_RGBA16;
}
break;
case VideoParams::kFormatFloat16:
switch (channel_layout) {
case 1:
return GL_R16F;
case 2:
return GL_RG16F;
case 3:
return GL_RGB16F;
case 4:
return GL_RGBA16F;
}
break;
case VideoParams::kFormatFloat32:
switch (channel_layout) {
case 1:
return GL_R32F;
case 2:
return GL_RG32F;
case 3:
return GL_RGB32F;
case 4:
return GL_RGBA32F;
}
break;
case VideoParams::kFormatInvalid:
case VideoParams::kFormatCount:
break;
}
return GL_INVALID_VALUE;
}
GLenum OpenGLRenderer::GetPixelType(VideoParams::Format format)
{
switch (format) {
case VideoParams::kFormatUnsigned8:
return GL_UNSIGNED_BYTE;
case VideoParams::kFormatUnsigned16:
return GL_UNSIGNED_SHORT;
case VideoParams::kFormatFloat16:
return GL_HALF_FLOAT;
case VideoParams::kFormatFloat32:
return GL_FLOAT;
case VideoParams::kFormatInvalid:
case VideoParams::kFormatCount:
break;
}
return GL_INVALID_VALUE;
}
GLenum OpenGLRenderer::GetPixelFormat(int channel_count)
{
switch (channel_count) {
case 1:
return GL_RED;
case 3:
return GL_RGB;
case 4:
return GL_RGBA;
default:
return GL_INVALID_VALUE;
}
}
void OpenGLRenderer::PrepareInputTexture(GLenum target, Texture::Interpolation interp)
{
switch (interp) {
case Texture::kNearest:
functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
break;
case Texture::kLinear:
functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
case Texture::kMipmappedLinear:
functions_->glGenerateMipmap(target);
functions_->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
functions_->glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
}
functions_->glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
functions_->glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (target == GL_TEXTURE_3D) {
functions_->glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
}
void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, double a)
{
functions_->glClearColor(r, g, b, a);
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 = GetCachedTexture(width, height, 1, format, channel_count);
// If no texture in cache, generate new texture
bool new_tex = (texture == 0);
if (new_tex) {
functions_->glGenTextures(1, &texture);
texture_params_.insert(texture, {width, height, 1, format, channel_count});
}
if (new_tex || data) {
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
GLint current_tex;
functions_->glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_tex);
functions_->glBindTexture(GL_TEXTURE_2D, texture);
{
PRINT_GL_ERRORS;
if (new_tex) {
functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(format, channel_count),
width, height, 0, GetPixelFormat(channel_count),
GetPixelType(format), data);
} else {
functions_->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
width, height,
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 &params, const void *data, int linesize)
{
return CreateNativeTexture2DInternal(params.effective_width(), params.effective_height(), params.format(), params.channel_count(), data, linesize);
}
GLuint OpenGLRenderer::GetCachedTexture(int width, int height, int depth, VideoParams::Format format, int channel_count)
{
TextureCacheKey input_key = {width, height, depth, format, channel_count};
for (int i=0; i<texture_cache_.size(); i++) {
const TextureCacheEntry &entry = texture_cache_.at(i);
if (entry.key == input_key) {
GLuint t = entry.texture;
texture_cache_.removeAt(i);
return t;
}
}
return 0;
}
GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
{
static const QString shader_preamble =
// Use appropriate GL 3.2 shader header
QStringLiteral("#version 150\n\n"
"precision highp float;\n\n");
QString complete_code;
if (!code.startsWith(QStringLiteral("#version"))) {
complete_code = shader_preamble;
}
if (code.isEmpty()) {
// Use default code
if (type == GL_FRAGMENT_SHADER) {
complete_code.append(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.frag")));
} else if (type == GL_VERTEX_SHADER) {
complete_code.append(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.vert")));
}
} else {
complete_code.append(code);
}
QByteArray code_utf8 = complete_code.toUtf8();
const char *code_cstr = code_utf8.constData();
GLuint shader = functions_->glCreateShader(type);
functions_->glShaderSource(shader, 1, &code_cstr, nullptr);
functions_->glCompileShader(shader);
GLint success;
functions_->glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
qWarning() << "Failed to compile OpenGL shader";
QByteArray error_log(10240, Qt::Uninitialized);
functions_->glGetShaderInfoLog(shader, error_log.size(), nullptr, error_log.data());
std::cout << error_log.constData() << std::endl << code_cstr << std::endl;
functions_->glDeleteShader(shader);
shader = 0;
}
return shader;
}
void OpenGLRenderer::GarbageCollectTextureCache()
{
qint64 max_age = QDateTime::currentMSecsSinceEpoch() - kTextureCacheMaxSize;
for (auto it=texture_cache_.begin(); it!=texture_cache_.end(); ) {
if (it->age < max_age) {
GL_PREAMBLE;
GLuint t = it->texture;
texture_params_.remove(t);
functions_->glDeleteTextures(1, &t);
it = texture_cache_.erase(it);
} else {
it++;
}
}
}
}