-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve the Wafer Map web worker class to support offscreen rendering (…
…#1929) # Pull Request ## 🤨 Rationale Wafer Map's canvas is the main component used to draw the actual dies on a wafer ![image](https://github.com/ni/nimble/assets/110180309/05141b98-2158-4909-ab3d-54781307c219) The old wafer renders the canvas on the main thread, for the new one we want to render the canvas inside the web worker <!--- Provide some background and a description of your work. What problem does this change solve? Include links to issues, work items, or other discussions. --> ## 👩💻 Implementation The main changes were done in matrix-renderer.ts, worker-renderer.ts and index.ts matrix-renderer.ts - holds the logic on how the canvas drawn, most of the changes are straightforward besides the typed array parsing algorithm from drawWafer(), but a comment explaining it can be found there worker-renderer.ts - holds the logic on how worker is setup index.ts - creates the worker and transfers the canvas control from main thread to the worker less important changes were done in WaferMapTests.ts from the Blazor component and zoom-handler.ts WaferMapTests.ts - after adding a new wafer map tag inside the wafermap template.ts the blazor tests were not able to distinguise between the 2 tags, I added IDs to each tag and changed the page.Locator to search for the ID and not for the tag name zoom-handler.ts - zoom behavior was managed inside update() method from index.ts, as the constructor of ZoomHandler takes an wafer map as input we implemented the observable design pattern using Notifier from Fast Element, similar to how it is done for table nimble component <!--- Describe how the change addresses the problem. Consider factors such as complexity, alternative solutions, performance impact, etc. Consider listing files with important changes or comment on them directly in the pull request. --> ## 🧪 Testing Added a few unit tests and a chromatic test <!--- Detail the testing done to ensure this submission meets requirements. Include automated/manual test additions or modifications, testing done on a local build, private CI run results, and additional testing not covered by automatic pull request validation. If any functionality is not covered by automated testing, provide justification. --> ## ✅ Checklist <!--- Review the list and put an x in the boxes that apply or ~~strike through~~ around items that don't (along with an explanation). --> - [ ] I have updated the project documentation to reflect my changes or determined no changes are needed. --------- Co-authored-by: Natan Muntean <natan.muntean@ni.com> Co-authored-by: Milan Raj <rajsite@users.noreply.github.com>
- Loading branch information
1 parent
0389e48
commit a3865cb
Showing
23 changed files
with
596 additions
and
88 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@ni-nimble-blazor-1cbfc49b-865a-4368-ab38-15ee937d0420.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "none", | ||
"comment": "Added an additional canvas in wafer map template caused the blazor tests to fail due to missing unique identifiers for canvases, adding different ids for the canvases and explicitly telling the tests to look for one canvas id solved the issue", | ||
"packageName": "@ni/nimble-blazor", | ||
"email": "110180309+Razvan1928@users.noreply.github.com", | ||
"dependentChangeType": "patch" | ||
} |
7 changes: 7 additions & 0 deletions
7
change/@ni-nimble-components-f866e449-14a0-410d-be26-c5570bf1006d.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "patch", | ||
"comment": "Updated wafer map web worker class to support offscreen rendering", | ||
"packageName": "@ni/nimble-components", | ||
"email": "110180309+Razvan1928@users.noreply.github.com", | ||
"dependentChangeType": "patch" | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
221 changes: 211 additions & 10 deletions
221
packages/nimble-components/build/generate-workers/source/matrix-renderer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,224 @@ | ||
import { expose } from 'comlink'; | ||
import type { | ||
Dimensions, | ||
Transform, | ||
WaferMapMatrix, | ||
WaferMapTypedMatrix | ||
} from './types'; | ||
|
||
/** | ||
* MatrixRenderer class is meant to be used within a Web Worker context, | ||
* using Comlink to facilitate communication between the main thread and the worker. | ||
* The MatrixRenderer class manages a matrix of dies, once an instance of MatrixRenderer is created, | ||
* MatrixRenderer class is meant to be used within a Web Worker context, | ||
* using Comlink to facilitate communication between the main thread and the worker. | ||
* The MatrixRenderer class manages a matrix of dies, once an instance of MatrixRenderer is created, | ||
* it is exposed to the main thread using Comlink's `expose` method. | ||
* This setup is used in the wafer-map component to perform heavy computational duties | ||
*/ | ||
export class MatrixRenderer { | ||
public dieMatrix: Uint8Array = Uint8Array.from([]); | ||
public columnIndexes = Int32Array.from([]); | ||
public rowIndexes = Int32Array.from([]); | ||
public values = Float64Array.from([]); | ||
public scaledColumnIndex = Float64Array.from([]); | ||
public scaledRowIndex = Float64Array.from([]); | ||
public columnIndexPositions = Int32Array.from([]); | ||
public canvas!: OffscreenCanvas; | ||
public context!: OffscreenCanvasRenderingContext2D; | ||
private scaleX: number = 1; | ||
private scaleY: number = 1; | ||
private baseX: number = 1; | ||
private baseY: number = 1; | ||
private dieDimensions: Dimensions = { width: 1, height: 1 }; | ||
private transform: Transform = { k: 1, x: 0, y: 0 }; | ||
private topLeftCanvasCorner!: { x: number; y: number }; | ||
private bottomRightCanvasCorner!: { x: number; y: number }; | ||
private readonly smallestMarginPossible: number = 20; | ||
private margin: { | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
left: number; | ||
} = { | ||
top: this.smallestMarginPossible, | ||
right: this.smallestMarginPossible, | ||
bottom: this.smallestMarginPossible, | ||
left: this.smallestMarginPossible | ||
}; | ||
|
||
public emptyMatrix(): void { | ||
this.dieMatrix = Uint8Array.from([]);; | ||
public calculateXScaledIndex(columnIndex: number): number { | ||
return this.scaleX * columnIndex + this.baseX + this.margin.left; | ||
} | ||
|
||
public calculateYScaledIndex(rowIndex: number): number { | ||
return this.scaleY * rowIndex + this.baseY + this.margin.top; | ||
} | ||
|
||
public setColumnIndexes(columnIndexes: Int32Array): void { | ||
this.columnIndexes = columnIndexes; | ||
if (columnIndexes.length === 0 || this.columnIndexes[0] === undefined) { | ||
return; | ||
} | ||
const scaledColumnIndex = [ | ||
this.calculateXScaledIndex(this.columnIndexes[0]) | ||
]; | ||
const columnPositions = [0]; | ||
let prev = this.columnIndexes[0]; | ||
for (let i = 1; i < this.columnIndexes.length; i++) { | ||
const xIndex = this.columnIndexes[i]; | ||
if (xIndex && xIndex !== prev) { | ||
const scaledX = this.calculateXScaledIndex( | ||
this.columnIndexes[i]! | ||
); | ||
scaledColumnIndex.push(scaledX); | ||
columnPositions.push(i); | ||
prev = xIndex; | ||
} | ||
} | ||
this.scaledColumnIndex = Float64Array.from(scaledColumnIndex); | ||
this.columnIndexPositions = Int32Array.from(columnPositions); | ||
} | ||
|
||
public updateMatrix( | ||
data: Iterable<number> | ||
public setRowIndexes(rowIndexesBuffer: Int32Array): void { | ||
this.rowIndexes = rowIndexesBuffer; | ||
this.scaledRowIndex = new Float64Array(this.rowIndexes.length); | ||
for (let i = 0; i < this.rowIndexes.length; i++) { | ||
this.scaledRowIndex[i] = this.calculateYScaledIndex( | ||
this.rowIndexes[i]! | ||
); | ||
} | ||
} | ||
|
||
public setMargin(margin: { | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
left: number; | ||
}): void { | ||
this.margin = margin; | ||
} | ||
|
||
public setCanvasCorners( | ||
topLeft: { x: number; y: number }, | ||
bottomRight: { x: number; y: number } | ||
): void { | ||
this.dieMatrix = Uint8Array.from(data); | ||
this.topLeftCanvasCorner = topLeft; | ||
this.bottomRightCanvasCorner = bottomRight; | ||
} | ||
|
||
public setDiesDimensions(data: Dimensions): void { | ||
this.dieDimensions = { width: data.width, height: data.height }; | ||
} | ||
|
||
public setScaling(scaleX: number, scaleY: number): void { | ||
this.scaleX = scaleX; | ||
this.scaleY = scaleY; | ||
} | ||
|
||
public setBases(baseX: number, baseY: number): void { | ||
this.baseX = baseX; | ||
this.baseY = baseY; | ||
} | ||
|
||
public setTransform(transform: Transform): void { | ||
this.transform = transform; | ||
} | ||
|
||
public setCanvas(canvas: OffscreenCanvas): void { | ||
this.canvas = canvas; | ||
this.context = canvas.getContext('2d')!; | ||
} | ||
|
||
public getMatrix(): WaferMapTypedMatrix { | ||
return { | ||
columnIndexes: this.columnIndexes, | ||
rowIndexes: this.rowIndexes, | ||
values: this.values | ||
}; | ||
} | ||
|
||
public emptyMatrix(): void { | ||
this.columnIndexes = Int32Array.from([]); | ||
this.rowIndexes = Int32Array.from([]); | ||
this.values = Float64Array.from([]); | ||
} | ||
|
||
public scaleCanvas(): void { | ||
this.context.translate(this.transform.x, this.transform.y); | ||
this.context.scale(this.transform.k, this.transform.k); | ||
} | ||
|
||
public updateMatrix(data: WaferMapMatrix): void { | ||
this.columnIndexes = Int32Array.from(data.columnIndexes); | ||
this.rowIndexes = Int32Array.from(data.rowIndexes); | ||
this.values = Float64Array.from(data.values); | ||
} | ||
|
||
public setCanvasDimensions(data: Dimensions): void { | ||
this.canvas.width = data.width; | ||
this.canvas.height = data.height; | ||
} | ||
|
||
public getCanvasDimensions(): Dimensions { | ||
return { | ||
width: this.canvas.width, | ||
height: this.canvas.height | ||
}; | ||
} | ||
|
||
public clearCanvas(): void { | ||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); | ||
} | ||
|
||
public drawWafer(): void { | ||
this.context.restore(); | ||
this.context.save(); | ||
this.clearCanvas(); | ||
this.scaleCanvas(); | ||
if ( | ||
this.topLeftCanvasCorner === undefined | ||
|| this.bottomRightCanvasCorner === undefined | ||
) { | ||
throw new Error('Canvas corners are not set'); | ||
} | ||
for (let i = 0; i < this.scaledColumnIndex.length; i++) { | ||
const scaledX = this.scaledColumnIndex[i]!; | ||
if ( | ||
!( | ||
scaledX >= this.topLeftCanvasCorner.x | ||
&& scaledX < this.bottomRightCanvasCorner.x | ||
) | ||
) { | ||
continue; | ||
} | ||
|
||
// columnIndexPositions is used to get chunks to determine the start and end index of the column, it looks something like [0, 1, 4, 9, 12] | ||
// This means that the first column has a start index of 0 and an end index of 1, the second column has a start index of 1 and an end index of 4, and so on | ||
// scaledRowIndex is used when we reach the end of the columnIndexPositions, when columnIndexPositions is [0, 1, 4, 9, 12], scaledRowIndex is 13 | ||
const columnEndIndex = this.columnIndexPositions[i + 1] !== undefined | ||
? this.columnIndexPositions[i + 1]! | ||
: this.scaledRowIndex.length; | ||
for ( | ||
let columnStartIndex = this.columnIndexPositions[i]!; | ||
columnStartIndex < columnEndIndex; | ||
columnStartIndex++ | ||
) { | ||
const scaledY = this.scaledRowIndex[columnStartIndex]!; | ||
if ( | ||
!( | ||
scaledY >= this.topLeftCanvasCorner.y | ||
&& scaledY < this.bottomRightCanvasCorner.y | ||
) | ||
) { | ||
continue; | ||
} | ||
// Fill style is temporary green for all dies, will be replaced with a color based on the value of the die in a future implementation | ||
this.context.fillStyle = 'Green'; | ||
this.context.fillRect( | ||
scaledX, | ||
scaledY, | ||
this.dieDimensions.width, | ||
this.dieDimensions.height | ||
); | ||
} | ||
} | ||
} | ||
} | ||
expose(MatrixRenderer); | ||
expose(MatrixRenderer); |
Oops, something went wrong.