Skip to content

Commit

Permalink
We don't always need to redraw the entire canvas
Browse files Browse the repository at this point in the history
If we end up dirtying only due to a series of TileRange reasons then we
only need to redraw the bounds of those tiles.

#7166

checked: split calc sheets and rtl calc sheets

Signed-off-by: Caolán McNamara <[email protected]>
Change-Id: If468b735bdff85408155fb23ebf6db891a449d5e
  • Loading branch information
caolanm authored and mmeeks committed Oct 31, 2023
1 parent 0bf054c commit 0453140
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 19 deletions.
82 changes: 65 additions & 17 deletions browser/src/layer/tile/CanvasSectionContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface SectionCallbacks {
onMultiTouchMove?: (point: Array<number>, dragDistance: number, e: TouchEvent) => void;
onMultiTouchEnd?: (e: TouchEvent) => void;
onResize?: () => void;
onDraw?: (frameCount?: number, elapsedTime?: number) => void;
onDraw?: (frameCount?: number, elapsedTime?: number, subsetBounds?: cool.Bounds) => void;
onDrawArea?: (area?: cool.Bounds, paneTopLeft?: cool.Point, canvasContext?: CanvasRenderingContext2D) => void;
onNewDocumentTopLeft?: (size: Array<number>) => void;
onRemove?: () => void;
Expand Down Expand Up @@ -320,9 +320,9 @@ class CanvasSectionObject {
}

/// Parameters: null || (frameCount, elapsedTime)
onDraw(frameCount?: number, elapsedTime?: number): void {
onDraw(frameCount?: number, elapsedTime?: number, subsetBounds?: cool.Bounds): void {
if (this.callbacks.onDraw)
return this.callbacks.onDraw(frameCount, elapsedTime);
return this.callbacks.onDraw(frameCount, elapsedTime, subsetBounds);
}

/// Optional Parameters: (area, paneTopLeft, canvasContext) - area is the area to be painted using canvasContext.
Expand Down Expand Up @@ -465,6 +465,12 @@ class CanvasSectionObject {
}
}

enum DirtyType {
NotDirty,
All,
TileRange
}

class CanvasSectionContainer {
/*
All events will be cached by this class and propagated to sections.
Expand Down Expand Up @@ -508,7 +514,8 @@ class CanvasSectionContainer {
// Above 2 properties can be used with documentBounds.
private drawingPaused: number = 0;
private drawingEnabled: boolean = true;
private dirty: boolean = false;
private dirty: DirtyType = DirtyType.NotDirty;
private dirtySubset: Set<any> = null; // If not null this is the set of coords that need redrawing.
private sectionsDirty: boolean = false;
private paintedEver: boolean = false;

Expand Down Expand Up @@ -659,15 +666,15 @@ class CanvasSectionContainer {
this.drawingEnabled = true;
if (this.drawingPaused === 0) {
// Trigger a forced repaint as drawing is not paused currently.
this.dirty = true;
this.setDirty(null);
this.paintOnResumeOrEnable();
}
}

pauseDrawing () {

if (this.drawingPaused++ === 0) {
this.dirty = false;
this.clearDirty();
}
}

Expand Down Expand Up @@ -698,13 +705,30 @@ class CanvasSectionContainer {
scrollSection.completePendingScroll(); // No painting, only dirtying.

if (this.dirty) {
this.requestReDraw();
this.dirty = false;
this.requestReDraw(this.dirtySubset);
this.clearDirty();
}
}

private setDirty() {
this.dirty = true;
private clearDirty() {
this.dirty = DirtyType.NotDirty;
this.dirtySubset = null;
}

private setDirty(coords: any) {
if (this.dirty == DirtyType.All)
return;
if (coords === null) {
this.dirty = DirtyType.All;
this.dirtySubset = null;
}
else
{
this.dirty = DirtyType.TileRange;
if (this.dirtySubset === null)
this.dirtySubset = new Set<any>();
this.dirtySubset.add(coords);
}
}

/**
Expand Down Expand Up @@ -941,15 +965,15 @@ class CanvasSectionContainer {
}
}

requestReDraw() {
requestReDraw(tileSubset: Set<any> = null) {
if (!this.drawingAllowed()) {
// Someone requested a redraw, but we're paused => schedule a redraw.
this.setDirty();
this.setDirty(null);
return;
}

if (!this.getAnimatingSectionName())
this.drawSections();
this.drawSections(null, null, tileSubset);
}

private propagateCursorPositionChanged() {
Expand Down Expand Up @@ -2028,16 +2052,36 @@ class CanvasSectionContainer {
this.context.translate(section.myTopLeft[0], section.myTopLeft[1]);
}

private drawSections (frameCount: number = null, elapsedTime: number = null) {
private shouldDrawSection (section: CanvasSectionObject) {
return section.isLocated && section.showSection && (!section.documentObject || section.isVisible);
}

private drawSections (frameCount: number = null, elapsedTime: number = null, tileSubset: Set<any> = null) {
this.context.setTransform(1, 0, 0, 1, 0, 0);

var subsetBounds: cool.Bounds = null;
// if there is a tileSubset we only want to draw the miniumum region of its bounds
if (tileSubset) {
var tileSection = <TilesSection>(this.getSectionWithName(L.CSections.Tiles.name));
if (tileSection && this.shouldDrawSection(tileSection)) {
subsetBounds = tileSection.getSubsetBounds(this.context, tileSubset);
}
if (subsetBounds) {
this.context.save();

this.context.translate(tileSection.myTopLeft[0], tileSection.myTopLeft[1]);
tileSection.clipSubsetBounds(this.context, subsetBounds);
this.context.translate(-tileSection.myTopLeft[0], -tileSection.myTopLeft[1]);
}
}

if (!this.zoomChanged) {
this.clearCanvas();
}

this.context.font = String(20 * app.dpiScale) + 'px Verdana';
for (var i: number = 0; i < this.sections.length; i++) {
if (this.sections[i].isLocated && this.sections[i].showSection && (!this.sections[i].documentObject || this.sections[i].isVisible)) {
if (this.shouldDrawSection(this.sections[i])) {
this.context.translate(this.sections[i].myTopLeft[0], this.sections[i].myTopLeft[1]);
if (this.sections[i].backgroundColor) {
this.context.globalAlpha = this.sections[i].backgroundOpacity;
Expand All @@ -2046,7 +2090,7 @@ class CanvasSectionContainer {
this.context.globalAlpha = 1;
}

this.sections[i].onDraw(frameCount, elapsedTime);
this.sections[i].onDraw(frameCount, elapsedTime, subsetBounds);

if (this.sections[i].borderColor) { // If section's border is set, draw its borders after section's "onDraw" function is called.
this.context.lineWidth = app.dpiScale;
Expand All @@ -2058,6 +2102,10 @@ class CanvasSectionContainer {
}
}

if (subsetBounds) {
this.context.restore();
}

this.paintedEver = true;
//this.drawSectionBorders();
}
Expand Down Expand Up @@ -2167,7 +2215,7 @@ class CanvasSectionContainer {
}
else {
this.sectionsDirty = true;
this.dirty = true;
this.setDirty(null);
}
}

Expand Down
2 changes: 1 addition & 1 deletion browser/src/layer/tile/CanvasTileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ L.CanvasTileLayer = L.Layer.extend({
// Don't paint the tile, only dirty the sectionsContainer if it is in the visible area.
// _emitSlurpedTileEvents() will repaint canvas (if it is dirty).
if (this._painter.coordsIntersectVisible(coords)) {
this._painter._sectionContainer.setDirty();
this._painter._sectionContainer.setDirty(coords);
}
},

Expand Down
46 changes: 45 additions & 1 deletion browser/src/layer/tile/TilesSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ class TilesSection extends CanvasSectionObject {
}
}

// the bounding box of this set of tiles
public getSubsetBounds(canvasCtx: CanvasRenderingContext2D, tileSubset: Set<any>): cool.Bounds {

// don't do anything for this atypical varient
if (app.file.fileBasedView)
return null;

var ctx = this.sectionProperties.tsManager._paintContext();

var bounds: cool.Bounds;
for (const coords of tileSubset) {
var topLeft = new L.Point(coords.getPos().x, coords.getPos().y);
var rightBottom = new L.Point(topLeft.x + ctx.tileSize.x, topLeft.y + ctx.tileSize.y);

if (bounds === undefined)
bounds = new cool.Bounds(topLeft, rightBottom);
else {
bounds.extend(topLeft).extend(rightBottom);
}
}

return bounds;
}

public clipSubsetBounds(canvasCtx: CanvasRenderingContext2D, subsetBounds: cool.Bounds): void {

var ctx = this.sectionProperties.tsManager._paintContext();
ctx.viewBounds.round();

canvasCtx.beginPath();
var rect = subsetBounds.toRectangle();

this.beforeDraw(canvasCtx);
canvasCtx.rect(rect[0] - ctx.viewBounds.min.x, rect[1] - ctx.viewBounds.min.y, rect[2], rect[3]);
this.afterDraw(canvasCtx);

canvasCtx.clip();
}

drawTileInPane (tile: any, tileBounds: any, paneBounds: any, paneOffset: any, canvasCtx: CanvasRenderingContext2D, clearBackground: boolean, now: Date) {
// intersect - to avoid state thrash through clipping
var crop = new L.Bounds(tileBounds.min, tileBounds.max);
Expand Down Expand Up @@ -343,7 +382,7 @@ class TilesSection extends CanvasSectionObject {
}
}

public onDraw () {
public onDraw (frameCount: number = null, elapsedTime: number = null, subsetBounds: cool.Bounds = null) {
if (this.containerObject.isInZoomAnimation())
return;

Expand Down Expand Up @@ -372,9 +411,14 @@ class TilesSection extends CanvasSectionObject {
var now = new Date();
var debugForcePaint = this.sectionProperties.docLayer._debug;
this.forEachTileInView(zoom, part, mode, ctx, function (tile: any, coords: any): boolean {

if (doneTiles.has(coords.key()))
return true;

// We can skip this tile if we only want to draw a subset, and it falls outside that
if (subsetBounds && !subsetBounds.intersects(docLayer._coordsToPixBounds(coords)))
return true;

// Ensure tile is within document bounds.
if (tile && docLayer._isValidTile(coords)) {
if (!this.isJSDOM) { // perf-test code
Expand Down

0 comments on commit 0453140

Please sign in to comment.