#include #include #include #include #include #include #include #include #include #include #include #include #include #include #if MLN_DRAWABLE_RENDERER #include #include #include #include #include #include #include #include #include #include #include #endif namespace mbgl { using namespace style; using namespace shaders; namespace { inline const HillshadeLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == HillshadeLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderHillshadeLayer::RenderHillshadeLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), unevaluated(impl_cast(baseImpl).paint.untransitioned()) { styleDependencies = unevaluated.getDependencies(); } RenderHillshadeLayer::~RenderHillshadeLayer() = default; std::array RenderHillshadeLayer::getLatRange(const UnwrappedTileID& id) { const LatLng latlng0 = LatLng(id); const LatLng latlng1 = LatLng(UnwrappedTileID(id.canonical.z, id.canonical.x, id.canonical.y + 1)); return {{static_cast(latlng0.latitude()), static_cast(latlng1.latitude())}}; } std::array RenderHillshadeLayer::getLight(const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; float azimuthal = util::deg2radf(evaluated.get()); if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) azimuthal = azimuthal - static_cast(parameters.state.getBearing()); return {{evaluated.get(), azimuthal}}; } void RenderHillshadeLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); } #if MLN_DRAWABLE_RENDERER void RenderHillshadeLayer::layerChanged(const TransitionParameters& parameters, const Immutable& impl, UniqueChangeRequestVec& changes) { RenderLayer::layerChanged(parameters, impl, changes); prepareLayerTweaker.reset(); } #endif void RenderHillshadeLayer::evaluate(const PropertyEvaluationParameters& parameters) { const auto previousProperties = staticImmutableCast(evaluatedProperties); auto properties = makeMutable( staticImmutableCast(baseImpl), unevaluated.evaluate(parameters, previousProperties->evaluated)); passes = (properties->evaluated.get() > 0) ? (RenderPass::Translucent | RenderPass::Pass3D) : RenderPass::None; properties->renderPasses = mbgl::underlying_type(passes); evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER if (layerTweaker) { layerTweaker->updateProperties(evaluatedProperties); } if (prepareLayerTweaker) { prepareLayerTweaker->updateProperties(evaluatedProperties); } #endif } bool RenderHillshadeLayer::hasTransition() const { return unevaluated.hasTransition(); } bool RenderHillshadeLayer::hasCrossfade() const { return false; } void RenderHillshadeLayer::prepare(const LayerPrepareParameters& params) { renderTiles = params.source->getRenderTiles(); maxzoom = params.source->getMaxZoom(); #if MLN_DRAWABLE_RENDERER updateRenderTileIDs(); #endif // MLN_DRAWABLE_RENDERER } #if MLN_LEGACY_RENDERER void RenderHillshadeLayer::render(PaintParameters& parameters) { assert(renderTiles); if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D) return; if (!parameters.shaders.getLegacyGroup().populate(hillshadeProgram)) return; if (!parameters.shaders.getLegacyGroup().populate(hillshadePrepareProgram)) return; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; auto draw = [&](const mat4& matrix, const auto& vertexBuffer, const auto& indexBuffer, const auto& segments, const UnwrappedTileID& id, const auto& textureBindings) { const HillshadeProgram::Binders paintAttributeData{evaluated, 0}; const auto allUniformValues = HillshadeProgram::computeAllUniformValues( HillshadeProgram::LayoutUniformValues{ uniforms::matrix::Value(matrix), uniforms::highlight::Value(evaluated.get()), uniforms::shadow::Value(evaluated.get()), uniforms::accent::Value(evaluated.get()), uniforms::light::Value(getLight(parameters)), uniforms::latrange::Value(getLatRange(id)), }, paintAttributeData, evaluated, static_cast(parameters.state.getZoom())); const auto allAttributeBindings = HillshadeProgram::computeAllAttributeBindings( vertexBuffer, paintAttributeData, evaluated); checkRenderability(parameters, HillshadeProgram::activeBindingCount(allAttributeBindings)); hillshadeProgram->draw(parameters.context, *parameters.renderPass, gfx::Triangles(), parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), indexBuffer, segments, allUniformValues, allAttributeBindings, textureBindings, getID()); }; mat4 mat; matrix::ortho(mat, 0, util::EXTENT, -util::EXTENT, 0, 0, 1); matrix::translate(mat, mat, 0, -util::EXTENT, 0); for (const RenderTile& tile : *renderTiles) { auto* bucket_ = tile.getBucket(*baseImpl); if (!bucket_) { continue; } auto& bucket = static_cast(*bucket_); if (!bucket.hasData()) { continue; } if (!bucket.isPrepared() && parameters.pass == RenderPass::Pass3D) { assert(bucket.dem); const uint16_t stride = bucket.getDEMData().stride; const uint16_t tilesize = bucket.getDEMData().dim; auto view = parameters.context.createOffscreenTexture({tilesize, tilesize}, gfx::TextureChannelDataType::UnsignedByte); auto renderPass = parameters.encoder->createRenderPass("hillshade prepare", {*view, Color{0.0f, 0.0f, 0.0f, 0.0f}, {}, {}}); const Properties<>::PossiblyEvaluated properties; const HillshadePrepareProgram::Binders paintAttributeData{properties, 0}; const auto allUniformValues = HillshadePrepareProgram::computeAllUniformValues( HillshadePrepareProgram::LayoutUniformValues{ uniforms::matrix::Value(mat), uniforms::dimension::Value({{stride, stride}}), uniforms::zoom::Value(static_cast(tile.id.canonical.z)), uniforms::maxzoom::Value(static_cast(maxzoom)), uniforms::unpack::Value(bucket.getDEMData().getUnpackVector()), }, paintAttributeData, properties, static_cast(parameters.state.getZoom())); const auto allAttributeBindings = HillshadePrepareProgram::computeAllAttributeBindings( *parameters.staticData.rasterVertexBuffer, paintAttributeData, properties); checkRenderability(parameters, HillshadePrepareProgram::activeBindingCount(allAttributeBindings)); // Copy over the segments so that we can create our own DrawScopes // that get destroyed after this draw call. auto segments = RenderStaticData::rasterSegments(); hillshadePrepareProgram->draw(parameters.context, *renderPass, gfx::Triangles(), parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), *parameters.staticData.quadTriangleIndexBuffer, segments, allUniformValues, allAttributeBindings, HillshadePrepareProgram::TextureBindings{ textures::image::Value{bucket.dem->getResource()}, }, "prepare"); bucket.texture = std::move(view->getTexture()); bucket.setPrepared(true); } else if (parameters.pass == RenderPass::Translucent) { assert(bucket.texture); if (bucket.vertexBuffer && bucket.indexBuffer) { // Draw only the parts of the tile that aren't drawn by another tile in the layer. draw(parameters.matrixForTile(tile.id, true), *bucket.vertexBuffer, *bucket.indexBuffer, bucket.segments, tile.id, HillshadeProgram::TextureBindings{ textures::image::Value{bucket.texture->getResource(), gfx::TextureFilterType::Linear}, }); } else { // Draw the full tile. if (bucket.segments.empty()) { // Copy over the segments so that we can create our own DrawScopes. bucket.segments = RenderStaticData::rasterSegments(); } draw(parameters.matrixForTile(tile.id, true), *parameters.staticData.rasterVertexBuffer, *parameters.staticData.quadTriangleIndexBuffer, bucket.segments, tile.id, HillshadeProgram::TextureBindings{ textures::image::Value{bucket.texture->getResource(), gfx::TextureFilterType::Linear}, }); } } } } #endif // MLN_LEGACY_RENDERER #if MLN_DRAWABLE_RENDERER namespace { void activateRenderTarget(const RenderTargetPtr& renderTarget_, bool activate, UniqueChangeRequestVec& changes) { if (renderTarget_) { if (activate) { // The RenderTree has determined this render target should be included in the renderable set for a frame changes.emplace_back(std::make_unique(renderTarget_)); } else { // The RenderTree is informing us we should not render anything changes.emplace_back(std::make_unique(renderTarget_)); } } } } // namespace void RenderHillshadeLayer::markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) { RenderLayer::markLayerRenderable(willRender, changes); removeRenderTargets(changes); } void RenderHillshadeLayer::layerRemoved(UniqueChangeRequestVec& changes) { RenderLayer::layerRemoved(changes); removeRenderTargets(changes); } void RenderHillshadeLayer::addRenderTarget(const RenderTargetPtr& renderTarget, UniqueChangeRequestVec& changes) { activateRenderTarget(renderTarget, true, changes); activatedRenderTargets.emplace_back(renderTarget); } void RenderHillshadeLayer::removeRenderTargets(UniqueChangeRequestVec& changes) { for (const auto& renderTarget : activatedRenderTargets) { activateRenderTarget(renderTarget, false, changes); } activatedRenderTargets.clear(); } static const std::string HillshadePrepareShaderGroupName = "HillshadePrepareShader"; static const std::string HillshadeShaderGroupName = "HillshadeShader"; void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, [[maybe_unused]] const TransformState& state, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, UniqueChangeRequestVec& changes) { if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; } // Set up a layer group if (!layerGroup) { if (auto layerGroup_ = context.createTileLayerGroup(layerIndex, /*initialCapacity=*/64, getID())) { setLayerGroup(std::move(layerGroup_), changes); } else { return; } } auto* tileLayerGroup = static_cast(layerGroup.get()); if (!layerTweaker) { layerTweaker = std::make_shared(getID(), evaluatedProperties); layerGroup->addLayerTweaker(layerTweaker); } if (!hillshadePrepareShader) { hillshadePrepareShader = context.getGenericShader(shaders, HillshadePrepareShaderGroupName); } if (!hillshadeShader) { hillshadeShader = context.getGenericShader(shaders, HillshadeShaderGroupName); } if (!hillshadePrepareShader || !hillshadeShader) { removeAllDrawables(); return; } auto renderPass = RenderPass::Translucent; if (!(mbgl::underlying_type(renderPass) & evaluatedProperties->renderPasses)) { return; } stats.drawablesRemoved += tileLayerGroup->removeDrawablesIf( [&](gfx::Drawable& drawable) { return drawable.getTileID() && !hasRenderTile(*drawable.getTileID()); }); if (!staticDataSharedVertices) { staticDataSharedVertices = std::make_shared(RenderStaticData::rasterVertices()); } const auto staticDataIndices = RenderStaticData::quadTriangleIndices(); const auto staticDataSegments = RenderStaticData::rasterSegments(); std::unique_ptr hillshadeBuilder; std::unique_ptr hillshadePrepareBuilder; gfx::VertexAttributeArrayPtr hillshadePrepareVertexAttrs; const auto getPrepareVertexAttributes = [&] { if (!hillshadePrepareVertexAttrs) { hillshadePrepareVertexAttrs = context.createVertexAttributeArray(); if (const auto& attr = hillshadePrepareVertexAttrs->set(idHillshadePosVertexAttribute)) { attr->setSharedRawData(staticDataSharedVertices, offsetof(HillshadeLayoutVertex, a1), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } if (const auto& attr = hillshadePrepareVertexAttrs->set(idHillshadeTexturePosVertexAttribute)) { attr->setSharedRawData(staticDataSharedVertices, offsetof(HillshadeLayoutVertex, a2), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } } return hillshadePrepareVertexAttrs; }; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); auto* bucket_ = tile.getBucket(*baseImpl); if (!bucket_ || !bucket_->hasData()) { removeTile(renderPass, tileID); continue; } auto& bucket = static_cast(*bucket_); const auto prevBucketID = getRenderTileBucketID(tileID); if (prevBucketID != util::SimpleIdentity::Empty && prevBucketID != bucket.getID()) { // This tile was previously set up from a different bucket, drop and re-create any drawables for it. removeTile(renderPass, tileID); } setRenderTileBucketID(tileID, bucket.getID()); if (!bucket.renderTargetPrepared) { // Set up tile render target const uint16_t tilesize = bucket.getDEMData().dim; auto renderTarget = context.createRenderTarget({tilesize, tilesize}, gfx::TextureChannelDataType::UnsignedByte); if (!renderTarget) { continue; } bucket.renderTarget = renderTarget; bucket.renderTargetPrepared = true; addRenderTarget(renderTarget, changes); auto singleTileLayerGroup = context.createTileLayerGroup(0, /*initialCapacity=*/1, getID()); if (!singleTileLayerGroup) { return; } renderTarget->addLayerGroup(singleTileLayerGroup, /*replace=*/true); if (!prepareLayerTweaker) { prepareLayerTweaker = std::make_shared(getID(), evaluatedProperties); } singleTileLayerGroup->addLayerTweaker(prepareLayerTweaker); hillshadePrepareBuilder = context.createDrawableBuilder("hillshadePrepare"); hillshadePrepareBuilder->setShader(hillshadePrepareShader); hillshadePrepareBuilder->setDepthType(gfx::DepthMaskType::ReadOnly); hillshadePrepareBuilder->setColorMode(gfx::ColorMode::unblended()); hillshadePrepareBuilder->setCullFaceMode(gfx::CullFaceMode::disabled()); hillshadePrepareBuilder->setRenderPass(renderPass); hillshadePrepareBuilder->setVertexAttributes(getPrepareVertexAttributes()); hillshadePrepareBuilder->setRawVertices( {}, staticDataSharedVertices->elements(), gfx::AttributeDataType::Short2); hillshadePrepareBuilder->setSegments( gfx::Triangles(), staticDataIndices.vector(), staticDataSegments.data(), staticDataSegments.size()); std::shared_ptr texture = context.createTexture2D(); texture->setImage(bucket.getDEMData().getImagePtr()); texture->setSamplerConfiguration( {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); hillshadePrepareBuilder->setTexture(texture, idHillshadeImageTexture); hillshadePrepareBuilder->flush(context); for (auto& drawable : hillshadePrepareBuilder->clearDrawables()) { drawable->setTileID(tileID); drawable->setLayerTweaker(prepareLayerTweaker); drawable->setData(std::make_unique( bucket.getDEMData().stride, bucket.getDEMData().encoding, maxzoom)); singleTileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } } // Set up tile drawable auto vertices = staticDataSharedVertices; auto* indices = &staticDataIndices; auto* segments = &staticDataSegments; if (!bucket.vertices.empty() && !bucket.indices.empty() && !bucket.segments.empty()) { vertices = bucket.sharedVertices; indices = &bucket.indices; segments = &bucket.segments; } hillshadeBuilder = context.createDrawableBuilder("hillshade"); gfx::VertexAttributeArrayPtr hillshadeVertexAttrs; auto buildVertexAttributes = [&] { if (!hillshadeVertexAttrs) { hillshadeVertexAttrs = context.createVertexAttributeArray(); if (const auto& attr = hillshadeVertexAttrs->set(idHillshadePosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(HillshadeLayoutVertex, a1), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } if (const auto& attr = hillshadeVertexAttrs->set(idHillshadeTexturePosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(HillshadeLayoutVertex, a2), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } } return hillshadeVertexAttrs; }; const auto updateExisting = [&](gfx::Drawable& drawable) { // Only current drawables are updated, ones produced for // a previous style retain the attribute values for that style. if (drawable.getLayerTweaker() != layerTweaker) { return false; } drawable.setVertexAttributes(buildVertexAttributes()); drawable.setVertices({}, vertices->elements(), gfx::AttributeDataType::Short2); // Rebuild segments, since they're not shared. std::vector> drawSegments; drawSegments.reserve(segments->size()); for (const auto& seg : *segments) { auto segCopy = SegmentBase{ // no copy constructor seg.vertexOffset, seg.indexOffset, seg.vertexLength, seg.indexLength, seg.sortKey, }; drawSegments.emplace_back(hillshadeBuilder->createSegment(gfx::Triangles(), std::move(segCopy))); } drawable.setIndexData(indices->vector(), std::move(drawSegments)); drawable.setTexture(bucket.renderTarget->getTexture(), idHillshadeImageTexture); return true; }; if (updateTile(renderPass, tileID, std::move(updateExisting))) { continue; } hillshadeBuilder->setShader(hillshadeShader); hillshadeBuilder->setDepthType(gfx::DepthMaskType::ReadOnly); hillshadeBuilder->setColorMode(gfx::ColorMode::alphaBlended()); hillshadeBuilder->setCullFaceMode(gfx::CullFaceMode::disabled()); hillshadeBuilder->setRenderPass(renderPass); hillshadeBuilder->setVertexAttributes(buildVertexAttributes()); hillshadeBuilder->setRawVertices({}, vertices->elements(), gfx::AttributeDataType::Short2); hillshadeBuilder->setSegments(gfx::Triangles(), indices->vector(), segments->data(), segments->size()); hillshadeBuilder->setTexture(bucket.renderTarget->getTexture(), idHillshadeImageTexture); hillshadeBuilder->flush(context); for (auto& drawable : hillshadeBuilder->clearDrawables()) { drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } } } #endif // MLN_DRAWABLE_RENDERER } // namespace mbgl