From 48c8fe65bc38b13208ad4f836ed3099b19ed2a89 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Mon, 26 Feb 2024 11:02:21 +0100 Subject: [PATCH 01/19] bind viewport to graph --- src/renderer/renderer.ts | 159 ++++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 27 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index b078da02..67854361 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2263,6 +2263,55 @@ export class SDFGRenderer extends EventEmitter { return true; } + // Checks if pan mouse movement is in the bounds of the graph. + // Takes the current visible_rect as input and computes if the pan mouse movement (movX, movY) is + // allowed and additionally corrects it to have a smooth view pan blocking. + // Returns: corrected movement x/y coordinates to input into this.canvas_manager?.translate() + public pan_movement_in_bounds(visible_rect: SimpleRect, movX: number, movY: number) { + + const visible_rectCenter = { + x: (visible_rect.x + (visible_rect.w / 2)), + y: (visible_rect.y + (visible_rect.h / 2)) + } + + const graphLimits = { + minX: 0, + minY: 0, + maxX: (this.graph as any).width, + maxY: (this.graph as any).height, + }; + + let outofboundsX = 0; + let outofboundsY = 0; + + if (visible_rectCenter.x < graphLimits.minX) { + outofboundsX = -1; + } + else if (visible_rectCenter.x > graphLimits.maxX) { + outofboundsX = 1; + } + if (visible_rectCenter.y < graphLimits.minY) { + outofboundsY = -1; + } + else if (visible_rectCenter.y > graphLimits.maxY) { + outofboundsY = 1; + } + + let correctedMovement = { + x: movX, + y: movY + } + + if ((outofboundsX === -1 && correctedMovement.x > 0) || (outofboundsX === 1 && correctedMovement.x < 0)) { + correctedMovement.x = 0; + } + if ((outofboundsY === -1 && correctedMovement.y > 0) || (outofboundsY === 1 && correctedMovement.y < 0)) { + correctedMovement.y = 0; + } + + return correctedMovement; + } + // TODO(later): Improve event system using event types (instanceof) instead // of passing string eventtypes. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ @@ -2392,20 +2441,41 @@ export class SDFGRenderer extends EventEmitter { // Mark for redraw dirty = true; } else { - this.canvas_manager?.translate( - event.movementX, event.movementY - ); + + // Mouse move in panning mode + if (this.visible_rect) { + + // Check if mouse panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, event.movementX, event.movementY + ); - // Mark for redraw - dirty = true; + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + + // Mark for redraw + dirty = true; + } + } } else if (this.drag_start && event.buttons & 4) { // Pan the view with the middle mouse button this.dragging = true; - this.canvas_manager?.translate( - event.movementX, event.movementY - ); - dirty = true; + if (this.visible_rect) { + + // Check if mouse panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, event.movementX, event.movementY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + dirty = true; + } element_focus_changed = true; } else { this.drag_start = null; @@ -2418,12 +2488,21 @@ export class SDFGRenderer extends EventEmitter { // Different number of touches, ignore and reset drag_start this.drag_start = event; } else if (event.touches.length === 1) { // Move/drag - this.canvas_manager?.translate( - event.touches[0].clientX - - this.drag_start.touches[0].clientX, - event.touches[0].clientY - - this.drag_start.touches[0].clientY - ); + if (this.visible_rect) { + + const movX = event.touches[0].clientX - this.drag_start.touches[0].clientX; + const movY = event.touches[0].clientY - this.drag_start.touches[0].clientY; + + // Check if panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + } this.drag_start = event; // Mark for redraw @@ -2448,15 +2527,28 @@ export class SDFGRenderer extends EventEmitter { ); const newCenter = [(x1 + x2) / 2.0, (y1 + y2) / 2.0]; - // First, translate according to movement of center point - this.canvas_manager?.translate( - newCenter[0] - oldCenter[0], newCenter[1] - oldCenter[1] - ); - // Then scale - this.canvas_manager?.scale( - currentDistance / initialDistance, newCenter[0], - newCenter[1] - ); + if (this.visible_rect) { + + // First, translate according to movement of center point + const movX = newCenter[0] - oldCenter[0]; + const movY = newCenter[1] - oldCenter[1]; + + // Check if movement is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + + // Then scale + this.canvas_manager?.scale( + currentDistance / initialDistance, newCenter[0], + newCenter[1] + ); + } this.drag_start = event; @@ -2470,9 +2562,22 @@ export class SDFGRenderer extends EventEmitter { // If vertical scroll navigation is turned on, use this to // move the viewport up and down. If the control key is held // down while scrolling, treat it as a typical zoom operation. - this.canvas_manager?.translate(0, -event.deltaY); - dirty = true; - element_focus_changed = true; + if (this.visible_rect) { + const movX = 0; + const movY = -event.deltaY; + + // Check if scroll is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + dirty = true; + element_focus_changed = true; + } } else { // Get physical x,y coordinates (rather than canvas coordinates) const br = this.canvas?.getBoundingClientRect(); From 01a2142587012198c9a17df4f96fbb9041388afe Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 28 Feb 2024 11:50:52 +0100 Subject: [PATCH 02/19] bind viewport to graph bounds && comments --- package-lock.json | 4 ++-- src/renderer/renderer.ts | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c48fcfd5..b88520dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@spcl/sdfv", - "version": "1.1.5", + "version": "1.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@spcl/sdfv", - "version": "1.1.5", + "version": "1.1.6", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 7d2ead9c..b8f86153 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2267,8 +2267,9 @@ export class SDFGRenderer extends EventEmitter { } // Checks if pan mouse movement is in the bounds of the graph. - // Takes the current visible_rect as input and computes if the pan mouse movement (movX, movY) is - // allowed and additionally corrects it to have a smooth view pan blocking. + // Takes the current visible_rect as input and computes if its center is + // within the graph bounds. The pan mouse movement (movX, movY) is + // corrected accordingly to have a smooth view pan blocking. // Returns: corrected movement x/y coordinates to input into this.canvas_manager?.translate() public pan_movement_in_bounds(visible_rect: SimpleRect, movX: number, movY: number) { @@ -2284,6 +2285,8 @@ export class SDFGRenderer extends EventEmitter { maxY: (this.graph as any).height, }; + // Compute where the visible_rectCenter is out of bounds: + // outofboundsX/Y === 0 means not out of bounds let outofboundsX = 0; let outofboundsY = 0; @@ -2300,15 +2303,19 @@ export class SDFGRenderer extends EventEmitter { outofboundsY = 1; } + // Take uncorrected mouse event movement as default let correctedMovement = { x: movX, y: movY } - if ((outofboundsX === -1 && correctedMovement.x > 0) || (outofboundsX === 1 && correctedMovement.x < 0)) { + // Correct mouse movement if necessary + if ((outofboundsX === -1 && correctedMovement.x > 0) || + (outofboundsX === 1 && correctedMovement.x < 0)) { correctedMovement.x = 0; } - if ((outofboundsY === -1 && correctedMovement.y > 0) || (outofboundsY === 1 && correctedMovement.y < 0)) { + if ((outofboundsY === -1 && correctedMovement.y > 0) || + (outofboundsY === 1 && correctedMovement.y < 0)) { correctedMovement.y = 0; } From e8c05423aa63608d94995a96591505894f7b83ef Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 15:04:12 +0100 Subject: [PATCH 03/19] first LOD optimizations --- src/renderer/canvas_manager.ts | 2 + src/renderer/renderer_elements.ts | 275 +++++++++++++++++++----------- src/sdfv.ts | 16 +- 3 files changed, 190 insertions(+), 103 deletions(-) diff --git a/src/renderer/canvas_manager.ts b/src/renderer/canvas_manager.ts index 93734fe3..b19e3c8f 100644 --- a/src/renderer/canvas_manager.ts +++ b/src/renderer/canvas_manager.ts @@ -733,6 +733,8 @@ export class CanvasManager { // (right-left)/width should be equivalent const left = this.mapPixelToCoordsX(0); const right = this.mapPixelToCoordsX(this.canvas.width); + // console.log('coordinates width: %f', (right - left)); + // console.log('canvas pixel width: %f', this.canvas.width); return (right - left) / this.canvas.width; } diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 01387404..39870e13 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -292,10 +292,14 @@ export class ControlFlowRegion extends ControlFlowBlock { if (visibleRect && visibleRect.x <= topleft.x && visibleRect.y <= topleft.y + SDFV.LINEHEIGHT && SDFVSettings.showStateNames) - ctx.fillText( - this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, - topleft.y + SDFV.LINEHEIGHT - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillText( + this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, + topleft.y + SDFV.LINEHEIGHT + ); + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -427,8 +431,12 @@ export class State extends BasicBlock { if (visible_rect && visible_rect.x <= topleft.x && visible_rect.y <= topleft.y + SDFV.LINEHEIGHT && - SDFVSettings.showStateNames) - ctx.fillText(this.label(), topleft.x, topleft.y + SDFV.LINEHEIGHT); + SDFVSettings.showStateNames) { + + if (!too_far_away_for_text(renderer, ctx)) { + ctx.fillText(this.label(), topleft.x, topleft.y + SDFV.LINEHEIGHT); + } + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -617,22 +625,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.lineTo(topleft.x + this.width, initBottomLineY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const initStatement = this.attributes().init_statement?.string_data; - const initTextY = ( - (topleft.y + (LoopRegion.INIT_SPACING / 2)) + - (SDFV.LINEHEIGHT / 2) - ); - if (initStatement) { - const initTextMetrics = ctx.measureText(initStatement); - const initTextX = this.x - (initTextMetrics.width / 2); - ctx.fillText(initStatement, initTextX, initTextY); - } + if (!too_far_away_for_text(renderer, ctx)) { - ctx.font = oldFont; - ctx.fillText( - 'init', topleft.x + LoopRegion.META_LABEL_MARGIN, initTextY - ); + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const initStatement = this.attributes().init_statement?.string_data; + const initTextY = ( + (topleft.y + (LoopRegion.INIT_SPACING / 2)) + + (SDFV.LINEHEIGHT / 2) + ); + if (initStatement) { + const initTextMetrics = ctx.measureText(initStatement); + const initTextX = this.x - (initTextMetrics.width / 2); + ctx.fillText(initStatement, initTextX, initTextY); + } + + ctx.font = oldFont; + ctx.fillText( + 'init', topleft.x + LoopRegion.META_LABEL_MARGIN, initTextY + ); + } } // Draw the condition (either on top if the loop is a regularly @@ -654,20 +665,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.moveTo(topleft.x, condLineY); ctx.lineTo(topleft.x + this.width, condLineY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const condStatement = this.attributes().loop_condition?.string_data; - const condTextY = ( - (condTopY + (LoopRegion.CONDITION_SPACING / 2)) + - (SDFV.LINEHEIGHT / 2) - ); - if (condStatement) { - const condTextMetrics = ctx.measureText(condStatement); - const condTextX = this.x - (condTextMetrics.width / 2); - ctx.fillText(condStatement, condTextX, condTextY); - ctx.font = oldFont; - ctx.fillText( - 'while', topleft.x + LoopRegion.META_LABEL_MARGIN, condTextY + + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const condStatement = this.attributes().loop_condition?.string_data; + const condTextY = ( + (condTopY + (LoopRegion.CONDITION_SPACING / 2)) + + (SDFV.LINEHEIGHT / 2) ); + if (condStatement) { + const condTextMetrics = ctx.measureText(condStatement); + const condTextX = this.x - (condTextMetrics.width / 2); + ctx.fillText(condStatement, condTextX, condTextY); + ctx.font = oldFont; + ctx.fillText( + 'while', topleft.x + LoopRegion.META_LABEL_MARGIN, condTextY + ); + } } // Draw the update statement if there is one. @@ -681,21 +697,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.lineTo(topleft.x + this.width, updateTopY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const updateStatement = - this.attributes().update_statement.string_data; - const updateTextY = ( - (updateTopY + (LoopRegion.UPDATE_SPACING / 2)) + - (SDFV.LINEHEIGHT / 2) - ); - const updateTextMetrics = ctx.measureText(updateStatement); - const updateTextX = this.x - (updateTextMetrics.width / 2); - ctx.fillText(updateStatement, updateTextX, updateTextY); - ctx.font = oldFont; - ctx.fillText( - 'update', topleft.x + LoopRegion.META_LABEL_MARGIN, - updateTextY - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const updateStatement = + this.attributes().update_statement.string_data; + const updateTextY = ( + (updateTopY + (LoopRegion.UPDATE_SPACING / 2)) + + (SDFV.LINEHEIGHT / 2) + ); + const updateTextMetrics = ctx.measureText(updateStatement); + const updateTextX = this.x - (updateTextMetrics.width / 2); + ctx.fillText(updateStatement, updateTextX, updateTextY); + ctx.font = oldFont; + ctx.fillText( + 'update', topleft.x + LoopRegion.META_LABEL_MARGIN, + updateTextY + ); + } } remainingHeight -= topSpacing; @@ -704,10 +724,14 @@ export class LoopRegion extends ControlFlowRegion { if (visibleRect && visibleRect.x <= topleft.x && visibleRect.y <= topleft.y + SDFV.LINEHEIGHT && SDFVSettings.showStateNames) - ctx.fillText( - this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, - topleft.y + topSpacing + SDFV.LINEHEIGHT - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillText( + this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, + topleft.y + topSpacing + SDFV.LINEHEIGHT + ); + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -792,17 +816,21 @@ export class SDFGNode extends SDFGElement { ctx.strokeRect(clamped.x, clamped.y, clamped.w, clamped.h); } if (this.label()) { - ctx.fillStyle = this.getCssProperty(renderer, fgstyle); - const textw = ctx.measureText(this.label()).width; - if (!visible_rect) - ctx.fillText( - this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 - ); - else if (visible_rect && visible_rect.x <= topleft.x && - visible_rect.y <= topleft.y + SDFV.LINEHEIGHT) - ctx.fillText( - this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty(renderer, fgstyle); + const textw = ctx.measureText(this.label()).width; + if (!visible_rect) + ctx.fillText( + this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 + ); + else if (visible_rect && visible_rect.x <= topleft.x && + visible_rect.y <= topleft.y + SDFV.LINEHEIGHT) + ctx.fillText( + this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 + ); + } } } @@ -1015,6 +1043,9 @@ export class Memlet extends Edge { // Straight line can be drawn ctx.lineTo(this.points[1].x, this.points[1].y); } else { + + // ctx.lineTo(this.points[this.points.length-1].x, this.points[this.points.length-1].y); + let i; for (i = 1; i < this.points.length - 2; i++) { const xm = (this.points[i].x + this.points[i + 1].x) / 2.0; @@ -1523,6 +1554,12 @@ export class AccessNode extends SDFGNode { if (this.strokeStyle(renderer) !== this.getCssProperty(renderer, '--color-default')) renderer.set_tooltip((c) => this.tooltip(c)); } + + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const textmetrics = ctx.measureText(this.label()); ctx.fillText( this.label(), this.x - textmetrics.width / 2.0, @@ -1637,6 +1674,11 @@ export class ScopeNode extends SDFGNode { renderer, '--node-foreground-color' ); + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + drawAdaptiveText( ctx, renderer, this.far_label(renderer), this.close_label(renderer), this.x, this.y, @@ -2063,6 +2105,11 @@ export class Tasklet extends SDFGNode { renderer, '--node-foreground-color' ); + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const ppp = canvas_manager.points_per_pixel(); if (!(ctx as any).lod || ppp < SDFV.TASKLET_LOD) { // If we are close to the tasklet, show its contents @@ -2122,17 +2169,20 @@ export class Reduce extends SDFGNode { if ((ctx as any).pdf) draw_shape(); ctx.fill(); - ctx.fillStyle = this.getCssProperty( - renderer, '--node-foreground-color' - ); - - const far_label = this.label().substring(4, this.label().indexOf(',')); - drawAdaptiveText( - ctx, renderer, far_label, - this.label(), this.x, this.y - this.height * 0.2, - this.width, this.height, - SDFV.SCOPE_LOD - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty( + renderer, '--node-foreground-color' + ); + const far_label = this.label().substring(4, this.label().indexOf(',')); + drawAdaptiveText( + ctx, renderer, far_label, + this.label(), this.x, this.y - this.height * 0.2, + this.width, this.height, + SDFV.SCOPE_LOD + ); + } } public shade( @@ -2189,17 +2239,22 @@ export class NestedSDFG extends SDFGNode { this.width - 5, this.height - 5 ); ctx.fill(); - ctx.fillStyle = this.getCssProperty( - renderer, '--node-foreground-color' - ); - let label = this.data.node.attributes.label; - if (!this.data.node.attributes.sdfg) - label += ' (not loaded)'; - const textmetrics = ctx.measureText(label); - ctx.fillText( - label, this.x - textmetrics.width / 2.0, - this.y + SDFV.LINEHEIGHT / 4.0 - ); + + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty( + renderer, '--node-foreground-color' + ); + let label = this.data.node.attributes.label; + if (!this.data.node.attributes.sdfg) + label += ' (not loaded)'; + const textmetrics = ctx.measureText(label); + ctx.fillText( + label, this.x - textmetrics.width / 2.0, + this.y + SDFV.LINEHEIGHT / 4.0 + ); + } } else { // Draw square around nested SDFG. super.draw( @@ -2213,16 +2268,19 @@ export class NestedSDFG extends SDFGNode { drawSDFG(renderer, ctx, this.data.graph, mousepos); } else { // Expanded, but no SDFG present or loaded yet. - const errColor = this.getCssProperty( - renderer, '--node-missing-background-color' - ); - const label = 'No SDFG loaded'; - const textmetrics = ctx.measureText(label); - ctx.fillStyle = errColor; - ctx.fillText( - label, this.x - textmetrics.width / 2.0, - this.y + SDFV.LINEHEIGHT / 4.0 - ); + if (!too_far_away_for_text(renderer, ctx)) { + + const errColor = this.getCssProperty( + renderer, '--node-missing-background-color' + ); + const label = 'No SDFG loaded'; + const textmetrics = ctx.measureText(label); + ctx.fillStyle = errColor; + ctx.fillText( + label, this.x - textmetrics.width / 2.0, + this.y + SDFV.LINEHEIGHT / 4.0 + ); + } } } } @@ -2325,6 +2383,12 @@ export class LibraryNode extends SDFGNode { ctx.fillStyle = this.getCssProperty( renderer, '--node-foreground-color' ); + + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const textw = ctx.measureText(this.label()).width; ctx.fillText( this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 @@ -2354,6 +2418,24 @@ export class LibraryNode extends SDFGNode { ////////////////////////////////////////////////////// +// Checks if graph is zoomed out far (defined by SDFV.TEXT_LOD), using Points-per-Pixel +// Used before ctx.fillText calls to only draw text when zoomed in close enough +function too_far_away_for_text(renderer: SDFGRenderer, ctx: CanvasRenderingContext2D): boolean { + + const canvas_manager = renderer.get_canvas_manager(); + const ppp = canvas_manager?.points_per_pixel(); + if (ppp) { + if ((!(ctx as any).lod || ppp > SDFV.TEXT_LOD)) { + return true; + } + else { + return false; + } + } + + return false; +} + /** * Batched drawing of graph edges, given a specific default color. * @@ -2525,6 +2607,7 @@ export function drawStateMachine( for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); + // TODO: Move this after the invisible state check below? const blockppp = Math.max(block.width, block.height) / ppp; if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); diff --git a/src/sdfv.ts b/src/sdfv.ts index 47fdbecd..e134497a 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -42,19 +42,21 @@ export class SDFV { public static LINEHEIGHT: number = 10; // Points-per-pixel threshold for drawing tasklet contents. - public static TASKLET_LOD: number = 0.35; + public static TASKLET_LOD: number = 0.35; // 0.35 // Points-per-pixel threshold for simple version of map nodes (label only). - public static SCOPE_LOD: number = 1.5; + public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. - public static EDGE_LOD: number = 8; + public static EDGE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. - public static NODE_LOD: number = 60.0; + public static NODE_LOD: number = 5.0; // 5.0 + // Points-per-pixel threshold for not drawing node text + public static TEXT_LOD: number = 1.5; // 1.5 // Pixel threshold for not drawing state contents. - public static STATE_LOD: number = 50; + public static STATE_LOD: number = 100; // 100 public static DEFAULT_CANVAS_FONTSIZE: number = 10; - public static DEFAULT_MAX_FONTSIZE: number = 50; - public static DEFAULT_FAR_FONT_MULTIPLIER: number = 16; + public static DEFAULT_MAX_FONTSIZE: number = 20; // 50 + public static DEFAULT_FAR_FONT_MULTIPLIER: number = 16; // 16 protected renderer: SDFGRenderer | null = null; protected localViewRenderer: LViewRenderer | null = null; From 443b284dec8eb5b4ff183065a3add3c225695559 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 15:57:40 +0100 Subject: [PATCH 04/19] minimap optimizations --- src/renderer/renderer.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index b8f86153..773bb36b 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -1535,13 +1535,20 @@ export class SDFGRenderer extends EventEmitter { let targetHeight = minDimSize; const maxPercentage = 0.22; if (targetHeight > this.canvas.height * maxPercentage) - targetHeight = this.canvas.height * maxPercentage; + targetHeight = Math.floor(this.canvas.height * maxPercentage); if (targetWidth > this.canvas.width * maxPercentage) - targetWidth = this.canvas.width * maxPercentage; - this.minimap_canvas.height = targetHeight; - this.minimap_canvas.width = targetWidth; - this.minimap_canvas.style.width = targetWidth.toString() + 'px'; - this.minimap_canvas.style.height = targetHeight.toString() + 'px'; + targetWidth = Math.floor(this.canvas.width * maxPercentage); + + // Prevent forced style reflow if nothing changed + // Can save about 0.5ms of computation + if (this.minimap_canvas.height !== targetHeight) { + this.minimap_canvas.height = targetHeight; + this.minimap_canvas.style.height = targetHeight.toString() + 'px'; + } + if (this.minimap_canvas.width !== targetWidth) { + this.minimap_canvas.width = targetWidth; + this.minimap_canvas.style.width = targetWidth.toString() + 'px'; + } // Set the zoom level and translation so everything is visible. const bb = { @@ -1566,11 +1573,17 @@ export class SDFGRenderer extends EventEmitter { if (n && this.minimap_ctx) n.simple_draw(this, this.minimap_ctx, undefined); }); - this.graph.edges().forEach(x => { - const e = this.graph?.edge(x); - if (e && this.minimap_ctx) - e.draw(this, this.minimap_ctx, undefined); - }); + + // Don't draw Interstate edges in the minimap: + // Small optimization thats can save ~1ms in computation + // The performance problem comes from the edges and their + // labels which are also drawn. + + // this.graph.edges().forEach(x => { + // const e = this.graph?.edge(x); + // if (e && this.minimap_ctx) + // e.draw(this, this.minimap_ctx, undefined); + // }); // Draw the viewport. if (this.visible_rect) { From cf6ded5d5d53c6b34c5841e7636bf367ddff7a3f Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 16:20:28 +0100 Subject: [PATCH 05/19] quickfix simple_draw after invisible check --- src/renderer/renderer_elements.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 39870e13..e4fac997 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2607,7 +2607,13 @@ export function drawStateMachine( for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); - // TODO: Move this after the invisible state check below? + // Skip invisible states. + if (lod && visibleRect && !block.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h + )) { + continue; + } + const blockppp = Math.max(block.width, block.height) / ppp; if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); @@ -2615,13 +2621,6 @@ export function drawStateMachine( continue; } - // Skip invisible states. - if (lod && visibleRect && !block.intersect( - visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h - )) { - continue; - } - block.draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); From cc73e54b6a8cf7d1474c0dc89a678d0e61b536d4 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 13 Mar 2024 14:11:04 +0100 Subject: [PATCH 06/19] added connector LOD --- src/renderer/renderer_elements.ts | 45 +++++++++++++++++-------------- src/sdfv.ts | 2 ++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index e4fac997..19f945f8 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2560,28 +2560,33 @@ export function drawStateContents( node.draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); - node.in_connectors.forEach((c: Connector) => { - let edge: Edge | null = null; - stateGraph.inEdges(nodeId)?.forEach((e) => { - const eobj = stateGraph.edge(e); - if (eobj.dst_connector == c.data.name) - edge = eobj as any; - }); - c.draw(renderer, ctx, mousePos, edge); - c.debug_draw(renderer, ctx); - }); - node.out_connectors.forEach((c: Connector) => { - let edge: Edge | null = null; - stateGraph.outEdges(nodeId)?.forEach((e) => { - const eobj = stateGraph.edge(e); - if (eobj.src_connector == c.data.name) - edge = eobj as any; - }); + // Only draw connectors when close enough to see them + if (!lod || ppp < SDFV.CONNECTOR_LOD) { - c.draw(renderer, ctx, mousePos, edge); - c.debug_draw(renderer, ctx); - }); + node.in_connectors.forEach((c: Connector) => { + let edge: Edge | null = null; + stateGraph.inEdges(nodeId)?.forEach((e) => { + const eobj = stateGraph.edge(e); + if (eobj.dst_connector == c.data.name) + edge = eobj as any; + }); + + c.draw(renderer, ctx, mousePos, edge); + c.debug_draw(renderer, ctx); + }); + node.out_connectors.forEach((c: Connector) => { + let edge: Edge | null = null; + stateGraph.outEdges(nodeId)?.forEach((e) => { + const eobj = stateGraph.edge(e); + if (eobj.src_connector == c.data.name) + edge = eobj as any; + }); + + c.draw(renderer, ctx, mousePos, edge); + c.debug_draw(renderer, ctx); + }); + } } if (lod && ppp > SDFV.EDGE_LOD) diff --git a/src/sdfv.ts b/src/sdfv.ts index e134497a..7bbb4ea6 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -47,6 +47,8 @@ export class SDFV { public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 + // Points-per-pixel threshold for not drawing connectors + public static CONNECTOR_LOD = 2.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node text From f330b934f06cd4190c35fd651094bc3ccd2c2e93 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 13 Mar 2024 16:36:36 +0100 Subject: [PATCH 07/19] first mousehandler optimizations (only highlight when close) --- src/renderer/renderer.ts | 188 +++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 86 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 773bb36b..516b1626 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2670,98 +2670,113 @@ export class SDFGRenderer extends EventEmitter { this.tooltip = null; - // De-highlight all elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - obj.hovered = false; - obj.highlighted = false; - if (obj instanceof Tasklet) { - for (const t of obj.inputTokens) - t.highlighted = false; - for (const t of obj.outputTokens) - t.highlighted = false; - } - } - ); - // Mark hovered and highlighted elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - const intersected = obj.intersect( - this.mousepos!.x, this.mousepos!.y, 0, 0 - ); - - // Highlight all edges of the memlet tree - if (intersected && obj instanceof Edge && - obj.parent_id !== null) { - const tree = this.get_nested_memlet_tree(obj); - tree.forEach(te => { - if (te !== obj && te !== undefined) { - te.highlighted = true; + // Only do highlighting re-computation if view is close enough to actually see the + // highlights. Done by points-per-pixel metric using SDFV.NODE_LOD as the threshold. + // Hence, the highlights only update/become visible if there are nodes visible to hover over. + // This creatly reduces CPU utilization when moving/hovering the mouse over large graphs. + if (this.canvas_manager) { + const ppp = this.canvas_manager.points_per_pixel(); + if (ppp < SDFV.NODE_LOD) { + + // De-highlight all elements. + this.doForVisibleElements( + (type: any, e: any, obj: any) => { + obj.hovered = false; + obj.highlighted = false; + if (obj instanceof Tasklet) { + for (const t of obj.inputTokens) + t.highlighted = false; + for (const t of obj.outputTokens) + t.highlighted = false; } - }); - } - - // Highlight all access nodes with the same name in the same - // nested sdfg - if (intersected && obj instanceof AccessNode) { - traverseSDFGScopes( - this.cfg_list[obj.sdfg.cfg_list_id], - (node: any) => { - // If node is a state, then visit sub-scope - if (node instanceof State) - return true; - if (node instanceof AccessNode && - node.data.node.label === obj.data.node.label) - node.highlighted = true; - // No need to visit sub-scope - return false; - } - ); - } - - if (intersected && obj instanceof Connector) { - // Highlight all access nodes with the same name as the - // hovered connector in the nested sdfg - if (e.graph) { - const nested_graph = - e.graph.node(obj.parent_id).data.graph; - if (nested_graph) { - traverseSDFGScopes(nested_graph, (node: any) => { - // If node is a state, then visit sub-scope - if (node instanceof State) { - return true; - } - if (node instanceof AccessNode && - node.data.node.label === obj.label()) { - node.highlighted = true; + } + ); + // Mark hovered and highlighted elements. + this.doForVisibleElements( + (type: any, e: any, obj: any) => { + const intersected = obj.intersect( + this.mousepos!.x, this.mousepos!.y, 0, 0 + ); + + // Highlight all edges of the memlet tree + if (intersected && obj instanceof Edge && + obj.parent_id !== null) { + const tree = this.get_nested_memlet_tree(obj); + tree.forEach(te => { + if (te !== obj && te !== undefined) { + te.highlighted = true; } - // No need to visit sub-scope - return false; }); } - } - - // Similarly, highlight any identifiers in a connector's - // tasklet, if applicable. - if (obj.linkedElem && obj.linkedElem instanceof Tasklet) { - if (obj.connectorType === 'in') { - for (const token of obj.linkedElem.inputTokens) { - if (token.token === obj.data.name) - token.highlighted = true; + + // Highlight all access nodes with the same name in the same + // nested sdfg + if (intersected && obj instanceof AccessNode) { + traverseSDFGScopes( + this.cfg_list[obj.sdfg.cfg_list_id], + (node: any) => { + // If node is a state, then visit sub-scope + if (node instanceof State) + return true; + if (node instanceof AccessNode && + node.data.node.label === obj.data.node.label) + node.highlighted = true; + // No need to visit sub-scope + return false; + } + ); + } + + if (intersected && obj instanceof Connector) { + // Highlight all access nodes with the same name as the + // hovered connector in the nested sdfg + if (e.graph) { + const nested_graph = + e.graph.node(obj.parent_id).data.graph; + if (nested_graph) { + traverseSDFGScopes(nested_graph, (node: any) => { + // If node is a state, then visit sub-scope + if (node instanceof State) { + return true; + } + if (node instanceof AccessNode && + node.data.node.label === obj.label()) { + node.highlighted = true; + } + // No need to visit sub-scope + return false; + }); + } } - } else { - for (const token of obj.linkedElem.outputTokens) { - if (token.token === obj.data.name) - token.highlighted = true; + + // Similarly, highlight any identifiers in a connector's + // tasklet, if applicable. + if (obj.linkedElem && obj.linkedElem instanceof Tasklet) { + if (obj.connectorType === 'in') { + for (const token of obj.linkedElem.inputTokens) { + if (token.token === obj.data.name) + token.highlighted = true; + } + } else { + for (const token of obj.linkedElem.outputTokens) { + if (token.token === obj.data.name) + token.highlighted = true; + } + } } } + + if (intersected) + obj.hovered = true; } - } + ); + + // TODO: only redraw when highlighting changed + dirty = true; - if (intersected) - obj.hovered = true; } - ); + } + // If adding an edge, mark/highlight the first/from element, if it has // already been selected. @@ -2772,10 +2787,11 @@ export class SDFGRenderer extends EventEmitter { this.add_edge_start_conn.highlighted = true; } - if (evtype === 'mousemove') { - // TODO: Draw only if elements have changed - dirty = true; - } + // TODO: check everything works if this is commented out + // if (evtype === 'mousemove') { + // // TODO: Draw only if elements have changed + // dirty = true; + // } if (evtype === 'dblclick') { const sdfg = (foreground_elem ? foreground_elem.sdfg : null); From cce8ee01c84f411b533c2cb8f6c948bc109a4f9f Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Thu, 14 Mar 2024 15:23:47 +0100 Subject: [PATCH 08/19] mousehandler highlighting optimization (only redraw on change) --- src/renderer/renderer.ts | 87 +++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 516b1626..d4d783c2 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2678,19 +2678,7 @@ export class SDFGRenderer extends EventEmitter { const ppp = this.canvas_manager.points_per_pixel(); if (ppp < SDFV.NODE_LOD) { - // De-highlight all elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - obj.hovered = false; - obj.highlighted = false; - if (obj instanceof Tasklet) { - for (const t of obj.inputTokens) - t.highlighted = false; - for (const t of obj.outputTokens) - t.highlighted = false; - } - } - ); + let highlighting_changed = false; // Mark hovered and highlighted elements. this.doForVisibleElements( (type: any, e: any, obj: any) => { @@ -2699,19 +2687,26 @@ export class SDFGRenderer extends EventEmitter { ); // Highlight all edges of the memlet tree - if (intersected && obj instanceof Edge && - obj.parent_id !== null) { + if (obj instanceof Edge && obj.parent_id !== null) { const tree = this.get_nested_memlet_tree(obj); tree.forEach(te => { if (te !== obj && te !== undefined) { - te.highlighted = true; + if (intersected && te.highlighted === false) { + + te.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && te.highlighted === true) { + te.highlighted = false; + highlighting_changed = true; + } } }); } // Highlight all access nodes with the same name in the same // nested sdfg - if (intersected && obj instanceof AccessNode) { + if (obj instanceof AccessNode) { traverseSDFGScopes( this.cfg_list[obj.sdfg.cfg_list_id], (node: any) => { @@ -2720,14 +2715,21 @@ export class SDFGRenderer extends EventEmitter { return true; if (node instanceof AccessNode && node.data.node.label === obj.data.node.label) - node.highlighted = true; + if (intersected && node.highlighted === false) { + node.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && node.highlighted === true) { + node.highlighted = false; + highlighting_changed = true; + } // No need to visit sub-scope return false; } ); } - if (intersected && obj instanceof Connector) { + if (obj instanceof Connector) { // Highlight all access nodes with the same name as the // hovered connector in the nested sdfg if (e.graph) { @@ -2741,7 +2743,14 @@ export class SDFGRenderer extends EventEmitter { } if (node instanceof AccessNode && node.data.node.label === obj.label()) { - node.highlighted = true; + if (intersected && node.highlighted === false) { + node.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && node.highlighted === true) { + node.highlighted = false; + highlighting_changed = true; + } } // No need to visit sub-scope return false; @@ -2755,29 +2764,49 @@ export class SDFGRenderer extends EventEmitter { if (obj.connectorType === 'in') { for (const token of obj.linkedElem.inputTokens) { if (token.token === obj.data.name) - token.highlighted = true; + if (intersected && token.highlighted === false) { + token.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && token.highlighted === true) { + token.highlighted = false; + highlighting_changed = true; + } } } else { for (const token of obj.linkedElem.outputTokens) { if (token.token === obj.data.name) - token.highlighted = true; + if (intersected && token.highlighted === false) { + token.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && token.highlighted === true) { + token.highlighted = false; + highlighting_changed = true; + } } } } } - if (intersected) + if (intersected && obj.hovered === false) { obj.hovered = true; + highlighting_changed = true; + } + else if (!intersected && obj.hovered === true) { + obj.hovered = false; + highlighting_changed = true; + } } ); - // TODO: only redraw when highlighting changed - dirty = true; + if (highlighting_changed) { + dirty = true; + } } } - // If adding an edge, mark/highlight the first/from element, if it has // already been selected. if (this.mouse_mode === 'add' && this.add_type === 'Edge') { @@ -2787,12 +2816,6 @@ export class SDFGRenderer extends EventEmitter { this.add_edge_start_conn.highlighted = true; } - // TODO: check everything works if this is commented out - // if (evtype === 'mousemove') { - // // TODO: Draw only if elements have changed - // dirty = true; - // } - if (evtype === 'dblclick') { const sdfg = (foreground_elem ? foreground_elem.sdfg : null); let sdfg_elem = null; From e3de51acad2a7acf3287285627c596dff301102b Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Thu, 14 Mar 2024 17:28:37 +0100 Subject: [PATCH 09/19] arrowhead LOD --- src/renderer/renderer_elements.ts | 16 +++++++++++----- src/sdfv.ts | 6 ++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 19f945f8..952884c2 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2507,11 +2507,17 @@ function batchedDrawEdges( ctx.fillStyle = ctx.strokeStyle = renderer.getCssProperty(color); ctx.stroke(); - arrowEdges.forEach(e => { - e.drawArrow( - ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 - ); - }); + // Only draw Arrowheads when close enough to see them + const canvas_manager = renderer.get_canvas_manager(); + const ppp = canvas_manager?.points_per_pixel(); + if (ppp && ppp < SDFV.ARROW_LOD) { + + arrowEdges.forEach(e => { + e.drawArrow( + ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 + ); + }); + } labelEdges.forEach(e => { (e as InterstateEdge).drawLabel(renderer, ctx); diff --git a/src/sdfv.ts b/src/sdfv.ts index 7bbb4ea6..e05bd386 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -47,11 +47,13 @@ export class SDFV { public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 - // Points-per-pixel threshold for not drawing connectors + // Points-per-pixel threshold for not drawing Arrowheads of memlets/interstate edges. + public static ARROW_LOD: number = 2.0; // 5.0 + // Points-per-pixel threshold for not drawing connectors. public static CONNECTOR_LOD = 2.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 - // Points-per-pixel threshold for not drawing node text + // Points-per-pixel threshold for not drawing node text. public static TEXT_LOD: number = 1.5; // 1.5 // Pixel threshold for not drawing state contents. public static STATE_LOD: number = 100; // 100 From 399772605dc5b8149ebbe21a23f01b4e66010814 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Fri, 15 Mar 2024 15:22:51 +0100 Subject: [PATCH 10/19] fixed connectors rendered when invisible --- src/renderer/renderer.ts | 6 +++++- src/renderer/renderer_elements.ts | 27 +++++++++++++++++++++++++++ src/sdfv.ts | 4 ++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index d4d783c2..3132b9d1 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -150,6 +150,7 @@ export class SDFGRenderer extends EventEmitter { protected bgcolor: string | null = null; protected visible_rect: SimpleRect | null = null; protected static cssProps: { [key: string]: string } = {}; + public static rendered_elements_count: number = 0; // Toolbar related fields. protected toolbar: JQuery | null = null; @@ -3140,8 +3141,11 @@ export class SDFGRenderer extends EventEmitter { dirty = dirty || ol_manager_dirty; } - if (dirty) + if (dirty) { + this.draw_async(); + // console.log(SDFGRenderer.rendered_elements_count) + } if (element_focus_changed || selection_changed) this.emit('selection_changed', multi_selection_changed); diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 952884c2..94e3e125 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2502,6 +2502,7 @@ function batchedDrawEdges( labelEdges.push(edge); edge.create_arrow_line(ctx); + // SDFGRenderer.rendered_elements_count++; }); ctx.setLineDash([1, 0]); ctx.fillStyle = ctx.strokeStyle = renderer.getCssProperty(color); @@ -2516,15 +2517,18 @@ function batchedDrawEdges( e.drawArrow( ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 ); + // SDFGRenderer.rendered_elements_count++; }); } labelEdges.forEach(e => { (e as InterstateEdge).drawLabel(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); deferredEdges.forEach(e => { e.draw(renderer, ctx, mousepos); + // SDFGRenderer.rendered_elements_count++; }); if (renderer.debug_draw) { @@ -2554,23 +2558,35 @@ export function drawStateContents( ) < SDFV.STATE_LOD) { node.simple_draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } } else { if (lod && ppp > SDFV.NODE_LOD) { node.simple_draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } } node.draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; // Only draw connectors when close enough to see them if (!lod || ppp < SDFV.CONNECTOR_LOD) { node.in_connectors.forEach((c: Connector) => { + + // Only draw connectors if actually visible. This is needed for large + // nodes in the background like NestedSDFGs, that are visible, but their + // connectors are actually not. + if (visibleRect && !c.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h)) { + return; + } + let edge: Edge | null = null; stateGraph.inEdges(nodeId)?.forEach((e) => { const eobj = stateGraph.edge(e); @@ -2580,8 +2596,15 @@ export function drawStateContents( c.draw(renderer, ctx, mousePos, edge); c.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); node.out_connectors.forEach((c: Connector) => { + + if (visibleRect && !c.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h)) { + return; + } + let edge: Edge | null = null; stateGraph.outEdges(nodeId)?.forEach((e) => { const eobj = stateGraph.edge(e); @@ -2591,6 +2614,7 @@ export function drawStateContents( c.draw(renderer, ctx, mousePos, edge); c.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); } } @@ -2629,11 +2653,13 @@ export function drawStateMachine( if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } block.draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; const ng = block.data.graph; if (!block.attributes().is_collapsed && ng) { @@ -2661,6 +2687,7 @@ export function drawSDFG( const ppp = cManager.points_per_pixel(); const visibleRect = renderer.get_visible_rect() ?? undefined; + SDFGRenderer.rendered_elements_count = 0; drawStateMachine( g, ctx, renderer, ppp, (ctx as any).lod, visibleRect, mousePos ); diff --git a/src/sdfv.ts b/src/sdfv.ts index e05bd386..eed5f25e 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -48,9 +48,9 @@ export class SDFV { // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing Arrowheads of memlets/interstate edges. - public static ARROW_LOD: number = 2.0; // 5.0 + public static ARROW_LOD: number = 2.0; // 2.0 // Points-per-pixel threshold for not drawing connectors. - public static CONNECTOR_LOD = 2.0; // 5.0 + public static CONNECTOR_LOD = 2.0; // 2.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node text. From 9c134bea61516d5cf11a81acf2c2d521820a7beb Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Mon, 18 Mar 2024 16:43:39 +0100 Subject: [PATCH 11/19] fixed edges rendered even if not visible --- src/renderer/renderer_elements.ts | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 94e3e125..6a2aa4a9 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -1015,8 +1015,9 @@ export abstract class Edge extends SDFGElement { x: number, y: number, w: number = 0, h: number = 0 ): boolean { // First, check bounding box - if (!super.intersect(x, y, w, h)) + if (!super.intersect(x, y, w, h)) { return false; + } // Then (if point), check distance from line if (w === 0 || h === 0) { @@ -1029,7 +1030,33 @@ export abstract class Edge extends SDFGElement { } return false; } - return true; + else { + // Its a rectangle. + // Check if the any of the rectangles, spanned by pairs of points of the line, + // intersect the input rectangle. + // This is needed for long Interstate edges that have a huge bounding box and + // intersect almost always with the viewport even if they are not visible. + // This is only an approximation to detect if a line is in the viewport and + // could be made more accurate at the cost of more computation. + for (let i = 0; i < this.points.length - 1; i++) { + const linepoint_0 = this.points[i]; + const linepoint_1 = this.points[i + 1]; + // Rectangle spanned by the two line points + const r = { + x: Math.min(linepoint_0.x, linepoint_1.x), + y: Math.min(linepoint_0.y, linepoint_1.y), + w: Math.abs(linepoint_1.x - linepoint_0.x), + h: Math.abs(linepoint_1.y - linepoint_0.y) + } + + // Check if the two rectangles intersect + if (r.x + r.w >= x && r.x <= x+w && + r.y + r.h >= y && r.y <= y+h) { + return true; + } + } + return false; + } } } @@ -2633,11 +2660,13 @@ export function drawStateMachine( renderer: SDFGRenderer, ppp: number, lod?: boolean, visibleRect?: SimpleRect, mousePos?: Point2D ): void { - if (!lod || ppp < SDFV.EDGE_LOD) + if (!lod || ppp < SDFV.EDGE_LOD) { + batchedDrawEdges( renderer, stateMachineGraph, ctx, visibleRect, mousePos, '--interstate-edge-color', SDFVSettings.alwaysOnISEdgeLabels ); + } for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); From c5ba20aeccb2ec766b9854cd3ba2dd03d6b46ae1 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 19 Mar 2024 19:33:55 +0100 Subject: [PATCH 12/19] added loading animation for relayout tasks --- index.html | 4 ++ scss/sdfv.scss | 14 ++++++ src/renderer/canvas_manager.ts | 5 +-- src/renderer/renderer.ts | 81 +++++++++++++++++++++++++++++----- src/sdfv.ts | 26 ++++++++--- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index 42009f02..e93e573a 100644 --- a/index.html +++ b/index.html @@ -101,6 +101,10 @@