#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 #endif namespace mbgl { using namespace style; namespace { inline const HeatmapLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == HeatmapLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderHeatmapLayer::RenderHeatmapLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), unevaluated(impl_cast(baseImpl).paint.untransitioned()) { colorRamp = std::make_shared(Size(256, 1)); } RenderHeatmapLayer::~RenderHeatmapLayer() = default; void RenderHeatmapLayer::prepare(const LayerPrepareParameters& parameters) { RenderLayer::prepare(parameters); #if MLN_DRAWABLE_RENDERER updateRenderTileIDs(); #endif // MLN_DRAWABLE_RENDERER } void RenderHeatmapLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); updateColorRamp(); } void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) { auto properties = makeMutable(staticImmutableCast(baseImpl), unevaluated.evaluate(parameters)); 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 (renderTarget) { renderTarget->getLayerGroup(0)->setLayerTweaker(std::make_shared(evaluatedProperties)); } if (layerGroup) { layerGroup->setLayerTweaker(std::make_shared(evaluatedProperties)); } #endif } bool RenderHeatmapLayer::hasTransition() const { return unevaluated.hasTransition(); } bool RenderHeatmapLayer::hasCrossfade() const { return false; } void RenderHeatmapLayer::upload(gfx::UploadPass& uploadPass) { if (!colorRampTexture) { colorRampTexture = uploadPass.createTexture(*colorRamp, gfx::TextureChannelDataType::UnsignedByte); } } #if MLN_LEGACY_RENDERER void RenderHeatmapLayer::render(PaintParameters& parameters) { assert(renderTiles); if (parameters.pass == RenderPass::Opaque) { return; } if (!parameters.shaders.getLegacyGroup().populate(heatmapProgram)) return; if (!parameters.shaders.getLegacyGroup().populate(heatmapTextureProgram)) return; if (parameters.pass == RenderPass::Pass3D) { const auto& viewportSize = parameters.staticData.backendSize; const auto size = Size{viewportSize.width / 4, viewportSize.height / 4}; assert(colorRampTexture); if (!renderTexture || renderTexture->getSize() != size) { renderTexture = parameters.context.createOffscreenTexture(size, gfx::TextureChannelDataType::HalfFloat); } auto renderPass = parameters.encoder->createRenderPass("heatmap texture", {*renderTexture, Color{0.0f, 0.0f, 0.0f, 1.0f}, {}, {}}); for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = getRenderDataForPass(tile, parameters.pass); if (!renderData) { continue; } auto& bucket = static_cast(*renderData->bucket); const auto& evaluated = getEvaluated(renderData->layerProperties); const auto extrudeScale = tile.id.pixelsToTileUnits(1.0f, static_cast(parameters.state.getZoom())); const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); const auto allUniformValues = HeatmapProgram::computeAllUniformValues( HeatmapProgram::LayoutUniformValues{ uniforms::intensity::Value(evaluated.get()), uniforms::matrix::Value(tile.matrix), uniforms::heatmap::extrude_scale::Value(extrudeScale)}, paintPropertyBinders, evaluated, static_cast(parameters.state.getZoom())); const auto allAttributeBindings = HeatmapProgram::computeAllAttributeBindings( *bucket.vertexBuffer, paintPropertyBinders, evaluated); checkRenderability(parameters, HeatmapProgram::activeBindingCount(allAttributeBindings)); heatmapProgram->draw(parameters.context, *renderPass, gfx::Triangles(), gfx::DepthMode::disabled(), gfx::StencilMode::disabled(), gfx::ColorMode::additive(), gfx::CullFaceMode::disabled(), *bucket.indexBuffer, bucket.segments, allUniformValues, allAttributeBindings, HeatmapProgram::TextureBindings{}, getID()); } } else if (parameters.pass == RenderPass::Translucent) { const auto& size = parameters.staticData.backendSize; mat4 viewportMat; matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); const Properties<>::PossiblyEvaluated properties; const HeatmapTextureProgram::Binders paintAttributeData{properties, 0}; const auto allUniformValues = HeatmapTextureProgram::computeAllUniformValues( HeatmapTextureProgram::LayoutUniformValues{ uniforms::matrix::Value(viewportMat), uniforms::world::Value(size), uniforms::opacity::Value( getEvaluated(evaluatedProperties).get())}, paintAttributeData, properties, static_cast(parameters.state.getZoom())); const auto allAttributeBindings = HeatmapTextureProgram::computeAllAttributeBindings( *parameters.staticData.heatmapTextureVertexBuffer, paintAttributeData, properties); checkRenderability(parameters, HeatmapTextureProgram::activeBindingCount(allAttributeBindings)); if (segments.empty()) { // Copy over the segments so that we can create our own DrawScopes. segments = RenderStaticData::heatmapTextureSegments(); } heatmapTextureProgram->draw( parameters.context, *parameters.renderPass, gfx::Triangles(), gfx::DepthMode::disabled(), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), *parameters.staticData.quadTriangleIndexBuffer, segments, allUniformValues, allAttributeBindings, HeatmapTextureProgram::TextureBindings{ textures::image::Value{renderTexture->getTexture().getResource(), gfx::TextureFilterType::Linear}, textures::color_ramp::Value{colorRampTexture->getResource(), gfx::TextureFilterType::Linear}, }, getID()); } } #endif // MLN_LEGACY_RENDERER void RenderHeatmapLayer::updateColorRamp() { auto colorValue = unevaluated.get().getValue(); if (colorValue.isUndefined()) { colorValue = HeatmapLayer::getDefaultHeatmapColor(); } const auto length = (*colorRamp).bytes(); for (uint32_t i = 0; i < length; i += 4) { const auto color = colorValue.evaluate(static_cast(i) / length); (*colorRamp).data[i + 0] = static_cast(std::floor(color.r * 255.f)); (*colorRamp).data[i + 1] = static_cast(std::floor(color.g * 255.f)); (*colorRamp).data[i + 2] = static_cast(std::floor(color.b * 255.f)); (*colorRamp).data[i + 3] = static_cast(std::floor(color.a * 255.f)); } if (colorRampTexture) { colorRampTexture = std::nullopt; } } bool RenderHeatmapLayer::queryIntersectsFeature(const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float zoom, const TransformState&, const float pixelsToTileUnits, const mat4&, const FeatureState&) const { (void)queryGeometry; (void)feature; (void)zoom; (void)pixelsToTileUnits; return false; } #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 RenderHeatmapLayer::markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) { RenderLayer::markLayerRenderable(willRender, changes); activateRenderTarget(renderTarget, willRender, changes); } void RenderHeatmapLayer::removeTile(RenderPass renderPass, const OverscaledTileID& tileID) { auto* tileLayerGroup = static_cast(renderTarget->getLayerGroup(0).get()); stats.drawablesRemoved += tileLayerGroup->removeDrawables(renderPass, tileID).size(); } void RenderHeatmapLayer::removeAllDrawables() { RenderLayer::removeAllDrawables(); if (renderTarget) { stats.drawablesRemoved += renderTarget->getLayerGroup(0)->getDrawableCount(); renderTarget->getLayerGroup(0)->clearDrawables(); } } namespace { struct alignas(16) HeatmapInterpolateUBO { float weight_t; float radius_t; std::array padding; }; static_assert(sizeof(HeatmapInterpolateUBO) % 16 == 0); constexpr auto HeatmapShaderGroupName = "HeatmapShader"; constexpr auto HeatmapTextureShaderGroupName = "HeatmapTextureShader"; constexpr auto HeatmapInterpolateUBOName = "HeatmapInterpolateUBO"; constexpr auto VertexAttribName = "a_pos"; } // namespace void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& state, [[maybe_unused]] const RenderTree& renderTree, UniqueChangeRequestVec& changes) { std::unique_lock guard(mutex); if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; } const auto& viewportSize = state.getSize(); const auto size = Size{viewportSize.width / 2, viewportSize.height / 2}; // Set up a render target if (!renderTarget) { renderTarget = context.createRenderTarget(size, gfx::TextureChannelDataType::HalfFloat); if (!renderTarget) { return; } activateRenderTarget(renderTarget, isRenderable, changes); // Set up tile layer group auto tileLayerGroup = context.createTileLayerGroup(0, /*initialCapacity=*/64, getID()); if (!tileLayerGroup) { return; } tileLayerGroup->setLayerTweaker(std::make_shared(evaluatedProperties)); renderTarget->addLayerGroup(tileLayerGroup, /*replace=*/true); } if (renderTarget->getTexture()->getSize() != size) { renderTarget->getTexture()->setSize(size); } auto* tileLayerGroup = static_cast(renderTarget->getLayerGroup(0).get()); if (!heatmapShaderGroup) { heatmapShaderGroup = shaders.getShaderGroup(HeatmapShaderGroupName); } if (!heatmapShaderGroup) { removeAllDrawables(); return; } std::unique_ptr heatmapBuilder; constexpr auto renderPass = RenderPass::Translucent; if (!(mbgl::underlying_type(renderPass) & evaluatedProperties->renderPasses)) { return; } stats.drawablesRemoved += tileLayerGroup->observeDrawablesRemove( [&](gfx::Drawable& drawable) { return (!drawable.getTileID() || hasRenderTile(*drawable.getTileID())); }); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); const LayerRenderData* renderData = getRenderDataForPass(tile, renderPass); if (!renderData) { removeTile(renderPass, tileID); continue; } const auto& bucket = static_cast(*renderData->bucket); const auto vertexCount = bucket.vertices.elements(); const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); 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()); const float zoom = static_cast(state.getZoom()); const HeatmapInterpolateUBO interpolateUBO = { /* .weight_t = */ std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), /* .radius_t = */ std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), /* .padding = */ {0}}; tileLayerGroup->observeDrawables(renderPass, tileID, [&](gfx::Drawable& drawable) { drawable.mutableUniformBuffers().createOrUpdate(HeatmapInterpolateUBOName, &interpolateUBO, context); }); if (tileLayerGroup->getDrawableCount(renderPass, tileID) > 0) { continue; } gfx::VertexAttributeArray heatmapVertexAttrs; const auto propertiesAsUniforms = heatmapVertexAttrs.readDataDrivenPaintProperties(paintPropertyBinders, evaluated); const auto heatmapShader = heatmapShaderGroup->getOrCreateShader(context, propertiesAsUniforms); if (!heatmapShader) { continue; } if (const auto& attr = heatmapVertexAttrs.add(VertexAttribName)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(HeatmapLayoutVertex, a1), /*vertexOffset=*/0, sizeof(HeatmapLayoutVertex), gfx::AttributeDataType::Short2); } heatmapBuilder = context.createDrawableBuilder("heatmap"); heatmapBuilder->setShader(std::static_pointer_cast(heatmapShader)); heatmapBuilder->setDepthType((renderPass == RenderPass::Opaque) ? gfx::DepthMaskType::ReadWrite : gfx::DepthMaskType::ReadOnly); heatmapBuilder->setColorMode(gfx::ColorMode::additive()); heatmapBuilder->setCullFaceMode(gfx::CullFaceMode::disabled()); heatmapBuilder->setRenderPass(renderPass); heatmapBuilder->setVertexAttributes(std::move(heatmapVertexAttrs)); heatmapBuilder->setRawVertices({}, vertexCount, gfx::AttributeDataType::Short2); heatmapBuilder->setSegments( gfx::Triangles(), bucket.sharedTriangles, bucket.segments.data(), bucket.segments.size()); heatmapBuilder->flush(); for (auto& drawable : heatmapBuilder->clearDrawables()) { drawable->setTileID(tileID); drawable->mutableUniformBuffers().createOrUpdate(HeatmapInterpolateUBOName, &interpolateUBO, context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } } // Set up texture layer group if (!layerGroup) { auto layerGroup_ = context.createLayerGroup(layerIndex, /*initialCapacity=*/1, getID()); if (!layerGroup_) { return; } layerGroup_->setLayerTweaker(std::make_shared(evaluatedProperties)); setLayerGroup(std::move(layerGroup_), changes); } if (!heatmapTextureShader) { heatmapTextureShader = context.getGenericShader(shaders, HeatmapTextureShaderGroupName); } if (!heatmapTextureShader) { removeAllDrawables(); return; } auto* textureLayerGroup = static_cast(layerGroup.get()); textureLayerGroup->clearDrawables(); std::unique_ptr heatmapTextureBuilder; if (!sharedTextureVertices) { sharedTextureVertices = std::make_shared(RenderStaticData::heatmapTextureVertices()); } const auto textureVertexCount = sharedTextureVertices->elements(); gfx::VertexAttributeArray textureVertexAttrs; if (const auto& attr = textureVertexAttrs.add(VertexAttribName)) { attr->setSharedRawData(sharedTextureVertices, offsetof(HeatmapLayoutVertex, a1), /*vertexOffset=*/0, sizeof(HeatmapLayoutVertex), gfx::AttributeDataType::Short2); } heatmapTextureBuilder = context.createDrawableBuilder("heatmapTexture"); heatmapTextureBuilder->setShader(heatmapTextureShader); heatmapTextureBuilder->setDepthType((renderPass == RenderPass::Opaque) ? gfx::DepthMaskType::ReadWrite : gfx::DepthMaskType::ReadOnly); heatmapTextureBuilder->setColorMode(gfx::ColorMode::alphaBlended()); heatmapTextureBuilder->setCullFaceMode(gfx::CullFaceMode::disabled()); heatmapTextureBuilder->setRenderPass(renderPass); heatmapTextureBuilder->setVertexAttributes(std::move(textureVertexAttrs)); heatmapTextureBuilder->setRawVertices({}, textureVertexCount, gfx::AttributeDataType::Short2); if (segments.empty()) { segments = RenderStaticData::heatmapTextureSegments(); } heatmapTextureBuilder->setSegments( gfx::Triangles(), RenderStaticData::quadTriangleIndices().vector(), segments.data(), segments.size()); auto imageLocation = heatmapTextureShader->getSamplerLocation("u_image"); if (imageLocation.has_value()) { heatmapTextureBuilder->setTexture(renderTarget->getTexture(), imageLocation.value()); } auto colorRampLocation = heatmapTextureShader->getSamplerLocation("u_color_ramp"); if (colorRampLocation.has_value()) { std::shared_ptr texture = context.createTexture2D(); texture->setImage(colorRamp); texture->setSamplerConfiguration( {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); heatmapTextureBuilder->setTexture(std::move(texture), colorRampLocation.value()); } heatmapTextureBuilder->flush(); for (auto& drawable : heatmapTextureBuilder->clearDrawables()) { textureLayerGroup->addDrawable(std::move(drawable)); ++stats.drawablesAdded; } } #endif // MLN_DRAWABLE_RENDERER } // namespace mbgl