@@ -19,6 +19,7 @@ let randFloatResources: Map<string, GPUObjectBase>;
19
19
20
20
let renderThread: Promise <void > | null = null ;
21
21
let abortRender = false ;
22
+ const pauseRender = ref (false );
22
23
let onRenderAborted: (() => void ) | null = null ;
23
24
24
25
const printfBufferElementSize = 12 ;
@@ -35,6 +36,8 @@ const pressedKeys = new Set<string>();
35
36
36
37
const canvas = useTemplateRef (" canvas" );
37
38
const frameTime = ref (0 );
39
+ const frameID = ref (0 );
40
+ const fps = ref (0 );
38
41
39
42
const { device } = defineProps <{
40
43
device: GPUDevice ;
@@ -46,9 +49,22 @@ let emit = defineEmits<{
46
49
}>();
47
50
48
51
defineExpose ({
49
- onRun ,
52
+ onRun
50
53
});
51
54
55
+ /**
56
+ * Toggle full screen on the canvas container.
57
+ */
58
+ function toggleFullscreen() {
59
+ const container = canvas .value ?.parentElement as HTMLElement | null ;
60
+ if (! container ) return ;
61
+ if (! document .fullscreenElement ) {
62
+ container .requestFullscreen ();
63
+ } else if (document .exitFullscreen ) {
64
+ document .exitFullscreen ();
65
+ }
66
+ }
67
+
52
68
onMounted (() => {
53
69
if (canvas .value == null ) {
54
70
throw new Error (" Could not get canvas element" );
@@ -70,6 +86,24 @@ onMounted(() => {
70
86
window .addEventListener (' keyup' , handleKeyUp );
71
87
})
72
88
89
+ /**
90
+ * Go to the specified frame index and render exactly that frame.
91
+ */
92
+ function setFrame(targetFrame : number ) {
93
+ if (! compiledCode || ! compiler ) return ;
94
+ // Clamp to non-negative
95
+ const t = Math .max (0 , Math .floor (targetFrame ));
96
+ // Prepare for single frame rendering
97
+ pauseRender .value = true ;
98
+ // Set internal counter so execFrame's increment brings us to t
99
+ frameID .value = t - 1 ;
100
+ execFrame (performance .now (), compiler .shaderType || null , compiledCode , t === 0 )
101
+ .catch (err => {
102
+ if (err instanceof Error ) emit (' logError' , ` Error rendering frame ${t }: ${err .message } ` );
103
+ else emit (' logError' , ` Error rendering frame ${t }: ${err } ` );
104
+ });
105
+ }
106
+
73
107
function handleKeyDown(event : KeyboardEvent ) {
74
108
pressedKeys .add (event .key );
75
109
pressedKeys .add (event .code );
@@ -328,6 +362,9 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
328
362
});
329
363
} else if (uniformComponent .type == " TIME" ) {
330
364
uniformBufferView .setFloat32 (offset , timeMS * 0.001 , true );
365
+ } else if (uniformComponent .type == " FRAME_ID" ) {
366
+ // provide current frame index as float
367
+ uniformBufferView .setFloat32 (offset , frameID .value , true );
331
368
} else if (uniformComponent .type == " MOUSE_POSITION" ) {
332
369
uniformBufferView .setFloat32 (offset , canvasCurrentMousePos .x , true );
333
370
uniformBufferView .setFloat32 (offset + 4 , canvasCurrentMousePos .y , true );
@@ -486,12 +523,14 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
486
523
487
524
const timeElapsed = performance .now () - startTime ;
488
525
526
+ frameID .value ++ ;
489
527
// Update performance info.
490
528
timeAggregate += timeElapsed ;
491
529
frameCount ++ ;
492
530
if (frameCount == 20 ) {
493
- let avgTime = ( timeAggregate / frameCount ) ;
531
+ let avgTime = timeAggregate / frameCount ;
494
532
frameTime .value = avgTime ;
533
+ fps .value = Math .round (1000 / avgTime );
495
534
timeAggregate = 0 ;
496
535
frameCount = 0 ;
497
536
}
@@ -777,6 +816,12 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
777
816
} else if (parsedCommand .type == " TIME" ) {
778
817
const buffer = allocatedResources .get (" uniformInput" ) as GPUBuffer
779
818
819
+ // Initialize the buffer with zeros.
820
+ let bufferDefault: BufferSource = new Float32Array ([0.0 ]);
821
+ device .queue .writeBuffer (buffer , parsedCommand .offset , bufferDefault );
822
+ } else if (parsedCommand .type == " FRAME_ID" ) {
823
+ const buffer = allocatedResources .get (" uniformInput" ) as GPUBuffer
824
+
780
825
// Initialize the buffer with zeros.
781
826
let bufferDefault: BufferSource = new Float32Array ([0.0 ]);
782
827
device .queue .writeBuffer (buffer , parsedCommand .offset , bufferDefault );
@@ -816,10 +861,15 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
816
861
}
817
862
818
863
function onRun(runCompiledCode : CompiledPlayground ) {
819
- if (! device )
820
- return ;
821
- if (! compiler )
822
- return ;
864
+ if (! device ) return ;
865
+ if (! compiler ) return ;
866
+
867
+ // reset frame counter and performance stats on (re)start
868
+ frameID .value = 0 ;
869
+ fps .value = 0 ;
870
+ frameTime .value = 0 ;
871
+ timeAggregate = 0 ;
872
+ frameCount = 0 ;
823
873
824
874
resetMouse ();
825
875
@@ -873,22 +923,38 @@ function onRun(runCompiledCode: CompiledPlayground) {
873
923
if (compiler == null ) {
874
924
throw new Error (" Could not get compiler" );
875
925
}
876
- if (compiler .shaderType !== null && ! abortRender ) {
877
- const keepRendering = await execFrame (timeMS , compiler ?.shaderType || null , compiledCode , firstFrame );
878
- firstFrame = false ;
879
- return keepRendering ;
926
+ if (abortRender ) return false ;
927
+ if (pauseRender .value ) return true ;
928
+
929
+ if (compiler .shaderType === null ) {
930
+ // handle this case
880
931
}
881
- return false ;
932
+ const keepRendering = await execFrame (timeMS , compiler ?.shaderType || null , compiledCode , firstFrame );
933
+ firstFrame = false ;
934
+ return keepRendering ;
882
935
});
883
936
}
884
937
</script >
885
938
886
939
<template >
887
- <div class =" renderOverlay" >
888
- <div id =" performanceInfo" >{{ `${frameTime.toFixed(1)} ms` }}</div >
889
- </div >
890
940
<canvas v-bind =" $attrs" class =" renderCanvas" @mousedown =" mousedown" @mousemove =" mousemove" @mouseup =" mouseup"
891
941
ref =" canvas" ></canvas >
942
+ <div class =" control-bar" >
943
+ <div class =" controls-left" >
944
+ <button @click =" setFrame(0)" title =" Reset frame to 0" >⏮ ;︎ ; </button >
945
+ <button @click =" setFrame(frameID - 1)" title =" Step backward" >⏴ ;︎ ; </button >
946
+ <button @click =" setFrame(frameID + 1)" title =" Step forward" >⏵ ;︎ ; </button >
947
+ <button @click =" pauseRender = !pauseRender"
948
+ :title =" pauseRender ? 'Resume' : 'Pause'" >⏯︎</button >
949
+ <span class =" frame-counter" >{{ String(frameID).padStart(5, '0') }}</span >
950
+ </div >
951
+ <div class =" controls-right" >
952
+ <span >{{ frameTime.toFixed(1) }} ms</span >
953
+ <span >{{ Math.min(Math.round(1000 / frameTime), 60) }} fps</span >
954
+ <span >{{ canvas?.width }}x{{ canvas?.height }}</span >
955
+ <button @click =" toggleFullscreen" title =" Toggle full screen" >⛶ ;︎ ; </button >
956
+ </div >
957
+ </div >
892
958
</template >
893
959
894
960
<style scoped>
@@ -898,21 +964,39 @@ function onRun(runCompiledCode: CompiledPlayground) {
898
964
height : 100% ;
899
965
}
900
966
901
- .renderOverlay {
967
+
968
+ .control-bar {
902
969
position : absolute ;
903
- padding : 8px ;
904
- border-radius : 5px ;
905
- width : fit-content ;
906
- background : rgba (0 , 0 , 0 , 0.3 );
970
+ bottom : 0 ;
971
+ left : 0 ;
972
+ right : 0 ;
973
+ height : 36px ;
974
+ padding : 4px 8px ;
975
+ background : rgba (0 , 0 , 0 , 0.5 );
976
+ display : flex ;
977
+ align-items : center ;
978
+ justify-content : space-between ;
979
+ color : white ;
980
+ font-size : 14px ;
981
+ overflow : hidden ;
982
+ white-space : nowrap ;
983
+ width : 100% ;
907
984
}
908
-
909
- #performanceInfo {
985
+ .control-bar .controls-left > * {
986
+ margin-right : 8px ;
987
+ }
988
+ .control-bar .controls-right > * {
989
+ margin-left : 8px ;
990
+ }
991
+ .control-bar button {
992
+ background : none ;
993
+ border : none ;
910
994
color : white ;
911
- width : fit-content ;
912
- -moz-user-select : -moz-none ;
913
- -khtml-user-select : none ;
914
- -webkit-user-select : none ;
915
- -ms-user-select : none ;
916
- user-select : none ;
995
+ cursor : pointer ;
996
+ font-variant-emoji : text ;
997
+ }
998
+ .control-bar button :disabled {
999
+ cursor : default ;
1000
+ opacity : 0.5 ;
917
1001
}
918
1002
</style >
0 commit comments