Skip to content

Commit 76ff7ea

Browse files
natevmDevon7925
andauthored
Add Play/Pause, Reset, Step Fwd / Bwd and Fullscreen to UI (#151)
* adding a pause/resume toggle * moving frame controls to bar under render canvas * ci related changes * small change to circle demo to demonstrate frame ID behavior * some cleanup * fixing bug where canvas resize wasn't updating text * migrating control bar code into RenderCanvas.vue, fixing mistake in circle.slang example * Update src/components/RenderCanvas.vue Co-authored-by: Devon <[email protected]> * moving ms down to control bar * Update src/components/RenderCanvas.vue Co-authored-by: Devon <[email protected]> * reverting a couple unnecessary changes * removing duplicate / redundant fields. Messing with control bar behavior on resize * small cleanup * testing font variant emoji text * attempt #2 at getting ios to not show emoji --------- Co-authored-by: Devon <[email protected]>
1 parent f308375 commit 76ff7ea

File tree

6 files changed

+146
-32
lines changed

6 files changed

+146
-32
lines changed

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<link rel="shortcut icon" href="/images/favicon.ico" />
1414
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
1515
<link href="src/assets/styles.css" rel="stylesheet" />
16-
<link rel="stylesheet" href="jsontree/jsontree.js.css">
17-
<script src="jsontree/jsontree.min.js"></script>
16+
<link rel="stylesheet" href="/jsontree/jsontree.js.css">
17+
<script type="module" src="/jsontree/jsontree.min.js"></script>
1818
<script type="module" src="src/main.ts"></script>
1919
<script async src="https://www.googletagmanager.com/gtag/js?id=G-TMTZVLLMBP"></script>
2020
<script>

public/demos/circle.slang

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import playground;
77
[playground::TIME]
88
uniform float time;
99

10+
[playground::FRAME_ID]
11+
uniform float frame;
12+
1013
typealias vec2 = float2;
1114
typealias vec3 = float3;
1215
typealias vec4 = float4;
@@ -17,10 +20,11 @@ float4 imageMain(uint2 dispatchThreadID, int2 screenSize)
1720
float tau = float.getPi() * 2.0;
1821
float a = atan2(p.x,p.y);
1922
float r = length(p)*0.75;
20-
vec2 uv = vec2(a/tau,r);
23+
vec2 uv = vec2(a / tau, r);
24+
float t = frame / 60.f;
2125

2226
//get the color
23-
float xCol = (uv.x - time/3) * 3.0;
27+
float xCol = (uv.x - t/3) * 3.0;
2428
xCol = fmod(abs(xCol), 3.0f);
2529
vec3 horColour = vec3(0.25, 0.25, 0.25);
2630

@@ -45,7 +49,7 @@ float4 imageMain(uint2 dispatchThreadID, int2 screenSize)
4549
// draw color beam
4650
uv = (2.0 * uv) - 1.0;
4751
float beamWidth = (0.7 +
48-
0.5 * cos(uv.x * 10.0 * tau * 0.15 * clamp(floor(5.0 + 10.0 * cos(time)), 0.0, 10.0)))
52+
0.5 * cos(uv.x * 10.0 * tau * 0.15 * clamp(floor(5.0 + 10.0 * cos(t)), 0.0, 10.0)))
4953
* abs(1.0 / (30.0 * uv.y));
5054
vec3 horBeam = vec3(beamWidth);
5155
return vec4(((horBeam) * horColour), 1.0);

src/components/Help.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ defineExpose({
6464
Initialize a <code>float</code> buffer with uniform random floats between 0 and 1.
6565
<h4 class="doc-header"><code>[playground::TIME]</code></h4>
6666
Gives a <code>float</code> uniform the current time in milliseconds.
67+
<h4 class="doc-header"><code>[playground::FRAME_ID]</code></h4>
68+
Gives a <code>float</code> uniform the current frame index (starting from 0).
6769
<h4 class="doc-header"><code>[playground::MOUSE_POSITION]</code></h4>
6870
Gives a <code>float4</code> uniform mouse data.
6971
<ul>

src/components/RenderCanvas.vue

Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let randFloatResources: Map<string, GPUObjectBase>;
1919
2020
let renderThread: Promise<void> | null = null;
2121
let abortRender = false;
22+
const pauseRender = ref(false);
2223
let onRenderAborted: (() => void) | null = null;
2324
2425
const printfBufferElementSize = 12;
@@ -35,6 +36,8 @@ const pressedKeys = new Set<string>();
3536
3637
const canvas = useTemplateRef("canvas");
3738
const frameTime = ref(0);
39+
const frameID = ref(0);
40+
const fps = ref(0);
3841
3942
const { device } = defineProps<{
4043
device: GPUDevice;
@@ -46,9 +49,22 @@ let emit = defineEmits<{
4649
}>();
4750
4851
defineExpose({
49-
onRun,
52+
onRun
5053
});
5154
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+
5268
onMounted(() => {
5369
if (canvas.value == null) {
5470
throw new Error("Could not get canvas element");
@@ -70,6 +86,24 @@ onMounted(() => {
7086
window.addEventListener('keyup', handleKeyUp);
7187
})
7288
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+
73107
function handleKeyDown(event: KeyboardEvent) {
74108
pressedKeys.add(event.key);
75109
pressedKeys.add(event.code);
@@ -328,6 +362,9 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
328362
});
329363
} else if (uniformComponent.type == "TIME") {
330364
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);
331368
} else if (uniformComponent.type == "MOUSE_POSITION") {
332369
uniformBufferView.setFloat32(offset, canvasCurrentMousePos.x, true);
333370
uniformBufferView.setFloat32(offset + 4, canvasCurrentMousePos.y, true);
@@ -486,12 +523,14 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
486523
487524
const timeElapsed = performance.now() - startTime;
488525
526+
frameID.value++;
489527
// Update performance info.
490528
timeAggregate += timeElapsed;
491529
frameCount++;
492530
if (frameCount == 20) {
493-
let avgTime = (timeAggregate / frameCount);
531+
let avgTime = timeAggregate / frameCount;
494532
frameTime.value = avgTime;
533+
fps.value = Math.round(1000 / avgTime);
495534
timeAggregate = 0;
496535
frameCount = 0;
497536
}
@@ -777,6 +816,12 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
777816
} else if (parsedCommand.type == "TIME") {
778817
const buffer = allocatedResources.get("uniformInput") as GPUBuffer
779818
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+
780825
// Initialize the buffer with zeros.
781826
let bufferDefault: BufferSource = new Float32Array([0.0]);
782827
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
@@ -816,10 +861,15 @@ async function processResourceCommands(resourceBindings: Bindings, resourceComma
816861
}
817862
818863
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;
823873
824874
resetMouse();
825875
@@ -873,22 +923,38 @@ function onRun(runCompiledCode: CompiledPlayground) {
873923
if (compiler == null) {
874924
throw new Error("Could not get compiler");
875925
}
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
880931
}
881-
return false;
932+
const keepRendering = await execFrame(timeMS, compiler?.shaderType || null, compiledCode, firstFrame);
933+
firstFrame = false;
934+
return keepRendering;
882935
});
883936
}
884937
</script>
885938

886939
<template>
887-
<div class="renderOverlay">
888-
<div id="performanceInfo">{{ `${frameTime.toFixed(1)} ms` }}</div>
889-
</div>
890940
<canvas v-bind="$attrs" class="renderCanvas" @mousedown="mousedown" @mousemove="mousemove" @mouseup="mouseup"
891941
ref="canvas"></canvas>
942+
<div class="control-bar">
943+
<div class="controls-left">
944+
<button @click="setFrame(0)" title="Reset frame to 0">&#x23EE;&#xFE0E;</button>
945+
<button @click="setFrame(frameID - 1)" title="Step backward">&#x23F4;&#xFE0E;</button>
946+
<button @click="setFrame(frameID + 1)" title="Step forward">&#x23F5;&#xFE0E;</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">&#x26F6;&#xFE0E;</button>
956+
</div>
957+
</div>
892958
</template>
893959

894960
<style scoped>
@@ -898,21 +964,39 @@ function onRun(runCompiledCode: CompiledPlayground) {
898964
height: 100%;
899965
}
900966
901-
.renderOverlay {
967+
968+
.control-bar {
902969
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%;
907984
}
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;
910994
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;
9171001
}
9181002
</style>

src/slang/playground.slang

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ public struct playground_TIMEAttribute
130130
{
131131
};
132132

133+
// Gives the current shader playback frame index.
134+
[__AttributeUsage(_AttributeTargets.Var)]
135+
public struct playground_FRAME_IDAttribute
136+
{
137+
};
138+
133139
// Gives mouse position info.
134140
// \`xy\`: mouse position (in pixels) during last button down.
135141
// \`abs(zw)\`: mouse position during last button click.

src/util.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ export type ParsedCommand = {
254254
} | {
255255
"type": "TIME",
256256
"offset": number,
257+
} | {
258+
"type": "FRAME_ID",
259+
"offset": number,
257260
} | {
258261
"type": "MOUSE_POSITION",
259262
"offset": number,
@@ -369,6 +372,14 @@ export function getResourceCommandsFromAttributes(reflection: ReflectionJSON): R
369372
type: playground_attribute_name,
370373
offset: parameter.binding.offset,
371374
};
375+
} else if (playground_attribute_name == "FRAME_ID") {
376+
if (parameter.type.kind != "scalar" || !parameter.type.scalarType.startsWith("float") || parameter.binding.kind != "uniform") {
377+
throw new Error(`${playground_attribute_name} attribute cannot be applied to ${parameter.name}, it only supports floats`)
378+
}
379+
command = {
380+
type: playground_attribute_name,
381+
offset: parameter.binding.offset,
382+
};
372383
} else if (playground_attribute_name == "MOUSE_POSITION") {
373384
if (parameter.type.kind != "vector" || parameter.type.elementCount <= 3 || parameter.type.elementType.kind != "scalar" || !parameter.type.elementType.scalarType.startsWith("float") || parameter.binding.kind != "uniform") {
374385
throw new Error(`${playground_attribute_name} attribute cannot be applied to ${parameter.name}, it only supports float vectors`)
@@ -450,6 +461,8 @@ export type UniformController = { buffer_offset: number } & ({
450461
value: [number, number, number],
451462
} | {
452463
type: "TIME",
464+
} | {
465+
type: "FRAME_ID",
453466
} | {
454467
type: "MOUSE_POSITION",
455468
} | {
@@ -486,6 +499,11 @@ export function getUniformControllers(resourceCommands: ResourceCommand[]): Unif
486499
type: resourceCommand.parsedCommand.type,
487500
buffer_offset: resourceCommand.parsedCommand.offset,
488501
})
502+
} else if (resourceCommand.parsedCommand.type == 'FRAME_ID') {
503+
controllers.push({
504+
type: resourceCommand.parsedCommand.type,
505+
buffer_offset: resourceCommand.parsedCommand.offset,
506+
})
489507
} else if (resourceCommand.parsedCommand.type == 'MOUSE_POSITION') {
490508
controllers.push({
491509
type: resourceCommand.parsedCommand.type,

0 commit comments

Comments
 (0)