LibWebView+UI/AppKit: Present IOSurfaces directly
Instead of copying the Bitmap that wraps the IOSurface, we can just present the IOSurface directly. This significantly reduces CPU usage in the UI process, particularly at high refresh rates such as 120Hz where it would saturate a full CPU core. This is done by using CAMetalLayer and blitting the IOSurface to the next drawable buffer, which handles triple buffering, locking the IOSurface and vsync automatically. This also allows the Metal HUD to work, but the only accurate stat is the frame intervals/FPS because it's in the UI layer, not WebContent. However, that's still useful to detect frame drops.
This commit is contained in:
parent
aeec2c804c
commit
0e958ca8d5
6 changed files with 113 additions and 22 deletions
|
|
@ -582,13 +582,18 @@ void ViewImplementation::did_allocate_iosurface_backing_stores(i32 front_id, Cor
|
|||
|
||||
auto bytes_per_row = front_iosurface.bytes_per_row();
|
||||
|
||||
auto* front_ref = front_iosurface.core_foundation_pointer();
|
||||
auto* back_ref = back_iosurface.core_foundation_pointer();
|
||||
|
||||
auto front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, front_size, bytes_per_row, front_iosurface.data(), [handle = move(front_iosurface)] { });
|
||||
auto back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, back_size, bytes_per_row, back_iosurface.data(), [handle = move(back_iosurface)] { });
|
||||
|
||||
m_client_state.front_bitmap.bitmap = front_bitmap.release_value_but_fixme_should_propagate_errors();
|
||||
m_client_state.front_bitmap.id = front_id;
|
||||
m_client_state.front_bitmap.iosurface_ref = front_ref;
|
||||
m_client_state.back_bitmap.bitmap = back_bitmap.release_value_but_fixme_should_propagate_errors();
|
||||
m_client_state.back_bitmap.id = back_id;
|
||||
m_client_state.back_bitmap.iosurface_ref = back_ref;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -300,6 +300,9 @@ protected:
|
|||
i32 id { -1 };
|
||||
Web::DevicePixelSize last_painted_size;
|
||||
RefPtr<Gfx::Bitmap const> bitmap;
|
||||
#ifdef AK_OS_MACOS
|
||||
void* iosurface_ref { nullptr };
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ClientState {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ target_compile_features(ladybird_impl PUBLIC cxx_std_23)
|
|||
add_executable(ladybird MACOSX_BUNDLE
|
||||
main.mm
|
||||
)
|
||||
target_link_libraries(ladybird_impl PUBLIC "-framework Cocoa -framework UniformTypeIdentifiers" LibUnicode)
|
||||
target_link_libraries(ladybird_impl PUBLIC "-framework Cocoa -framework Metal -framework QuartzCore -framework UniformTypeIdentifiers" LibUnicode)
|
||||
target_link_libraries(ladybird PRIVATE ladybird_impl)
|
||||
|
||||
create_ladybird_bundle(ladybird)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#import <Interface/Event.h>
|
||||
#import <Interface/LadybirdWebView.h>
|
||||
#import <Interface/Menu.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
#import <Utilities/Conversions.h>
|
||||
|
||||
|
|
@ -41,6 +43,9 @@ struct HideCursor {
|
|||
|
||||
Optional<HideCursor> m_hidden_cursor;
|
||||
|
||||
id<MTLDevice> m_metal_device;
|
||||
id<MTLCommandQueue> m_metal_queue;
|
||||
|
||||
// We have to send key events for modifer keys, but AppKit does not generate key down/up events when only a modifier
|
||||
// key is pressed. Instead, we only receive an event that the modifier flags have changed, and we must determine for
|
||||
// ourselves whether the modifier key was pressed or released.
|
||||
|
|
@ -96,6 +101,9 @@ struct HideCursor {
|
|||
if (self = [super init]) {
|
||||
self.observer = observer;
|
||||
|
||||
m_metal_device = MTLCreateSystemDefaultDevice();
|
||||
m_metal_queue = [m_metal_device newCommandQueue];
|
||||
|
||||
auto* screens = [NSScreen screens];
|
||||
|
||||
Vector<Web::DevicePixelRect> screen_rects;
|
||||
|
|
@ -246,7 +254,10 @@ struct HideCursor {
|
|||
if (self == nil) {
|
||||
return;
|
||||
}
|
||||
[self setNeedsDisplay:YES];
|
||||
if (m_metal_device)
|
||||
[self presentMetalFrame];
|
||||
else
|
||||
[self setNeedsDisplay:YES];
|
||||
};
|
||||
|
||||
m_web_view_bridge->on_new_web_view = [weak_self](auto activate_tab, auto, auto page_index) {
|
||||
|
|
@ -909,32 +920,100 @@ struct HideCursor {
|
|||
|
||||
#pragma mark - NSView
|
||||
|
||||
- (void)drawRect:(NSRect)rect
|
||||
- (CALayer*)makeBackingLayer
|
||||
{
|
||||
CAMetalLayer* layer = [CAMetalLayer layer];
|
||||
layer.device = m_metal_device;
|
||||
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
layer.framebufferOnly = YES;
|
||||
layer.displaySyncEnabled = YES;
|
||||
layer.contentsGravity = kCAGravityTopLeft;
|
||||
return layer;
|
||||
}
|
||||
|
||||
- (BOOL)wantsUpdateLayer
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)presentMetalFrame
|
||||
{
|
||||
auto paintable = m_web_view_bridge->paintable();
|
||||
if (!paintable.has_value())
|
||||
return;
|
||||
|
||||
auto [bitmap, bitmap_size, iosurface_ref] = *paintable;
|
||||
if (!iosurface_ref)
|
||||
return;
|
||||
|
||||
CAMetalLayer* metal_layer = (CAMetalLayer*)self.layer;
|
||||
metal_layer.drawableSize = CGSizeMake(bitmap_size.width(), bitmap_size.height());
|
||||
metal_layer.contentsScale = m_web_view_bridge->device_pixel_ratio();
|
||||
|
||||
id<CAMetalDrawable> drawable = [metal_layer nextDrawable];
|
||||
if (!drawable)
|
||||
return;
|
||||
|
||||
MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
|
||||
width:bitmap.width()
|
||||
height:bitmap.height()
|
||||
mipmapped:NO];
|
||||
desc.storageMode = MTLStorageModeShared;
|
||||
desc.usage = MTLTextureUsageShaderRead;
|
||||
id<MTLTexture> src_texture = [m_metal_device newTextureWithDescriptor:desc
|
||||
iosurface:(IOSurfaceRef)iosurface_ref
|
||||
plane:0];
|
||||
|
||||
id<MTLCommandBuffer> cmd_buf = [m_metal_queue commandBuffer];
|
||||
id<MTLBlitCommandEncoder> blit = [cmd_buf blitCommandEncoder];
|
||||
[blit copyFromTexture:src_texture
|
||||
sourceSlice:0
|
||||
sourceLevel:0
|
||||
sourceOrigin:MTLOriginMake(0, 0, 0)
|
||||
sourceSize:MTLSizeMake(bitmap_size.width(), bitmap_size.height(), 1)
|
||||
toTexture:drawable.texture
|
||||
destinationSlice:0
|
||||
destinationLevel:0
|
||||
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
||||
[blit endEncoding];
|
||||
[cmd_buf presentDrawable:drawable];
|
||||
[cmd_buf commit];
|
||||
}
|
||||
|
||||
- (void)viewWillStartLiveResize
|
||||
{
|
||||
[super viewWillStartLiveResize];
|
||||
self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
|
||||
}
|
||||
|
||||
- (void)viewDidEndLiveResize
|
||||
{
|
||||
[super viewDidEndLiveResize];
|
||||
self.layerContentsPlacement = NSViewLayerContentsPlacementScaleAxesIndependently;
|
||||
}
|
||||
|
||||
- (void)updateLayer
|
||||
{
|
||||
// Metal path is driven directly by on_ready_to_paint via presentMetalFrame.
|
||||
if (m_metal_device)
|
||||
return;
|
||||
|
||||
// Fallback for non-IOSurface path (e.g. Intel Macs)
|
||||
auto paintable = m_web_view_bridge->paintable();
|
||||
if (!paintable.has_value()) {
|
||||
[super drawRect:rect];
|
||||
self.layer.contents = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
auto [bitmap, bitmap_size] = *paintable;
|
||||
auto [bitmap, bitmap_size, iosurface_ref] = *paintable;
|
||||
|
||||
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
|
||||
|
||||
static constexpr size_t BITS_PER_COMPONENT = 8;
|
||||
static constexpr size_t BITS_PER_PIXEL = 32;
|
||||
|
||||
auto* context = [[NSGraphicsContext currentContext] CGContext];
|
||||
CGContextSaveGState(context);
|
||||
|
||||
auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
|
||||
auto inverse_device_pixel_ratio = m_web_view_bridge->inverse_device_pixel_ratio();
|
||||
|
||||
CGContextScaleCTM(context, inverse_device_pixel_ratio, inverse_device_pixel_ratio);
|
||||
static auto color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
|
||||
auto* provider = CGDataProviderCreateWithData(nil, bitmap.scanline_u8(0), bitmap.size_in_bytes(), nil);
|
||||
auto image_rect = CGRectMake(rect.origin.x * device_pixel_ratio, rect.origin.y * device_pixel_ratio, bitmap_size.width(), bitmap_size.height());
|
||||
|
||||
static auto color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
|
||||
// Ideally, this would be NSBitmapImageRep, but the equivalent factory initWithBitmapDataPlanes: does
|
||||
// not seem to actually respect endianness. We need NSBitmapFormatThirtyTwoBitLittleEndian, but the
|
||||
|
|
@ -952,14 +1031,15 @@ struct HideCursor {
|
|||
NO,
|
||||
kCGRenderingIntentDefault);
|
||||
|
||||
auto* image = [[NSImage alloc] initWithCGImage:bitmap_image size:NSZeroSize];
|
||||
[image drawInRect:image_rect];
|
||||
self.layer.contentsScale = m_web_view_bridge->device_pixel_ratio();
|
||||
self.layer.contentsGravity = kCAGravityTopLeft;
|
||||
self.layer.contentsRect = CGRectMake(0, 0,
|
||||
(CGFloat)bitmap_size.width() / bitmap.width(),
|
||||
(CGFloat)bitmap_size.height() / bitmap.height());
|
||||
self.layer.contents = (__bridge id)bitmap_image;
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
CGDataProviderRelease(provider);
|
||||
CGImageRelease(bitmap_image);
|
||||
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
|
|
|
|||
|
|
@ -94,10 +94,12 @@ Optional<WebViewBridge::Paintable> WebViewBridge::paintable()
|
|||
{
|
||||
Gfx::Bitmap const* bitmap = nullptr;
|
||||
Gfx::IntSize bitmap_size;
|
||||
void* iosurface_ref = nullptr;
|
||||
|
||||
if (m_client_state.has_usable_bitmap) {
|
||||
bitmap = m_client_state.front_bitmap.bitmap.ptr();
|
||||
bitmap_size = m_client_state.front_bitmap.last_painted_size.to_type<int>();
|
||||
iosurface_ref = m_client_state.front_bitmap.iosurface_ref;
|
||||
} else {
|
||||
bitmap = m_backup_bitmap.ptr();
|
||||
bitmap_size = m_backup_bitmap_size.to_type<int>();
|
||||
|
|
@ -105,7 +107,7 @@ Optional<WebViewBridge::Paintable> WebViewBridge::paintable()
|
|||
|
||||
if (!bitmap)
|
||||
return {};
|
||||
return Paintable { *bitmap, bitmap_size };
|
||||
return Paintable { *bitmap, bitmap_size, iosurface_ref };
|
||||
}
|
||||
|
||||
void WebViewBridge::update_zoom()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public:
|
|||
struct Paintable {
|
||||
Gfx::Bitmap const& bitmap;
|
||||
Gfx::IntSize bitmap_size;
|
||||
void* iosurface_ref { nullptr };
|
||||
};
|
||||
Optional<Paintable> paintable();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue