Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Limit WebGL contexts in use by replacing thumbnails with snapshots when done #517

Merged
merged 4 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading