You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
9.4 KiB
197 lines
9.4 KiB
// Copyright © 2018 The CefSharp Authors. All rights reserved. |
|
// |
|
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. |
|
|
|
using System; |
|
using System.IO.MemoryMappedFiles; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Media.Imaging; |
|
using System.Windows.Threading; |
|
using Rect = CefSharp.Structs.Rect; |
|
|
|
namespace CefSharp.Wpf.Rendering |
|
{ |
|
/// <summary> |
|
/// WritableBitmapRenderHandler - creates/updates an WritableBitmap |
|
/// Uses a MemoryMappedFile for double buffering when the size matches |
|
/// or creates a new WritableBitmap when required |
|
/// </summary> |
|
/// <seealso cref="CefSharp.Wpf.IRenderHandler" /> |
|
public class WritableBitmapRenderHandler : AbstractRenderHandler |
|
{ |
|
private readonly double dpiX; |
|
private readonly double dpiY; |
|
private readonly bool invalidateDirtyRect; |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="WritableBitmapRenderHandler"/> class. |
|
/// </summary> |
|
/// <param name="dpiX">The dpi x.</param> |
|
/// <param name="dpiY">The dpi y.</param> |
|
/// <param name="invalidateDirtyRect">if true then only the direct rectangle will be updated, otherwise the whole bitmap will be redrawn</param> |
|
/// <param name="dispatcherPriority">priority at which the bitmap will be updated on the UI thread</param> |
|
public WritableBitmapRenderHandler(double dpiX, double dpiY, bool invalidateDirtyRect = true, DispatcherPriority dispatcherPriority = DispatcherPriority.Render) |
|
{ |
|
this.dpiX = dpiX; |
|
this.dpiY = dpiY; |
|
this.invalidateDirtyRect = invalidateDirtyRect; |
|
this.dispatcherPriority = dispatcherPriority; |
|
} |
|
|
|
/// <summary> |
|
/// When true if the Dirty Rect (rectangle that's to be updated) |
|
/// is smaller than the full width/height then only copy the Dirty Rect |
|
/// from the CEF native buffer to our own managed buffer. |
|
/// Set to true to improve performance when only a small portion of the screen is updated. |
|
/// Defaults to false currently. |
|
/// </summary> |
|
public bool CopyOnlyDirtyRect { get; set; } |
|
|
|
/// <inheritdoc/> |
|
protected override void CreateOrUpdateBitmap(bool isPopup, Rect dirtyRect, IntPtr buffer, int width, int height, Image image, ref Size currentSize, ref MemoryMappedFile mappedFile, ref MemoryMappedViewAccessor viewAccessor) |
|
{ |
|
bool createNewBitmap = false; |
|
|
|
lock (lockObject) |
|
{ |
|
int pixels = width * height; |
|
int numberOfBytes = pixels * BytesPerPixel; |
|
|
|
createNewBitmap = mappedFile == null || currentSize.Height != height || currentSize.Width != width; |
|
|
|
if (createNewBitmap) |
|
{ |
|
//If the MemoryMappedFile is smaller than we need then create a larger one |
|
//If it's larger then we need then rather than going through the costly expense of |
|
//allocating a new one we'll just use the old one and only access the number of bytes we require. |
|
if (viewAccessor == null || viewAccessor.Capacity < numberOfBytes) |
|
{ |
|
ReleaseMemoryMappedView(ref mappedFile, ref viewAccessor); |
|
|
|
mappedFile = MemoryMappedFile.CreateNew(null, numberOfBytes, MemoryMappedFileAccess.ReadWrite); |
|
|
|
viewAccessor = mappedFile.CreateViewAccessor(); |
|
} |
|
|
|
currentSize.Height = height; |
|
currentSize.Width = width; |
|
} |
|
|
|
if (CopyOnlyDirtyRect) |
|
{ |
|
// For full buffer update we just perform a simple copy |
|
// otherwise only a portion will be updated. |
|
if (width == dirtyRect.Width && height == dirtyRect.Height) |
|
{ |
|
NativeMethodWrapper.MemoryCopy(viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(), buffer, numberOfBytes); |
|
} |
|
else |
|
{ |
|
//TODO: We can probably perform some minor optimisations here. |
|
//var numberOfBytesToCopy = dirtyRect.Width * BytesPerPixel; |
|
//var safeMemoryMappedViewHandle = viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(); |
|
|
|
//for (int offset = width * dirtyRect.Y + dirtyRect.X; offset < (dirtyRect.Y + dirtyRect.Height) * width; offset += width) |
|
//{ |
|
// var b = offset * BytesPerPixel; |
|
// NativeMethodWrapper.MemoryCopy(safeMemoryMappedViewHandle + b, buffer + b, numberOfBytesToCopy); |
|
//} |
|
|
|
for (int offset = width * dirtyRect.Y + dirtyRect.X; offset < (dirtyRect.Y + dirtyRect.Height) * width; offset += width) |
|
{ |
|
NativeMethodWrapper.MemoryCopy(viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle() + offset * BytesPerPixel, buffer + offset * BytesPerPixel, dirtyRect.Width * BytesPerPixel); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
NativeMethodWrapper.MemoryCopy(viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(), buffer, numberOfBytes); |
|
} |
|
|
|
//Take a reference to the sourceBuffer that's used to update our WritableBitmap, |
|
//once we're on the UI thread we need to check if it's still valid |
|
var sourceBuffer = viewAccessor.SafeMemoryMappedViewHandle; |
|
|
|
image.Dispatcher.BeginInvoke((Action)(() => |
|
{ |
|
lock (lockObject) |
|
{ |
|
if (sourceBuffer.IsClosed || sourceBuffer.IsInvalid) |
|
{ |
|
return; |
|
} |
|
|
|
var size = isPopup ? popupSize : viewSize; |
|
|
|
//If OnPaint is called multiple times before |
|
//our BeginInvoke call we check the size matches our most recent |
|
//update, the buffer has already been overriden (frame is dropped effectively) |
|
//so we ignore this call |
|
//https://github.com/cefsharp/CefSharp/issues/3114 |
|
if (size.Width != width || size.Height != height) |
|
{ |
|
return; |
|
} |
|
|
|
if (createNewBitmap || image.Source is null) |
|
{ |
|
if (image.Source != null) |
|
{ |
|
image.Source = null; |
|
GC.Collect(1); |
|
} |
|
|
|
image.Source = new WriteableBitmap(width, height, dpiX, dpiY, PixelFormat, null); |
|
} |
|
|
|
var stride = width * BytesPerPixel; |
|
var noOfBytes = stride * height; |
|
|
|
var bitmap = (WriteableBitmap)image.Source; |
|
|
|
//When agressively resizing the ChromiumWebBrowser sometimes |
|
//we can end up with our buffer size not matching our bitmap size |
|
//Just ignore these frames as the rendering should eventually catch up |
|
//(CEF can generate multiple frames before WPF has performed a render cycle) |
|
//https://github.com/cefsharp/CefSharp/issues/3474 |
|
if (width > bitmap.PixelWidth || height > bitmap.PixelHeight) |
|
{ |
|
return; |
|
} |
|
|
|
var sourceBufferPtr = sourceBuffer.DangerousGetHandle(); |
|
|
|
// Issue https://github.com/cefsharp/CefSharp/issues/4426 |
|
if (sourceBufferPtr == IntPtr.Zero) |
|
{ |
|
return; |
|
} |
|
|
|
//By default we'll only update the dirty rect, for those that run into a MILERR_WIN32ERROR Exception (#2035) |
|
//it's desirably to either upgrade to a newer .Net version (only client runtime needs to be installed, not compiled |
|
//against a newer version. Or invalidate the whole bitmap |
|
if (invalidateDirtyRect) |
|
{ |
|
// Update the dirty region |
|
var sourceRect = new Int32Rect(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height); |
|
|
|
bitmap.Lock(); |
|
bitmap.WritePixels(sourceRect, sourceBufferPtr, noOfBytes, stride, dirtyRect.X, dirtyRect.Y); |
|
bitmap.Unlock(); |
|
} |
|
else |
|
{ |
|
// Update whole bitmap |
|
var sourceRect = new Int32Rect(0, 0, width, height); |
|
|
|
bitmap.Lock(); |
|
bitmap.WritePixels(sourceRect, sourceBufferPtr, noOfBytes, stride); |
|
bitmap.Unlock(); |
|
} |
|
} |
|
}), dispatcherPriority); |
|
} |
|
} |
|
} |
|
}
|
|
|