/*========================================================================= Program: Visualization Toolkit Module: vtkGPUVolumeRayCastMapper.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkGPUVolumeRayCastMapper.h" #include #include #include // for VolumeMapperRender{Start|End|Progress}Event #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Return nullptr if no override is supplied. vtkAbstractObjectFactoryNewMacro(vtkGPUVolumeRayCastMapper) vtkCxxSetObjectMacro(vtkGPUVolumeRayCastMapper, MaskInput, vtkImageData); vtkCxxSetObjectMacro(vtkGPUVolumeRayCastMapper, TransformedInput, vtkImageData); vtkGPUVolumeRayCastMapper::vtkGPUVolumeRayCastMapper() : LockSampleDistanceToInputSpacing(0) { this->AutoAdjustSampleDistances = 1; this->ImageSampleDistance = 1.0; this->MinimumImageSampleDistance = 1.0; this->MaximumImageSampleDistance = 10.0; this->RenderToImage = 0; this->DepthImageScalarType = VTK_FLOAT; this->ClampDepthToBackface = 0; this->UseJittering = 0; this->UseDepthPass = 0; this->DepthPassContourValues = nullptr; this->SampleDistance = 1.0; this->SmallVolumeRender = 0; this->BigTimeToDraw = 0.0; this->SmallTimeToDraw = 0.0; this->FinalColorWindow = 1.0; this->FinalColorLevel = 0.5; this->GeneratingCanonicalView = 0; this->CanonicalViewImageData = nullptr; this->MaskInput = nullptr; this->MaskBlendFactor = 1.0f; this->MaskType = vtkGPUVolumeRayCastMapper::LabelMapMaskType; this->ColorRangeType = TFRangeType::SCALAR; this->ScalarOpacityRangeType = TFRangeType::SCALAR; this->GradientOpacityRangeType = TFRangeType::SCALAR; this->AMRMode = 0; this->CellFlag = 0; this->ClippedCroppingRegionPlanes[0]=VTK_DOUBLE_MAX; this->ClippedCroppingRegionPlanes[1]=VTK_DOUBLE_MIN; this->ClippedCroppingRegionPlanes[2]=VTK_DOUBLE_MAX; this->ClippedCroppingRegionPlanes[3]=VTK_DOUBLE_MIN; this->ClippedCroppingRegionPlanes[4]=VTK_DOUBLE_MAX; this->ClippedCroppingRegionPlanes[5]=VTK_DOUBLE_MIN; this->MaxMemoryInBytes=0; vtkGPUInfoList *l = vtkGPUInfoList::New(); l->Probe(); if(l->GetNumberOfGPUs()>0) { vtkGPUInfo *info=l->GetGPUInfo(0); this->MaxMemoryInBytes = info->GetDedicatedVideoMemory(); if(this->MaxMemoryInBytes == 0) { this->MaxMemoryInBytes = info->GetDedicatedSystemMemory(); } // we ignore info->GetSharedSystemMemory(); as this is very slow. } l->Delete(); if(this->MaxMemoryInBytes == 0) // use some default value: 128MB. { this->MaxMemoryInBytes = 128*1024*1024; } this->MaxMemoryFraction = 0.75; this->ReportProgress = true; this->TransformedInput = nullptr; this->LastInput = nullptr; } // ---------------------------------------------------------------------------- vtkGPUVolumeRayCastMapper::~vtkGPUVolumeRayCastMapper() { this->SetMaskInput(nullptr); this->SetTransformedInput(nullptr); this->LastInput = nullptr; if (this->DepthPassContourValues) { this->DepthPassContourValues->Delete(); } } // ---------------------------------------------------------------------------- // The render method that is called from the volume. If this is a canonical // view render, a specialized version of this method will be called instead. // Otherwise we will // - Invoke a start event // - Start timing // - Check that everything is OK for rendering // - Render // - Stop the timer and record results // - Invoke an end event // ---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::Render( vtkRenderer *ren, vtkVolume *vol ) { // Catch renders that are happening due to a canonical view render and // handle them separately. if (this->GeneratingCanonicalView ) { this->CanonicalViewRender(ren, vol); return; } // Invoke a VolumeMapperRenderStartEvent this->InvokeEvent(vtkCommand::VolumeMapperRenderStartEvent,nullptr); // Start the timer to time the length of this render vtkTimerLog *timer = vtkTimerLog::New(); timer->StartTimer(); // Make sure everything about this render is OK. // This is where the input is updated. if ( this->ValidateRender(ren, vol ) ) { // Everything is OK - so go ahead and really do the render this->GPURender( ren, vol); } // Stop the timer timer->StopTimer(); double t = timer->GetElapsedTime(); // cout << "Render Timer " << t << " seconds, " << 1.0/t << " frames per second" << endl; this->TimeToDraw = t; timer->Delete(); if ( vol->GetAllocatedRenderTime() < 1.0 ) { this->SmallTimeToDraw = t; } else { this->BigTimeToDraw = t; } // Invoke a VolumeMapperRenderEndEvent this->InvokeEvent(vtkCommand::VolumeMapperRenderEndEvent,nullptr); } // ---------------------------------------------------------------------------- // Special version for rendering a canonical view - we don't do things like // invoke start or end events, and we don't capture the render time. // ---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::CanonicalViewRender(vtkRenderer *ren, vtkVolume *vol ) { // Make sure everything about this render is OK if ( this->ValidateRender(ren, vol ) ) { // Everything is OK - so go ahead and really do the render this->GPURender( ren, vol); } } // ---------------------------------------------------------------------------- // This method us used by the render method to validate everything before // attempting to render. This method returns 0 if something is not right - // such as missing input, a null renderer or a null volume, no scalars, etc. // In some cases it will produce a vtkErrorMacro message, and in others // (for example, in the case of cropping planes that define a region with // a volume or 0 or less) it will fail silently. If everything is OK, it will // return with a value of 1. // ---------------------------------------------------------------------------- int vtkGPUVolumeRayCastMapper::ValidateRender(vtkRenderer *ren, vtkVolume *vol) { // Check that we have everything we need to render. int goodSoFar = 1; // Check for a renderer - we MUST have one if ( !ren ) { goodSoFar = 0; vtkErrorMacro("Renderer cannot be null."); } // Check for the volume - we MUST have one if ( goodSoFar && !vol ) { goodSoFar = 0; vtkErrorMacro("Volume cannot be null."); } // Don't need to check if we have a volume property // since the volume will create one if we don't. Also // don't need to check for the scalar opacity function // or the RGB transfer function since the property will // create them if they do not yet exist. // However we must currently check that the number of // color channels is 3 // TODO: lift this restriction - should work with // gray functions as well. Right now turning off test // because otherwise 4 component rendering isn't working. // Will revisit. if ( goodSoFar && vol->GetProperty()->GetColorChannels() != 3 ) { // goodSoFar = 0; // vtkErrorMacro("Must have a color transfer function."); } // Check the cropping planes. If they are invalid, just silently // fail. This will happen when an interactive widget is dragged // such that it defines 0 or negative volume - this can happen // and should just not render the volume. // Check the cropping planes if( goodSoFar && this->Cropping && (this->CroppingRegionPlanes[0]>=this->CroppingRegionPlanes[1] || this->CroppingRegionPlanes[2]>=this->CroppingRegionPlanes[3] || this->CroppingRegionPlanes[4]>=this->CroppingRegionPlanes[5] )) { // No error message here - we want to be silent goodSoFar = 0; } // Check that we have input data vtkImageData *input=this->GetInput(); if(goodSoFar && input==nullptr) { vtkErrorMacro("Input is nullptr but is required"); goodSoFar = 0; } if(goodSoFar) { this->GetInputAlgorithm()->Update(); } // If we have a timestamp change or data change then create a new clone. if(goodSoFar && (input != this->LastInput || input->GetMTime() > this->TransformedInput->GetMTime())) { this->LastInput = input; vtkImageData* clone; if(!this->TransformedInput) { clone = vtkImageData::New(); this->SetTransformedInput(clone); clone->Delete(); } else { clone = this->TransformedInput; } clone->ShallowCopy(input); // @TODO: This is the workaround to deal with GPUVolumeRayCastMapper // not able to handle extents starting from non zero values. // There is not a easy fix in the GPU volume ray cast mapper hence // this fix has been introduced. // Get the current extents. int extents[6], real_extents[6]; clone->GetExtent(extents); clone->GetExtent(real_extents); // Get the current origin and spacing. double origin[3], spacing[3]; clone->GetOrigin(origin); clone->GetSpacing(spacing); for (int cc=0; cc < 3; cc++) { // Transform the origin and the extents. origin[cc] = origin[cc] + extents[2*cc]*spacing[cc]; extents[2*cc+1] -= extents[2*cc]; extents[2*cc] = 0; } clone->SetOrigin(origin); clone->SetExtent(extents); } // Update the date then make sure we have scalars. Note // that we must have point or cell scalars because field // scalars are not supported. vtkDataArray *scalars = nullptr; if ( goodSoFar ) { // Now make sure we can find scalars scalars=this->GetScalars(this->TransformedInput,this->ScalarMode, this->ArrayAccessMode, this->ArrayId, this->ArrayName, this->CellFlag); // We couldn't find scalars if ( !scalars ) { vtkErrorMacro("No scalars named \"" << this->ArrayName << "\" or with id " << this->ArrayId << " found on input."); goodSoFar = 0; } // Even if we found scalars, if they are field data scalars that isn't good else if ( this->CellFlag == 2 ) { vtkErrorMacro("Only point or cell scalar support - found field scalars instead."); goodSoFar = 0; } } // Make sure the scalar type is actually supported. This mappers supports // almost all standard scalar types. if ( goodSoFar ) { switch(scalars->GetDataType()) { case VTK_CHAR: vtkErrorMacro(<< "scalar of type VTK_CHAR is not supported " << "because this type is platform dependent. " << "Use VTK_SIGNED_CHAR or VTK_UNSIGNED_CHAR instead."); goodSoFar = 0; break; case VTK_BIT: vtkErrorMacro("scalar of type VTK_BIT is not supported by this mapper."); goodSoFar = 0; break; case VTK_ID_TYPE: vtkErrorMacro("scalar of type VTK_ID_TYPE is not supported by this mapper."); goodSoFar = 0; break; case VTK_STRING: vtkErrorMacro("scalar of type VTK_STRING is not supported by this mapper."); goodSoFar = 0; break; default: // Don't need to do anything here break; } } // Check on the blending type - we support composite, additive, average // and min / max intensity if ( goodSoFar ) { if(this->BlendMode!=vtkVolumeMapper::COMPOSITE_BLEND && this->BlendMode!=vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND && this->BlendMode!=vtkVolumeMapper::MINIMUM_INTENSITY_BLEND && this->BlendMode!=vtkVolumeMapper::AVERAGE_INTENSITY_BLEND && this->BlendMode!=vtkVolumeMapper::ADDITIVE_BLEND) { goodSoFar = 0; vtkErrorMacro(<< "Selected blend mode not supported. " << "Only Composite, MIP, MinIP, averageIP and additive modes " << "are supported by the current implementation."); } } int numberOfComponents = goodSoFar ? scalars->GetNumberOfComponents() : 0; #ifdef VTK_OPENGL2 // This mapper supports anywhere from 1-4 components. Number of components // outside this range is not supported. if( goodSoFar ) { if( numberOfComponents <= 0 || numberOfComponents > 4 ) { goodSoFar = 0; vtkErrorMacro(<< "Only 1 - 4 component scalars " << "are supported by this mapper." << "The input data has " << numberOfComponents << " component(s)."); } } // If the dataset has dependent components (as set in the volume property), // only 2 or 4 component scalars are supported. if( goodSoFar ) { if( !(vol->GetProperty()->GetIndependentComponents()) && (numberOfComponents == 1 || numberOfComponents == 3) ) { goodSoFar = 0; vtkErrorMacro(<< "If IndependentComponents is Off in the " << "volume property, then the data must have " << "either 2 or 4 component scalars. " << "The input data has " << numberOfComponents << " component(s)."); } } #else // This mapper supports 1 component data, or 4 component if it is not independent // component (i.e. the four components define RGBA) if ( goodSoFar ) { if( !(numberOfComponents == 1 || numberOfComponents == 4) ) { goodSoFar = 0; vtkErrorMacro(<< "Only one component scalars, or four " << "component with non-independent components, " << "are supported by this mapper."); } } // If this is four component data, then it better be unsigned char (RGBA). if( goodSoFar && numberOfComponents == 4 && scalars->GetDataType() != VTK_UNSIGNED_CHAR) { goodSoFar = 0; vtkErrorMacro("Only unsigned char is supported for 4-component scalars!"); } if(goodSoFar && numberOfComponents!=1 && this->BlendMode==vtkVolumeMapper::ADDITIVE_BLEND) { goodSoFar=0; vtkErrorMacro("Additive mode only works with 1-component scalars!"); } #endif // return our status return goodSoFar; } // ---------------------------------------------------------------------------- // Description: // Called by the AMR Volume Mapper. // Set the flag that tells if the scalars are on point data (0) or // cell data (1). void vtkGPUVolumeRayCastMapper::SetCellFlag(int cellFlag) { this->CellFlag=cellFlag; } // ---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::CreateCanonicalView( vtkRenderer *ren, vtkVolume *volume, vtkImageData *image, int vtkNotUsed(blend_mode), double viewDirection[3], double viewUp[3]) { this->GeneratingCanonicalView = 1; int oldSwap = ren->GetRenderWindow()->GetSwapBuffers(); ren->GetRenderWindow()->SwapBuffersOff(); int dim[3]; image->GetDimensions(dim); int *size = ren->GetRenderWindow()->GetSize(); vtkImageData *bigImage = vtkImageData::New(); bigImage->SetDimensions(size[0], size[1], 1); bigImage->AllocateScalars(VTK_UNSIGNED_CHAR, 3); this->CanonicalViewImageData = bigImage; double scale[2]; scale[0] = dim[0] / static_cast(size[0]); scale[1] = dim[1] / static_cast(size[1]); // Save the visibility flags of the renderers and set all to false except // for the ren. vtkRendererCollection *renderers=ren->GetRenderWindow()->GetRenderers(); int numberOfRenderers=renderers->GetNumberOfItems(); bool *rendererVisibilities=new bool[numberOfRenderers]; renderers->InitTraversal(); int i=0; while(iGetNextItem(); rendererVisibilities[i]=r->GetDraw()==1; if(r!=ren) { r->SetDraw(false); } ++i; } // Save the visibility flags of the props and set all to false except // for the volume. vtkPropCollection *props=ren->GetViewProps(); int numberOfProps=props->GetNumberOfItems(); bool *propVisibilities=new bool[numberOfProps]; props->InitTraversal(); i=0; while(iGetNextProp(); propVisibilities[i]=p->GetVisibility()==1; if(p!=volume) { p->SetVisibility(false); } ++i; } vtkCamera *savedCamera=ren->GetActiveCamera(); savedCamera->Modified(); vtkCamera *canonicalViewCamera=vtkCamera::New(); // Code from vtkFixedPointVolumeRayCastMapper: double *center=volume->GetCenter(); double bounds[6]; volume->GetBounds(bounds); #if 0 double d=sqrt((bounds[1]-bounds[0])*(bounds[1]-bounds[0]) + (bounds[3]-bounds[2])*(bounds[3]-bounds[2]) + (bounds[5]-bounds[4])*(bounds[5]-bounds[4])); #endif // For now use x distance - need to change this double d=bounds[1]-bounds[0]; // Set up the camera in parallel canonicalViewCamera->SetFocalPoint(center); canonicalViewCamera->ParallelProjectionOn(); canonicalViewCamera->SetPosition(center[0] - d*viewDirection[0], center[1] - d*viewDirection[1], center[2] - d*viewDirection[2]); canonicalViewCamera->SetViewUp(viewUp); canonicalViewCamera->SetParallelScale(d/2); ren->SetActiveCamera(canonicalViewCamera); ren->GetRenderWindow()->Render(); ren->SetActiveCamera(savedCamera); canonicalViewCamera->Delete(); // Shrink to image to the desired size vtkImageResample *resample = vtkImageResample::New(); resample->SetInputData( bigImage ); resample->SetAxisMagnificationFactor(0,scale[0]); resample->SetAxisMagnificationFactor(1,scale[1]); resample->SetAxisMagnificationFactor(2,1); resample->UpdateWholeExtent(); // Copy the pixels over image->DeepCopy(resample->GetOutput()); bigImage->Delete(); resample->Delete(); // Restore the visibility flags of the props props->InitTraversal(); i=0; while(iGetNextProp(); p->SetVisibility(propVisibilities[i]); ++i; } delete[] propVisibilities; // Restore the visibility flags of the renderers renderers->InitTraversal(); i=0; while(iGetNextItem(); r->SetDraw(rendererVisibilities[i]); ++i; } delete[] rendererVisibilities; ren->GetRenderWindow()->SetSwapBuffers(oldSwap); this->CanonicalViewImageData = nullptr; this->GeneratingCanonicalView = 0; } // ---------------------------------------------------------------------------- // Print method for vtkGPUVolumeRayCastMapper void vtkGPUVolumeRayCastMapper::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "AutoAdjustSampleDistances: " << this->AutoAdjustSampleDistances << endl; os << indent << "MinimumImageSampleDistance: " << this->MinimumImageSampleDistance << endl; os << indent << "MaximumImageSampleDistance: " << this->MaximumImageSampleDistance << endl; os << indent << "ImageSampleDistance: " << this->ImageSampleDistance << endl; os << indent << "SampleDistance: " << this->SampleDistance << endl; os << indent << "FinalColorWindow: " << this->FinalColorWindow << endl; os << indent << "FinalColorLevel: " << this->FinalColorLevel << endl; os << indent << "MaskInput: " << this->MaskInput << endl; os << indent << "MaskType: " << this->MaskType << endl; os << indent << "MaskBlendFactor: " << this->MaskBlendFactor << endl; os << indent << "MaxMemoryInBytes: " << this->MaxMemoryInBytes << endl; os << indent << "MaxMemoryFraction: " << this->MaxMemoryFraction << endl; os << indent << "ReportProgress: " << this->ReportProgress << endl; } // ---------------------------------------------------------------------------- // Description: // Compute the cropping planes clipped by the bounds of the volume. // The result is put into this->ClippedCroppingRegionPlanes. // NOTE: IT WILL BE MOVED UP TO vtkVolumeMapper after bullet proof usage // in this mapper. Other subclasses will use the ClippedCroppingRegionsPlanes // members instead of CroppingRegionPlanes. // \pre volume_exists: this->GetInput()!=0 // \pre valid_cropping: this->Cropping && // this->CroppingRegionPlanes[0]CroppingRegionPlanes[1] && // this->CroppingRegionPlanes[2]CroppingRegionPlanes[3] && // this->CroppingRegionPlanes[4]CroppingRegionPlanes[5]) void vtkGPUVolumeRayCastMapper::ClipCroppingRegionPlanes() { assert("pre: volume_exists" && this->GetInput()!=nullptr); assert("pre: valid_cropping" && this->Cropping && this->CroppingRegionPlanes[0]CroppingRegionPlanes[1] && this->CroppingRegionPlanes[2]CroppingRegionPlanes[3] && this->CroppingRegionPlanes[4]CroppingRegionPlanes[5]); // vtkVolumeMapper::Render() will have something like: // if(this->Cropping && (this->CroppingRegionPlanes[0]>=this->CroppingRegionPlanes[1] || // this->CroppingRegionPlanes[2]>=this->CroppingRegionPlanes[3] || // this->CroppingRegionPlanes[4]>=this->CroppingRegionPlanes[5])) // { // // silentely stop because the cropping is not valid. // return; // } double volBounds[6]; this->GetInput()->GetBounds(volBounds); int i=0; while(i<6) { // max of the mins if(this->CroppingRegionPlanes[i]ClippedCroppingRegionPlanes[i]=volBounds[i]; } else { this->ClippedCroppingRegionPlanes[i]=this->CroppingRegionPlanes[i]; } ++i; // min of the maxs if(this->CroppingRegionPlanes[i]>volBounds[i]) { this->ClippedCroppingRegionPlanes[i]=volBounds[i]; } else { this->ClippedCroppingRegionPlanes[i]=this->CroppingRegionPlanes[i]; } ++i; } } //---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::SetMaskTypeToBinary() { this->MaskType = vtkGPUVolumeRayCastMapper::BinaryMaskType; } //---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::SetMaskTypeToLabelMap() { this->MaskType = vtkGPUVolumeRayCastMapper::LabelMapMaskType; } //---------------------------------------------------------------------------- vtkContourValues* vtkGPUVolumeRayCastMapper::GetDepthPassContourValues() { if (!this->DepthPassContourValues) { this->DepthPassContourValues = vtkContourValues::New(); } return this->DepthPassContourValues; } //---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::SetDepthImageScalarTypeToUnsignedChar() { this->SetDepthImageScalarType(VTK_UNSIGNED_CHAR); } //---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::SetDepthImageScalarTypeToUnsignedShort() { this->SetDepthImageScalarType(VTK_UNSIGNED_SHORT); } //---------------------------------------------------------------------------- void vtkGPUVolumeRayCastMapper::SetDepthImageScalarTypeToFloat() { this->SetDepthImageScalarType(VTK_FLOAT); }