Пример #1
0
void
TrackMarker::resetCenter()
{
    KnobItemsTablePtr model = getModel();
    if (!model) {
        return;
    }
    RectD rod;
    NodePtr input = model->getNode()->getInput(0);
    if (!input) {
        Format f;
        getApp()->getProject()->getProjectDefaultFormat(&f);
        rod = f.toCanonicalFormat();
    } else {
        TimeValue time(input->getApp()->getTimeLine()->currentFrame());
        RenderScale scale(1.);
        RectD rod;
        {
            GetRegionOfDefinitionResultsPtr results;
            ActionRetCodeEnum stat = input->getEffectInstance()->getRegionOfDefinition_public(time, scale, ViewIdx(0), &results);
            if (!isFailureRetCode(stat)) {
                rod = results->getRoD();
            }
        }
        Point center;
        center.x = 0;
        center.y = 0;
        center.x = (rod.x1 + rod.x2) / 2.;
        center.y = (rod.y1 + rod.y2) / 2.;


        KnobDoublePtr centerKnob = getCenterKnob();
        centerKnob->setValue(center.x, ViewSetSpec::all(), DimIdx(0));
        centerKnob->setValue(center.y, ViewSetSpec::all(), DimIdx(1));
    }
}
Пример #2
0
ActionRetCodeEnum
RotoShapeRenderNode::render(const RenderActionArgs& args)
{

#if !defined(ROTO_SHAPE_RENDER_CPU_USES_CAIRO) && !defined(HAVE_OSMESA)
    getNode()->setPersistentMessage(eMessageTypeError, kNatronPersistentErrorGenericRenderMessage, tr("Roto requires either OSMesa (CONFIG += enable-osmesa) or Cairo (CONFIG += enable-cairo) in order to render on CPU").toStdString());
    return eActionStatusFailed;
#endif

#if !defined(ROTO_SHAPE_RENDER_CPU_USES_CAIRO)
    if (args.backendType == eRenderBackendTypeCPU) {
        getNode()->setPersistentMessage(eMessageTypeError, kNatronPersistentErrorGenericRenderMessage, tr("An OpenGL context is required to draw with the Roto node. This might be because you are trying to render an image too big for OpenGL.").toStdString());
        return eActionStatusFailed;
    }
#endif

    RenderScale combinedScale = EffectInstance::getCombinedScale(args.mipMapLevel, args.proxyScale);

    // Get the Roto item attached to this node. It will be a render-local clone of the original item.
    RotoDrawableItemPtr rotoItem = getAttachedRotoItem();
    assert(rotoItem);
    if (!rotoItem) {
        return eActionStatusFailed;
    }

    // To be thread-safe we can only operate on a render clone.
    assert(rotoItem->isRenderClone());

    // Is it a smear or regular solid render ?
    assert(_imp->renderType.lock());
    RotoShapeRenderTypeEnum type = (RotoShapeRenderTypeEnum)_imp->renderType.lock()->getValue();

    // We only support rendering Bezier or strokes
    RotoStrokeItemPtr isStroke = toRotoStrokeItem(rotoItem);
    BezierPtr isBezier = toBezier(rotoItem);

    // Get the real stroke (the one the user interacts with)
    RotoStrokeItemPtr nonRenderStroke = toRotoStrokeItem(getOriginalAttachedItem());

    if (type == eRotoShapeRenderTypeSmear && !isStroke) {
        return eActionStatusFailed;
    }

    // Check that the item is really activated... it should have been caught in isIdentity otherwise.
    assert(rotoItem->isActivated(args.time, args.view) && (!isBezier || ((isBezier->isCurveFinished(args.view) || isBezier->isOpenBezier()) && ( isBezier->getControlPointsCount(args.view) > 1 ))));

    const OSGLContextPtr& glContext = args.glContext;

    // There must be an OpenGL context bound when using OpenGL.
    if ((args.backendType == eRenderBackendTypeOpenGL || args.backendType == eRenderBackendTypeOSMesa) && !glContext) {
        getNode()->setPersistentMessage(eMessageTypeError, kNatronPersistentErrorGenericRenderMessage, tr("An OpenGL context is required to draw with the Roto node").toStdString());
        return eActionStatusFailed;
    }

    // This is the image plane where we render, we are not multiplane so we only render out one plane
    assert(args.outputPlanes.size() == 1);
    const std::pair<ImagePlaneDesc,ImagePtr>& outputPlane = args.outputPlanes.front();

    // True if this render was trigger because the user is painting (with a pen or mouse)
    bool isDuringPainting = isStroke && isStroke->isCurrentlyDrawing();

    // These variables are useful to pick the stroke drawing algorithm where it was at the previous draw step.
    double distNextIn = 0.;
    Point lastCenterIn = { INT_MIN, INT_MIN };
    int strokeStartPointIndex = 0;
    int strokeMultiIndex = 0;

    // For strokes and open-bezier evaluate them to get the points and their pressure
    // We also compute the bounding box of the item taking into account the motion blur
    if (isStroke) {
        strokeStartPointIndex = isStroke->getRenderCloneCurrentStrokeStartPointIndex();
        strokeMultiIndex = isStroke->getRenderCloneCurrentStrokeIndex();
        isStroke->getStrokeState(&lastCenterIn, &distNextIn);
    }

    // Ensure that the indices of the draw step are valid.
#ifdef DEBUG
    if (isDuringPainting && isStroke->getRenderCloneCurrentStrokeEndPointIndex() >= strokeStartPointIndex) {
        if (strokeStartPointIndex == 0) {
            assert((isStroke->getRenderCloneCurrentStrokeEndPointIndex() + 1) == isStroke->getNumControlPoints(0));
        } else {
            // +2 because we also add the last point of the previous draw step in the call to cloneIndexRange(), otherwise it would make holes in the drawing
            assert((isStroke->getRenderCloneCurrentStrokeEndPointIndex() + 2 - strokeStartPointIndex) == isStroke->getNumControlPoints(0));
        }
    }
#endif

    // Now we are good to start rendering

    // This is the state of the stroke aglorithm in output of this draw step
    double distToNextOut = 0.;
    Point lastCenterOut;

    // Retrieve the OpenGL context local data that were allocated in attachOpenGLContext
    RotoShapeRenderNodeOpenGLDataPtr glData;
    if (args.glContextData) {
        glData = boost::dynamic_pointer_cast<RotoShapeRenderNodeOpenGLData>(args.glContextData);
        assert(glData);
    }

    // Firs time we draw this clear the background since we are not going to render the full image with OpenGL.
    if (strokeStartPointIndex == 0 && strokeMultiIndex == 0) {
        outputPlane.second->fillBoundsZero();
    }

    bool clipToFormat = _imp->clipToFormatKnob.lock()->getValue();

    switch (type) {
        case eRotoShapeRenderTypeSolid: {

            // Account for motion-blur
            RangeD range;
            int divisions;
            rotoItem->getMotionBlurSettings(args.time, args.view, &range, &divisions);

            if (isDuringPainting) {
                // Do not use motion-blur when drawing.
                range.min = range.max = args.time;
                divisions = 1;
            }

#ifdef ROTO_SHAPE_RENDER_CPU_USES_CAIRO
            // When cairo is enabled, render with it for a CPU render
            if (args.backendType == eRenderBackendTypeCPU) {
                RotoShapeRenderCairo::renderMaskInternal_cairo(rotoItem, args.roi, outputPlane.first, args.time, args.view, range, divisions, combinedScale, isDuringPainting, distNextIn, lastCenterIn, outputPlane.second, &distToNextOut, &lastCenterOut);
                if (isDuringPainting && isStroke) {
                    nonRenderStroke->updateStrokeData(lastCenterOut, distToNextOut, isStroke->getRenderCloneCurrentStrokeEndPointIndex());
                }
            } else
#endif
            // Otherwise render with OpenGL or OSMesa
            if (args.backendType == eRenderBackendTypeOpenGL || args.backendType == eRenderBackendTypeOSMesa) {

                // Figure out the shape color
                ColorRgbaD shapeColor;
                {
                    const double t = args.time;
                    KnobColorPtr colorKnob = rotoItem->getColorKnob();
                    if (colorKnob) {
                        shapeColor.r = colorKnob->getValueAtTime(TimeValue(t), DimIdx(0), args.view);
                        shapeColor.g = colorKnob->getValueAtTime(TimeValue(t), DimIdx(1), args.view);
                        shapeColor.b = colorKnob->getValueAtTime(TimeValue(t), DimIdx(2), args.view);
                        shapeColor.a = colorKnob->getValueAtTime(TimeValue(t), DimIdx(3), args.view);
                    }
                }


                // Figure out the opacity
                double opacity = rotoItem->getOpacityKnob() ? rotoItem->getOpacityKnob()->getValueAtTime(args.time, DimIdx(0), args.view) : 1.;

                // For a stroke or an opened Bezier, use the generic stroke algorithm
                if ( isStroke || ( isBezier && (isBezier->isOpenBezier() || !isBezier->isFillEnabled()) ) ) {
                    const bool doBuildUp = !isStroke ? false : isStroke->getBuildupKnob()->getValueAtTime(args.time, DimIdx(0), args.view);
                    RotoShapeRenderGL::renderStroke_gl(glContext, glData, args.roi, outputPlane.second, isDuringPainting, distNextIn, lastCenterIn, rotoItem, doBuildUp, opacity, args.time, args.view, range, divisions, combinedScale, &distToNextOut, &lastCenterOut);

                    // Update the stroke algorithm in output
                    if (isDuringPainting && isStroke) {
                        nonRenderStroke->updateStrokeData(lastCenterOut, distToNextOut, isStroke->getRenderCloneCurrentStrokeEndPointIndex());
                    }
                } else {
                    // Render a Bezier
                    //qDebug() << QThread::currentThread() << this  << isBezier.get()<< "RoD while render:";
                    //isBezier->getBoundingBox(args.time, args.view).debug();
                    RotoShapeRenderGL::renderBezier_gl(glContext, glData,
                                                       args.roi,
                                                       isBezier, outputPlane.second, clipToFormat, opacity, args.time, args.view, range, divisions, combinedScale, GL_TEXTURE_2D);
                }
            } // useOpenGL
        }   break;
        case eRotoShapeRenderTypeSmear: {

            OSGLContextAttacherPtr contextAttacher;
            if (args.backendType == eRenderBackendTypeOSMesa && !glContext->isGPUContext()) {
                // When rendering smear with OSMesa we need to write to the full image bounds and not only the RoI, so re-attach the default framebuffer
                // with the image bounds
                Image::CPUData imageData;
                outputPlane.second->getCPUData(&imageData);

                contextAttacher = OSGLContextAttacher::create(glContext, imageData.bounds.width(), imageData.bounds.height(), imageData.bounds.width(), imageData.ptrs[0]);
            }

            // Ensure that initially everything in the background is the source image
            if (strokeStartPointIndex == 0 && strokeMultiIndex == 0) {

                GetImageOutArgs outArgs;
                GetImageInArgs inArgs(&args.mipMapLevel, &args.proxyScale, &args.roi, &args.backendType);
                inArgs.inputNb = 0;
                if (!getImagePlane(inArgs, &outArgs)) {
                    getNode()->setPersistentMessage(eMessageTypeError, kNatronPersistentErrorGenericRenderMessage, tr("Failed to fetch source image").toStdString());
                    return eActionStatusFailed;
                }

                ImagePtr bgImage = outArgs.image;


                if (args.backendType == eRenderBackendTypeCPU || glContext->isGPUContext()) {

                    // Copy the BG image to the output image
                    Image::CopyPixelsArgs cpyArgs;
                    cpyArgs.roi = outputPlane.second->getBounds();
                    outputPlane.second->copyPixels(*bgImage, cpyArgs);
                } else {

                    // With OSMesa we cannot re-use the existing output plane as source because mesa clears the framebuffer out upon the first draw
                    // after a binding.
                    // The only option is to draw in a tmp texture that will live for the whole stroke painting life

                    Image::InitStorageArgs initArgs;
                    initArgs.bounds = bgImage->getBounds();
                    initArgs.bitdepth = outputPlane.second->getBitDepth();
                    initArgs.storage = eStorageModeGLTex;
                    initArgs.glContext = glContext;
                    initArgs.textureTarget = GL_TEXTURE_2D;
                    _imp->osmesaSmearTmpTexture = Image::create(initArgs);
                    if (!_imp->osmesaSmearTmpTexture) {
                        return eActionStatusFailed;
                    }

                    // Make sure the texture is ready before rendering the smear
                    GL_CPU::Flush();
                    GL_CPU::Finish();
                }
            } else {
                if (args.backendType == eRenderBackendTypeOSMesa && !glContext->isGPUContext() && strokeStartPointIndex == 0) {
                    // Ensure the tmp texture has correct size
                    assert(_imp->osmesaSmearTmpTexture);
                    ActionRetCodeEnum stat = _imp->osmesaSmearTmpTexture->ensureBounds(outputPlane.second->getBounds(), args.mipMapLevel, std::vector<RectI>(), shared_from_this());
                    if (isFailureRetCode(stat)) {
                        return stat;
                    }
                }
            }

            bool renderedDot;
#ifdef ROTO_SHAPE_RENDER_CPU_USES_CAIRO
            // Render with cairo if we need to render on CPU
            if (args.backendType == eRenderBackendTypeCPU) {
                renderedDot = RotoShapeRenderCairo::renderSmear_cairo(args.time, args.view, combinedScale, isStroke, args.roi, outputPlane.second, distNextIn, lastCenterIn, &distToNextOut, &lastCenterOut);
            } else
#endif
            if (args.backendType == eRenderBackendTypeOpenGL || args.backendType == eRenderBackendTypeOSMesa) {

                // Render with OpenGL
                ImagePtr dstImage = glContext->isGPUContext() ? outputPlane.second : _imp->osmesaSmearTmpTexture;
                assert(dstImage);
                renderedDot = RotoShapeRenderGL::renderSmear_gl(glContext, glData, args.roi, dstImage, distNextIn, lastCenterIn, isStroke, 1., args.time, args.view, combinedScale, &distToNextOut, &lastCenterOut);
            }

            // Update the stroke algorithm in output
            if (isDuringPainting) {
                Q_UNUSED(renderedDot);
                nonRenderStroke->updateStrokeData(lastCenterOut, distToNextOut, isStroke->getRenderCloneCurrentStrokeEndPointIndex());
            }

        }   break;
    } // type



    return eActionStatusOK;

} // RotoShapeRenderNode::render
Пример #3
0
void
ViewerRenderFrameSubResult::onTreeRenderFinished(int inputIndex)
{
    PerViewerInputRenderData& inputData = perInputsData[inputIndex];

    if (inputIndex == 1 && copyInputBFromA) {
        inputData = perInputsData[0];
        return;
    }

    FrameViewRequestPtr outputRequest;
    if (inputData.render) {
        inputData.retCode = inputData.render->getStatus();
        outputRequest = inputData.render->getOutputRequest();
    }
    if (outputRequest) {
        inputData.viewerProcessImage = outputRequest->getRequestedScaleImagePlane();
    }

    // There might be no output image if the RoI that was passed to render is outside of the RoD of the effect
    if (isFailureRetCode(inputData.retCode) || !inputData.viewerProcessImage) {
        inputData.viewerProcessImage.reset();
        inputData.render.reset();
        return;
    }


    // Find the key of the image and store it so that in the gui
    // we can later on re-use this key to check the cache for the timeline's cache line
    ImageCacheEntryPtr cacheEntry = inputData.viewerProcessImage->getCacheEntry();
    if (cacheEntry) {
        inputData.viewerProcessImageKey = cacheEntry->getCacheKey();
    }

    // Convert the image to a format that can be uploaded to a OpenGL texture


    RectI imageConvertRoI;
    RectD ctorCanonicalRoI = inputData.render->getCtorRoI();
    if (inputData.render && !ctorCanonicalRoI.isNull()) {
        RenderScale scale = EffectInstance::getCombinedScale(inputData.viewerProcessImage->getMipMapLevel(), inputData.viewerProcessImage->getProxyScale());
        double par = inputData.viewerProcessNode->getEffectInstance()->getAspectRatio(-1);
        ctorCanonicalRoI.toPixelEnclosing(scale, par, &imageConvertRoI);
    } else {
        imageConvertRoI = inputData.viewerProcessImage->getBounds();
    }

    // If we are drawing with the RotoPaint node, only update the texture portion
    if (textureTransferType == OpenGLViewerI::TextureTransferArgs::eTextureTransferTypeModify) {
        RectI strokeArea;
        bool strokeAreaSet = inputData.render->getRotoPaintActiveStrokeUpdateArea(&strokeArea);
        if (strokeAreaSet) {
            imageConvertRoI = strokeArea;
        }
    }

    // The viewer-process node may not have rendered a 4 channel image, but this is required but the OpenGL viewer
    // which only draws RGBA images.

    // If we are in accumulation, force a copy of the image because another render thread might modify it in a future render whilst it may
    // still be read from the main-thread when updating the ViewerGL texture.
    // If texture transfer is eTextureTransferTypeOverlay, we want to upload the texture to exactly what was requested
    const bool forceOutputImageCopy = (inputData.viewerProcessImage == inputData.viewerProcessNode->getEffectInstance()->getAccumBuffer(inputData.viewerProcessImage->getLayer()) ||
                                       (textureTransferType == OpenGLViewerI::TextureTransferArgs::eTextureTransferTypeOverlay && inputData.viewerProcessImage->getBounds() != imageConvertRoI));
    inputData.viewerProcessImage = convertImageForViewerDisplay(imageConvertRoI, forceOutputImageCopy, true /*the texture must have 4 channels*/, inputData.viewerProcessImage);

    // Extra color-picker images as-well.
    if (inputData.colorPickerNode) {
        {
            FrameViewRequestPtr req = inputData.render->getExtraRequestedResultsForNode(inputData.colorPickerNode);
            if (req) {
                inputData.colorPickerImage = req->getRequestedScaleImagePlane();
                if (inputData.colorPickerImage) {
                    inputData.colorPickerImage = convertImageForViewerDisplay(inputData.colorPickerImage->getBounds(), false, false /*the picker can accept non 4-channel image*/, inputData.colorPickerImage);
                }
            }
        }
        if (inputData.colorPickerInputNode) {
            FrameViewRequestPtr req = inputData.render->getExtraRequestedResultsForNode(inputData.colorPickerInputNode);
            if (req) {
                inputData.colorPickerInputImage = req->getRequestedScaleImagePlane();
                if (inputData.colorPickerInputImage) {
                    inputData.colorPickerInputImage = convertImageForViewerDisplay(inputData.colorPickerInputImage->getBounds(), false, false /*the picker can accept non 4-channel image*/, inputData.colorPickerInputImage);
                }
            }
        }
    }

    inputData.render.reset();
} // onTreeRenderFinished
Пример #4
0
ActionRetCodeEnum
RotoShapeRenderNode::getRegionOfDefinition(TimeValue time, const RenderScale& scale, ViewIdx view, RectD* rod)
{

    RotoDrawableItemPtr item = getAttachedRotoItem();
    assert(item);
    assert((isRenderClone() && item->isRenderClone()) ||
           (!isRenderClone() && !item->isRenderClone()));
    const bool isPainting = isDuringPaintStrokeCreation();
    RectD shapeRoD;
    getRoDFromItem(item, time, view, isPainting, &shapeRoD);

    bool clipToFormat = _imp->clipToFormatKnob.lock()->getValue();

    RotoShapeRenderTypeEnum type = (RotoShapeRenderTypeEnum)_imp->renderType.lock()->getValue();
    switch (type) {
        case eRotoShapeRenderTypeSmear: {
            RectD defaultRod;
            ActionRetCodeEnum stat = EffectInstance::getRegionOfDefinition(time, scale, view, &defaultRod);
            if (isFailureRetCode(stat)) {
                return stat;
            }
            if (!defaultRod.isNull()) {
                *rod = shapeRoD;
                rod->merge(defaultRod);
            }
        }   break;
        case eRotoShapeRenderTypeSolid: {
            RotoPaintOutputRoDTypeEnum rodType = (RotoPaintOutputRoDTypeEnum)_imp->outputRoDTypeKnob.lock()->getValue();
            switch (rodType) {
                case eRotoPaintOutputRoDTypeDefault: {
                    *rod = shapeRoD;
                    // No format is set, use the format from the input
                    if (clipToFormat) {
                        EffectInstancePtr inputEffect = getInputRenderEffectAtAnyTimeView(0);
                        if (inputEffect) {
                            RectI outputFormat = inputEffect->getOutputFormat();
                            RectD outputFormatCanonical;
                            outputFormat.toCanonical_noClipping(scale, inputEffect->getAspectRatio(-1), &outputFormatCanonical);
                            rod->intersect(outputFormatCanonical, rod);
                        }
                    }
                }   break;
                case eRotoPaintOutputRoDTypeFormat: {
                    KnobIntPtr sizeKnob = _imp->outputFormatSizeKnob.lock();
                    int w = sizeKnob->getValue(DimIdx(0));
                    int h = _imp->outputFormatSizeKnob.lock()->getValue(DimIdx(1));
                    double par = _imp->outputFormatParKnob.lock()->getValue();

                    RectI pixelFormat;
                    pixelFormat.x1 = pixelFormat.y1 = 0;
                    pixelFormat.x2 = w;
                    pixelFormat.y2 = h;
                    RenderScale renderScale(1.);
                    pixelFormat.toCanonical_noClipping(renderScale, par, rod);
                    if (!clipToFormat) {
                        rod->merge(shapeRoD);
                    }
                }   break;

                case eRotoPaintOutputRoDTypeProject: {
                    Format f;
                    getApp()->getProject()->getProjectDefaultFormat(&f);
                    f.toCanonical_noClipping(RenderScale(1.), f.getPixelAspectRatio(), rod);
                    if (!clipToFormat) {
                        rod->merge(shapeRoD);
                    }
                }   break;
            }
        }   break;
    }

    return eActionStatusOK;

}
Пример #5
0
void
NodeAnimPrivate::computeRetimeRange()
{
    NodeGuiPtr nodeUI = nodeGui.lock();
    NodePtr node = nodeUI->getNode();
    if (!node) {
        return;
    }
    NodePtr input = node->getInput(0);
    if (!input) {
        return;
    }
    if (input) {
        RangeD inputRange = {0, 0};
        {
            GetFrameRangeResultsPtr results;
            ActionRetCodeEnum stat = input->getEffectInstance()->getFrameRange_public(&results);
            if (!isFailureRetCode(stat)) {
                results->getFrameRangeResults(&inputRange);
            }
        }

        FramesNeededMap framesFirst, framesLast;
        {
            GetFramesNeededResultsPtr results;
            ActionRetCodeEnum stat = node->getEffectInstance()->getFramesNeeded_public(TimeValue(inputRange.min), ViewIdx(0), &results);
            if (!isFailureRetCode(stat)) {
                results->getFramesNeeded(&framesFirst);
            }

            stat = node->getEffectInstance()->getFramesNeeded_public(TimeValue(inputRange.max), ViewIdx(0), &results);
            if (!isFailureRetCode(stat)) {
                results->getFramesNeeded(&framesLast);
            }
        }
        assert( !framesFirst.empty() && !framesLast.empty() );
        if ( framesFirst.empty() || framesLast.empty() ) {
            return;
        }

        {
            const FrameRangesMap& rangeFirst = framesFirst[0];
            assert( !rangeFirst.empty() );
            if ( rangeFirst.empty() ) {
                return;
            }
            FrameRangesMap::const_iterator it = rangeFirst.find( ViewIdx(0) );
            assert( it != rangeFirst.end() );
            if ( it == rangeFirst.end() ) {
                return;
            }
            const std::vector<OfxRangeD>& frames = it->second;
            assert( !frames.empty() );
            if ( frames.empty() ) {
                return;
            }
            frameRange.min = (frames.front().min);
        }
        {
            FrameRangesMap& rangeLast = framesLast[0];
            assert( !rangeLast.empty() );
            if ( rangeLast.empty() ) {
                return;
            }
            FrameRangesMap::const_iterator it = rangeLast.find( ViewIdx(0) );
            assert( it != rangeLast.end() );
            if ( it == rangeLast.end() ) {
                return;
            }
            const std::vector<OfxRangeD>& frames = it->second;
            assert( !frames.empty() );
            if ( frames.empty() ) {
                return;
            }
            frameRange.max = (frames.front().min);
        }
    }

} // computeRetimeRange