// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkOpenGLLowMemoryBatchedPolyDataMapper.h" #include "vtkArrayDispatch.h" #include "vtkCellData.h" #include "vtkColorTransferFunction.h" #include "vtkCompositePolyDataMapper.h" #include "vtkConstantArray.h" #include "vtkFloatArray.h" #include "vtkHardwareSelector.h" #include "vtkImageData.h" #include "vtkLookupTable.h" #include "vtkObjectFactory.h" #include "vtkOpenGLCellToVTKCellMap.h" #include "vtkOpenGLCompositePolyDataMapperDelegator.h" #include "vtkOpenGLIndexBufferObject.h" #include "vtkOpenGLLowMemoryCellTypeAgent.h" #include "vtkOpenGLRenderWindow.h" #include "vtkOpenGLShaderCache.h" #include "vtkOpenGLState.h" #include "vtkOpenGLTexture.h" #include "vtkOpenGLVertexBufferObject.h" #include "vtkOpenGLVertexBufferObjectGroup.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkProperty.h" #include "vtkRenderWindow.h" #include "vtkRenderer.h" #include "vtkSetGet.h" #include "vtkShaderProgram.h" #include "vtkTexture.h" #include "vtkTextureObject.h" #include "vtkTransform.h" #include "vtkUnsignedIntArray.h" #include namespace { // This color is chosen for textures that shouldn't be used for rendering. If this color shows up in // a render, an error might have occured. constexpr unsigned char TextureErrorColor[3] = { 255, 0, 255 }; template class ScopedValueRollback { public: ScopedValueRollback(T& value, T newValue) { Value = value; Pointer = &value; *Pointer = newValue; } ~ScopedValueRollback() { *Pointer = Value; } private: T* Pointer = nullptr; T Value; }; } // end anonymous namespace VTK_ABI_NAMESPACE_BEGIN #define SCOPED_ROLLBACK(type, varName) \ ScopedValueRollback saver_##varName(this->varName, batchElement.varName) #define SCOPED_ROLLBACK_CUSTOM_VARIABLE(type, varName, newVarName) \ ScopedValueRollback saver_##varName(this->varName, newVarName) #define SCOPED_ROLLBACK_ARRAY_ELEMENT(type, varName, idx) \ ScopedValueRollback saver_##varName##idx(this->varName[idx], batchElement.varName[idx]) //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkOpenGLLowMemoryBatchedPolyDataMapper); //------------------------------------------------------------------------------ vtkOpenGLLowMemoryBatchedPolyDataMapper::vtkOpenGLLowMemoryBatchedPolyDataMapper() { // force static this->Static = true; this->BlockTexturePrototype = vtkSmartPointer::New(); vtkNew imageData; imageData->SetDimensions(1, 1, 1); imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 3); vtkUnsignedCharArray* scalars = vtkArrayDownCast(imageData->GetPointData()->GetScalars()); // Gives a magenta color to make sure we don't use this texture anywhere // We must give at least one pixel of data here to give the information that the texture has 3 // components (see vtkOpenGLPolyDataMapper::ReplaceShaderTCoord). scalars->FillComponent(0, ::TextureErrorColor[0]); scalars->FillComponent(1, ::TextureErrorColor[1]); scalars->FillComponent(2, ::TextureErrorColor[2]); this->BlockTexturePrototype->SetInputData(imageData); this->BlockTexturePrototype->Update(); } //------------------------------------------------------------------------------ vtkOpenGLLowMemoryBatchedPolyDataMapper::~vtkOpenGLLowMemoryBatchedPolyDataMapper() = default; //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Override Color Used: " << this->OverideColorUsed << endl; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::AddBatchElement( unsigned int flatIndex, BatchElement&& element) { auto address = reinterpret_cast(element.PolyData); auto found = this->VTKPolyDataToGLBatchElement.find(address); if (found == this->VTKPolyDataToGLBatchElement.end()) { GLBatchElement glBatchElement; glBatchElement.CellGroupId = 0; glBatchElement.Parent = std::move(element); glBatchElement.Parent.Marked = true; this->VTKPolyDataToGLBatchElement[address] = std::unique_ptr(new GLBatchElement(std::move(glBatchElement))); } else { auto& glBatchElement = found->second; glBatchElement->Parent.FlatIndex = flatIndex; glBatchElement->Parent.Marked = true; } } //------------------------------------------------------------------------------ vtkCompositePolyDataMapperDelegator::BatchElement* vtkOpenGLLowMemoryBatchedPolyDataMapper::GetBatchElement(vtkPolyData* polydata) { auto address = reinterpret_cast(polydata); auto found = this->VTKPolyDataToGLBatchElement.find(address); if (found != this->VTKPolyDataToGLBatchElement.end()) { auto& glBatchElement = found->second; return &(glBatchElement->Parent); } return nullptr; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ClearBatchElements() { this->VTKPolyDataToGLBatchElement.clear(); } //------------------------------------------------------------------------------ std::vector vtkOpenGLLowMemoryBatchedPolyDataMapper::GetRenderedList() const { std::vector result; result.reserve(this->VTKPolyDataToGLBatchElement.size()); for (const auto& iter : this->VTKPolyDataToGLBatchElement) { result.emplace_back(iter.second->Parent.PolyData); } return result; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::SetParent(vtkCompositePolyDataMapper* parent) { this->Parent = parent; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::RenderPiece(vtkRenderer* renderer, vtkActor* actor) { // Make sure that we have been properly initialized. if (renderer->GetRenderWindow()->CheckAbortStatus()) { return; } this->CurrentSelector = renderer->GetSelector(); if (this->CurrentSelector) { for (auto& iter : this->VTKPolyDataToGLBatchElement) { this->CurrentInput = iter.second->Parent.PolyData; this->UpdateMaximumPointCellIds(renderer, actor); } } // Cache the bounding box of all points. if (this->ShiftScaleMethod == ShiftScaleMethodType::AUTO_SHIFT_SCALE) { double bounds[6] = {}; vtkMath::UninitializeBounds(bounds); auto firstPolyData = this->VTKPolyDataToGLBatchElement.begin()->second->Parent.PolyData; if (firstPolyData->GetNumberOfPoints() > 0) { firstPolyData->GetPoints()->GetBounds(bounds); } this->PointsBBox.SetBounds(bounds); for (auto& iter : this->VTKPolyDataToGLBatchElement) { this->CurrentInput = iter.second->Parent.PolyData; if (this->CurrentInput->GetNumberOfPoints() > 0) { this->CurrentInput->GetPoints()->GetBounds(bounds); this->PointsBBox.AddBounds(bounds); } } } if (this->VTKPolyDataToGLBatchElement.empty()) { return; } this->CurrentInput = this->VTKPolyDataToGLBatchElement.begin()->second->Parent.PolyData; this->ComputeCameraBasedShiftScale(renderer, actor, this->CurrentInput); this->RenderPieceStart(renderer, actor); this->RenderPieceDraw(renderer, actor); this->RenderPieceFinish(renderer, actor); } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::UnmarkBatchElements() { for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto& glBatchElement = iter.second; glBatchElement->Parent.Marked = false; } } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ClearUnmarkedBatchElements() { for (auto iter = this->VTKPolyDataToGLBatchElement.begin(); iter != this->VTKPolyDataToGLBatchElement.end();) { if (!iter->second->Parent.Marked) { this->VTKPolyDataToGLBatchElement.erase(iter++); this->Modified(); } else { ++iter; } } } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::DrawPrimitives( vtkRenderer* renderer, vtkActor* actor, PrimitiveInformation& primitive) { bool selecting = this->CurrentSelector != nullptr; bool tpass = actor->IsRenderingTranslucentPolygonalGeometry(); auto& agent = primitive.Agent; agent->PreDraw(renderer, actor, this); for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto glBatchElement = iter.second.get(); auto& batchElement = glBatchElement->Parent; bool shouldDraw = batchElement.Visibility // must be visible && (!selecting || batchElement.Pickability) // and pickable when selecting && (((selecting || batchElement.IsOpaque || actor->GetForceOpaque()) && !tpass) // opaque during opaque or when selecting || ((!batchElement.IsOpaque || actor->GetForceTranslucent()) && tpass && !selecting)); // translucent during translucent and never selecting if (!shouldDraw) { continue; } this->SetShaderValues(glBatchElement); agent->Draw(renderer, actor, this, primitive.CellGroups, glBatchElement->CellGroupId); } agent->PostDraw(renderer, actor, this); } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::RenderPieceStart( vtkRenderer* renderer, vtkActor* actor) { if (!this->BlockTexturePrototype->GetTextureObject()) { this->BlockTexturePrototype->Load(renderer); } for (auto& [_, element] : this->VTKPolyDataToGLBatchElement) { vtkOpenGLTexture* texture = vtkOpenGLTexture::SafeDownCast(element->Parent.Texture); if (texture) { texture->Load(renderer); texture->GetTextureObject()->Activate(); } } this->Superclass::RenderPieceStart(renderer, actor); } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::RenderPieceDraw( vtkRenderer* renderer, vtkActor* actor) { this->ReadyShaderProgram(renderer); this->SetShaderParameters(renderer, actor); this->OverideColorUsed = this->ShaderProgram->IsUniformUsed("overridesColor"); bool pointPicking = false; if (this->CurrentSelector && this->PopulateSelectionSettings && this->CurrentSelector->GetFieldAssociation() == vtkDataObject::FIELD_ASSOCIATION_POINTS) { pointPicking = true; } for (auto& primitive : this->Primitives) { if (!pointPicking) { this->DrawPrimitives(renderer, actor, primitive); } // vertex visibility pass if ((actor->GetProperty()->GetVertexVisibility() && primitive.Agent->ImplementsVertexVisibilityPass()) || pointPicking) { primitive.Agent->BeginVertexVisibilityPass(); this->DrawPrimitives(renderer, actor, primitive); primitive.Agent->EndVertexVisibilityPass(); } } } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessSelectorPixelBuffers( vtkHardwareSelector* sel, std::vector& pixeloffsets, vtkProp* prop) { if (!this->PopulateSelectionSettings) { return; } if (sel->GetCurrentPass() == vtkHardwareSelector::ACTOR_PASS) { this->PickPixels.clear(); return; } if (this->PickPixels.empty() && !pixeloffsets.empty()) { // preprocess the image to find matching pixels and // store them in a map of vectors based on flat index // this makes the block processing far faster as we just // loop over the pixels for our block unsigned char* compositedata = sel->GetRawPixelBuffer(vtkHardwareSelector::COMPOSITE_INDEX_PASS); if (!compositedata) { return; } size_t maxFlatIndex = 0; for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto& glBatchElement = iter.second; auto& batchElement = glBatchElement->Parent; maxFlatIndex = (batchElement.FlatIndex > maxFlatIndex) ? batchElement.FlatIndex : maxFlatIndex; } this->PickPixels.resize(maxFlatIndex + 1); for (auto pos : pixeloffsets) { unsigned int compval = compositedata[pos + 2]; compval = compval << 8; compval |= compositedata[pos + 1]; compval = compval << 8; compval |= compositedata[pos]; if (compval <= maxFlatIndex) { this->PickPixels[compval].push_back(pos); } } } // for each block update the image for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto glBatchElement = iter.second.get(); auto& batchElement = glBatchElement->Parent; if (!this->PickPixels[batchElement.FlatIndex].empty()) { this->ProcessCompositePixelBuffers( sel, prop, glBatchElement, this->PickPixels[batchElement.FlatIndex]); } } } namespace { struct ProcessFunctor { template void operator()(TArray* array, unsigned char* rawplowdata, unsigned char* rawphighdata, unsigned char* processdata, std::vector& mypixels) { auto arrayRange = vtk::DataArrayValueRange<1>(array); for (auto pos : mypixels) { unsigned int inval = 0; if (rawphighdata) { inval = rawphighdata[pos]; inval = inval << 8; } inval |= rawplowdata[pos + 2]; inval = inval << 8; inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; // as this pass happens after both low and high point passes const auto outval = static_cast(arrayRange[inval]) + 1; processdata[pos] = outval & 0xff; processdata[pos + 1] = (outval & 0xff00) >> 8; processdata[pos + 2] = (outval & 0xff0000) >> 16; } } }; struct CompositeFunctor { template void operator()(TArray* array, unsigned char* rawclowdata, unsigned char* rawchighdata, unsigned char* compositedata, std::vector& mypixels) { auto arrayRange = vtk::DataArrayValueRange<1>(array); for (auto pos : mypixels) { unsigned int inval = 0; if (rawchighdata) { inval = rawchighdata[pos]; inval = inval << 8; } inval |= rawclowdata[pos + 2]; inval = inval << 8; inval |= rawclowdata[pos + 1]; inval = inval << 8; inval |= rawclowdata[pos]; // always gets called after the cell high and low are available // so it is safe vtkIdType cellId = inval; const auto outval = static_cast(arrayRange[cellId]); compositedata[pos] = outval & 0xff; compositedata[pos + 1] = (outval & 0xff00) >> 8; compositedata[pos + 2] = (outval & 0xff0000) >> 16; } } }; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ProcessCompositePixelBuffers(vtkHardwareSelector* sel, vtkProp*, GLBatchElement* glBatchElement, std::vector& mypixels) { auto& batchElement = glBatchElement->Parent; vtkPolyData* poly = batchElement.PolyData; if (!poly) { return; } // which pass are we processing ? int currPass = sel->GetCurrentPass(); // get some common useful values vtkPointData* pd = poly->GetPointData(); vtkCellData* cd = poly->GetCellData(); // get some values unsigned char* rawplowdata = sel->GetRawPixelBuffer(vtkHardwareSelector::POINT_ID_LOW24); unsigned char* rawphighdata = sel->GetRawPixelBuffer(vtkHardwareSelector::POINT_ID_HIGH24); // do we need to do anything to the process pass data? if (currPass == vtkHardwareSelector::PROCESS_PASS) { unsigned char* processdata = sel->GetPixelBuffer(vtkHardwareSelector::PROCESS_PASS); vtkDataArray* processArray = nullptr; if (sel->GetUseProcessIdFromData()) { processArray = this->ProcessIdArrayName ? pd->GetArray(this->ProcessIdArrayName) : nullptr; } if (processdata && (processArray && processArray->GetDataType() == VTK_UNSIGNED_INT) && rawplowdata) { using UIntArrays = vtkTypeList::Create, vtkConstantArray>; using Dispatcher = vtkArrayDispatch::DispatchByArray; ProcessFunctor functor; if (!Dispatcher::Execute( processArray, functor, rawplowdata, rawphighdata, processdata, mypixels)) { functor(processArray, rawplowdata, rawphighdata, processdata, mypixels); } } } // do we need to do anything to the point id data? if (currPass == vtkHardwareSelector::POINT_ID_LOW24) { vtkIdTypeArray* pointArrayId = this->PointIdArrayName ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? if (rawplowdata) { unsigned char* plowdata = sel->GetPixelBuffer(vtkHardwareSelector::POINT_ID_LOW24); bool hasHighPointIds = sel->HasHighPointIds(); for (auto pos : mypixels) { unsigned int inval = 0; if (rawphighdata) { inval = rawphighdata[pos]; inval = inval << 8; } inval |= rawplowdata[pos + 2]; inval = inval << 8; inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; // this pass happens before the high pass which means the value // could underflow etc when the high data is not around yet and high // data is needed. if (rawphighdata || !hasHighPointIds) { vtkIdType outval = inval; if (pointArrayId && static_cast(inval) <= pointArrayId->GetMaxId()) { outval = pointArrayId->GetValue(inval); } plowdata[pos] = outval & 0xff; plowdata[pos + 1] = (outval & 0xff00) >> 8; plowdata[pos + 2] = (outval & 0xff0000) >> 16; } } } } if (currPass == vtkHardwareSelector::POINT_ID_HIGH24) { vtkIdTypeArray* pointArrayId = this->PointIdArrayName ? vtkArrayDownCast(pd->GetArray(this->PointIdArrayName)) : nullptr; // do we need to do anything to the point id data? if (rawphighdata) { unsigned char* phighdata = sel->GetPixelBuffer(vtkHardwareSelector::POINT_ID_HIGH24); for (auto pos : mypixels) { unsigned int inval = 0; inval = rawphighdata[pos]; inval = inval << 8; inval |= rawplowdata[pos + 2]; inval = inval << 8; inval |= rawplowdata[pos + 1]; inval = inval << 8; inval |= rawplowdata[pos]; // always happens after the low pass so we should be safe vtkIdType outval = inval; if (pointArrayId) { outval = pointArrayId->GetValue(inval); } phighdata[pos] = (outval & 0xff000000) >> 24; phighdata[pos + 1] = (outval & 0xff00000000) >> 32; phighdata[pos + 2] = (outval & 0xff0000000000) >> 40; } } } unsigned char* rawclowdata = sel->GetRawPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); unsigned char* rawchighdata = sel->GetRawPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); // do we need to do anything to the composite pass data? if (currPass == vtkHardwareSelector::COMPOSITE_INDEX_PASS) { unsigned char* compositedata = sel->GetPixelBuffer(vtkHardwareSelector::COMPOSITE_INDEX_PASS); vtkDataArray* compositeArray = this->CompositeIdArrayName ? cd->GetArray(this->CompositeIdArrayName) : nullptr; if (compositedata && (compositeArray && compositeArray->GetDataType() == VTK_UNSIGNED_INT) && rawclowdata) { using UIntArrays = vtkTypeList::Create, vtkConstantArray>; using Dispatcher = vtkArrayDispatch::DispatchByArray; CompositeFunctor functor; if (!Dispatcher::Execute( compositeArray, functor, rawclowdata, rawchighdata, compositedata, mypixels)) { functor(compositeArray, rawclowdata, rawchighdata, compositedata, mypixels); } } } if (currPass == vtkHardwareSelector::CELL_ID_LOW24) { vtkIdTypeArray* cellArrayId = this->CellIdArrayName ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* clowdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_LOW24); bool hasHighCellIds = sel->HasHighCellIds(); if (rawclowdata) { for (auto pos : mypixels) { unsigned int inval = 0; if (rawchighdata) { inval = rawchighdata[pos]; inval = inval << 8; } inval |= rawclowdata[pos + 2]; inval = inval << 8; inval |= rawclowdata[pos + 1]; inval = inval << 8; inval |= rawclowdata[pos]; // this pass happens before the high pass which means the value // could underflow etc when the high data is not around yet and high // data is needed. This underflow would happen in the ConvertToOpenGLCellId // code when passed too low a number if (rawchighdata || !hasHighCellIds) { vtkIdType outval = inval; if (cellArrayId && outval <= cellArrayId->GetMaxId()) { outval = cellArrayId->GetValue(outval); } clowdata[pos] = outval & 0xff; clowdata[pos + 1] = (outval & 0xff00) >> 8; clowdata[pos + 2] = (outval & 0xff0000) >> 16; } } } } if (currPass == vtkHardwareSelector::CELL_ID_HIGH24) { vtkIdTypeArray* cellArrayId = this->CellIdArrayName ? vtkArrayDownCast(cd->GetArray(this->CellIdArrayName)) : nullptr; unsigned char* chighdata = sel->GetPixelBuffer(vtkHardwareSelector::CELL_ID_HIGH24); if (rawchighdata) { for (auto pos : mypixels) { unsigned int inval = 0; inval = rawchighdata[pos]; inval = inval << 8; inval |= rawclowdata[pos + 2]; inval = inval << 8; inval |= rawclowdata[pos + 1]; inval = inval << 8; inval |= rawclowdata[pos]; // always called after low24 so safe vtkIdType outval = inval; if (cellArrayId) { outval = cellArrayId->GetValue(outval); } chighdata[pos] = (outval & 0xff000000) >> 24; chighdata[pos + 1] = (outval & 0xff00000000) >> 32; chighdata[pos + 2] = (outval & 0xff0000000000) >> 40; } } } } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::SetShaderValues(GLBatchElement* glBatchElement) { auto& batchElement = glBatchElement->Parent; if (this->CurrentSelector) { if (this->CurrentSelector->GetCurrentPass() == vtkHardwareSelector::COMPOSITE_INDEX_PASS && this->ShaderProgram->IsUniformUsed("mapperIndex")) { this->CurrentSelector->RenderCompositeIndex(batchElement.FlatIndex); this->ShaderProgram->SetUniform3f("mapperIndex", this->CurrentSelector->GetPropColorValue()); } return; } SCOPED_ROLLBACK(int, ColorMode); SCOPED_ROLLBACK(int, ScalarMode); SCOPED_ROLLBACK(int, ArrayAccessMode); SCOPED_ROLLBACK(int, ArrayComponent); SCOPED_ROLLBACK(int, ArrayId); SCOPED_ROLLBACK_CUSTOM_VARIABLE(char*, ArrayName, static_cast(batchElement.ArrayName.empty() ? nullptr : &batchElement.ArrayName.front())); SCOPED_ROLLBACK(vtkIdType, FieldDataTupleId); SCOPED_ROLLBACK(vtkTypeBool, ScalarVisibility); SCOPED_ROLLBACK(vtkTypeBool, UseLookupTableScalarRange); SCOPED_ROLLBACK(vtkTypeBool, InterpolateScalarsBeforeMapping); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 0); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 1); // If requested, color partial / missing arrays with NaN color. bool useNanColor = false; double nanColor[4] = { -1., -1., -1., -1 }; if (this->Parent->GetColorMissingArraysWithNanColor() && this->GetScalarVisibility()) { int cellFlag = 0; vtkAbstractArray* scalars = vtkAbstractMapper::GetAbstractScalars(batchElement.PolyData, this->ScalarMode, this->ArrayAccessMode, this->ArrayId, this->ArrayName, cellFlag); if (scalars == nullptr) { vtkLookupTable* lut = vtkLookupTable::SafeDownCast(this->GetLookupTable()); vtkColorTransferFunction* ctf = lut ? nullptr : vtkColorTransferFunction::SafeDownCast(this->GetLookupTable()); if (lut) { lut->GetNanColor(nanColor); useNanColor = true; } else if (ctf) { ctf->GetNanColor(nanColor); useNanColor = true; } } } // override the opacity and color this->ShaderProgram->SetUniformf("intensity_opacity_override", batchElement.Opacity); if (useNanColor) { float fnancolor[3] = { static_cast(nanColor[0]), static_cast(nanColor[1]), static_cast(nanColor[2]) }; this->ShaderProgram->SetUniform3f("color_ambient_override", fnancolor); this->ShaderProgram->SetUniform3f("color_diffuse_override", fnancolor); this->ShaderProgram->SetUniformi("overridesColor", true); } else { // if (this->DrawingSelection) // { // vtkColor3d& sColor = batchElement.SelectionColor; // float selectionColor[3] = { static_cast(sColor[0]), static_cast(sColor[1]), // static_cast(sColor[2]) }; // this->ShaderProgram->SetUniform3f("color_ambient", selectionColor); // this->ShaderProgram->SetUniform3f("color_diffuse", selectionColor); // this->ShaderProgram->SetUniformf("intensity_opacity_override", // batchElement.SelectionOpacity); // } // else { vtkColor3d& aColor = batchElement.AmbientColor; float ambientColor[3] = { static_cast(aColor[0]), static_cast(aColor[1]), static_cast(aColor[2]) }; vtkColor3d& dColor = batchElement.DiffuseColor; float diffuseColor[3] = { static_cast(dColor[0]), static_cast(dColor[1]), static_cast(dColor[2]) }; this->ShaderProgram->SetUniform3f("color_ambient_override", ambientColor); this->ShaderProgram->SetUniform3f("color_diffuse_override", diffuseColor); } if (this->OverideColorUsed) { this->ShaderProgram->SetUniformi("overridesColor", batchElement.OverridesColor); } } if (batchElement.Texture) { this->ShaderProgram->SetUniformi("blocktexture", batchElement.Texture->GetTextureUnit()); } } //------------------------------------------------------------------------------ std::vector vtkOpenGLLowMemoryBatchedPolyDataMapper::GetTextures(vtkActor* actor) { std::vector textures = this->Superclass::GetTextures(actor); bool needBlockTextureSlot = false; for (const auto& [_, batchElement] : this->VTKPolyDataToGLBatchElement) { needBlockTextureSlot |= batchElement->Parent.Texture != nullptr; } if (this->BlockTexturePrototype && needBlockTextureSlot) { textures.emplace_back(this->BlockTexturePrototype, "blocktexture"); } return textures; } //------------------------------------------------------------------------------ bool vtkOpenGLLowMemoryBatchedPolyDataMapper::IsDataObjectUpToDate() { bool uptodate = true; for (auto& iter : this->VTKPolyDataToGLBatchElement) { uptodate &= (this->RenderTimeStamp > iter.second->Parent.PolyData->GetMTime()); } return uptodate; } //------------------------------------------------------------------------------ vtkDataArray* vtkOpenGLLowMemoryBatchedPolyDataMapper::GetColors(vtkPolyData* mesh) { // Get rid of old texture color coordinates if any if (this->ColorCoordinates) { this->ColorCoordinates->UnRegister(this); this->ColorCoordinates = nullptr; } // Get rid of old texture color coordinates if any if (this->Colors) { this->Colors->UnRegister(this); this->Colors = nullptr; } return this->Superclass::GetColors(mesh); } //------------------------------------------------------------------------------ bool vtkOpenGLLowMemoryBatchedPolyDataMapper::BindArraysToTextureBuffers(vtkRenderer* renderer, vtkActor* actor, vtkCellGraphicsPrimitiveMap::CellTypeMapperOffsets& offsets) { std::size_t cellGroupId = 0; for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto glBatchElement = iter.second.get(); auto& batchElement = glBatchElement->Parent; this->CurrentInput = batchElement.PolyData; SCOPED_ROLLBACK(int, ColorMode); SCOPED_ROLLBACK(int, ScalarMode); SCOPED_ROLLBACK(int, ArrayAccessMode); SCOPED_ROLLBACK(int, ArrayComponent); SCOPED_ROLLBACK(int, ArrayId); SCOPED_ROLLBACK_CUSTOM_VARIABLE(char*, ArrayName, static_cast( batchElement.ArrayName.empty() ? nullptr : &batchElement.ArrayName.front())); SCOPED_ROLLBACK(vtkIdType, FieldDataTupleId); SCOPED_ROLLBACK(vtkTypeBool, ScalarVisibility); SCOPED_ROLLBACK(vtkTypeBool, UseLookupTableScalarRange); SCOPED_ROLLBACK(vtkTypeBool, InterpolateScalarsBeforeMapping); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 0); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 1); if (this->Superclass::BindArraysToTextureBuffers(renderer, actor, offsets)) { iter.second->CellGroupId = cellGroupId++; } } return true; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::UpdateShiftScale( vtkRenderer* renderer, vtkActor* actor) { if (this->ShiftScaleMethod == ShiftScaleMethodType::AUTO_SHIFT_SCALE) { double bounds[6]; this->PointsBBox.GetBounds(bounds); std::vector shift; std::vector scale; for (int i = 0; i < 3; i++) { shift.push_back(0.5 * (bounds[i * 2] + bounds[i * 2 + 1])); scale.push_back( (bounds[i * 2 + 1] - bounds[i * 2]) ? 1.0 / (bounds[i * 2 + 1] - bounds[i * 2]) : 1.0); } this->SetShiftValues(shift[0], shift[1], shift[2]); this->SetScaleValues(scale[0], scale[1], scale[2]); } else { // compute shift & scale on first block auto firstPolyData = this->VTKPolyDataToGLBatchElement.begin()->second->Parent.PolyData; if (firstPolyData->GetNumberOfPoints() > 0) { this->ComputeShiftScale(renderer, actor, firstPolyData->GetPoints()->GetData()); } } } //------------------------------------------------------------------------------ bool vtkOpenGLLowMemoryBatchedPolyDataMapper::IsShaderColorSourceUpToDate(vtkActor* actor) { bool uptodate = true; for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto glBatchElement = iter.second.get(); auto& batchElement = glBatchElement->Parent; this->CurrentInput = batchElement.PolyData; SCOPED_ROLLBACK(int, ColorMode); SCOPED_ROLLBACK(int, ScalarMode); SCOPED_ROLLBACK(int, ArrayAccessMode); SCOPED_ROLLBACK(int, ArrayComponent); SCOPED_ROLLBACK(int, ArrayId); SCOPED_ROLLBACK_CUSTOM_VARIABLE(char*, ArrayName, static_cast( batchElement.ArrayName.empty() ? nullptr : &batchElement.ArrayName.front())); SCOPED_ROLLBACK(vtkIdType, FieldDataTupleId); SCOPED_ROLLBACK(vtkTypeBool, ScalarVisibility); SCOPED_ROLLBACK(vtkTypeBool, UseLookupTableScalarRange); SCOPED_ROLLBACK(vtkTypeBool, InterpolateScalarsBeforeMapping); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 0); SCOPED_ROLLBACK_ARRAY_ELEMENT(double, ScalarRange, 1); uptodate &= this->Superclass::IsShaderColorSourceUpToDate(actor); } return uptodate; } //------------------------------------------------------------------------------ bool vtkOpenGLLowMemoryBatchedPolyDataMapper::IsShaderNormalSourceUpToDate(vtkActor* actor) { bool uptodate = true; for (auto& iter : this->VTKPolyDataToGLBatchElement) { this->CurrentInput = iter.second->Parent.PolyData; uptodate &= this->Superclass::IsShaderNormalSourceUpToDate(actor); } return uptodate; } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::UpdateShaders(vtkRenderer* renderer, vtkActor* actor) { this->Superclass::UpdateShaders(renderer, actor); if (this->ShaderProgram && this->Parent) { // allow the application to set what it wants on our shader program. this->Parent->InvokeEvent(vtkCommand::UpdateShaderEvent, this->ShaderProgram); } } //------------------------------------------------------------------------------ void vtkOpenGLLowMemoryBatchedPolyDataMapper::ReplaceShaderColor( vtkRenderer* renderer, vtkActor* actor, std::string& vsSource, std::string& fsSource) { if (!this->CurrentSelector) { vtkShaderProgram::Substitute(fsSource, "//VTK::Color::Dec", "uniform bool overridesColor;\n" "uniform float intensity_opacity_override;\n" "uniform vec3 color_ambient_override;\n" "uniform vec3 color_diffuse_override;" "//VTK::Color::Dec", false); vtkShaderProgram::Substitute(fsSource, "//VTK::Color::Impl", "//VTK::Color::Impl\n" " opacity = intensity_opacity_override;\n" " if (overridesColor && vertex_pass == 0) {\n" " ambientColor = color_ambient_override * intensity_ambient;\n" " diffuseColor = color_diffuse_override * intensity_diffuse; }\n", false); } this->Superclass::ReplaceShaderColor(renderer, actor, vsSource, fsSource); } //------------------------------------------------------------------------------ // Returns if we can use texture maps for scalar coloring. Note this doesn't say // we "will" use scalar coloring. It says, if we do use scalar coloring, we will // use a texture. // When rendering multiblock datasets, if any 2 blocks provide different // lookup tables for the scalars, then also we cannot use textures. This case can // be handled if required. int vtkOpenGLLowMemoryBatchedPolyDataMapper::CanUseTextureMapForColoring(vtkDataObject*) { if (!this->InterpolateScalarsBeforeMapping) { return 0; // user doesn't want us to use texture maps at all. } int cellFlag = 0; vtkScalarsToColors* scalarsLookupTable = nullptr; for (auto& iter : this->VTKPolyDataToGLBatchElement) { auto polydata = iter.second->Parent.PolyData; vtkDataArray* scalars = vtkAbstractMapper::GetScalars( polydata, this->ScalarMode, this->ArrayAccessMode, this->ArrayId, this->ArrayName, cellFlag); if (scalars) { if (cellFlag) { return 0; } // Don't use texture if direct coloring using RGB unsigned chars was requested. if ((this->ColorMode == VTK_COLOR_MODE_DEFAULT && vtkArrayDownCast(scalars)) || this->ColorMode == VTK_COLOR_MODE_DIRECT_SCALARS) { // Don't use texture if direct coloring using RGB unsigned chars is // requested. return 0; } if (scalarsLookupTable && scalars->GetLookupTable() && (scalarsLookupTable != scalars->GetLookupTable())) { // Two datasets are requesting different lookup tables to color with. // We don't handle this case right now for composite datasets. return 0; } if (scalars->GetLookupTable()) { scalarsLookupTable = scalars->GetLookupTable(); } } } if ((scalarsLookupTable && scalarsLookupTable->GetIndexedLookup()) || (!scalarsLookupTable && this->LookupTable && this->LookupTable->GetIndexedLookup())) { return 0; } return 1; } vtkMTimeType vtkOpenGLLowMemoryBatchedPolyDataMapper::GetMTime() { if (this->Parent) { return std::max(this->Superclass::GetMTime(), this->Parent->GetMTime()); } else { return this->Superclass::GetMTime(); } } VTK_ABI_NAMESPACE_END