1
0
Эх сурвалжийг харах

feat: conditional rendering

alvarosabu 1 жил өмнө
parent
commit
7ad2e31649

+ 14 - 0
playground/src/pages/rendering-modes/index.vue

@@ -0,0 +1,14 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+
+import Scene from './scene.vue'
+</script>
+
+<template>
+  <TresCanvas
+    clear-color="#82DBC5"
+    render-mode="on-demand"
+  >
+    <Scene />
+  </TresCanvas>
+</template>

+ 22 - 0
playground/src/pages/rendering-modes/scene.vue

@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { useRenderLoop, useTres } from '@tresjs/core'
+import { OrbitControls } from '@tresjs/cientos'
+
+const { invalidate } = useTres()
+
+function onControlChange() {
+  invalidate()
+}
+
+invalidate()
+</script>
+
+<template>
+  <OrbitControls @change="onControlChange" />
+  <TresGridHelper />
+  <TresMesh>
+    <TresBoxGeometry />
+    <TresMeshNormalMaterial />
+  </TresMesh>
+  <TresAmbientLight :intensity="1" />
+</template>

+ 5 - 0
playground/src/router.ts

@@ -76,6 +76,11 @@ const routes = [
     name: 'Perf',
     component: () => import('./pages/perf/index.vue'),
   },
+  {
+    path: '/rendering-modes',
+    name: 'Rendering Modes',
+    component: () => import('./pages/rendering-modes/index.vue'),
+  },
   {
     path: '/empty',
     name: 'empty',

+ 4 - 2
src/components/TresCanvas.vue

@@ -45,6 +45,7 @@ export interface TresCanvasProps
   useLegacyLights?: boolean
   outputColorSpace?: ColorSpace
   toneMappingExposure?: number
+  renderMode?: 'always' | 'on-demand' 
 
   // required by useTresContextProvider
   camera?: TresCamera
@@ -65,6 +66,7 @@ const props = withDefaults(defineProps<TresCanvasProps>(), {
   preserveDrawingBuffer: undefined,
   logarithmicDepthBuffer: undefined,
   failIfMajorPerformanceCaveat: undefined,
+  renderMode: 'always',
 })
 
 const { logWarning } = useLogger()
@@ -129,8 +131,8 @@ onMounted(() => {
   context.value = useTresContextProvider({
     scene: scene.value,
     canvas: existingCanvas,
-    windowSize: props.windowSize,
-    disableRender,
+    windowSize: props.windowSize ?? true,
+    disableRender: disableRender.value ?? false,
     rendererOptions: props,
   })
 

+ 35 - 11
src/composables/useRenderer/index.ts

@@ -96,6 +96,7 @@ export interface UseRendererOptions extends TransformToMaybeRefOrGetter<WebGLRen
   clearColor?: MaybeRefOrGetter<TresColor>
   windowSize?: MaybeRefOrGetter<boolean | string>
   preset?: MaybeRefOrGetter<RendererPresetsType>
+  renderMode?: MaybeRefOrGetter<'always' | 'on-demand'>
 }
 
 /**
@@ -110,7 +111,7 @@ export function useRenderer(
     canvas,
     options,
     disableRender,
-    contextParts: { sizes, camera },
+    contextParts: { sizes, camera, internal },
   }:
   {
     canvas: MaybeRef<HTMLCanvasElement>
@@ -159,6 +160,30 @@ export function useRenderer(
 
   const { logError } = useLogger()
 
+  // TheLoop
+
+  const { resume, onLoop } = useRenderLoop()
+
+  onLoop(() => {
+    if (camera.value && !toValue(disableRender) && internal.frames.value > 0)
+      renderer.value.render(scene, camera.value)
+
+    // Call subscribers' render callbacks
+    internal.subscribers.value.forEach(({ callback }) => callback())
+
+    // Reset priority
+    internal.priority.value = 0
+
+    internal.frames.value = Math.max(0, internal.frames.value - 1)
+
+    if (toValue(options.renderMode) === 'always') {
+      internal.frames.value = 1
+    }
+
+  })
+
+  resume()
+
   const getThreeRendererDefaults = () => {
 
     const plainRenderer = new WebGLRenderer()
@@ -189,6 +214,15 @@ export function useRenderer(
       merge(renderer.value, rendererPresets[rendererPreset])
     }
 
+    // Render mode
+
+    const renderMode = toValue(options.renderMode)
+
+    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 +268,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()
   })

+ 57 - 13
src/composables/useTresContextProvider/index.ts

@@ -9,6 +9,32 @@ import type { UseRendererOptions } from '../useRenderer'
 import { useRenderer } from '../useRenderer'
 import { extend } from '../../core/catalogue'
 
+export interface InternalSubscriber {
+  callback: (timestamp: number) => void
+  priority: number
+}
+
+export interface InternalState {
+  priority: Ref<number>
+  frames: Ref<number>
+  subscribers: Ref<InternalSubscriber[]>
+  maxFrames: number
+  subscribe: (callback: (timestamp: number) => void, priority: number) => () => void
+}
+
+export interface PerformanceState {
+  maxFrames: number
+  fps: {
+    value: number
+    accumulator: number[]
+  }
+  memory: {
+    currentMem: number
+    allocatedMem: number
+    accumulator: number[]
+  }
+}
+
 export interface TresContext {
   scene: ShallowRef<Scene>
   sizes: { height: Ref<number>; width: Ref<number>; aspectRatio: ComputedRef<number> }
@@ -18,18 +44,9 @@ export interface TresContext {
   controls: Ref<(EventDispatcher & { enabled: boolean }) | null>
   renderer: ShallowRef<WebGLRenderer>
   raycaster: ShallowRef<Raycaster>
-  perf: {
-    maxFrames: number
-    fps: {
-      value: number
-      accumulator: number[]
-    }
-    memory: {
-      currentMem: number
-      allocatedMem: number
-      accumulator: number[]
-    }
-  }
+  perf: PerformanceState
+  internal: InternalState
+  invalidate: () => void
   registerCamera: (camera: Camera) => void
   setCameraActive: (cameraOrUuid: Camera | string) => void
   deregisterCamera: (camera: Camera) => void
@@ -74,15 +91,40 @@ export function useTresContextProvider({
     setCameraActive,
   } = useCamera({ sizes, scene })
 
+  // Initialize internal state
+  const internal: InternalState = {
+    priority: ref(0),
+    frames: ref(0),
+    subscribers: ref([]),
+    maxFrames: 60,
+    subscribe: (callback, priority) => {
+      const subscription = { callback, priority }
+      internal.subscribers.value.push(subscription)
+      // Sort subscribers based on priority
+      internal.subscribers.value.sort((a, b) => b.priority - a.priority)
+  
+      // Return an unsubscribe function
+      return () => {
+        const index = internal.subscribers.value.indexOf(subscription)
+        if (index > -1) internal.subscribers.value.splice(index, 1)
+      }
+    },
+  }
+
   const { renderer } = useRenderer(
     {
       scene,
       canvas,
       options: rendererOptions,
-      contextParts: { sizes, camera },
+      contextParts: { sizes, camera, internal },
       disableRender,
     })
 
+  function invalidate(frames = 1) {
+    // Increase the frame count, ensuring not to exceed a maximum if desired
+    internal.frames.value = Math.min(internal.maxFrames, internal.frames.value + frames)
+  }
+
   const toProvide: TresContext = {
     sizes,
     scene: localScene,
@@ -103,7 +145,9 @@ export function useTresContextProvider({
         accumulator: [],
       },
     },
+    internal,
     extend,
+    invalidate,
     registerCamera,
     setCameraActive,
     deregisterCamera,