#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(NDEBUG) #include #endif namespace mbgl { namespace vulkan { struct IndexBuffer : public gfx::IndexBufferBase { IndexBuffer(std::unique_ptr&& buffer_) : buffer(std::move(buffer_)) {} ~IndexBuffer() override = default; std::unique_ptr buffer; }; #if !defined(NDEBUG) static std::string debugLabel(const gfx::Drawable& drawable) { std::ostringstream oss; oss << drawable.getID().id() << "/" << drawable.getName() << "/tile="; if (const auto& tileID = drawable.getTileID()) { oss << util::toString(*tileID); } else { oss << "(none)"; } return oss.str(); } #endif // !defined(NDEBUG) Drawable::Drawable(std::string name_) : gfx::Drawable(std::move(name_)), impl(std::make_unique()) {} Drawable::~Drawable() {} void Drawable::setEnableColor(bool value) { gfx::Drawable::setEnableColor(value); if (value) { impl->pipelineInfo.colorMask = vk::FlagTraits::allFlags; } else { impl->pipelineInfo.colorMask = vk::ColorComponentFlags(); } } void Drawable::setColorMode(const gfx::ColorMode& value) { gfx::Drawable::setColorMode(value); impl->pipelineInfo.setColorBlend(value); } void Drawable::setEnableDepth(bool value) { gfx::Drawable::setEnableDepth(value); impl->pipelineInfo.depthTest = value; } void Drawable::setDepthType(gfx::DepthMaskType value) { gfx::Drawable::setDepthType(value); impl->pipelineInfo.setDepthWrite(value); } void Drawable::setDepthModeFor3D(const gfx::DepthMode& value) { impl->depthFor3D = value; } void Drawable::setStencilModeFor3D(const gfx::StencilMode& value) { impl->stencilFor3D = value; } void Drawable::setLineWidth(int32_t value) { gfx::Drawable::setLineWidth(value); impl->pipelineInfo.wideLines = value != 1; impl->pipelineInfo.dynamicValues.lineWidth = static_cast(value); } void Drawable::setCullFaceMode(const gfx::CullFaceMode& mode) { gfx::Drawable::setCullFaceMode(mode); impl->pipelineInfo.setCullMode(mode); } void Drawable::updateVertexAttributes(gfx::VertexAttributeArrayPtr vertices, std::size_t vertexCount, gfx::DrawMode mode, gfx::IndexVectorBasePtr indexes, const SegmentBase* segments, std::size_t segmentCount) { gfx::Drawable::setVertexAttributes(std::move(vertices)); impl->vertexCount = vertexCount; std::vector> drawSegs; drawSegs.reserve(segmentCount); for (std::size_t i = 0; i < segmentCount; ++i) { const auto& seg = segments[i]; auto segCopy = SegmentBase{ // no copy constructor seg.vertexOffset, seg.indexOffset, seg.vertexLength, seg.indexLength, seg.sortKey, }; drawSegs.push_back(std::make_unique(mode, std::move(segCopy))); } impl->indexes = std::move(indexes); impl->segments = std::move(drawSegs); } void Drawable::upload(gfx::UploadPass& uploadPass_) { MLN_TRACE_FUNC(); if (isCustom) { return; } if (!shader) { Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName()); assert(false); return; } auto& uploadPass = static_cast(uploadPass_); constexpr auto usage = gfx::BufferUsageType::StaticDraw; // We need either raw index data or a buffer already created from them. // We can have a buffer and no indexes, but only if it's not marked dirty. if (!impl->indexes || (impl->indexes->empty() && (!impl->indexes->getBuffer() || impl->indexes->getDirty()))) { assert(!"Missing index data"); return; } if (!impl->indexes->getBuffer() || impl->indexes->getDirty()) { // Create a buffer for the index data. We don't update any // existing buffer because it may still be in use by the previous frame. auto indexBufferResource{uploadPass.createIndexBufferResource( impl->indexes->data(), impl->indexes->bytes(), usage, /*persistent=*/false)}; auto indexBuffer = std::make_unique(impl->indexes->elements(), std::move(indexBufferResource)); auto buffer = std::make_unique(std::move(indexBuffer)); impl->indexes->setBuffer(std::move(buffer)); impl->indexes->setDirty(false); } const bool buildAttribs = !vertexAttributes || !attributeUpdateTime || vertexAttributes->isModifiedAfter(*attributeUpdateTime) || impl->pipelineInfo.inputAttributes.empty(); if (buildAttribs) { #if !defined(NDEBUG) const auto debugGroup = uploadPass.createDebugGroup(debugLabel(*this)); #endif if (!vertexAttributes) { vertexAttributes = std::make_shared(); } // Apply drawable values to shader defaults std::vector> vertexBuffers; auto attributeBindings_ = uploadPass.buildAttributeBindings(impl->vertexCount, impl->vertexType, /*vertexAttributeIndex=*/-1, /*vertexData=*/{}, shader->getVertexAttributes(), *vertexAttributes, usage, attributeUpdateTime, vertexBuffers); vertexAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); if (impl->attributeBindings != attributeBindings_) { impl->attributeBindings = std::move(attributeBindings_); } } // build instance buffer const bool buildInstanceBuffer = (instanceAttributes && (!attributeUpdateTime || instanceAttributes->isModifiedAfter(*attributeUpdateTime))); if (buildInstanceBuffer) { // Build instance attribute buffers std::vector> instanceBuffers; auto instanceBindings_ = uploadPass.buildAttributeBindings(instanceAttributes->getMaxCount(), /*vertexType*/ gfx::AttributeDataType::Byte, /*vertexAttributeIndex=*/-1, /*vertexData=*/{}, shader->getInstanceAttributes(), *instanceAttributes, usage, attributeUpdateTime, instanceBuffers); // clear dirty flag instanceAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); if (impl->instanceBindings != instanceBindings_) { impl->instanceBindings = std::move(instanceBindings_); } } if (buildAttribs || buildInstanceBuffer) { buildVulkanInputBindings(); } const bool texturesNeedUpload = std::any_of( textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); }); if (texturesNeedUpload) { uploadTextures(uploadPass); } attributeUpdateTime = util::MonotonicTimer::now(); } void Drawable::draw(PaintParameters& parameters) const { MLN_TRACE_FUNC(); if (isCustom) { return; } auto& context = static_cast(parameters.context); auto& dispatcher = context.getBackend().getDispatcher(); auto& renderPass_ = static_cast(*parameters.renderPass); auto& encoder = renderPass_.getEncoder(); auto& commandBuffer = encoder.getCommandBuffer(); auto& shaderImpl = static_cast(*shader); if (!bindAttributes(encoder)) return; if (!bindDescriptors(encoder)) return; commandBuffer->pushConstants( context.getGeneralPipelineLayout().get(), vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, sizeof(uboIndex), &uboIndex, dispatcher); if (enableDepth) { if (impl->depthFor3D.has_value()) { impl->pipelineInfo.setDepthMode(impl->depthFor3D.value()); } else if (is3D) { impl->pipelineInfo.setDepthMode(parameters.depthModeFor3D()); } else { const auto& depthMode = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()); impl->pipelineInfo.setDepthMode(depthMode); } } else { impl->pipelineInfo.setDepthMode(gfx::DepthMode::disabled()); } if (enableStencil) { if (impl->stencilFor3D.has_value()) { impl->pipelineInfo.setStencilMode(impl->stencilFor3D.value()); } else if (is3D) { impl->pipelineInfo.setStencilMode(parameters.stencilModeFor3D()); } else { const auto& stencilMode = parameters.stencilModeForClipping(tileID->toUnwrapped()); impl->pipelineInfo.setStencilMode(stencilMode); } } else { impl->pipelineInfo.setStencilMode(gfx::StencilMode::disabled()); } impl->pipelineInfo.setRenderable(renderPass_.getDescriptor().renderable); const auto instances = instanceAttributes ? instanceAttributes->getMaxCount() : 1; for (const auto& seg : impl->segments) { const auto& segment = seg->getSegment(); // update pipeline info with per segment modifiers impl->pipelineInfo.setDrawMode(seg->getMode()); impl->pipelineInfo.setScissorRect(parameters.scissorRect); impl->pipelineInfo.setDynamicValues(context.getBackend(), commandBuffer); const auto& pipeline = shaderImpl.getPipeline(impl->pipelineInfo); commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get(), dispatcher); if (segment.indexLength) { commandBuffer->drawIndexed(static_cast(segment.indexLength), static_cast(instances), static_cast(segment.indexOffset), static_cast(segment.vertexOffset), 0, dispatcher); } else { commandBuffer->draw(static_cast(segment.vertexLength), static_cast(instances), static_cast(segment.vertexOffset), 0, dispatcher); } context.renderingStats().numDrawCalls++; } } void Drawable::setIndexData(gfx::IndexVectorBasePtr indexes, std::vector segments) { assert(indexes && indexes->elements()); impl->indexes = std::move(indexes); impl->segments = std::move(segments); } void Drawable::setVertices(std::vector&& data, std::size_t count, gfx::AttributeDataType type_) { impl->vertexCount = count; impl->vertexType = type_; if (count && type_ != gfx::AttributeDataType::Invalid && !data.empty()) { if (!vertexAttributes) { vertexAttributes = std::make_shared(); } if (auto& attrib = vertexAttributes->set(impl->vertexAttrId, /*index=*/-1, type_)) { attrib->setRawData(std::move(data)); attrib->setStride(VertexAttribute::getStrideOf(type_)); } else { using namespace std::string_literals; Log::Warning(Event::General, "Vertex attribute type mismatch: "s + name + " / " + util::toString(impl->vertexAttrId)); assert(false); } } } const gfx::UniformBufferArray& Drawable::getUniformBuffers() const { return impl->uniformBuffers; } gfx::UniformBufferArray& Drawable::mutableUniformBuffers() { return impl->uniformBuffers; } void Drawable::buildVulkanInputBindings() noexcept { MLN_TRACE_FUNC(); impl->vulkanVertexBuffers.clear(); impl->vulkanVertexOffsets.clear(); impl->pipelineInfo.inputAttributes.clear(); impl->pipelineInfo.inputBindings.clear(); std::vector uniqueBuffers; const auto buildBindings = [&](const gfx::AttributeBindingArray& bindings, vk::VertexInputRate inputRate) { for (size_t i = 0; i < bindings.size(); ++i) { const auto& binding = bindings[i]; if (!binding.has_value() || !binding->vertexBufferResource) continue; const auto& vertexBuffer = static_cast(binding->vertexBufferResource); const auto& buffer = vertexBuffer->get(); const auto& buffIt = std::find(uniqueBuffers.begin(), uniqueBuffers.end(), binding->vertexBufferResource); std::size_t bindingIndex = 0; if (buffIt == uniqueBuffers.end()) { bindingIndex = impl->pipelineInfo.inputBindings.size(); uniqueBuffers.push_back(binding->vertexBufferResource); // add new buffer binding impl->pipelineInfo.inputBindings.push_back(vk::VertexInputBindingDescription() .setBinding(static_cast(bindingIndex)) .setStride(binding->vertexStride) .setInputRate(inputRate)); impl->vulkanVertexBuffers.push_back(buffer.getVulkanBuffer()); impl->vulkanVertexOffsets.push_back(0u); } else { bindingIndex = std::distance(uniqueBuffers.begin(), buffIt); } impl->pipelineInfo.inputAttributes.push_back( vk::VertexInputAttributeDescription() .setBinding(static_cast(bindingIndex)) .setLocation(static_cast(i)) .setFormat(PipelineInfo::vulkanFormat(binding->attribute.dataType)) .setOffset(binding->attribute.offset)); } }; buildBindings(impl->attributeBindings, vk::VertexInputRate::eVertex); buildBindings(impl->instanceBindings, vk::VertexInputRate::eInstance); impl->pipelineInfo.updateVertexInputHash(); } bool Drawable::bindAttributes(CommandEncoder& encoder) const noexcept { MLN_TRACE_FUNC(); if (impl->vulkanVertexBuffers.empty()) return false; const auto& dispatcher = encoder.getContext().getBackend().getDispatcher(); const auto& commandBuffer = encoder.getCommandBuffer(); commandBuffer->bindVertexBuffers(0, impl->vulkanVertexBuffers, impl->vulkanVertexOffsets, dispatcher); if (impl->indexes) { if (const auto* indexBuffer = static_cast(impl->indexes->getBuffer())) { const auto& indexBufferResource = indexBuffer->buffer->getResource().get(); commandBuffer->bindIndexBuffer( indexBufferResource.getVulkanBuffer(), 0, vk::IndexType::eUint16, dispatcher); } } return true; } bool Drawable::bindDescriptors(CommandEncoder& encoder) const noexcept { MLN_TRACE_FUNC(); if (!shader) return false; // bind uniforms impl->uniformBuffers.bindDescriptorSets(encoder); const auto& shaderImpl = static_cast(*shader); if (shaderImpl.hasTextures()) { // update image set if (!impl->imageDescriptorSet) { impl->imageDescriptorSet = std::make_unique(encoder.getContext()); } for (const auto& texture : textures) { if (!texture) continue; const auto textureImpl = static_cast(texture.get()); if (textureImpl->isModifiedAfter(impl->imageDescriptorSet->getLastModified())) { impl->imageDescriptorSet->markDirty(); break; } } impl->imageDescriptorSet->update(textures); impl->imageDescriptorSet->bind(encoder); } return true; } void Drawable::uploadTextures(UploadPass&) const noexcept { MLN_TRACE_FUNC(); for (const auto& texture : textures) { if (texture) { texture->upload(); } } } } // namespace vulkan } // namespace mbgl