|
@@ -1,5 +1,5 @@
|
|
|
import { Color, WebGLRenderer } from 'three'
|
|
|
-import { shallowRef, watchEffect, onUnmounted, type MaybeRef, computed, watch } from 'vue'
|
|
|
+import { shallowRef, watchEffect, onUnmounted, type MaybeRef, computed, watch, nextTick } from 'vue'
|
|
|
import {
|
|
|
toValue,
|
|
|
unrefElement,
|
|
@@ -96,8 +96,8 @@ export interface UseRendererOptions extends TransformToMaybeRefOrGetter<WebGLRen
|
|
|
clearColor?: MaybeRefOrGetter<TresColor>
|
|
|
windowSize?: MaybeRefOrGetter<boolean | string>
|
|
|
preset?: MaybeRefOrGetter<RendererPresetsType>
|
|
|
+ renderMode?: MaybeRefOrGetter<'always' | 'on-demand' | 'manual'>
|
|
|
}
|
|
|
-
|
|
|
/**
|
|
|
* Reactive three.js WebGLRenderer instance
|
|
|
*
|
|
@@ -110,13 +110,15 @@ export function useRenderer(
|
|
|
canvas,
|
|
|
options,
|
|
|
disableRender,
|
|
|
- contextParts: { sizes, camera },
|
|
|
+ emit,
|
|
|
+ contextParts: { sizes, camera, internal, invalidate, advance },
|
|
|
}:
|
|
|
{
|
|
|
canvas: MaybeRef<HTMLCanvasElement>
|
|
|
scene: Scene
|
|
|
options: UseRendererOptions
|
|
|
- contextParts: Pick<TresContext, 'sizes' | 'camera'>
|
|
|
+ emit: (event: string, ...args: any[]) => void
|
|
|
+ contextParts: Pick<TresContext, 'sizes' | 'camera' | 'internal'> & { invalidate: () => void; advance: () => void }
|
|
|
disableRender: MaybeRefOrGetter<boolean>
|
|
|
},
|
|
|
) {
|
|
@@ -140,25 +142,61 @@ export function useRenderer(
|
|
|
|
|
|
const renderer = shallowRef<WebGLRenderer>(new WebGLRenderer(webGLRendererConstructorParameters.value))
|
|
|
|
|
|
+ function invalidateOnDemand() {
|
|
|
+ if (options.renderMode === 'on-demand') {
|
|
|
+ invalidate()
|
|
|
+ }
|
|
|
+ }
|
|
|
// since the properties set via the constructor can't be updated dynamically,
|
|
|
// the renderer is recreated once they change
|
|
|
watch(webGLRendererConstructorParameters, () => {
|
|
|
renderer.value.dispose()
|
|
|
renderer.value = new WebGLRenderer(webGLRendererConstructorParameters.value)
|
|
|
+
|
|
|
+ invalidateOnDemand()
|
|
|
})
|
|
|
|
|
|
- watchEffect(() => {
|
|
|
+ watch([sizes.width, sizes.height], () => {
|
|
|
renderer.value.setSize(sizes.width.value, sizes.height.value)
|
|
|
+ invalidateOnDemand()
|
|
|
+ }, {
|
|
|
+ immediate: true,
|
|
|
})
|
|
|
|
|
|
+ watch(() => options.clearColor, invalidateOnDemand)
|
|
|
+
|
|
|
const { pixelRatio } = useDevicePixelRatio()
|
|
|
|
|
|
- watchEffect(() => {
|
|
|
+ watch(pixelRatio, () => {
|
|
|
renderer.value.setPixelRatio(pixelRatio.value)
|
|
|
})
|
|
|
|
|
|
const { logError } = useLogger()
|
|
|
|
|
|
+ // TheLoop
|
|
|
+
|
|
|
+ const { resume, onLoop } = useRenderLoop()
|
|
|
+
|
|
|
+ onLoop(() => {
|
|
|
+ if (camera.value && !toValue(disableRender) && internal.frames.value > 0) {
|
|
|
+ renderer.value.render(scene, camera.value)
|
|
|
+ emit('render', renderer.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Reset priority
|
|
|
+ internal.priority.value = 0
|
|
|
+
|
|
|
+ if (toValue(options.renderMode) === 'always') {
|
|
|
+ internal.frames.value = 1
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ internal.frames.value = Math.max(0, internal.frames.value - 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ })
|
|
|
+
|
|
|
+ resume()
|
|
|
+
|
|
|
const getThreeRendererDefaults = () => {
|
|
|
|
|
|
const plainRenderer = new WebGLRenderer()
|
|
@@ -179,6 +217,20 @@ export function useRenderer(
|
|
|
|
|
|
const threeDefaults = getThreeRendererDefaults()
|
|
|
|
|
|
+ const renderMode = toValue(options.renderMode)
|
|
|
+
|
|
|
+ if (renderMode === 'on-demand') {
|
|
|
+ // Invalidate for the first time
|
|
|
+ invalidate()
|
|
|
+ }
|
|
|
+
|
|
|
+ if (renderMode === 'manual') {
|
|
|
+ // Advance for the first time, setTimeout to make sure there is something to render
|
|
|
+ setTimeout(() => {
|
|
|
+ advance()
|
|
|
+ }, 1)
|
|
|
+ }
|
|
|
+
|
|
|
watchEffect(() => {
|
|
|
const rendererPreset = toValue(options.preset)
|
|
|
|
|
@@ -189,6 +241,13 @@ export function useRenderer(
|
|
|
merge(renderer.value, rendererPresets[rendererPreset])
|
|
|
}
|
|
|
|
|
|
+ // Render mode
|
|
|
+
|
|
|
+ if (renderMode === 'always') {
|
|
|
+ // If the render mode is 'always', ensure there's always a frame pending
|
|
|
+ internal.frames.value = Math.max(1, internal.frames.value)
|
|
|
+ }
|
|
|
+
|
|
|
const getValue = <T>(option: MaybeRefOrGetter<T>, pathInThree: string): T | undefined => {
|
|
|
const value = toValue(option)
|
|
|
|
|
@@ -234,17 +293,7 @@ export function useRenderer(
|
|
|
|
|
|
})
|
|
|
|
|
|
- const { pause, resume, onLoop } = useRenderLoop()
|
|
|
-
|
|
|
- onLoop(() => {
|
|
|
- if (camera.value && !toValue(disableRender))
|
|
|
- renderer.value.render(scene, camera.value)
|
|
|
- })
|
|
|
-
|
|
|
- resume()
|
|
|
-
|
|
|
onUnmounted(() => {
|
|
|
- pause() // TODO should the render loop pause itself if there is no more renderer? 🤔 What if there is another renderer which needs the loop?
|
|
|
renderer.value.dispose()
|
|
|
renderer.value.forceContextLoss()
|
|
|
})
|