#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 #include #include #include // Glew include #include // Boost includes #include #include #include // 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 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 m_params; //!< Parameters for the shader fx. The actual parameter //!< type depends on the shader interface declaration. std::vector m_uiConcepts; //!< UI concepts related to m_params. boost::ptr_vector 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 &inputRects, std::vector &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 m_shadingContext; TAtomicVar m_activeRenderInstances; std::unique_ptr 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("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; //**************************************************************************** // 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 &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(&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(&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(&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(&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(&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(&m_params.back())); break; } default: break; } } // Add composite UI concepts const std::vector &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 &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::max)(), -(std::numeric_limits::max)(), (std::numeric_limits::max)(), (std::numeric_limits::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 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 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 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(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 &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(&m_params[p]); program->setUniformValue(siParam.m_name.toUtf8().data(), (GLboolean)param->getValue()); break; } case ShaderInterface::FLOAT: { const TDoubleParamP ¶m = *boost::unsafe_any_cast(&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(&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(&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(&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(worldToDst.a11), static_cast(worldToDst.a12), static_cast(worldToDst.a13), static_cast(worldToDst.a21), static_cast(worldToDst.a22), static_cast(worldToDst.a23), 0.0f, 0.0f, 1.0f}; program->setUniformValue("worldToOutput", QMatrix3x3(qwToD)); const TAffine &dToW = worldToDst.inv(); float qdToW[9] = {static_cast(dToW.a11), static_cast(dToW.a12), static_cast(dToW.a13), static_cast(dToW.a21), static_cast(dToW.a22), static_cast(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 &inputRects, std::vector &inputAffines, ShadingContext &context) { struct locals { static inline void addNames(std::vector &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 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 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 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 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(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(&buf[0])), *rBufEnd(rBufBegin + pCount); std::copy(rBufBegin, rBufEnd, &inputRects[0]); const AffineF *aBufBegin(reinterpret_cast(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 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 shadingContextPtr( new ShadingContext(manager->getSurface())); ShadingContext &context = *shadingContextPtr.get(); // ShadingContext& context = manager->shadingContext(); int pCount = getInputPortCount(); const TRectD &tileRect = ::tileRect(tile); std::vector inputRects(pCount); std::vector inputAffines(pCount); // Calculate input tiles ::ContextLocker cLocker(context); std::unique_ptr 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 inputs(pCount); std::vector screenToInput(pCount); std::vector 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(iToS.a11), static_cast(iToS.a12), static_cast(iToS.a13), static_cast(iToS.a21), static_cast(iToS.a22), static_cast(iToS.a23), 0.0f, 0.0f, 1.0f}; float qsToI[9] = {static_cast(sToI.a11), static_cast(sToI.a12), static_cast(sToI.a13), static_cast(sToI.a21), static_cast(sToI.a22), static_cast(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 shadingContextPtr( new ShadingContext(manager->getSurface())); ShadingContext &context = *shadingContextPtr.get(); int pCount = getInputPortCount(); if (pCount > 0) { ::ContextLocker cLocker(context); std::vector inputRects(pCount); std::vector 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))); } } }