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)); } }
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
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
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; }
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