803 lines
25 KiB
Plaintext
803 lines
25 KiB
Plaintext
#import "MLNDisplayUtils.h"
|
|
#import "MLNFoundation_Private.h"
|
|
#import "MLNLoggingConfiguration_Private.h"
|
|
#import "MLNMapView+WebGPU.h"
|
|
|
|
#import <mbgl/util/logging.hpp>
|
|
#import <mbgl/webgpu/renderable_resource.hpp>
|
|
|
|
#import <Metal/Metal.h>
|
|
#import <QuartzCore/CAMetalLayer.h>
|
|
|
|
#include <webgpu/webgpu.h>
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
#include <dawn/dawn_proc.h>
|
|
#include <dawn/native/DawnNative.h>
|
|
#include <dawn/native/MetalBackend.h>
|
|
#include <webgpu/webgpu_cpp.h>
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
#include <webgpu/webgpu_cpp.h>
|
|
#include <wgpu.h>
|
|
#endif
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
|
|
#if MLN_WEBGPU_IMPL_WGPU
|
|
#include <chrono>
|
|
#include <thread>
|
|
#endif
|
|
|
|
@interface MLNWebGPUView : UIView
|
|
@end
|
|
|
|
@implementation MLNWebGPUView
|
|
+ (Class)layerClass {
|
|
return [CAMetalLayer class];
|
|
}
|
|
@end
|
|
|
|
class MLNMapViewWebGPURenderableResource final : public mbgl::webgpu::RenderableResource {
|
|
public:
|
|
MLNMapViewWebGPURenderableResource(MLNMapViewWebGPUImpl& backend_) : backend(backend_) {}
|
|
|
|
void bind() override {}
|
|
|
|
void swap() override { backend.markNeedsPresent(); }
|
|
|
|
const mbgl::webgpu::RendererBackend& getBackend() const override { return backend; }
|
|
|
|
const WGPUCommandEncoder& getCommandEncoder() const override {
|
|
assert(false);
|
|
static WGPUCommandEncoder dummy = nullptr;
|
|
return dummy;
|
|
}
|
|
|
|
WGPURenderPassEncoder getRenderPassEncoder() const override {
|
|
assert(false);
|
|
return nullptr;
|
|
}
|
|
|
|
WGPUTextureView getColorTextureView() override {
|
|
return static_cast<WGPUTextureView>(backend.getCurrentTextureView());
|
|
}
|
|
|
|
WGPUTextureView getDepthStencilTextureView() override {
|
|
return static_cast<WGPUTextureView>(backend.getDepthStencilView());
|
|
}
|
|
|
|
private:
|
|
MLNMapViewWebGPUImpl& backend;
|
|
};
|
|
|
|
class MLNMapViewWebGPUImpl::Impl {
|
|
public:
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
std::unique_ptr<dawn::native::Instance> instance;
|
|
WGPUAdapter adapter = nullptr;
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
wgpu::Instance instance;
|
|
wgpu::Adapter adapter;
|
|
#endif
|
|
wgpu::Device device;
|
|
wgpu::Queue queue;
|
|
|
|
WGPUSurface surface = nullptr;
|
|
WGPUTextureFormat surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
|
bool surfaceConfigured = false;
|
|
|
|
WGPUTexture depthStencilTexture = nullptr;
|
|
WGPUTextureView depthStencilView = nullptr;
|
|
|
|
WGPUTexture currentTexture = nullptr;
|
|
WGPUTextureView currentTextureView = nullptr;
|
|
bool needsPresent = false;
|
|
|
|
mbgl::Size framebufferSize;
|
|
|
|
MLNWebGPUView* webGPUView = nil;
|
|
bool presentsWithTransaction = false;
|
|
NSUInteger activationCount = 0;
|
|
};
|
|
|
|
MLNMapViewWebGPUImpl::MLNMapViewWebGPUImpl(MLNMapView* nativeView_)
|
|
: MLNMapViewImpl(nativeView_),
|
|
mbgl::webgpu::RendererBackend(mbgl::gfx::ContextMode::Unique),
|
|
mbgl::gfx::Renderable({0, 0}, std::make_unique<MLNMapViewWebGPURenderableResource>(*this)),
|
|
impl(std::make_unique<Impl>()) {
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
// Required when not using the monolithic Dawn library.
|
|
dawnProcSetProcs(&dawn::native::GetProcs());
|
|
|
|
WGPUInstanceFeatureName instanceFeatures[] = {WGPUInstanceFeatureName_TimedWaitAny};
|
|
WGPUInstanceLimits instanceLimits = WGPU_INSTANCE_LIMITS_INIT;
|
|
instanceLimits.timedWaitAnyMaxCount = 64;
|
|
|
|
WGPUInstanceDescriptor instanceDesc = WGPU_INSTANCE_DESCRIPTOR_INIT;
|
|
instanceDesc.requiredFeatureCount = 1;
|
|
instanceDesc.requiredFeatures = instanceFeatures;
|
|
instanceDesc.requiredLimits = &instanceLimits;
|
|
impl->instance = std::make_unique<dawn::native::Instance>(&instanceDesc);
|
|
|
|
wgpu::RequestAdapterOptions metalOpts = {};
|
|
metalOpts.backendType = wgpu::BackendType::Metal;
|
|
std::vector<dawn::native::Adapter> adapters = impl->instance->EnumerateAdapters(&metalOpts);
|
|
if (adapters.empty()) {
|
|
adapters = impl->instance->EnumerateAdapters();
|
|
}
|
|
if (adapters.empty()) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: No adapters found");
|
|
return;
|
|
}
|
|
|
|
dawn::native::Adapter& selectedAdapter = adapters[0];
|
|
|
|
bool hasDepth32FloatStencil8 = false;
|
|
WGPUSupportedFeatures features = {};
|
|
wgpuAdapterGetFeatures(selectedAdapter.Get(), &features);
|
|
if (features.featureCount > 0 && features.features) {
|
|
for (size_t i = 0; i < features.featureCount; ++i) {
|
|
if (static_cast<wgpu::FeatureName>(features.features[i]) ==
|
|
wgpu::FeatureName::Depth32FloatStencil8) {
|
|
hasDepth32FloatStencil8 = true;
|
|
break;
|
|
}
|
|
}
|
|
wgpuSupportedFeaturesFreeMembers(features);
|
|
}
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
wgpu::InstanceDescriptor instanceDesc = {};
|
|
impl->instance = wgpu::createInstance(instanceDesc);
|
|
if (!impl->instance) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create instance");
|
|
return;
|
|
}
|
|
|
|
wgpu::RequestAdapterOptions adapterOpts = {};
|
|
adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance;
|
|
adapterOpts.backendType = wgpu::BackendType::Metal;
|
|
|
|
impl->adapter = impl->instance.requestAdapter(adapterOpts);
|
|
if (!impl->adapter) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: No adapter found");
|
|
return;
|
|
}
|
|
|
|
WGPUSupportedFeatures features = {};
|
|
bool hasDepth32FloatStencil8 = false;
|
|
wgpuAdapterGetFeatures(static_cast<WGPUAdapter>(impl->adapter), &features);
|
|
if (features.featureCount > 0 && features.features) {
|
|
for (size_t i = 0; i < features.featureCount; ++i) {
|
|
if (static_cast<wgpu::FeatureName>(features.features[i]) ==
|
|
wgpu::FeatureName::Depth32FloatStencil8) {
|
|
hasDepth32FloatStencil8 = true;
|
|
break;
|
|
}
|
|
}
|
|
wgpuSupportedFeaturesFreeMembers(features);
|
|
}
|
|
#endif
|
|
|
|
std::vector<wgpu::FeatureName> requiredFeatures;
|
|
wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
|
|
if (hasDepth32FloatStencil8) {
|
|
requiredFeatures.push_back(wgpu::FeatureName::Depth32FloatStencil8);
|
|
depthStencilFormat = wgpu::TextureFormat::Depth32FloatStencil8;
|
|
}
|
|
|
|
wgpu::DeviceDescriptor deviceDesc = {};
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
deviceDesc.label = "MapLibre iOS WebGPU Device";
|
|
deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType type,
|
|
wgpu::StringView message) {
|
|
NSLog(@"[WebGPU] Device error (type=%d): %.*s", (int)type, (int)message.length, message.data);
|
|
});
|
|
deviceDesc.requiredFeatures = requiredFeatures.data();
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
deviceDesc.requiredFeatures = reinterpret_cast<const WGPUFeatureName*>(requiredFeatures.data());
|
|
#endif
|
|
deviceDesc.requiredFeatureCount = requiredFeatures.size();
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUDevice rawDevice = selectedAdapter.CreateDevice(&deviceDesc);
|
|
if (!rawDevice) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create device");
|
|
return;
|
|
}
|
|
|
|
impl->device = wgpu::Device::Acquire(rawDevice);
|
|
impl->queue = impl->device.GetQueue();
|
|
impl->adapter = selectedAdapter.Get();
|
|
wgpuAdapterAddRef(impl->adapter);
|
|
|
|
setDevice(impl->device.Get());
|
|
setQueue(impl->queue.Get());
|
|
setInstance(impl->instance->Get());
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
impl->device = impl->adapter.requestDevice(deviceDesc);
|
|
if (!impl->device) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create device");
|
|
return;
|
|
}
|
|
|
|
impl->queue = impl->device.getQueue();
|
|
|
|
setDevice(static_cast<WGPUDevice>(impl->device));
|
|
setQueue(static_cast<WGPUQueue>(impl->queue));
|
|
setInstance(static_cast<WGPUInstance>(impl->instance));
|
|
#endif
|
|
setDepthStencilFormat(depthStencilFormat);
|
|
}
|
|
|
|
MLNMapViewWebGPUImpl::~MLNMapViewWebGPUImpl() {
|
|
if (!impl) return;
|
|
|
|
if (impl->currentTextureView) {
|
|
wgpuTextureViewRelease(impl->currentTextureView);
|
|
impl->currentTextureView = nullptr;
|
|
}
|
|
if (impl->currentTexture) {
|
|
wgpuTextureRelease(impl->currentTexture);
|
|
impl->currentTexture = nullptr;
|
|
}
|
|
if (impl->depthStencilView) {
|
|
wgpuTextureViewRelease(impl->depthStencilView);
|
|
impl->depthStencilView = nullptr;
|
|
}
|
|
if (impl->depthStencilTexture) {
|
|
wgpuTextureDestroy(impl->depthStencilTexture);
|
|
wgpuTextureRelease(impl->depthStencilTexture);
|
|
impl->depthStencilTexture = nullptr;
|
|
}
|
|
|
|
if (impl->surface) {
|
|
if (impl->surfaceConfigured) {
|
|
wgpuSurfaceUnconfigure(impl->surface);
|
|
}
|
|
wgpuSurfaceRelease(impl->surface);
|
|
impl->surface = nullptr;
|
|
}
|
|
|
|
impl->queue = nullptr;
|
|
impl->device = nullptr;
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
if (impl->adapter) {
|
|
wgpuAdapterRelease(impl->adapter);
|
|
impl->adapter = nullptr;
|
|
}
|
|
impl->instance.reset();
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
impl->adapter = nullptr;
|
|
impl->instance = nullptr;
|
|
#endif
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::createView() {
|
|
if (impl->webGPUView) {
|
|
return;
|
|
}
|
|
|
|
const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView);
|
|
|
|
impl->webGPUView = [[MLNWebGPUView alloc] initWithFrame:mapView.bounds];
|
|
impl->webGPUView.autoresizingMask =
|
|
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
impl->webGPUView.contentScaleFactor = scaleFactor;
|
|
impl->webGPUView.contentMode = UIViewContentModeCenter;
|
|
impl->webGPUView.opaque = mapView.opaque;
|
|
impl->webGPUView.layer.opaque = mapView.opaque;
|
|
impl->webGPUView.userInteractionEnabled = NO;
|
|
|
|
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
|
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
|
metalLayer.framebufferOnly = NO;
|
|
|
|
CGSize drawableSize =
|
|
CGSizeMake(mapView.bounds.size.width * scaleFactor, mapView.bounds.size.height * scaleFactor);
|
|
metalLayer.drawableSize = drawableSize;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
// Dawn needs the Metal device set on the layer before creating a surface.
|
|
id<MTLDevice> mtlDevice = dawn::native::metal::GetMTLDevice(impl->device.Get());
|
|
metalLayer.device = mtlDevice;
|
|
#endif
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
metalLayer.presentsWithTransaction = impl->presentsWithTransaction;
|
|
}
|
|
|
|
[mapView insertSubview:impl->webGPUView atIndex:0];
|
|
|
|
createSurface();
|
|
|
|
uint32_t w = static_cast<uint32_t>(drawableSize.width);
|
|
uint32_t h = static_cast<uint32_t>(drawableSize.height);
|
|
if (w > 0 && h > 0) {
|
|
impl->framebufferSize = {w, h};
|
|
size = impl->framebufferSize;
|
|
configureSurface(w, h);
|
|
}
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::createSurface() {
|
|
if (!impl->webGPUView) return;
|
|
|
|
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
if (!impl->instance) return;
|
|
|
|
WGPUSurfaceSourceMetalLayer metalDesc = WGPU_SURFACE_SOURCE_METAL_LAYER_INIT;
|
|
metalDesc.layer = (__bridge void*)metalLayer;
|
|
|
|
WGPUSurfaceDescriptor surfaceDesc = WGPU_SURFACE_DESCRIPTOR_INIT;
|
|
surfaceDesc.nextInChain = &metalDesc.chain;
|
|
|
|
WGPUInstance rawInstance = impl->instance->Get();
|
|
impl->surface = wgpuInstanceCreateSurface(rawInstance, &surfaceDesc);
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
if (!impl->instance) return;
|
|
|
|
WGPUSurfaceSourceMetalLayer metalDesc = {};
|
|
metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
|
|
metalDesc.chain.next = nullptr;
|
|
metalDesc.layer = (__bridge void*)metalLayer;
|
|
|
|
WGPUSurfaceDescriptor surfaceDesc = {};
|
|
surfaceDesc.nextInChain = &metalDesc.chain;
|
|
|
|
impl->surface =
|
|
wgpuInstanceCreateSurface(static_cast<WGPUInstance>(impl->instance), &surfaceDesc);
|
|
#endif
|
|
|
|
if (!impl->surface) {
|
|
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create surface");
|
|
}
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::configureSurface(uint32_t width, uint32_t height) {
|
|
if (!impl->surface || !impl->device || width == 0 || height == 0) return;
|
|
|
|
WGPUSurfaceCapabilities capabilities = {};
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUStatus capStatus = wgpuSurfaceGetCapabilities(impl->surface, impl->adapter, &capabilities);
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
WGPUStatus capStatus = wgpuSurfaceGetCapabilities(
|
|
impl->surface, static_cast<WGPUAdapter>(impl->adapter), &capabilities);
|
|
#endif
|
|
|
|
WGPUCompositeAlphaMode selectedAlphaMode = WGPUCompositeAlphaMode_Opaque;
|
|
WGPUPresentMode selectedPresentMode = WGPUPresentMode_Fifo;
|
|
|
|
if (capStatus == WGPUStatus_Success) {
|
|
const WGPUTextureFormat preferredFormats[] = {
|
|
WGPUTextureFormat_BGRA8Unorm,
|
|
WGPUTextureFormat_RGBA8Unorm,
|
|
};
|
|
for (auto preferred : preferredFormats) {
|
|
for (size_t i = 0; i < capabilities.formatCount; ++i) {
|
|
if (capabilities.formats[i] == preferred) {
|
|
impl->surfaceFormat = preferred;
|
|
goto formatFound;
|
|
}
|
|
}
|
|
}
|
|
if (capabilities.formatCount > 0) {
|
|
impl->surfaceFormat = capabilities.formats[0];
|
|
}
|
|
formatFound:
|
|
|
|
if (capabilities.alphaModeCount > 0) {
|
|
selectedAlphaMode = capabilities.alphaModes[0];
|
|
}
|
|
if (capabilities.presentModeCount > 0) {
|
|
selectedPresentMode = capabilities.presentModes[0];
|
|
}
|
|
|
|
wgpuSurfaceCapabilitiesFreeMembers(capabilities);
|
|
}
|
|
|
|
setColorFormat(static_cast<wgpu::TextureFormat>(impl->surfaceFormat));
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUSurfaceConfiguration config = WGPU_SURFACE_CONFIGURATION_INIT;
|
|
config.device = impl->device.Get();
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
WGPUSurfaceConfiguration config = {};
|
|
config.device = static_cast<WGPUDevice>(impl->device);
|
|
#endif
|
|
config.format = impl->surfaceFormat;
|
|
config.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
|
|
config.alphaMode = selectedAlphaMode;
|
|
config.width = width;
|
|
config.height = height;
|
|
config.presentMode = selectedPresentMode;
|
|
|
|
wgpuSurfaceConfigure(impl->surface, &config);
|
|
impl->surfaceConfigured = true;
|
|
|
|
createDepthStencilTexture(width, height);
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::createDepthStencilTexture(uint32_t width, uint32_t height) {
|
|
if (impl->depthStencilView) {
|
|
wgpuTextureViewRelease(impl->depthStencilView);
|
|
impl->depthStencilView = nullptr;
|
|
}
|
|
if (impl->depthStencilTexture) {
|
|
wgpuTextureDestroy(impl->depthStencilTexture);
|
|
wgpuTextureRelease(impl->depthStencilTexture);
|
|
impl->depthStencilTexture = nullptr;
|
|
}
|
|
|
|
if (!impl->device || width == 0 || height == 0) return;
|
|
|
|
WGPUTextureFormat dsFormat = static_cast<WGPUTextureFormat>(getDepthStencilFormat());
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUTextureDescriptor depthDesc = WGPU_TEXTURE_DESCRIPTOR_INIT;
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
WGPUTextureDescriptor depthDesc = {};
|
|
#endif
|
|
depthDesc.usage = WGPUTextureUsage_RenderAttachment;
|
|
depthDesc.dimension = WGPUTextureDimension_2D;
|
|
depthDesc.size = {width, height, 1};
|
|
depthDesc.format = dsFormat;
|
|
depthDesc.mipLevelCount = 1;
|
|
depthDesc.sampleCount = 1;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
impl->depthStencilTexture = wgpuDeviceCreateTexture(impl->device.Get(), &depthDesc);
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
impl->depthStencilTexture =
|
|
wgpuDeviceCreateTexture(static_cast<WGPUDevice>(impl->device), &depthDesc);
|
|
#endif
|
|
if (!impl->depthStencilTexture) {
|
|
mbgl::Log::Warning(mbgl::Event::Render, "WebGPU iOS: Failed to create depth/stencil texture");
|
|
return;
|
|
}
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
WGPUTextureViewDescriptor viewDesc = {};
|
|
#endif
|
|
viewDesc.format = dsFormat;
|
|
viewDesc.dimension = WGPUTextureViewDimension_2D;
|
|
viewDesc.baseMipLevel = 0;
|
|
viewDesc.mipLevelCount = 1;
|
|
viewDesc.baseArrayLayer = 0;
|
|
viewDesc.arrayLayerCount = 1;
|
|
viewDesc.aspect = WGPUTextureAspect_All;
|
|
|
|
impl->depthStencilView = wgpuTextureCreateView(impl->depthStencilTexture, &viewDesc);
|
|
if (!impl->depthStencilView) {
|
|
mbgl::Log::Warning(mbgl::Event::Render, "WebGPU iOS: Failed to create depth/stencil view");
|
|
}
|
|
}
|
|
|
|
void* MLNMapViewWebGPUImpl::getCurrentTextureView() {
|
|
if (!impl->surface || !impl->surfaceConfigured) {
|
|
return nullptr;
|
|
}
|
|
|
|
presentSurface();
|
|
|
|
WGPUSurfaceTexture surfaceTexture = {};
|
|
wgpuSurfaceGetCurrentTexture(impl->surface, &surfaceTexture);
|
|
|
|
if (!surfaceTexture.texture) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto status = static_cast<WGPUSurfaceGetCurrentTextureStatus>(surfaceTexture.status);
|
|
if (status == WGPUSurfaceGetCurrentTextureStatus_Outdated ||
|
|
status == WGPUSurfaceGetCurrentTextureStatus_Lost) {
|
|
wgpuTextureRelease(surfaceTexture.texture);
|
|
configureSurface(impl->framebufferSize.width, impl->framebufferSize.height);
|
|
return nullptr;
|
|
}
|
|
|
|
if (status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal &&
|
|
status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) {
|
|
wgpuTextureRelease(surfaceTexture.texture);
|
|
return nullptr;
|
|
}
|
|
|
|
impl->currentTexture = surfaceTexture.texture;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
WGPUTextureViewDescriptor viewDesc = {};
|
|
#endif
|
|
viewDesc.format = impl->surfaceFormat;
|
|
viewDesc.dimension = WGPUTextureViewDimension_2D;
|
|
viewDesc.baseMipLevel = 0;
|
|
viewDesc.mipLevelCount = 1;
|
|
viewDesc.baseArrayLayer = 0;
|
|
viewDesc.arrayLayerCount = 1;
|
|
viewDesc.aspect = WGPUTextureAspect_All;
|
|
|
|
impl->currentTextureView = wgpuTextureCreateView(impl->currentTexture, &viewDesc);
|
|
|
|
return impl->currentTextureView;
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::markNeedsPresent() { impl->needsPresent = true; }
|
|
|
|
void MLNMapViewWebGPUImpl::presentSurface() {
|
|
if (!impl->surface || !impl->surfaceConfigured || !impl->needsPresent) {
|
|
return;
|
|
}
|
|
|
|
impl->needsPresent = false;
|
|
|
|
if (impl->currentTextureView) {
|
|
wgpuSurfacePresent(impl->surface);
|
|
wgpuTextureViewRelease(impl->currentTextureView);
|
|
impl->currentTextureView = nullptr;
|
|
}
|
|
if (impl->currentTexture) {
|
|
wgpuTextureRelease(impl->currentTexture);
|
|
impl->currentTexture = nullptr;
|
|
}
|
|
|
|
if (impl->device) {
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
wgpuDeviceTick(impl->device.Get());
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
wgpuDevicePoll(static_cast<WGPUDevice>(impl->device), false, nullptr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void* MLNMapViewWebGPUImpl::getDepthStencilView() { return impl->depthStencilView; }
|
|
|
|
mbgl::Size MLNMapViewWebGPUImpl::getFramebufferSize() const { return impl->framebufferSize; }
|
|
|
|
UIView* MLNMapViewWebGPUImpl::getView() { return impl->webGPUView; }
|
|
|
|
void MLNMapViewWebGPUImpl::setOpaque(const bool opaque) {
|
|
impl->webGPUView.opaque = opaque;
|
|
impl->webGPUView.layer.opaque = opaque;
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::setPresentsWithTransaction(const bool value) {
|
|
impl->presentsWithTransaction = value;
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
if (CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(impl->webGPUView.layer, CAMetalLayer)) {
|
|
metalLayer.presentsWithTransaction = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::display() { render(); }
|
|
|
|
void MLNMapViewWebGPUImpl::deleteView() {
|
|
if (impl->currentTextureView) {
|
|
wgpuTextureViewRelease(impl->currentTextureView);
|
|
impl->currentTextureView = nullptr;
|
|
}
|
|
if (impl->currentTexture) {
|
|
wgpuTextureRelease(impl->currentTexture);
|
|
impl->currentTexture = nullptr;
|
|
}
|
|
if (impl->depthStencilView) {
|
|
wgpuTextureViewRelease(impl->depthStencilView);
|
|
impl->depthStencilView = nullptr;
|
|
}
|
|
if (impl->depthStencilTexture) {
|
|
wgpuTextureDestroy(impl->depthStencilTexture);
|
|
wgpuTextureRelease(impl->depthStencilTexture);
|
|
impl->depthStencilTexture = nullptr;
|
|
}
|
|
|
|
if (impl->surface) {
|
|
if (impl->surfaceConfigured) {
|
|
wgpuSurfaceUnconfigure(impl->surface);
|
|
impl->surfaceConfigured = false;
|
|
}
|
|
wgpuSurfaceRelease(impl->surface);
|
|
impl->surface = nullptr;
|
|
}
|
|
|
|
[impl->webGPUView removeFromSuperview];
|
|
impl->webGPUView = nil;
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::layoutChanged() {
|
|
const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView);
|
|
const auto screenSize = mapView.bounds.size;
|
|
|
|
impl->webGPUView.contentScaleFactor = scaleFactor;
|
|
|
|
CGSize drawableSize = CGSizeMake(screenSize.width * scaleFactor, screenSize.height * scaleFactor);
|
|
|
|
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
|
metalLayer.drawableSize = drawableSize;
|
|
|
|
uint32_t w = static_cast<uint32_t>(drawableSize.width);
|
|
uint32_t h = static_cast<uint32_t>(drawableSize.height);
|
|
|
|
impl->framebufferSize = {w, h};
|
|
size = impl->framebufferSize;
|
|
|
|
if (impl->currentTextureView) {
|
|
wgpuTextureViewRelease(impl->currentTextureView);
|
|
impl->currentTextureView = nullptr;
|
|
}
|
|
if (impl->currentTexture) {
|
|
wgpuTextureRelease(impl->currentTexture);
|
|
impl->currentTexture = nullptr;
|
|
}
|
|
|
|
if (w > 0 && h > 0) {
|
|
configureSurface(w, h);
|
|
}
|
|
}
|
|
|
|
UIImage* MLNMapViewWebGPUImpl::snapshot() {
|
|
if (!impl->currentTexture || !impl->device || !impl->queue || impl->framebufferSize.width == 0 ||
|
|
impl->framebufferSize.height == 0) {
|
|
return nil;
|
|
}
|
|
|
|
const auto& fbSize = impl->framebufferSize;
|
|
constexpr uint32_t bytesPerPixel = 4;
|
|
constexpr uint32_t bytesPerRowAlignment = 256u;
|
|
const uint32_t rowStride = fbSize.width * bytesPerPixel;
|
|
const uint32_t alignedRowStride =
|
|
((rowStride + bytesPerRowAlignment - 1) / bytesPerRowAlignment) * bytesPerRowAlignment;
|
|
const uint64_t alignedDataSize = static_cast<uint64_t>(alignedRowStride) * fbSize.height;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
auto device = impl->device.Get();
|
|
auto queue = impl->queue.Get();
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
auto device = static_cast<WGPUDevice>(impl->device);
|
|
auto queue = static_cast<WGPUQueue>(impl->queue);
|
|
#endif
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
wgpuDeviceTick(device);
|
|
#endif
|
|
|
|
WGPUBufferDescriptor bufferDesc = {};
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
WGPUStringView bufferLabel = {"Snapshot Buffer", strlen("Snapshot Buffer")};
|
|
bufferDesc.label = bufferLabel;
|
|
#endif
|
|
bufferDesc.size = alignedDataSize;
|
|
bufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
|
|
|
|
WGPUBuffer stagingBuffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
|
|
if (!stagingBuffer) {
|
|
return nil;
|
|
}
|
|
|
|
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
|
|
if (encoder) {
|
|
WGPUTexelCopyTextureInfo src = {};
|
|
src.texture = impl->currentTexture;
|
|
src.mipLevel = 0;
|
|
src.origin = {0, 0, 0};
|
|
src.aspect = WGPUTextureAspect_All;
|
|
|
|
WGPUTexelCopyBufferInfo dst = {};
|
|
dst.buffer = stagingBuffer;
|
|
dst.layout.offset = 0;
|
|
dst.layout.bytesPerRow = alignedRowStride;
|
|
dst.layout.rowsPerImage = fbSize.height;
|
|
|
|
WGPUExtent3D copySize = {fbSize.width, fbSize.height, 1};
|
|
wgpuCommandEncoderCopyTextureToBuffer(encoder, &src, &dst, ©Size);
|
|
|
|
WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
|
|
wgpuQueueSubmit(queue, 1, &commands);
|
|
wgpuCommandBufferRelease(commands);
|
|
wgpuCommandEncoderRelease(encoder);
|
|
}
|
|
|
|
struct MapContext {
|
|
WGPUMapAsyncStatus status = WGPUMapAsyncStatus_Error;
|
|
bool completed = false;
|
|
};
|
|
MapContext mapContext;
|
|
|
|
WGPUBufferMapCallbackInfo callbackInfo = {};
|
|
callbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView, void* userdata1, void*) {
|
|
auto* ctx = static_cast<MapContext*>(userdata1);
|
|
ctx->status = status;
|
|
ctx->completed = true;
|
|
};
|
|
callbackInfo.userdata1 = &mapContext;
|
|
|
|
#if MLN_WEBGPU_IMPL_DAWN
|
|
callbackInfo.mode = WGPUCallbackMode_WaitAnyOnly;
|
|
WGPUFuture future =
|
|
wgpuBufferMapAsync(stagingBuffer, WGPUMapMode_Read, 0, alignedDataSize, callbackInfo);
|
|
|
|
WGPUInstance instance = impl->instance->Get();
|
|
if (instance) {
|
|
WGPUFutureWaitInfo waitInfo = {};
|
|
waitInfo.future = future;
|
|
waitInfo.completed = false;
|
|
wgpuInstanceWaitAny(instance, 1, &waitInfo, UINT64_MAX);
|
|
}
|
|
#elif MLN_WEBGPU_IMPL_WGPU
|
|
callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
|
|
wgpuBufferMapAsync(stagingBuffer, WGPUMapMode_Read, 0, alignedDataSize, callbackInfo);
|
|
|
|
for (int i = 0; i < 1000 && !mapContext.completed; ++i) {
|
|
wgpuDevicePoll(device, true, nullptr);
|
|
if (!mapContext.completed) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
UIImage* result = nil;
|
|
|
|
if (mapContext.status == WGPUMapAsyncStatus_Success) {
|
|
const auto* mappedData = static_cast<const uint8_t*>(
|
|
wgpuBufferGetConstMappedRange(stagingBuffer, 0, alignedDataSize));
|
|
if (mappedData) {
|
|
auto rgbaData = std::make_unique<uint8_t[]>(static_cast<size_t>(rowStride) * fbSize.height);
|
|
const bool swizzle = (impl->surfaceFormat == WGPUTextureFormat_BGRA8Unorm);
|
|
for (uint32_t row = 0; row < fbSize.height; ++row) {
|
|
const auto* src = mappedData + static_cast<size_t>(row) * alignedRowStride;
|
|
auto* dst = rgbaData.get() + static_cast<size_t>(row) * rowStride;
|
|
if (swizzle) {
|
|
for (uint32_t x = 0; x < fbSize.width; ++x) {
|
|
dst[x * 4 + 0] = src[x * 4 + 2];
|
|
dst[x * 4 + 1] = src[x * 4 + 1];
|
|
dst[x * 4 + 2] = src[x * 4 + 0];
|
|
dst[x * 4 + 3] = src[x * 4 + 3];
|
|
}
|
|
} else {
|
|
std::memcpy(dst, src, rowStride);
|
|
}
|
|
}
|
|
|
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
CGContextRef ctx =
|
|
CGBitmapContextCreate(rgbaData.get(), fbSize.width, fbSize.height, 8, rowStride,
|
|
colorSpace, kCGImageAlphaPremultipliedLast);
|
|
if (ctx) {
|
|
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
|
|
if (cgImage) {
|
|
const CGFloat scale = impl->webGPUView.contentScaleFactor;
|
|
result = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp];
|
|
CGImageRelease(cgImage);
|
|
}
|
|
CGContextRelease(ctx);
|
|
}
|
|
CGColorSpaceRelease(colorSpace);
|
|
}
|
|
}
|
|
|
|
wgpuBufferUnmap(stagingBuffer);
|
|
wgpuBufferRelease(stagingBuffer);
|
|
|
|
presentSurface();
|
|
|
|
return result;
|
|
}
|
|
|
|
MLNBackendResource* MLNMapViewWebGPUImpl::getObject() { return [[MLNBackendResource alloc] init]; }
|
|
|
|
void MLNMapViewWebGPUImpl::activate() {
|
|
if (impl->activationCount++) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MLNMapViewWebGPUImpl::deactivate() {
|
|
if (--impl->activationCount) {
|
|
return;
|
|
}
|
|
}
|