Files
MapLibreParticleLab/src/mbgl/webgpu/context.cpp

474 lines
18 KiB
C++

#include <mbgl/webgpu/context.hpp>
#include <webgpu/webgpu.h>
#include <mbgl/webgpu/vertex_buffer_resource.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <array>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/webgpu/command_encoder.hpp>
#include <mbgl/webgpu/drawable_builder.hpp>
#include <mbgl/webgpu/draw_scope_resource.hpp>
#include <mbgl/webgpu/dynamic_texture.hpp>
#include <mbgl/webgpu/offscreen_texture.hpp>
#include <mbgl/gfx/upload_pass.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
#include <mbgl/webgpu/upload_pass.hpp>
#include <mbgl/webgpu/vertex_attribute.hpp>
#include <mbgl/webgpu/texture2d.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_target.hpp>
#include <mbgl/webgpu/tile_layer_group.hpp>
#include <mbgl/webgpu/layer_group.hpp>
#include <mbgl/webgpu/render_pass.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/util/hash.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/shaders/attributes.hpp>
#include <mbgl/shaders/webgpu/clipping_mask.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/program_parameters.hpp>
#include <mbgl/gfx/gfx_types.hpp>
namespace mbgl {
namespace webgpu {
namespace {
const auto clipMaskStencilMode = gfx::StencilMode{
/*.test=*/gfx::StencilMode::Always(),
/*.ref=*/0,
/*.mask=*/0xFF,
/*.fail=*/gfx::StencilOpType::Keep,
/*.depthFail=*/gfx::StencilOpType::Keep,
/*.pass=*/gfx::StencilOpType::Replace,
};
const auto clipMaskDepthMode = gfx::DepthMode{.func = gfx::DepthFunctionType::Always,
.mask = gfx::DepthMaskType::ReadOnly};
} // namespace
Context::Context(RendererBackend& backend_)
: gfx::Context(gfx::Context::minimumRequiredVertexBindingCount),
backend(backend_),
globalUniformBuffers(std::make_unique<UniformBufferArray>()) {}
Context::~Context() = default;
void Context::beginFrame() {
// Begin a new frame - WebGPU command recording starts here
}
void Context::endFrame() {
// End the frame - submit WebGPU commands
}
void Context::performCleanup() {
// Clean up unused resources
}
void Context::reduceMemoryUsage() {
// Free cached resources to reduce memory
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size,
gfx::TextureChannelDataType type,
bool depth,
bool stencil) {
return std::make_unique<OffscreenTexture>(*this, size, type, depth, stencil);
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) {
return std::make_unique<OffscreenTexture>(*this, size, type, true, false);
}
std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {
auto encoder = std::make_unique<CommandEncoder>(*this);
currentCommandEncoder = encoder.get();
return encoder;
}
gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const {
return std::make_shared<VertexAttributeArray>();
}
gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) {
return std::make_unique<DrawableBuilder>(std::move(name));
}
gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size, bool persistent, bool) {
(void)persistent;
return std::make_shared<UniformBuffer>(*this, data, size);
}
gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
return std::make_unique<UniformBufferArray>();
}
gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& registry, const std::string& name) {
// Align with Metal - just get shader from registry without caching
const auto shaderGroup = registry.getShaderGroup(name);
auto shader = shaderGroup ? shaderGroup->getOrCreateShader(*this, {}) : gfx::ShaderProgramBasePtr{};
return std::static_pointer_cast<gfx::ShaderProgramBase>(std::move(shader));
}
TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
auto tileLayerGroup = std::make_shared<webgpu::TileLayerGroup>(layerIndex, initialCapacity, std::move(name));
return tileLayerGroup;
}
LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
auto layerGroup = std::make_shared<webgpu::LayerGroup>(layerIndex, initialCapacity, std::move(name));
return layerGroup;
}
gfx::Texture2DPtr Context::createTexture2D() {
return std::make_shared<Texture2D>(*this);
}
gfx::DynamicTexturePtr Context::createDynamicTexture(Size size, gfx::TexturePixelType pixelType) {
return std::make_shared<DynamicTexture>(*this, size, pixelType);
}
RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) {
return std::make_shared<RenderTarget>(*this, size, type);
}
void Context::resetState(gfx::DepthMode, gfx::ColorMode) {
// Reset WebGPU render state
}
void Context::setDirtyState() {
// Mark state as needing update
}
void Context::clearStencilBuffer(int32_t value) {
// Clear stencil buffer to specified value
(void)value;
}
bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& ptr,
const void* data,
std::size_t size,
bool persistent) {
if (!ptr) {
ptr = createUniformBuffer(data, size, persistent);
return true;
}
// Update existing buffer
auto* buffer = static_cast<UniformBuffer*>(ptr.get());
buffer->update(data, size);
return false;
}
const gfx::UniformBufferArray& Context::getGlobalUniformBuffers() const {
return *globalUniformBuffers;
}
gfx::UniformBufferArray& Context::mutableGlobalUniformBuffers() {
return *globalUniformBuffers;
}
void Context::bindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept {
// In WebGPU, we can't bind buffers globally to the render pass like Metal does.
// Instead, we need to ensure these buffers are available for drawables to include
// in their bind groups. The actual binding happens per-drawable.
// Store a reference to global buffers in the render pass for drawables to access
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
webgpuRenderPass.setGlobalUniformBuffers(globalUniformBuffers.get());
}
void Context::unbindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept {
// Clear the global buffer reference from the render pass
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
webgpuRenderPass.setGlobalUniformBuffers(nullptr);
}
#if !defined(NDEBUG)
void Context::visualizeStencilBuffer() {
// Debug visualization of stencil buffer
}
void Context::visualizeDepthBuffer([[maybe_unused]] float depthRangeSize) {
// Debug visualization of depth buffer
}
#endif
std::unique_ptr<gfx::RenderbufferResource> Context::createRenderbufferResource(gfx::RenderbufferPixelType type,
Size size) {
(void)type;
(void)size;
return std::make_unique<RenderbufferResource>();
}
std::unique_ptr<gfx::DrawScopeResource> Context::createDrawScopeResource() {
return std::make_unique<DrawScopeResource>(*this);
}
// Buffer creation (aligned with Metal)
BufferResource Context::createBuffer(
const void* data, std::size_t size, uint32_t usage, bool isIndexBuffer, bool persistent) const {
return BufferResource(const_cast<Context&>(*this), data, size, usage, isIndexBuffer, persistent);
}
// Get reusable tile vertex buffer (aligned with Metal)
const BufferResource& Context::getTileVertexBuffer() {
if (!tileVertexBuffer) {
const auto vertices = RenderStaticData::tileVertices();
const uint32_t usage = WGPUBufferUsage_Vertex;
tileVertexBuffer = createBuffer(vertices.data(), vertices.bytes(), usage, false, true);
}
return *tileVertexBuffer;
}
// Get reusable tile index buffer (aligned with Metal)
const BufferResource& Context::getTileIndexBuffer() {
if (!tileIndexBuffer) {
const auto indices = RenderStaticData::quadTriangleIndices();
const uint32_t usage = WGPUBufferUsage_Index;
tileIndexBuffer = createBuffer(indices.data(), indices.bytes(), usage, true, true);
}
return *tileIndexBuffer;
}
bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass,
RenderStaticData& staticData,
const std::vector<shaders::ClipUBO>& tileUBOs) {
using ShaderClass = shaders::ShaderSource<shaders::BuiltIn::ClippingMaskProgram, gfx::Backend::Type::WebGPU>;
if (tileUBOs.empty()) {
return false;
}
if (!clipMaskShader) {
if (auto group = staticData.shaders->getShaderGroup(ShaderClass::name)) {
clipMaskShader = std::static_pointer_cast<gfx::ShaderProgramBase>(group->getOrCreateShader(*this, {}));
}
}
if (!clipMaskShader) {
Log::Error(Event::Render, "WebGPU: Failed to acquire clipping mask shader");
return false;
}
auto& shader = static_cast<webgpu::ShaderProgram&>(*clipMaskShader);
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
const auto encoder = webgpuRenderPass.getEncoder();
if (!encoder) {
Log::Error(Event::Render, "WebGPU: render pass encoder unavailable for clipping masks");
return false;
}
const auto& renderable = webgpuRenderPass.getDescriptor().renderable;
if (clipMaskRenderable != &renderable) {
clipMaskPipelineHash.reset();
clipMaskRenderable = &renderable;
}
const auto& vertexResource = getTileVertexBuffer();
const auto& indexResource = getTileIndexBuffer();
if (!vertexResource.getBuffer() || !indexResource.getBuffer()) {
Log::Error(Event::Render, "WebGPU: Missing tile geometry buffers for clipping masks");
return false;
}
const gfx::ColorMode colorMode = gfx::ColorMode::disabled();
WGPUVertexAttribute vertexAttribute{};
vertexAttribute.format = WGPUVertexFormat_Sint16x2;
vertexAttribute.offset = 0;
vertexAttribute.shaderLocation = static_cast<uint32_t>(ShaderClass::attributes[0].index);
WGPUVertexBufferLayout vertexLayout{};
vertexLayout.arrayStride = sizeof(gfx::Vertex<PositionOnlyLayoutAttributes>);
vertexLayout.attributeCount = 1;
vertexLayout.attributes = &vertexAttribute;
vertexLayout.stepMode = WGPUVertexStepMode_Vertex;
if (!clipMaskPipelineHash) {
clipMaskPipelineHash = util::hash(colorMode.hash(),
vertexLayout.arrayStride,
static_cast<uint32_t>(vertexAttribute.format),
vertexAttribute.shaderLocation);
}
const auto pipeline = shader.getRenderPipeline(renderable,
&vertexLayout,
1,
colorMode,
clipMaskDepthMode,
clipMaskStencilMode,
gfx::DrawModeType::Triangles,
clipMaskPipelineHash);
if (!pipeline) {
Log::Error(Event::Render, "WebGPU: Failed to create clipping mask pipeline");
return false;
}
wgpuRenderPassEncoderSetPipeline(encoder, pipeline);
wgpuRenderPassEncoderSetVertexBuffer(encoder,
/*slot=*/0,
vertexResource.getBuffer(),
/*offset=*/0,
vertexResource.getSizeInBytes());
wgpuRenderPassEncoderSetIndexBuffer(encoder,
indexResource.getBuffer(),
WGPUIndexFormat_Uint16,
/*offset=*/0,
indexResource.getSizeInBytes());
const uint64_t uboSize = sizeof(shaders::ClipUBO);
clipMaskUniformBuffers.clear();
clipMaskUniformBuffers.reserve(tileUBOs.size());
for (const auto& ubo : tileUBOs) {
auto buffer = createBuffer(&ubo, uboSize, WGPUBufferUsage_Uniform, false, false);
if (!buffer.getBuffer()) {
Log::Error(Event::Render, "WebGPU: Failed to allocate uniform buffer for clipping mask tile");
return false;
}
clipMaskUniformBuffers.emplace_back(std::move(buffer));
}
auto& rendererBackend = static_cast<RendererBackend&>(getBackend());
WGPUDevice device = static_cast<WGPUDevice>(rendererBackend.getDevice());
if (!device) {
Log::Error(Event::Render, "WebGPU: Device unavailable for clipping masks");
return false;
}
const auto& bindGroupOrder = shader.getBindGroupOrder();
if (bindGroupOrder.empty()) {
Log::Error(Event::Render, "WebGPU: No bind group layouts available for clipping mask shader");
return false;
}
const uint32_t bindGroupIndex = bindGroupOrder.front();
const auto layout = shader.getBindGroupLayout(bindGroupIndex);
if (!layout) {
Log::Error(Event::Render, "WebGPU: Bind group layout missing for clipping mask shader");
return false;
}
const auto& bindingInfos = shader.getBindingInfosForGroup(bindGroupIndex);
uint32_t uniformBinding = 0;
bool bindingFound = false;
for (const auto& info : bindingInfos) {
if (info.type == webgpu::ShaderProgram::BindingType::UniformBuffer) {
uniformBinding = info.binding;
bindingFound = true;
break;
}
}
if (!bindingFound) {
Log::Error(Event::Render, "WebGPU: Uniform binding not found for clipping mask shader");
return false;
}
clipMaskActiveBindGroups.clear();
clipMaskActiveBindGroups.reserve(tileUBOs.size());
for (std::size_t i = 0; i < tileUBOs.size(); ++i) {
WGPUBindGroupEntry entry{};
entry.binding = uniformBinding;
const auto& uboBuffer = clipMaskUniformBuffers[i];
entry.buffer = uboBuffer.getBuffer();
entry.offset = 0;
entry.size = uboSize;
WGPUBindGroupDescriptor descriptor{};
descriptor.layout = layout;
descriptor.entryCount = 1;
descriptor.entries = &entry;
const auto bindGroup = wgpuDeviceCreateBindGroup(device, &descriptor);
if (!bindGroup) {
Log::Error(Event::Render, "WebGPU: Failed to create bind group for clipping mask draw");
continue;
}
clipMaskActiveBindGroups.push_back(bindGroup);
wgpuRenderPassEncoderSetBindGroup(encoder, bindGroupIndex, bindGroup, 0, nullptr);
wgpuRenderPassEncoderSetStencilReference(encoder, tileUBOs[i].stencil_ref);
wgpuRenderPassEncoderDrawIndexed(encoder,
/*indexCount=*/6,
/*instanceCount=*/1,
/*firstIndex=*/0,
/*baseVertex=*/0,
/*firstInstance=*/0);
}
for (auto bindGroup : clipMaskActiveBindGroups) {
if (bindGroup) {
wgpuBindGroupRelease(bindGroup);
}
}
clipMaskActiveBindGroups.clear();
auto& renderStats = renderingStats();
renderStats.numDrawCalls += tileUBOs.size();
renderStats.totalDrawCalls += tileUBOs.size();
return true;
}
gfx::AttributeBindingArray Context::getOrCreateVertexBindings(gfx::Context&,
const gfx::AttributeDataType vertexType,
const size_t vertexAttributeIndex,
const std::vector<uint8_t>& vertexData,
const gfx::VertexAttributeArray& defaults,
const gfx::VertexAttributeArray& overrides,
gfx::BufferUsageType,
const std::optional<std::chrono::duration<double>>&,
std::shared_ptr<VertexBufferResource>&) noexcept {
static_cast<void>(vertexType);
static_cast<void>(vertexAttributeIndex);
static_cast<void>(vertexData);
gfx::AttributeBindingArray result;
result.resize(overrides.allocatedSize());
// Process each vertex attribute
overrides.visitAttributes([&](const gfx::VertexAttribute& attr) {
const auto index = static_cast<std::size_t>(attr.getIndex());
if (index >= result.size()) return;
// Get the shared raw data if available
gfx::VertexVectorBasePtr sharedRaw = attr.getSharedRawData();
if (!sharedRaw) {
// Try default attributes by ID
defaults.visitAttributes([&](const gfx::VertexAttribute& defaultAttr) {
if (!sharedRaw && defaultAttr.getIndex() == attr.getIndex()) {
sharedRaw = defaultAttr.getSharedRawData();
}
});
}
if (sharedRaw) {
// Create vertex buffer from the shared data
const auto dataSize = sharedRaw->getRawSize() * sharedRaw->getRawCount();
if (dataSize > 0) {
// Create GPU buffer for this vertex attribute
uint32_t usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
auto bufferResource = std::make_shared<VertexBufferResource>(
createBuffer(sharedRaw->getRawData(), dataSize, usage, false, false));
// Create attribute binding
result[index] = {/*.attribute = */ {attr.getDataType(), /*offset=*/0},
/*.vertexStride = */ static_cast<uint32_t>(attr.getStride()),
/*.vertexBufferResource = */ bufferResource.get(),
/*.vertexOffset = */ 0,
/*.bufferIndex = */ 0};
}
}
});
return result;
}
} // namespace webgpu
} // namespace mbgl