#include "stdfx/shaderfx.h"
// TnzStdfx includes
#include "stdfx.h"
#include "stdfx/shaderinterface.h"
#include "stdfx/shadingcontext.h"
// TnzBase includes
#include "tfxparam.h"
#include "tparamset.h"
#include "trenderresourcemanager.h"
#include "trenderer.h"
// TnzCore includes
#include "tthread.h"
#include "tfilepath.h"
#include "tstream.h"
#include "tfunctorinvoker.h"
#include "tmsgcore.h"
// Qt includes
#include <QDir>
#include <QOpenGLShaderProgram>
#include <QCoreApplication>
#include <QOffscreenSurface>
// Glew include
#include <GL/glew.h>
// Boost includes
#include <boost/any.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
// Diagnostics include
// #define DIAGNOSTICS
#ifdef DIAGNOSTICS
#include "diagnostics.h"
#endif
//===================================================
// Forward Declarations
class ShaderFxDeclaration;
//===================================================
//****************************************************************************
// Local Namespace stuff
//****************************************************************************
namespace {
// Classes
struct ContextLocker {
ShadingContext &m_ctx;
bool m_locked;
public:
ContextLocker(ShadingContext &ctx) : m_ctx(ctx), m_locked(false) { relock(); }
~ContextLocker() {
if (m_locked) unlock();
}
void relock() {
assert(!m_locked), m_locked = true;
m_ctx.makeCurrent();
}
void unlock() {
assert(m_locked), m_locked = false;
m_ctx.doneCurrent();
}
};
struct ProgramBinder {
QOpenGLShaderProgram *m_prog;
public:
ProgramBinder(QOpenGLShaderProgram *prog) : m_prog(prog) { m_prog->bind(); }
~ProgramBinder() {
glUseProgram(0); // m_prog->release();
}
};
struct RectF {
GLfloat m_val[4];
RectF(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1) {
m_val[0] = x0, m_val[1] = y0, m_val[2] = x1, m_val[3] = y1;
}
RectF(const TRectD &rect) {
m_val[0] = rect.x0, m_val[1] = rect.y0, m_val[2] = rect.x1,
m_val[3] = rect.y1;
}
operator TRectD() const {
return TRectD(m_val[0], m_val[1], m_val[2], m_val[3]);
}
bool operator==(const RectF &rect) const {
return (memcmp(m_val, rect.m_val, sizeof(this)) == 0);
}
};
struct AffineF {
GLfloat m_val[9];
operator TAffine() const {
return TAffine(m_val[0], m_val[3], m_val[6], m_val[1], m_val[4], m_val[7]);
}
// Observe that mat3 from GLSL stores elements column-wise; this explains the
// weird indexing
};
// Global Variables
typedef std::map<QString, ShaderFxDeclaration *> FxDeclarationsMap;
FxDeclarationsMap l_shaderFxDeclarations;
enum Measures { NONE, PERCENT, LENGTH, ANGLE, MEASURESCOUNT };
static const std::string l_measureNames[MEASURESCOUNT] = {"", "percentage",
"fxLength", "angle"};
static const TParamUIConcept::Type
l_conceptTypes[ShaderInterface::CONCEPTSCOUNT -
ShaderInterface::UI_CONCEPTS] = {
TParamUIConcept::RADIUS, TParamUIConcept::WIDTH,
TParamUIConcept::ANGLE, TParamUIConcept::POINT,
TParamUIConcept::POINT_2, TParamUIConcept::VECTOR,
TParamUIConcept::POLAR, TParamUIConcept::SIZE,
TParamUIConcept::QUAD, TParamUIConcept::RECT,
TParamUIConcept::COMPASS, TParamUIConcept::COMPASS_SPIN};
// Functions
inline bool isObsolete(const TFilePath &fp, const QDateTime &lastModified) {
QFileInfo fInfo(QString::fromStdWString(fp.getWideString()));
return (lastModified != fInfo.lastModified());
}
inline TRectD tileRect(const TTile &tile) {
const TDimension &dim = tile.getRaster()->getSize();
return TRectD(tile.m_pos, TDimensionD(dim.lx, dim.ly));
}
inline void ceilRect(TRectD &rect) {
rect.x0 = tfloor(rect.x0), rect.y0 = tfloor(rect.y0);
rect.x1 = tceil(rect.x1), rect.y1 = tceil(rect.y1);
}
} // namespace
//****************************************************************************
// Shader Fx declaration
//****************************************************************************
class ShaderFx final : public TStandardZeraryFx {
FX_PLUGIN_DECLARATION(ShaderFx)
const ShaderInterface *m_shaderInterface; //!< Shader fx 'description'.
std::vector<boost::any>
m_params; //!< Parameters for the shader fx. The actual parameter
//!< type depends on the shader interface declaration.
std::vector<TParamUIConcept>
m_uiConcepts; //!< UI concepts related to m_params.
boost::ptr_vector<TRasterFxPort>
m_inputPorts; //!< Input ports for the shader fx.
public:
ShaderFx() : m_shaderInterface() {
assert(false);
} // Necessary due to TPersist inheritance, but must NOT be used
ShaderFx(const ShaderInterface *shaderInterface)
: m_shaderInterface(shaderInterface) {
initialize();
}
// void setShaderInterface(const ShaderInterface& shaderInterface);
void initialize();
void getParamUIs(TParamUIConcept *¶ms, int &length) override;
bool doGetBBox(double frame, TRectD &bBox,
const TRenderSettings &info) override;
bool canHandle(const TRenderSettings &info, double frame) override;
void doDryCompute(TRectD &rect, double frame,
const TRenderSettings &ri) override;
void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override;
private:
QOpenGLShaderProgram *touchShaderProgram(
const ShaderInterface::ShaderData &sd, ShadingContext &context,
int varyingsCount = 0, const GLchar **varyings = 0);
void bindParameters(QOpenGLShaderProgram *shaderProgram, double frame);
void bindWorldTransform(QOpenGLShaderProgram *shaderProgram,
const TAffine &worldToDst);
void getInputData(const TRectD &rect, double frame, const TRenderSettings &ri,
std::vector<TRectD> &inputRects,
std::vector<TAffine> &inputAffines,
ShadingContext &context);
};
//****************************************************************************
// ShaderFxDeclaration definition
//****************************************************************************
class ShaderFxDeclaration final : public TFxDeclaration {
ShaderInterface m_shaderInterface;
public:
ShaderFxDeclaration(const ShaderInterface &shaderInterface)
: TFxDeclaration(
TFxInfo(shaderInterface.mainShader().m_name.toStdString(), false))
, m_shaderInterface(shaderInterface) {}
TPersist *create() const override { return new ShaderFx(&m_shaderInterface); }
};
//****************************************************************************
// ShadingContextManager definition
//****************************************************************************
class ShadingContextManager final : public QObject {
mutable QMutex m_mutex;
std::unique_ptr<ShadingContext> m_shadingContext;
TAtomicVar m_activeRenderInstances;
std::unique_ptr<QOffscreenSurface> m_surface;
public:
ShadingContextManager() {
/*
The ShadingContext's QGLPixelBuffer must be destroyed *before* the global
QApplication
is. So, we will attach to a suitable parent object whose lifespan is shorter.
FYI - yes, this approach was adopted after a long and PAINFUL wrestling session
with Qt.
Suggestions are welcome as this is a tad beyond ridiculous...
*/
QObject *mainScopeBoundObject =
QCoreApplication::instance()->findChild<QObject *>("mainScope");
assert(thread() ==
mainScopeBoundObject
->thread()); // Parent object must be in the same thread,
// setParent(mainScopeBoundObject); // otherwise reparenting fails
m_surface.reset(new QOffscreenSurface());
m_surface->create();
m_shadingContext.reset(new ShadingContext(m_surface.get()));
}
static ShadingContextManager *instance() {
static ShadingContextManager *theManager = new ShadingContextManager;
return theManager;
}
QMutex *mutex() const { return &m_mutex; }
const ShadingContext &shadingContext() const { return *m_shadingContext; }
ShadingContext &shadingContext() { return *m_shadingContext; }
void onRenderInstanceStart() { ++m_activeRenderInstances; }
void onRenderInstanceEnd() {
if (--m_activeRenderInstances == 0) {
QMutexLocker mLocker(&m_mutex);
// Release the shading context's output buffer
::ContextLocker cLocker(*m_shadingContext);
m_shadingContext->resize(0, 0);
#ifdef DIAGNOSTICS
DIAGNOSTICS_DUMP("ShaderLogs");
DIAGNOSTICS_CLEAR;
#endif
}
}
ShadingContext::Support touchSupport() {
struct {
ShadingContextManager *m_this;
ShadingContext::Support support() {
QMutexLocker mLocker(&m_this->m_mutex);
::ContextLocker cLocker(*m_this->m_shadingContext);
return ShadingContext::support();
}
} locals = {this};
static ShadingContext::Support sup = locals.support();
static bool sentMsg = false;
if (!sentMsg) {
switch (sup) {
case ShadingContext::NO_PIXEL_BUFFER:
DVGui::warning(QOpenGLShaderProgram::tr(
"This system configuration does not support OpenGL Pixel Buffers. "
"Shader Fxs will not be able to render."));
break;
case ShadingContext::NO_SHADERS:
DVGui::warning(QOpenGLShaderProgram::tr(
"This system configuration does not support OpenGL Shader "
"Programs. Shader Fxs will not be able to render."));
break;
default:
break;
}
sentMsg = true;
}
return sup;
}
QOffscreenSurface *getSurface() { return m_surface.get(); }
};
template class DV_EXPORT_API TFxDeclarationT<ShaderFx>;
//****************************************************************************
// ShadingContextManagerDelegate definition
//****************************************************************************
class MessageCreateContext final : public TThread::Message {
ShadingContextManager *man;
public:
MessageCreateContext(ShadingContextManager *ctx) : man(ctx) {}
void onDeliver() override { man->onRenderInstanceEnd(); }
TThread::Message *clone() const override {
return new MessageCreateContext(*this);
}
};
class SCMDelegate final : public TRenderResourceManager {
T_RENDER_RESOURCE_MANAGER
void onRenderInstanceStart(unsigned long id) override {
ShadingContextManager::instance()->onRenderInstanceStart();
}
void onRenderInstanceEnd(unsigned long id) override {
if (!TThread::isMainThread()) {
/* tofflinegl のときとは逆で main thread に dispatch する */
MessageCreateContext(ShadingContextManager::instance()).sendBlocking();
} else {
ShadingContextManager::instance()->onRenderInstanceEnd();
}
}
};
//-------------------------------------------------------------------
class SCMDelegateGenerator final : public TRenderResourceManagerGenerator {
public:
SCMDelegateGenerator() : TRenderResourceManagerGenerator(false) {
/*
Again, this has to do with the manager's lifetime issue.
The SCM must be created in the MAIN THREAD, but NOT BEFORE the
QCoreApplication itself has been created. The easiest way to do so
is scheduling a slot to be executed as soon as event processing starts.
*/
struct InstanceSCM final : public TFunctorInvoker::BaseFunctor {
void operator()() override { ShadingContextManager::instance(); }
};
TFunctorInvoker::instance()->invokeQueued(new InstanceSCM);
}
TRenderResourceManager *operator()() override { return new SCMDelegate; }
};
MANAGER_FILESCOPE_DECLARATION(SCMDelegate, SCMDelegateGenerator)
//****************************************************************************
// Shader Fx implementation
//****************************************************************************
void ShaderFx::initialize() {
struct {
ShaderFx *m_this;
inline void addUiConcept(const ShaderInterface::Parameter &siParam,
const TParamP ¶m) {
if (siParam.m_concept.m_type >= ShaderInterface::UI_CONCEPTS &&
siParam.m_concept.m_type < ShaderInterface::CONCEPTSCOUNT) {
m_this->m_uiConcepts.push_back(TParamUIConcept());
TParamUIConcept &uiConcept = m_this->m_uiConcepts.back();
uiConcept.m_type = ::l_conceptTypes[siParam.m_concept.m_type -
ShaderInterface::UI_CONCEPTS];
uiConcept.m_label = siParam.m_concept.m_label.toStdString();
uiConcept.m_params.push_back(param);
}
}
inline void addUiConcept(const ShaderInterface::ParameterConcept &concept) {
if (!concept.isUI() || concept.m_parameterNames.empty()) return;
TParamUIConcept uiConcept = {
::l_conceptTypes[concept.m_type - ShaderInterface::UI_CONCEPTS],
concept.m_label.toStdString()};
int n, nCount = int(concept.m_parameterNames.size());
for (n = 0; n != nCount; ++n) {
TParam *param = m_this->getParams()->getParam(
concept.m_parameterNames[n].toStdString());
if (!param) break;
uiConcept.m_params.push_back(param);
}
if (uiConcept.m_params.size() == concept.m_parameterNames.size())
m_this->m_uiConcepts.push_back(uiConcept);
}
} locals = {this};
assert(m_params.empty()); // Interfaces should not be re-set
// Allocate parameters following the specified interface
const std::vector<ShaderInterface::Parameter> &siParams =
m_shaderInterface->parameters();
int p, pCount = int(siParams.size());
m_params.reserve(pCount);
for (p = 0; p != pCount; ++p) {
const ShaderInterface::Parameter &siParam = siParams[p];
switch (siParam.m_type) {
case ShaderInterface::BOOL: {
TBoolParamP param(siParam.m_default.m_bool);
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TBoolParamP>(&m_params.back()));
break;
}
case ShaderInterface::FLOAT: {
TDoubleParamP param(siParam.m_default.m_float);
param->setValueRange(siParam.m_range[0].m_float,
siParam.m_range[1].m_float);
locals.addUiConcept(siParam, param);
switch (siParam.m_concept.m_type) {
case ShaderInterface::PERCENT:
param->setMeasureName(l_measureNames[PERCENT]);
break;
case ShaderInterface::LENGTH:
case ShaderInterface::RADIUS_UI:
case ShaderInterface::WIDTH_UI:
case ShaderInterface::SIZE_UI:
param->setMeasureName(l_measureNames[LENGTH]);
break;
case ShaderInterface::ANGLE:
case ShaderInterface::ANGLE_UI:
param->setMeasureName(l_measureNames[ANGLE]);
break;
default:
break;
}
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TDoubleParamP>(&m_params.back()));
break;
}
case ShaderInterface::VEC2: {
TPointParamP param(
TPointD(siParam.m_default.m_vec2[0], siParam.m_default.m_vec2[1]));
param->getX()->setValueRange(siParam.m_range[0].m_vec2[0],
siParam.m_range[1].m_vec2[0]);
param->getY()->setValueRange(siParam.m_range[0].m_vec2[1],
siParam.m_range[1].m_vec2[1]);
locals.addUiConcept(siParam, param);
switch (siParam.m_concept.m_type) {
case ShaderInterface::PERCENT:
param->getX()->setMeasureName(l_measureNames[PERCENT]);
param->getY()->setMeasureName(l_measureNames[PERCENT]);
break;
case ShaderInterface::LENGTH:
case ShaderInterface::POINT:
case ShaderInterface::POINT_UI:
case ShaderInterface::VECTOR_UI:
case ShaderInterface::WIDTH_UI:
case ShaderInterface::SIZE_UI:
param->getX()->setMeasureName(l_measureNames[LENGTH]);
param->getY()->setMeasureName(l_measureNames[LENGTH]);
break;
case ShaderInterface::ANGLE:
case ShaderInterface::ANGLE_UI:
param->getX()->setMeasureName(l_measureNames[ANGLE]);
param->getY()->setMeasureName(l_measureNames[ANGLE]);
break;
default:
break;
}
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TPointParamP>(&m_params.back()));
break;
}
case ShaderInterface::INT: {
TIntParamP param(siParam.m_default.m_int);
param->setValueRange(siParam.m_range[0].m_int, siParam.m_range[1].m_int);
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TIntParamP>(&m_params.back()));
break;
}
case ShaderInterface::RGBA: {
TPixelParamP param(
TPixel32(siParam.m_default.m_rgba[0], siParam.m_default.m_rgba[1],
siParam.m_default.m_rgba[2], siParam.m_default.m_rgba[3]));
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TPixelParamP>(&m_params.back()));
break;
}
case ShaderInterface::RGB: {
TPixelParamP param(TPixel32(siParam.m_default.m_rgb[0],
siParam.m_default.m_rgb[1],
siParam.m_default.m_rgb[2]));
param->enableMatte(false);
m_params.push_back(param);
bindParam(this, siParam.m_name.toStdString(),
*boost::unsafe_any_cast<TPixelParamP>(&m_params.back()));
break;
}
default:
break;
}
}
// Add composite UI concepts
const std::vector<ShaderInterface::ParameterConcept> &parConcepts =
m_shaderInterface->m_parConcepts;
int c, cCount = int(parConcepts.size());
for (c = 0; c != cCount; ++c) locals.addUiConcept(parConcepts[c]);
// Add input ports
const std::vector<QString> &inputPorts = m_shaderInterface->inputPorts();
int i, iCount = int(inputPorts.size());
m_inputPorts.reserve(iCount);
for (i = 0; i != iCount; ++i) {
m_inputPorts.push_back(new TRasterFxPort);
addInputPort(inputPorts[i].toStdString(), m_inputPorts[i]);
}
}
//-------------------------------------------------------------------
void ShaderFx::getParamUIs(TParamUIConcept *¶ms, int &length) {
length = int(m_uiConcepts.size());
params = new TParamUIConcept[length];
std::copy(m_uiConcepts.begin(), m_uiConcepts.end(), params);
}
//-------------------------------------------------------------------
bool ShaderFx::doGetBBox(double frame, TRectD &bbox,
const TRenderSettings &info) {
static const ::RectF infiniteRectF(-(std::numeric_limits<GLfloat>::max)(),
-(std::numeric_limits<GLfloat>::max)(),
(std::numeric_limits<GLfloat>::max)(),
(std::numeric_limits<GLfloat>::max)());
bbox = TConsts::infiniteRectD;
const ShaderInterface::ShaderData &sd = m_shaderInterface->bboxShader();
if (!sd.isValid()) return true;
ShadingContextManager *manager = ShadingContextManager::instance();
if (manager->touchSupport() != ShadingContext::OK) return true;
// Remember: info.m_affine MUST NOT BE CONSIDERED in doGetBBox's
// implementation
::RectF bboxF(infiniteRectF);
QMutexLocker mLocker(manager->mutex());
// ShadingContext& context = manager->shadingContext();
std::shared_ptr<ShadingContext> shadingContextPtr(
new ShadingContext(manager->getSurface()));
ShadingContext &context = *shadingContextPtr.get();
::ContextLocker cLocker(context);
// Build the varyings data
QOpenGLShaderProgram *prog = 0;
{
const GLchar *varyingNames[] = {"outputBBox"};
prog = touchShaderProgram(sd, context, 1, &varyingNames[0]);
}
int pCount = getInputPortCount();
std::vector<RectF> inputBBoxes(pCount, ::RectF(TRectD()));
for (int p = 0; p != pCount; ++p) {
TRasterFxPort &port = m_inputPorts[p];
if (port.isConnected()) {
TRectD inputBBox;
cLocker.unlock();
mLocker.unlock();
if (port->doGetBBox(frame, inputBBox, info))
inputBBoxes[p] = (inputBBox == TConsts::infiniteRectD)
? infiniteRectF
: ::RectF(inputBBox);
mLocker.relock();
cLocker.relock();
}
}
{
ProgramBinder progBinder(prog);
// Bind uniform parameters
bindParameters(prog, frame);
prog->setUniformValue("infiniteRect", infiniteRectF.m_val[0],
infiniteRectF.m_val[1], infiniteRectF.m_val[2],
infiniteRectF.m_val[3]);
prog->setUniformValueArray("inputBBox", inputBBoxes[0].m_val,
int(inputBBoxes.size()), 4);
// Perform transform feedback
const GLsizeiptr varyingSizes[] = {sizeof(::RectF)};
GLvoid *bufs[] = {bboxF.m_val};
context.transformFeedback(1, varyingSizes, bufs);
}
// Finalize output
bbox = (bboxF == infiniteRectF) ? TConsts::infiniteRectD : TRectD(bboxF);
return true;
}
//-------------------------------------------------------------------
bool ShaderFx::canHandle(const TRenderSettings &info, double frame) {
return (m_shaderInterface->hwtType() == ShaderInterface::ANY)
? true
: isAlmostIsotropic(info.m_affine);
}
//-------------------------------------------------------------------
QOpenGLShaderProgram *ShaderFx::touchShaderProgram(
const ShaderInterface::ShaderData &sd, ShadingContext &context,
int varyingsCount, const GLchar **varyings) {
typedef std::pair<QOpenGLShaderProgram *, QDateTime> CompiledShader;
struct locals {
inline static void logCompilation(QOpenGLShaderProgram *program) {
// Log shaders - observe that we'll look into the program's *children*,
// not its
// shaders. This is necessary as uncompiled shaders are not added to the
// program.
const QObjectList &children = program->children();
int c, cCount = children.size();
for (c = 0; c != cCount; ++c) {
if (QOpenGLShader *shader =
dynamic_cast<QOpenGLShader *>(children[c])) {
const QString &log = shader->log();
if (!log.isEmpty()) DVGui::info(log);
}
}
// ShaderProgram linking logs
const QString &log = program->log();
if (!log.isEmpty()) DVGui::info(log);
}
}; // locals
// ShadingContext& context =
// ShadingContextManager::instance()->shadingContext();
CompiledShader cs = context.shaderData(sd.m_name);
if (!cs.first || ::isObsolete(sd.m_path, cs.second)) {
cs = m_shaderInterface->makeProgram(sd, varyingsCount, varyings);
context.addShaderProgram(sd.m_name, cs.first, cs.second);
locals::logCompilation(cs.first);
}
assert(cs.first);
return cs.first;
}
//-------------------------------------------------------------------
void ShaderFx::bindParameters(QOpenGLShaderProgram *program, double frame) {
// Bind fx parameters
const std::vector<ShaderInterface::Parameter> &siParams =
m_shaderInterface->parameters();
assert(siParams.size() == m_params.size());
int p, pCount = int(siParams.size());
for (p = 0; p != pCount; ++p) {
const ShaderInterface::Parameter &siParam = siParams[p];
switch (siParam.m_type) {
case ShaderInterface::BOOL: {
const TBoolParamP ¶m =
*boost::unsafe_any_cast<TBoolParamP>(&m_params[p]);
program->setUniformValue(siParam.m_name.toUtf8().data(),
(GLboolean)param->getValue());
break;
}
case ShaderInterface::FLOAT: {
const TDoubleParamP ¶m =
*boost::unsafe_any_cast<TDoubleParamP>(&m_params[p]);
program->setUniformValue(siParam.m_name.toUtf8().data(),
(GLfloat)param->getValue(frame));
break;
}
case ShaderInterface::VEC2: {
const TPointParamP ¶m =
*boost::unsafe_any_cast<TPointParamP>(&m_params[p]);
const TPointD &value = param->getValue(frame);
program->setUniformValue(siParam.m_name.toUtf8().data(), (GLfloat)value.x,
(GLfloat)value.y);
break;
}
case ShaderInterface::INT: {
const TIntParamP ¶m =
*boost::unsafe_any_cast<TIntParamP>(&m_params[p]);
program->setUniformValue(siParam.m_name.toUtf8().data(),
(GLint)param->getValue());
break;
}
case ShaderInterface::RGBA:
case ShaderInterface::RGB: {
const TPixelParamP ¶m =
*boost::unsafe_any_cast<TPixelParamP>(&m_params[p]);
const TPixel32 &value = param->getValue(frame);
program->setUniformValue(
siParam.m_name.toUtf8().data(), (GLfloat)value.r / 255.0f,
(GLfloat)value.g / 255.0f, (GLfloat)value.b / 255.0f,
(GLfloat)value.m / 255.0f);
break;
}
default:
break;
}
}
}
//-------------------------------------------------------------------
void ShaderFx::bindWorldTransform(QOpenGLShaderProgram *program,
const TAffine &worldToDst) {
// Bind transformation affine
float qwToD[9] = {static_cast<float>(worldToDst.a11),
static_cast<float>(worldToDst.a12),
static_cast<float>(worldToDst.a13),
static_cast<float>(worldToDst.a21),
static_cast<float>(worldToDst.a22),
static_cast<float>(worldToDst.a23),
0.0f,
0.0f,
1.0f};
program->setUniformValue("worldToOutput", QMatrix3x3(qwToD));
const TAffine &dToW = worldToDst.inv();
float qdToW[9] = {static_cast<float>(dToW.a11),
static_cast<float>(dToW.a12),
static_cast<float>(dToW.a13),
static_cast<float>(dToW.a21),
static_cast<float>(dToW.a22),
static_cast<float>(dToW.a23),
0.0f,
0.0f,
1.0f};
program->setUniformValue("outputToWorld", QMatrix3x3(qdToW));
}
//-------------------------------------------------------------------
void ShaderFx::getInputData(const TRectD &rect, double frame,
const TRenderSettings &ri,
std::vector<TRectD> &inputRects,
std::vector<TAffine> &inputAffines,
ShadingContext &context) {
struct locals {
static inline void addNames(std::vector<std::string> &names,
const char *prefix, int pCount) {
for (int p = 0; p != pCount; ++p)
names.push_back((prefix + QString("[%1]").arg(p)).toStdString());
}
};
const ShaderInterface::ShaderData &sd = m_shaderInterface->inputPortsShader();
if (!sd.isValid()) {
inputRects.resize(getInputPortCount());
std::fill(inputRects.begin(), inputRects.end(), rect);
inputAffines.resize(getInputPortCount());
std::fill(inputAffines.begin(), inputAffines.end(), ri.m_affine);
return;
}
// ShadingContext& context =
// ShadingContextManager::instance()->shadingContext();
std::vector<GLfloat> buf;
int pCount = getInputPortCount();
// Build the varyings data
QOpenGLShaderProgram *prog = 0;
{
// Unsubscripted varying arrays on transform feedback seems to be
// unsupported
// on ATI cards. We have to declare EACH array name - e.g. inputRect[0],
// intputRect[1], etc..
const GLchar *varyingPrefixes[] = {"inputRect", "worldToInput"};
const int varyingsCount = sizeof(varyingPrefixes) / sizeof(GLchar *);
std::vector<std::string> varyingStrings;
varyingStrings.reserve(varyingsCount);
for (int v = 0; v != varyingsCount; ++v)
locals::addNames(varyingStrings, varyingPrefixes[v], pCount);
#if defined(__APPLE_CC__)
/* OSX10.8 の clang -stdlib=libc++ だと link 時 &std::string::c_str が
* undefined になってしまう */
std::vector<const GLchar *> varyingNames(varyingStrings.size());
auto conv = [](const std::string &i) { return i.c_str(); };
std::transform(varyingStrings.begin(), varyingStrings.end(),
varyingNames.begin(), conv);
#else
std::vector<const GLchar *> varyingNames(
boost::make_transform_iterator(varyingStrings.begin(),
std::mem_fun_ref(&std::string::c_str)),
boost::make_transform_iterator(varyingStrings.end(),
std::mem_fun_ref(&std::string::c_str)));
#endif
prog = touchShaderProgram(sd, context, int(varyingNames.size()),
&varyingNames[0]);
}
{
ProgramBinder progBinder(prog);
// Build varying buffers
int bufFloatsCount =
pCount * (sizeof(RectF) + sizeof(AffineF)) / sizeof(GLfloat);
buf.resize(bufFloatsCount);
// Bind uniform parameters
bindParameters(prog, frame);
bindWorldTransform(prog, ri.m_affine);
prog->setUniformValue("outputRect", (GLfloat)rect.x0, (GLfloat)rect.y0,
(GLfloat)rect.x1, (GLfloat)rect.y1);
// Perform transform feedback
const GLsizeiptr varyingSizes[] = {
static_cast<GLsizeiptr>(bufFloatsCount * sizeof(GLfloat))};
GLvoid *bufs[] = {&buf[0]};
context.transformFeedback(1, varyingSizes, bufs);
#ifdef TRANSFORM_FEEDBACK_COUT
std::cout << "trFeedback: ";
for (int f = 0; f != bufFloatsCount; ++f) std::cout << buf[f] << " ";
std::cout << "\n" << std::endl;
#endif
}
// Finalize output
const RectF *rBufBegin(reinterpret_cast<const RectF *>(&buf[0])),
*rBufEnd(rBufBegin + pCount);
std::copy(rBufBegin, rBufEnd, &inputRects[0]);
const AffineF *aBufBegin(reinterpret_cast<const AffineF *>(rBufEnd)),
*aBufEnd(aBufBegin + pCount);
std::copy(aBufBegin, aBufEnd, &inputAffines[0]);
}
//-------------------------------------------------------------------
void ShaderFx::doCompute(TTile &tile, double frame,
const TRenderSettings &info) {
struct locals {
struct TexturesStorage {
ShadingContext &m_ctx;
std::vector<GLuint> m_texIds;
TexturesStorage(ShadingContext &ctx, int pCount) : m_ctx(ctx) {
m_texIds.reserve(pCount);
}
~TexturesStorage() {
for (auto const &texId : m_texIds) {
m_ctx.unloadTexture(texId);
}
}
void load(const TRasterP &ras, GLuint texUnit) {
if (ras) m_texIds.push_back(m_ctx.loadTexture(ras, texUnit));
}
};
inline static QOpenGLFramebufferObjectFormat makeFormat(int bpp) {
QOpenGLFramebufferObjectFormat fmt;
if (bpp == 64) fmt.setInternalTextureFormat(GL_RGBA16);
return fmt;
}
inline static void touchOutputSize(ShadingContext &context,
const TDimension &size, int bpp) {
const QOpenGLFramebufferObjectFormat &fmt = makeFormat(bpp);
const TDimension ¤tSize = context.size();
const QOpenGLFramebufferObjectFormat ¤tFmt = context.format();
if (currentSize.lx < size.lx || currentSize.ly < size.ly ||
currentFmt != fmt)
context.resize(std::max(size.lx, currentSize.lx),
std::max(size.ly, currentSize.ly), fmt);
}
}; // locals
ShadingContextManager *manager = ShadingContextManager::instance();
if (manager->touchSupport() != ShadingContext::OK) return;
QMutexLocker mLocker(
manager->mutex()); // As GPU access can be considered sequential anyway,
// lock the full-scale mutex
std::shared_ptr<ShadingContext> shadingContextPtr(
new ShadingContext(manager->getSurface()));
ShadingContext &context = *shadingContextPtr.get();
// ShadingContext& context = manager->shadingContext();
int pCount = getInputPortCount();
const TRectD &tileRect = ::tileRect(tile);
std::vector<TRectD> inputRects(pCount);
std::vector<TAffine> inputAffines(pCount);
// Calculate input tiles
::ContextLocker cLocker(context);
std::unique_ptr<TTile[]> inTiles(
new TTile[pCount]); // NOTE: Input tiles must be STORED - they cannot
// be passed immediately to OpenGL, since *other shader
if (pCount > 0) // fxs*, with the very same host context, could lie
{ // inside this fx's input branches...
getInputData(tileRect, frame, info, inputRects, inputAffines, context);
// Release context and mutex
cLocker.unlock();
mLocker.unlock();
for (int p = 0; p != pCount; ++p) {
TRasterFxPort &port = m_inputPorts[p];
if (port.isConnected()) {
// Compute input tile
TRectD &inRect = inputRects[p];
if (inRect.getLx() > 0.0 && inRect.getLy() > 0.0) {
::ceilRect(inRect);
TRenderSettings inputInfo(info);
inputInfo.m_affine = inputAffines[p];
#ifdef TRANSFORM_FEEDBACK_COUT
const TAffine &inAff = inputAffines[p];
std::cout << "inRect " << p << ": " << inRect.x0 << " " << inRect.y0
<< " " << inRect.x1 << " " << inRect.y1 << "\n";
std::cout << "inAff " << p << ": " << inAff.a11 << " " << inAff.a12
<< " " << inAff.a13 << "\n";
std::cout << " " << inAff.a21 << " " << inAff.a22 << " "
<< inAff.a23 << "\n"
<< std::endl;
#endif
port->allocateAndCompute(
inTiles[p], inRect.getP00(),
TDimension(tround(inRect.getLx()), tround(inRect.getLy())),
tile.getRaster(), frame, inputInfo);
}
}
}
// Load input tiles on the GPU as textures
mLocker.relock();
cLocker.relock();
// Input tiles are NOT supplied to OpenGL here - but rather just before
// drawing.
// It's probably because a uniform integer variable must have already been
// bound
// to prepare the associated sampler variable in the linkes program...
}
// Perform the actual fragment shading
{
locals::touchOutputSize(context, tile.getRaster()->getSize(), info.m_bpp);
QOpenGLShaderProgram *program =
touchShaderProgram(m_shaderInterface->mainShader(), context);
{
ProgramBinder binder(program);
// Bind parameters and textures
bindParameters(program, frame);
bindWorldTransform(program, TTranslation(-tile.m_pos) * info.m_affine);
// Setup input data, if any
locals::TexturesStorage texStorage(context, pCount);
if (pCount > 0) {
std::vector<GLint> inputs(pCount);
std::vector<QMatrix3x3> screenToInput(pCount);
std::vector<QMatrix3x3> inputToScreen(pCount);
for (int p = 0; p != pCount; ++p) {
TAffine iToS(
TTranslation(-tile.m_pos) * // Output to Screen
info.m_affine * // World to Output
inputAffines[p].inv() * // Input to World
TTranslation(inputRects[p].getP00()) * // Texture to Input
TScale(inputRects[p].getLx(), inputRects[p].getLy())); //
TAffine sToI(iToS.inv());
float qiToS[9] = {static_cast<float>(iToS.a11),
static_cast<float>(iToS.a12),
static_cast<float>(iToS.a13),
static_cast<float>(iToS.a21),
static_cast<float>(iToS.a22),
static_cast<float>(iToS.a23),
0.0f,
0.0f,
1.0f};
float qsToI[9] = {static_cast<float>(sToI.a11),
static_cast<float>(sToI.a12),
static_cast<float>(sToI.a13),
static_cast<float>(sToI.a21),
static_cast<float>(sToI.a22),
static_cast<float>(sToI.a23),
0.0f,
0.0f,
1.0f};
inputs[p] = p, screenToInput[p] = QMatrix3x3(qsToI),
inputToScreen[p] = QMatrix3x3(qiToS);
}
program->setUniformValueArray("inputImage", &inputs[0], pCount);
program->setUniformValueArray("outputToInput", &screenToInput[0],
pCount);
program->setUniformValueArray("inputToOutput", &inputToScreen[0],
pCount);
// Load textures
for (int p = 0; p != pCount; ++p)
texStorage.load(inTiles[p].getRaster(), p);
}
#ifdef DIAGNOSTICS
DIAGNOSTICS_TIMER("Shader Overall Times | " +
m_shaderInterface->m_mainShader.m_name);
#endif
context.draw(tile.getRaster());
}
}
}
//-------------------------------------------------------------------
void ShaderFx::doDryCompute(TRectD &rect, double frame,
const TRenderSettings &info) {
ShadingContextManager *manager = ShadingContextManager::instance();
if (manager->touchSupport() != ShadingContext::OK) return;
QMutexLocker mLocker(manager->mutex());
// ShadingContext& context = manager->shadingContext();
std::shared_ptr<ShadingContext> shadingContextPtr(
new ShadingContext(manager->getSurface()));
ShadingContext &context = *shadingContextPtr.get();
int pCount = getInputPortCount();
if (pCount > 0) {
::ContextLocker cLocker(context);
std::vector<TRectD> inputRects(pCount);
std::vector<TAffine> inputAffines(pCount);
getInputData(rect, frame, info, inputRects, inputAffines, context);
for (int p = 0; p != pCount; ++p) {
TRasterFxPort &port = m_inputPorts[p];
if (port.isConnected()) {
TRectD &inRect = inputRects[p];
if (inRect.getLx() > 0.0 && inRect.getLy() > 0.0) {
::ceilRect(inRect);
TRenderSettings inputInfo(info);
inputInfo.m_affine = inputAffines[p];
cLocker.unlock();
mLocker.unlock();
port->dryCompute(inRect, frame, inputInfo);
mLocker.relock();
cLocker.relock();
}
}
}
}
}
//------------------------------------------------------------------
const TPersistDeclaration *ShaderFx::getDeclaration() const {
FxDeclarationsMap::iterator it =
::l_shaderFxDeclarations.find(m_shaderInterface->mainShader().m_name);
return (it == ::l_shaderFxDeclarations.end()) ? 0 : it->second;
}
//****************************************************************************
// Shader Interfaces loading function
//****************************************************************************
void loadShaderInterfaces(const TFilePath &shadersFolder) {
// Scan the shaders folder for xml (shader interface) files
QDir shadersDir(QString::fromStdWString(shadersFolder.getWideString()));
QStringList namesFilter("*.xml");
QStringList files = shadersDir.entryList(namesFilter, QDir::Files,
QDir::Name | QDir::LocaleAware);
int f, fCount = files.size();
for (f = 0; f != fCount; ++f) {
TIStream is(shadersFolder + TFilePath(files[f].toStdWString()));
// Try to load a ShaderInterface instance for the file
ShaderInterface shaderInterface;
is >> shaderInterface;
if (shaderInterface.isValid()) {
// Store a ShaderFx factory for the interface
::l_shaderFxDeclarations.insert(
std::make_pair(shaderInterface.mainShader().m_name,
new ShaderFxDeclaration(shaderInterface)));
}
}
}