Skip to content

Commit 6764c9f

Browse files
committed
feat: Limit WebGL contexts in use by replacing thumbnails with snapshots when done (#517)
* feat: keep count of how many thumbnail gl contexts in use * feat: limit Thumbnails to 8 WebGL graphics contexts * refactor: replace thumbnail canvases with snapshot images when done * feat: If a WebGL graphics context not available for thumbnail, wait
1 parent d5e7c9c commit 6764c9f

6 files changed

Lines changed: 81 additions & 3 deletions

File tree

e2e/tests/scope.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ test.describe('Scope: on some featured visualization', () => {
8383
})
8484

8585
test('minimizing a tab', async ({page}) => {
86+
await expect(page.locator('#visualiserTab')).not.toHaveClass(
87+
/minimized/
88+
)
8689
await page.locator('#visualiserTab .minimize').click()
8790
await expect(page.locator('#visualiserTab')).toHaveClass(/minimized/)
8891
await expect(

src/components/Thumbnail.vue

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,80 @@
33
</template>
44

55
<script setup lang="ts">
6-
import {onMounted, onUnmounted, ref} from 'vue'
6+
import {onMounted, onUnmounted, ref, watch} from 'vue'
7+
8+
import {thumbnailGCcount} from './thumbnails'
9+
710
import {Specimen} from '@/shared/Specimen'
811
import {DrawingUnmounted} from '@/visualizers/VisualizerInterface'
912
1013
const canvasContainer = ref<HTMLDivElement | null>(null)
1114
let savedContainer: HTMLDivElement | null = null
1215
let specimen: Specimen | undefined = undefined
16+
let usingGC = false
17+
let needsSetup = true
18+
const TGClim = 7
1319
const props = defineProps<{query: string}>()
1420
21+
function setupSpecimen() {
22+
needsSetup = false
23+
if (!specimen || !savedContainer) return
24+
specimen.setup(savedContainer)
25+
setTimeout(() => {
26+
if (usingGC) {
27+
thumbnailGCcount.value -= 1
28+
usingGC = false
29+
}
30+
if (!specimen || !savedContainer) return
31+
const canvas = savedContainer.querySelector('canvas')
32+
if (canvas instanceof HTMLCanvasElement) {
33+
const {width, height} = canvas.getBoundingClientRect()
34+
const vizShot = new Image(width, height)
35+
vizShot.src = canvas.toDataURL()
36+
specimen.visualizer.depart(savedContainer)
37+
savedContainer.appendChild(vizShot)
38+
} else {
39+
specimen.visualizer.stop()
40+
}
41+
}, 4000)
42+
}
43+
1544
onMounted(async () => {
1645
specimen = await Specimen.fromQuery(props.query)
1746
if (!(canvasContainer.value instanceof HTMLElement)) return
1847
savedContainer = canvasContainer.value
19-
specimen.setup(savedContainer)
20-
setTimeout(() => specimen?.visualizer.stop(), 4000)
48+
if (specimen.visualizer.usesGL()) {
49+
usingGC = true
50+
if (thumbnailGCcount.value > TGClim) {
51+
// defer setup
52+
const limitMsg = document.createTextNode(
53+
'[... waiting for WebGL graphics context]'
54+
)
55+
savedContainer.appendChild(limitMsg)
56+
watch(thumbnailGCcount, newCount => {
57+
if (!savedContainer) return
58+
if (newCount <= TGClim && needsSetup) {
59+
if (savedContainer.lastChild) {
60+
savedContainer.removeChild(
61+
savedContainer.lastChild
62+
)
63+
}
64+
setupSpecimen()
65+
thumbnailGCcount.value += 1
66+
}
67+
})
68+
} else {
69+
setupSpecimen()
70+
thumbnailGCcount.value += 1
71+
}
72+
} else setupSpecimen()
2173
})
2274
2375
onUnmounted(() => {
76+
if (usingGC) {
77+
thumbnailGCcount.value -= 1
78+
usingGC = false
79+
}
2480
// Turns out canvasContainer has already been de-refed
2581
// by the time we get here, so we can't depart using that.
2682
// Hence the need for savedContainer, unfortunately.

src/components/thumbnails.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {ref} from 'vue'
2+
3+
export const thumbnailGCcount = ref(0)

src/visualizers/P5GLVisualizer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export function P5GLVisualizer<PD extends GenericParamDescription>(desc: PD) {
2828
this.name = this.category
2929
}
3030

31+
usesGL() {
32+
return true
33+
}
34+
3135
// Just like P5Visualizer, but use WebGL renderer, load the brush,
3236
// and create a camera.
3337
// However we override rather than extend so there is only one call

src/visualizers/P5Visualizer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export function P5Visualizer<PD extends GenericParamDescription>(desc: PD) {
9292
drawingState: DrawingState = DrawingUnmounted
9393

9494
within?: HTMLElement
95+
usesGL() {
96+
return false
97+
}
9598
get sketch(): p5 {
9699
if (this._sketch === undefined) {
97100
throw (

src/visualizers/VisualizerInterface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ implementations for all or almost all of them.
8686
**/
8787

8888
export interface VisualizerInterface extends ParamableInterface {
89+
/** md */
90+
usesGL(): boolean
91+
/* **/
92+
/** md
93+
: Should return true if this visualizer requires a WebGL graphics context
94+
(of which a limited number are available concurrently in a browser),
95+
false otherwise.
96+
<!-- -->
97+
**/
8998
/** md */
9099
view(sequence: SequenceInterface): Promise<void>
91100
/* **/

0 commit comments

Comments
 (0)