Files
MapLibreParticleLab/src/mbgl/vulkan/drawable.cpp
2025-11-19 18:30:50 +01:00

478 lines
18 KiB
C++

#include <mbgl/vulkan/drawable.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/vulkan/command_encoder.hpp>
#include <mbgl/vulkan/context.hpp>
#include <mbgl/vulkan/drawable_impl.hpp>
#include <mbgl/vulkan/render_pass.hpp>
#include <mbgl/vulkan/renderable_resource.hpp>
#include <mbgl/vulkan/renderer_backend.hpp>
#include <mbgl/vulkan/upload_pass.hpp>
#include <mbgl/vulkan/index_buffer_resource.hpp>
#include <mbgl/vulkan/vertex_buffer_resource.hpp>
#include <mbgl/vulkan/texture2d.hpp>
#include <mbgl/shaders/vulkan/shader_program.hpp>
#include <mbgl/shaders/segment.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/hash.hpp>
#include <mbgl/util/instrumentation.hpp>
#include <cassert>
#if !defined(NDEBUG)
#include <sstream>
#endif
namespace mbgl {
namespace vulkan {
struct IndexBuffer : public gfx::IndexBufferBase {
IndexBuffer(std::unique_ptr<gfx::IndexBuffer>&& buffer_)
: buffer(std::move(buffer_)) {}
~IndexBuffer() override = default;
std::unique_ptr<mbgl::gfx::IndexBuffer> 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<Impl>()) {}
Drawable::~Drawable() {}
void Drawable::setEnableColor(bool value) {
gfx::Drawable::setEnableColor(value);
if (value) {
impl->pipelineInfo.colorMask = vk::FlagTraits<vk::ColorComponentFlagBits>::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<float>(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<std::unique_ptr<Drawable::DrawSegment>> 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<Drawable::DrawSegment>(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&>(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<gfx::IndexBuffer>(impl->indexes->elements(),
std::move(indexBufferResource));
auto buffer = std::make_unique<IndexBuffer>(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<VertexAttributeArray>();
}
// Apply drawable values to shader defaults
std::vector<std::unique_ptr<gfx::VertexBufferResource>> 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<std::unique_ptr<gfx::VertexBufferResource>> 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<Context&>(parameters.context);
auto& dispatcher = context.getBackend().getDispatcher();
auto& renderPass_ = static_cast<RenderPass&>(*parameters.renderPass);
auto& encoder = renderPass_.getEncoder();
auto& commandBuffer = encoder.getCommandBuffer();
auto& shaderImpl = static_cast<mbgl::vulkan::ShaderProgram&>(*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<uint32_t>(segment.indexLength),
static_cast<uint32_t>(instances),
static_cast<uint32_t>(segment.indexOffset),
static_cast<int32_t>(segment.vertexOffset),
0,
dispatcher);
} else {
commandBuffer->draw(static_cast<uint32_t>(segment.vertexLength),
static_cast<uint32_t>(instances),
static_cast<uint32_t>(segment.vertexOffset),
0,
dispatcher);
}
context.renderingStats().numDrawCalls++;
}
}
void Drawable::setIndexData(gfx::IndexVectorBasePtr indexes, std::vector<UniqueDrawSegment> segments) {
assert(indexes && indexes->elements());
impl->indexes = std::move(indexes);
impl->segments = std::move(segments);
}
void Drawable::setVertices(std::vector<uint8_t>&& 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<VertexAttributeArray>();
}
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<const gfx::VertexBufferResource*> 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<const VertexBufferResource*>(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<uint32_t>(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<uint32_t>(bindingIndex))
.setLocation(static_cast<uint32_t>(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<const IndexBuffer*>(impl->indexes->getBuffer())) {
const auto& indexBufferResource = indexBuffer->buffer->getResource<IndexBufferResource>().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<const mbgl::vulkan::ShaderProgram&>(*shader);
if (shaderImpl.hasTextures()) {
// update image set
if (!impl->imageDescriptorSet) {
impl->imageDescriptorSet = std::make_unique<ImageDescriptorSet>(encoder.getContext());
}
for (const auto& texture : textures) {
if (!texture) continue;
const auto textureImpl = static_cast<const Texture2D*>(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