/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2025 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr glm::uvec2 BlackoutTextureSize = glm::uvec2(3840, 2160); void checkCornerSpecification(const std::vector& corners) { if (corners.size() != 4) { openspace::documentation::TestResult res; res.success = false; res.offenses.push_back(openspace::documentation::TestResult::Offense()); res.offenses[0].offender = "ScreenSpaceInsetBlackout.Blackoutshape.Corners"; res.offenses[0].explanation = "Asset must contain exactly 4 corners"; res.offenses[0].reason = openspace::documentation::TestResult::Offense::Reason::Verification; throw openspace::documentation::SpecificationError( res, "ScreenSpaceInsetBlackout" ); } } std::pair calculatePadding(const std::vector& v) { ghoul_assert(v.size() >= 2, "Too few points in the list"); const glm::vec2& pf0 = v[1]; const glm::vec2& pf1 = v[2]; const glm::vec2& pb0 = v[v.size() - 1]; const glm::vec2& pb1 = v[v.size() - 2]; const glm::vec2 firstPaddingPoint = 2.f * pf0 - pf1; const glm::vec2 lastPaddingPoint = 2.f * pb0 - pb1; return std::pair(firstPaddingPoint, lastPaddingPoint); } std::vector sampleSpline(const std::vector& controlPoints) { ghoul_assert(controlPoints.size() >= 3, "Too few control points"); constexpr int Subdivisions = 100; std::vector splineData; const int numberOfSegments = static_cast(controlPoints.size() - 3); const float stepSize = 1.f / Subdivisions; for (int i = 0; i < numberOfSegments; i++) { for (int s = 0; s < Subdivisions; s++) { float tValue = stepSize * s; glm::vec2 value = ghoul::interpolateCatmullRom( tValue, *(controlPoints.begin() + i + 0), *(controlPoints.begin() + i + 1), *(controlPoints.begin() + i + 2), *(controlPoints.begin() + i + 3) ); splineData.push_back(std::move(value)); } } return splineData; } void offsetCoordinates(std::vector& vec) { for (glm::vec2& v : vec) { v.x = (v.x * 2.f) - 1.f; v.y = (v.y * 2.f) - 1.f; } } std::string formatLine(std::string_view id, const std::vector& data) { if (data.empty()) { return ""; } std::string str = std::format("{} = {{ ", id); for (size_t i = 0; i < data.size(); i++) { std::string xVal = std::format("{}", data[i].x); std::string yVal = std::format("{}", data[i].y); xVal += (xVal.find(".") == std::string::npos) ? ".0" : ""; yVal += (yVal.find(".") == std::string::npos) ? ".0" : ""; str.append(std::format("{{{}, {}}}", xVal, yVal)); std::string delimiter = (i < data.size() - 1) ? ", " : " "; str.append(delimiter); } str.append("},\n"); return str; } void copyToClipboard(std::vector& cornerData, std::vector& topSplineData, std::vector& rightSplineData, std::vector& bottomSplineData, std::vector& leftSplineData) { std::string strCorners = formatLine("Corners", cornerData); std::string strTop = formatLine("Top", topSplineData); std::string strRight = formatLine("Right", rightSplineData); std::string strBottom = formatLine("Bottom", bottomSplineData); std::string strLeft = formatLine("Left", leftSplineData); ghoul::setClipboardText(strCorners + strTop + strRight + strBottom + strLeft); } constexpr openspace::properties::Property::PropertyInfo CopyToClipboardInfo = { "CopyToClipboard", "Copy to clipboard", "Copies the current configuration to the clipboard so that it can be pasted " "into the asset file.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo CalibrationPatternInfo = { "EnableCalibrationPattern", "Enable calibration pattern", "Enables the calibration pattern. The calibration can be used to find which " "values to use when setting up the blackout shape.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo CalibrationColorInfo = { "EnableCalibrationColor", "Enable calibration color", "Set Blackout Shape to a bright color for easier calibration.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo AddControlPointInfo = { "AddControlPoint", "Add control point", "Adds a new control point to the spline.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo RemoveControlPointInfo = { "RemoveControlPoint", "Remove control point", "Removes the selected control point from the spline.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo RemoveSelectorInfo = { "RemoveSelector", "Select point to remove", "Removes the selected control point.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo AddSelectorInfo = { "AddSelector", "Select where to add", "Select where to add a new point.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo NewPointPositionInfo = { "NewPointPosition", "Point position", "X and Y coordinates for where to add the new control point.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo CalibrationTextureInfo = { "CalibrationTexture", "Calibration texture", "Texture used as calibration pattern.", openspace::properties::Property::Visibility::Developer }; // A ScreenSpaceInsetBlackout can be used to render a screen-space shape used to // black out part of the rendering. This can be useful in a dome environment where // you have a secondary presentation projector that can project on the dome surface. // The blackout is used to avoid overlapping rendering between the dome projectors // and the presentation projector. struct [[codegen::Dictionary(ScreenSpaceInsetBlackout)]] Parameters { struct BlackoutShape { // List of corner positions for the blackout shape. The order of // corner points are Top-Left, Top-Right, Bottom-Right, Bottom-Left with the // range of 0 to 1 for `{X,Y}`, where `{0,1}` is the Top-Left corner and // `{1,0}` is the Bottom-Right corner. std::vector corners; // List of points between the Top-Left and Top-Right corners that will be // used to define top spline of the blackout shape. Each point is specified // in the range of 0 to 1, where `{0,1}` is the Top-Left corner and `{1,1}` // is the Top-Right corner. std::optional> top; // List of points between the Bottom-Right and Bottom-Left corners that will // be used to define bottom spline of the blackout shape. Each point is // specified in the range of 0 to 1, where `{1,0}` is the Bottom-Right // corner and `{0,0}` is the Bottom-Left corner. std::optional> bottom; // List of points between the Bottom-Left and Top-Left corners that will be // used to define left spline of the blackout shape. Each point is specified // in the range of 0 to 1, where `{0,0}` is the Bottom-Left corner and // `{0,1}` is the Top-Left corner. std::optional> left; // List of points between the Top-Right and Bottom-Right corners that will // be used to define right spline of the blackout shape. Each point is // specified in the range of 0 to 1, where `{1,1}` is the Top-Right corner // and `{1,0}` is the Bottom-Right corner. std::optional> right; // File path for the texture that should be used when displaying the // calibration grid. std::optional calibrationTexturePath; }; BlackoutShape blackoutshape; }; #include "screenspaceinsetblackout_codegen.cpp" } // namespace namespace openspace { documentation::Documentation ScreenSpaceInsetBlackout::Documentation() { return codegen::doc("base_screenspace_inset_blackout"); } ScreenSpaceInsetBlackout::BlackoutShape::Point::Point(glm::vec2& inData, std::string identifier, std::string guiName) { // Creates PropertyInfo used to create Property propInfo = std::make_unique( identifier.c_str(), guiName.c_str(), "Position (x,y) for where the control point is located." ); // Stores pointer to data and creates Vec2Property for the given position dataptr = &inData; prop = std::make_unique( *propInfo, *dataptr, glm::vec2(0.f), glm::vec2(1.f), glm::vec2(0.01f) ); } void ScreenSpaceInsetBlackout::BlackoutShape::Point::updateData() { *dataptr = prop->value(); } ScreenSpaceInsetBlackout::BlackoutShape::Spline::Spline(std::vector& inData, std::string baseString) : properties::PropertyOwner({ baseString , baseString, "" }) , data(inData) , newPointPosition( NewPointPositionInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(1.f) ) , addSelector(AddSelectorInfo) , addButton(AddControlPointInfo) , removeSelector(RemoveSelectorInfo) , removeButton(RemoveControlPointInfo) { base = baseString; // Generate all Point objects and add them to GUI for (size_t i = 0; i < data.size(); i++) { points.push_back(std::make_unique( data[i], std::format("Point{}Position", i), std::format("{} Point #{}", base, i + 1) )); points[i]->prop->onChange([this, i]() { points[i]->updateData(); dataHasChanged = true; }); addProperty(points[i]->prop.get()); } // Configure onChange and adding GUI controls addButton.onChange([this]() { dataHasChanged = true; pointAdded = true; }); addProperty(addSelector); addProperty(newPointPosition); addProperty(addButton); // Add options used when inserting a new point for (size_t i = 0; i < points.size() + 1; i++) { addSelector.addOption(static_cast(i), std::format("At position #{}", i + 1)); } // Only add controls for removing a point if there are any points that can be removed if (!data.empty()) { removeButton.onChange([this]() { dataHasChanged = true; pointRemoved = true; }); addProperty(removeSelector); addProperty(removeButton); for (size_t i = 0; i < points.size(); i++) { removeSelector.addOption( static_cast(i), std::format("Point #{}", i + 1) ); } } } void ScreenSpaceInsetBlackout::BlackoutShape::Spline::addPoint() { // Add new position to raw data const int index = addSelector; data.insert(data.begin() + index, newPointPosition); } void ScreenSpaceInsetBlackout::BlackoutShape::Spline::removePoint() { // Remove raw data for the deleted point const int indexToRemove = removeSelector; data.erase(data.begin() + indexToRemove); } ScreenSpaceInsetBlackout::BlackoutShape::Corners::Corners(std::vector& inData) : properties::PropertyOwner({ "Corners" , "Corners", "" }) , data(inData) { // Create corner Points (TopLeft -> TopRight -> BottomRight -> BottomLeft) points.push_back(std::make_unique(data[0], "TopLeft", "Top-Left")); points.push_back(std::make_unique(data[1], "TopRight", "Top-Right")); points.push_back(std::make_unique(data[2], "BottomRight", "Bottom-Right")); points.push_back(std::make_unique(data[3], "BottomLeft", "Bottom-Left")); // OnChange functions and add to GUI for (int i = 0; i < 4; i++) { points[i]->prop->onChange([this, i]() { points[i]->updateData(); dataHasChanged = true; }); addProperty(points[i]->prop.get()); } } ScreenSpaceInsetBlackout::BlackoutShape::BlackoutShape( const ghoul::Dictionary& dictionary) : properties::PropertyOwner({ "BlackoutShape", "Blackout Shape", "" }) , enableCalibrationColor(CalibrationColorInfo, false) , enableCalibrationPattern(CalibrationPatternInfo, false) , calibrationTexturePath(CalibrationTextureInfo) , copyToClipboardTrigger(CopyToClipboardInfo) { const Parameters params = codegen::bake(dictionary); cornerData = params.blackoutshape.corners; corners = std::unique_ptr(new Corners(cornerData)); addPropertySubOwner(*corners); topSplineData = params.blackoutshape.top.value_or(std::vector()); topSpline = std::unique_ptr(new Spline(topSplineData, "Top")); addPropertySubOwner(*topSpline); rightSplineData = params.blackoutshape.right.value_or(std::vector()); rightSpline = std::unique_ptr(new Spline(rightSplineData, "Right")); addPropertySubOwner(*rightSpline); bottomSplineData = params.blackoutshape.bottom.value_or(std::vector()); bottomSpline = std::unique_ptr(new Spline(bottomSplineData, "Bottom")); addPropertySubOwner(*bottomSpline); leftSplineData = params.blackoutshape.left.value_or(std::vector()); leftSpline = std::unique_ptr(new Spline(leftSplineData, "Left")); addPropertySubOwner(*leftSpline); // Add additional controls to GUI enableCalibrationPattern.onChange([this]() { textureTypeHasChanged = true; }); addProperty(enableCalibrationPattern); enableCalibrationColor.onChange([this]() { textureTypeHasChanged = true; }); addProperty(enableCalibrationColor); copyToClipboardTrigger.onChange([this]() { copyToClipboard( cornerData, topSplineData, rightSplineData, bottomSplineData, leftSplineData ); }); addProperty(copyToClipboardTrigger); } bool ScreenSpaceInsetBlackout::BlackoutShape::checkHasChanged() { return textureTypeHasChanged || corners->dataHasChanged || topSpline->dataHasChanged || rightSpline->dataHasChanged || bottomSpline->dataHasChanged || leftSpline->dataHasChanged; } void ScreenSpaceInsetBlackout::BlackoutShape::resetHasChanged() { textureTypeHasChanged = false; corners->dataHasChanged = false; topSpline->dataHasChanged = false; rightSpline->dataHasChanged = false; bottomSpline->dataHasChanged = false; leftSpline->dataHasChanged = false; } void ScreenSpaceInsetBlackout::BlackoutShape::checkAndUpdateGUI() { std::array>,4> refs = { std::ref(topSpline), std::ref(rightSpline), std::ref(bottomSpline), std::ref(leftSpline) }; // Check which splines that needs to be updated and perform correct actions // Note: Need to remove propertySubOwner and add it again for GUI to work properly bool updatePropertyTree = false; for (std::unique_ptr& spline : refs) { if (spline->pointAdded) { std::string baseString = spline->base; std::vector& dataRef = spline->data; removePropertySubOwner(*spline); spline->addPoint(); spline.reset(); spline = std::unique_ptr(new Spline(dataRef, baseString)); spline->dataHasChanged = true; updatePropertyTree = true; } else if (spline->pointRemoved) { std::string baseString = spline->base; std::vector& dataRef = spline->data; removePropertySubOwner(*spline); spline->removePoint(); spline.reset(); spline = std::unique_ptr(new Spline(dataRef, baseString)); spline->dataHasChanged = true; updatePropertyTree = true; } } // Remove GUI elements so that we can add them in correct order again if (updatePropertyTree) { std::vector subs = propertySubOwners(); for (size_t i = 0; i < subs.size(); i++) { removePropertySubOwner(subs[i]); } addPropertySubOwner(*corners); for (std::unique_ptr& spline : refs) { addPropertySubOwner(*spline); } } } ScreenSpaceInsetBlackout::ScreenSpaceInsetBlackout(const ghoul::Dictionary& dictionary) : ScreenSpaceRenderable(dictionary) , _blackoutShape(dictionary) { const Parameters p = codegen::bake(dictionary); // Makes sure that User has specified 4 corners in the asset file checkCornerSpecification(p.blackoutshape.corners); addPropertySubOwner(_blackoutShape); // Handling of calibration texture std::optional optTexturePath = p.blackoutshape.calibrationTexturePath; if (optTexturePath.has_value()) { if (std::filesystem::is_regular_file(*optTexturePath)) { std::unique_ptr texture = ghoul::io::TextureReader::ref().loadTexture(*optTexturePath, 2); if (texture) { // Images don't need to start on 4-byte boundaries, for example if the // image is only RGB glPixelStorei(GL_UNPACK_ALIGNMENT, 1); if (texture->format() == ghoul::opengl::Texture::Format::Red) { texture->setSwizzleMask({ GL_RED, GL_RED, GL_RED, GL_ONE }); } texture->uploadTexture(); texture->setFilter(ghoul::opengl::Texture::FilterMode::LinearMipMap); texture->purgeFromRAM(); _calibrationTexture = std::move(texture); } } else { LWARNINGC( "ScreenSpaceInsetBlackout", std::format( "Could not find calibration texture '{}'.", *optTexturePath ) ); } } } void ScreenSpaceInsetBlackout::initializeGL() { ScreenSpaceRenderable::initializeGL(); // Setup vertex buffer glGenVertexArrays(1, &_vao); glGenBuffers(1, &_vbo); // Setup program object and shaders _fboProgram = BaseModule::ProgramObjectManager.request( "ScreenSpaceInsetBlackout", []() -> std::unique_ptr { return global::renderEngine->buildRenderProgram( "ScreenSpaceInsetBlackout", absPath("${MODULE_BASE}/shaders/screenspaceinsetblackout_vs.glsl"), absPath("${MODULE_BASE}/shaders/screenspaceinsetblackout_fs.glsl") ); } ); // Setup FBO & Texture (UHD resolution) glGenFramebuffers(1, &_fbo); glBindFramebuffer(GL_FRAMEBUFFER, _fbo); _blackoutTexture = std::make_unique( glm::uvec3(BlackoutTextureSize, 1), GL_TEXTURE_2D, ghoul::opengl::Texture::Format::RGBA ); _blackoutTexture->bind(); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, BlackoutTextureSize.x, BlackoutTextureSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *_blackoutTexture, 0 ); _blackoutTexture->purgeFromRAM(); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); _uniformCache.color = _fboProgram->uniformLocation("color"); // Generate vertex data and texture generateVertexArrayData(); generateTexture(); } void ScreenSpaceInsetBlackout::deinitializeGL() { _blackoutTexture = nullptr; _calibrationTexture = nullptr; glDeleteVertexArrays(1, &_vao); glDeleteBuffers(1, &_vbo); glDeleteFramebuffers(1, &_fbo); if (_fboProgram) { BaseModule::ProgramObjectManager.release( _fboProgram, [](ghoul::opengl::ProgramObject* p) { global::renderEngine->removeRenderProgram(p); } ); _fboProgram = nullptr; } ScreenSpaceRenderable::deinitializeGL(); } void ScreenSpaceInsetBlackout::update() { _blackoutShape.checkAndUpdateGUI(); if (_blackoutShape.checkHasChanged()) { generateVertexArrayData(); generateTexture(); _blackoutShape.resetHasChanged(); } } void ScreenSpaceInsetBlackout::generateVertexArrayData() { // Clear old data _vboData.clear(); // Set vertex buffer data based on calibration pattern or blackout shape if (_blackoutShape.enableCalibrationPattern && _calibrationTexture.get()) { _vboData = { glm::vec2(-1.f, 1.f), glm::vec2(1.f, 1.f), glm::vec2(1.f, -1.f), glm::vec2(-1.f, -1.f) }; return; } // Create vector with a temporary element as first element std::vector pointsTop = { glm::vec2(0.f) }; std::vector pointsRight = { glm::vec2(0.f) }; std::vector pointsBottom = { glm::vec2(0.f) }; std::vector pointsLeft = { glm::vec2(0.f) }; // Push first corner to each spline pointsTop.push_back(_blackoutShape.cornerData[0]); pointsRight.push_back(_blackoutShape.cornerData[1]); pointsBottom.push_back(_blackoutShape.cornerData[2]); pointsLeft.push_back(_blackoutShape.cornerData[3]); // Insert the existing control points for each spline pointsTop.insert( pointsTop.begin() + 2, _blackoutShape.topSplineData.begin(), _blackoutShape.topSplineData.end() ); pointsRight.insert( pointsRight.begin() + 2, _blackoutShape.rightSplineData.begin(), _blackoutShape.rightSplineData.end() ); pointsBottom.insert( pointsBottom.begin() + 2, _blackoutShape.bottomSplineData.begin(), _blackoutShape.bottomSplineData.end() ); pointsLeft.insert( pointsLeft.begin() + 2, _blackoutShape.leftSplineData.begin(), _blackoutShape.leftSplineData.end() ); // Push the last corner for each spline pointsTop.push_back(_blackoutShape.cornerData[1]); pointsRight.push_back(_blackoutShape.cornerData[2]); pointsBottom.push_back(_blackoutShape.cornerData[3]); pointsLeft.push_back(_blackoutShape.cornerData[0]); // Calculates the extra first and last point needed during spline sampling step std::pair tPad = calculatePadding(pointsTop); std::pair rPad = calculatePadding(pointsRight); std::pair bPad = calculatePadding(pointsBottom); std::pair lPad = calculatePadding(pointsLeft); // Adds the extra points pointsTop[0] = tPad.first; pointsRight[0] = rPad.first; pointsBottom[0] = bPad.first; pointsLeft[0] = lPad.first; pointsTop.push_back(tPad.second); pointsRight.push_back(rPad.second); pointsBottom.push_back(bPad.second); pointsLeft.push_back(lPad.second); // Samples the spline and returns all points along the spline curve std::vector splineTop = sampleSpline(pointsTop); std::vector splineRight = sampleSpline(pointsRight); std::vector splineBottom = sampleSpline(pointsBottom); std::vector splineLeft = sampleSpline(pointsLeft); // Translate points from range [0,1] to [-1,1] for X and Y offsetCoordinates(splineTop); offsetCoordinates(splineRight); offsetCoordinates(splineBottom); offsetCoordinates(splineLeft); // Incoming vertex data is: top -> right -> bottom -> left (clockwise) // VBO data needs to be counter-clockwise for correct winding // Also adds extra point first and last for Triangle Fan drawing _vboData.push_back(glm::vec2(0.f, 0.f)); _vboData.insert(_vboData.end(), splineTop.rbegin(), splineTop.rend()); _vboData.insert(_vboData.end(), splineLeft.rbegin(), splineLeft.rend()); _vboData.insert(_vboData.end(), splineBottom.rbegin(), splineBottom.rend()); _vboData.insert(_vboData.end(), splineRight.rbegin(), splineRight.rend()); _vboData.push_back(splineTop.back()); } void ScreenSpaceInsetBlackout::generateTexture() { // OpenGL stuff glBindVertexArray(_vao); glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBufferData( GL_ARRAY_BUFFER, _vboData.size() * sizeof(glm::vec2), _vboData.data(), GL_STATIC_DRAW ); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2), nullptr); glBindVertexArray(0); _fboProgram->activate(); _fboProgram->setUniform( "color", _blackoutShape.enableCalibrationColor ? glm::vec3(0.f, 1.f, 0.f) : glm::vec3(0.f) ); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glBindFramebuffer(GL_FRAMEBUFFER, _fbo); bindTexture(); // Clear current buffer glViewport(0, 0, BlackoutTextureSize.x, BlackoutTextureSize.y); glClearColor(0.f, 0.f, 0.f, 0.f); glClear(GL_COLOR_BUFFER_BIT); // Draw glBindVertexArray(_vao); glDrawArrays(GL_TRIANGLE_FAN, 0, static_cast(_vboData.size())); glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _fboProgram->deactivate(); } void ScreenSpaceInsetBlackout::bindTexture() { if (_blackoutShape.enableCalibrationPattern && _calibrationTexture.get()) { _calibrationTexture->bind(); _objectSize = _calibrationTexture->dimensions(); } else { _blackoutTexture->bind(); _objectSize = _blackoutTexture->dimensions(); } } } // namespace openspace