Skip to content

Commit

Permalink
feat: Limit WebGL contexts in use by replacing thumbnails with snapsh…
Browse files Browse the repository at this point in the history
…ots 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
  • Loading branch information
gwhitney committed Jan 20, 2025
1 parent d5e7c9c commit 6764c9f
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 3 deletions.
3 changes: 3 additions & 0 deletions e2e/tests/scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ test.describe('Scope: on some featured visualization', () => {
})

test('minimizing a tab', async ({page}) => {
await expect(page.locator('#visualiserTab')).not.toHaveClass(
/minimized/
)
await page.locator('#visualiserTab .minimize').click()
await expect(page.locator('#visualiserTab')).toHaveClass(/minimized/)
await expect(
Expand Down
62 changes: 59 additions & 3 deletions src/components/Thumbnail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,80 @@
</template>

<script setup lang="ts">
import {onMounted, onUnmounted, ref} from 'vue'
import {onMounted, onUnmounted, ref, watch} from 'vue'
import {thumbnailGCcount} from './thumbnails'
import {Specimen} from '@/shared/Specimen'
import {DrawingUnmounted} from '@/visualizers/VisualizerInterface'
const canvasContainer = ref<HTMLDivElement | null>(null)
let savedContainer: HTMLDivElement | null = null
let specimen: Specimen | undefined = undefined
let usingGC = false
let needsSetup = true
const TGClim = 7
const props = defineProps<{query: string}>()
function setupSpecimen() {
needsSetup = false
if (!specimen || !savedContainer) return
specimen.setup(savedContainer)
setTimeout(() => {
if (usingGC) {
thumbnailGCcount.value -= 1
usingGC = false
}
if (!specimen || !savedContainer) return
const canvas = savedContainer.querySelector('canvas')
if (canvas instanceof HTMLCanvasElement) {
const {width, height} = canvas.getBoundingClientRect()
const vizShot = new Image(width, height)
vizShot.src = canvas.toDataURL()
specimen.visualizer.depart(savedContainer)
savedContainer.appendChild(vizShot)
} else {
specimen.visualizer.stop()
}
}, 4000)
}
onMounted(async () => {
specimen = await Specimen.fromQuery(props.query)
if (!(canvasContainer.value instanceof HTMLElement)) return
savedContainer = canvasContainer.value
specimen.setup(savedContainer)
setTimeout(() => specimen?.visualizer.stop(), 4000)
if (specimen.visualizer.usesGL()) {
usingGC = true
if (thumbnailGCcount.value > TGClim) {
// defer setup
const limitMsg = document.createTextNode(
'[... waiting for WebGL graphics context]'
)
savedContainer.appendChild(limitMsg)
watch(thumbnailGCcount, newCount => {
if (!savedContainer) return
if (newCount <= TGClim && needsSetup) {
if (savedContainer.lastChild) {
savedContainer.removeChild(
savedContainer.lastChild
)
}
setupSpecimen()
thumbnailGCcount.value += 1
}
})
} else {
setupSpecimen()
thumbnailGCcount.value += 1
}
} else setupSpecimen()
})
onUnmounted(() => {
if (usingGC) {
thumbnailGCcount.value -= 1
usingGC = false
}
// Turns out canvasContainer has already been de-refed
// by the time we get here, so we can't depart using that.
// Hence the need for savedContainer, unfortunately.
Expand Down
3 changes: 3 additions & 0 deletions src/components/thumbnails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {ref} from 'vue'

export const thumbnailGCcount = ref(0)
4 changes: 4 additions & 0 deletions src/visualizers/P5GLVisualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export function P5GLVisualizer<PD extends GenericParamDescription>(desc: PD) {
this.name = this.category
}

usesGL() {
return true
}

// Just like P5Visualizer, but use WebGL renderer, load the brush,
// and create a camera.
// However we override rather than extend so there is only one call
Expand Down
3 changes: 3 additions & 0 deletions src/visualizers/P5Visualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export function P5Visualizer<PD extends GenericParamDescription>(desc: PD) {
drawingState: DrawingState = DrawingUnmounted

within?: HTMLElement
usesGL() {
return false
}
get sketch(): p5 {
if (this._sketch === undefined) {
throw (
Expand Down
9 changes: 9 additions & 0 deletions src/visualizers/VisualizerInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ implementations for all or almost all of them.
**/

export interface VisualizerInterface extends ParamableInterface {
/** md */
usesGL(): boolean
/* **/
/** md
: Should return true if this visualizer requires a WebGL graphics context
(of which a limited number are available concurrently in a browser),
false otherwise.
<!-- -->
**/
/** md */
view(sequence: SequenceInterface): Promise<void>
/* **/
Expand Down

0 comments on commit 6764c9f

Please sign in to comment.