129 Commits

Author SHA1 Message Date
Aiden
3ffb562ff7 docs update
All checks were successful
CI / React UI Build (push) Successful in 49s
CI / Native Windows Build And Tests (push) Successful in 3m27s
CI / Windows Release Package (push) Successful in 3m39s
2026-05-13 01:06:20 +10:00
Aiden
c2d548499c Timing is finally good
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m57s
CI / Windows Release Package (push) Has been skipped
2026-05-13 00:58:32 +10:00
Aiden
6a0340d1b4 proactive realignment 2026-05-13 00:28:11 +10:00
Aiden
5c1fc2a6cf telemetry and timing updates
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m58s
CI / Windows Release Package (push) Has been skipped
2026-05-13 00:21:28 +10:00
Aiden
d411453f80 timing refactor
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 3m1s
CI / Windows Release Package (push) Successful in 3m20s
2026-05-12 23:39:57 +10:00
Aiden
4a049a557a Render timing 2026-05-12 22:18:27 +10:00
Aiden
13586c611a Start up settle 2026-05-12 22:04:46 +10:00
Aiden
3a83d9617f Clock updates 2026-05-12 21:44:26 +10:00
Aiden
5c66cfdc64 Input telemetry
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m56s
CI / Windows Release Package (push) Has been skipped
2026-05-12 21:13:22 +10:00
Aiden
d72272b5a8 2 frame buffer
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped
2026-05-12 21:08:02 +10:00
Aiden
c25ae7b25b input buffer
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-12 21:05:42 +10:00
Aiden
a39be6fb20 Alignment
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped
2026-05-12 20:38:26 +10:00
Aiden
0a1fe440d9 docs pass 2026-05-12 20:32:32 +10:00
Aiden
3e45bba54b Update InputFrameMailbox.cpp
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped
2026-05-12 20:30:19 +10:00
Aiden
fd4b70ec9c Input GPU decoding
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m4s
CI / Windows Release Package (push) Has been skipped
2026-05-12 20:26:03 +10:00
Aiden
ce28904891 input testing
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m2s
CI / Windows Release Package (push) Has been skipped
2026-05-12 20:06:23 +10:00
Aiden
2c5e925b97 Video input fallback
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m7s
CI / Windows Release Package (push) Has been skipped
2026-05-12 18:53:46 +10:00
Aiden
957c0be05a Draw video if everything bypassed 2026-05-12 18:41:48 +10:00
Aiden
0a8b335048 INput
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped
2026-05-12 18:39:08 +10:00
Aiden
6e32941675 Fixed trigger
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m57s
CI / Windows Release Package (push) Has been skipped
2026-05-12 18:11:43 +10:00
Aiden
5fb4607d8c Clean up
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m55s
CI / Windows Release Package (push) Has been skipped
2026-05-12 18:03:54 +10:00
Aiden
f43b6f6519 shader control
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m3s
CI / Windows Release Package (push) Has been skipped
2026-05-12 17:52:55 +10:00
Aiden
dfd49fd0e3 Multipass shaders 2026-05-12 17:08:35 +10:00
Aiden
1429b2e660 Update shader
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m0s
CI / Windows Release Package (push) Has been skipped
2026-05-12 16:52:15 +10:00
Aiden
02b221f481 restructure 2026-05-12 15:47:59 +10:00
Aiden
6a33bd02ab Websocket split
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 3m0s
CI / Windows Release Package (push) Has been skipped
2026-05-12 15:36:02 +10:00
Aiden
da7e1a93f6 Websockets
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m58s
CI / Windows Release Package (push) Has been skipped
2026-05-12 15:32:01 +10:00
Aiden
334693f28c Render udpates 2026-05-12 15:26:02 +10:00
Aiden
c5fd8e72b4 non-blocking http
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m58s
CI / Windows Release Package (push) Has been skipped
2026-05-12 15:05:54 +10:00
Aiden
95b4a54326 Seperation 2026-05-12 14:57:18 +10:00
Aiden
d07ea1f63a Render changes
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m59s
CI / Windows Release Package (push) Has been skipped
2026-05-12 14:36:36 +10:00
Aiden
1ddcf5d621 More http post end points filled
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 3m1s
CI / Windows Release Package (push) Has been skipped
2026-05-12 14:23:53 +10:00
Aiden
38d729b346 CI update
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m56s
CI / Windows Release Package (push) Has been skipped
2026-05-12 13:55:10 +10:00
Aiden
4b62627479 removed render thread touching
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-12 13:52:04 +10:00
Aiden
430cf0733d end point adjsutments
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-12 13:50:32 +10:00
Aiden
b44504500a Ui serving
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m55s
CI / Windows Release Package (push) Successful in 3m21s
2026-05-12 13:25:34 +10:00
Aiden
bc690e2a87 Clean up pass
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m55s
CI / Windows Release Package (push) Successful in 3m14s
2026-05-12 13:14:52 +10:00
Aiden
9938a6cc26 http
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m54s
CI / Windows Release Package (push) Successful in 3m2s
2026-05-12 12:38:54 +10:00
Aiden
79f7ac6c86 Json telemetry
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m52s
CI / Windows Release Package (push) Successful in 3m19s
2026-05-12 12:13:21 +10:00
Aiden
44b198b14d logging
All checks were successful
CI / React UI Build (push) Successful in 38s
CI / Native Windows Build And Tests (push) Successful in 3m12s
CI / Windows Release Package (push) Successful in 3m7s
2026-05-12 11:58:29 +10:00
Aiden
511b67c9bc New rules based order
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m53s
CI / Windows Release Package (push) Successful in 3m18s
2026-05-12 02:35:15 +10:00
Aiden
c0d7e84495 Shader ownership change
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m52s
CI / Windows Release Package (push) Successful in 2m59s
2026-05-12 02:15:03 +10:00
Aiden
4ea829af85 Shader test past
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m52s
CI / Windows Release Package (push) Has been cancelled
2026-05-12 02:08:48 +10:00
Aiden
e0ca548ef5 V2 working
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m54s
CI / Windows Release Package (push) Successful in 3m14s
2026-05-12 01:59:02 +10:00
Aiden
2531d871e8 Doc cleanup
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m49s
CI / Windows Release Package (push) Successful in 3m8s
2026-05-12 01:37:20 +10:00
Aiden
709d3d3fa4 Test works
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m53s
CI / Windows Release Package (push) Successful in 3m1s
2026-05-12 01:30:30 +10:00
Aiden
ea31d0ca13 Clean 2026-05-12 01:21:42 +10:00
Aiden
f1f4e3421b Frame timing
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m53s
CI / Windows Release Package (push) Successful in 3m6s
2026-05-12 01:08:32 +10:00
Aiden
ac729dc2b9 Stage 1 rewrite 2026-05-12 00:52:33 +10:00
Aiden
bf23cd880a faliure
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 2m52s
CI / Windows Release Package (push) Has been skipped
2026-05-12 00:35:01 +10:00
Aiden
9e3412712c Improvement
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m52s
CI / Windows Release Package (push) Successful in 3m0s
2026-05-12 00:00:23 +10:00
Aiden
a434a88108 Performance chasing
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m51s
CI / Windows Release Package (push) Successful in 2m55s
2026-05-11 23:10:45 +10:00
Aiden
c5cead6003 Phase 7.5 step 2
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m45s
CI / Windows Release Package (push) Successful in 2m52s
2026-05-11 21:36:17 +10:00
Aiden
f8adbbe0fe Phase 7.5 timing logs
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m45s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 21:32:40 +10:00
Aiden
0a7954e879 Phase 7 done
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m47s
CI / Windows Release Package (push) Successful in 3m2s
2026-05-11 21:15:51 +10:00
Aiden
f288455709 Phase 7
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m47s
CI / Windows Release Package (push) Successful in 3m2s
2026-05-11 21:05:11 +10:00
Aiden
50d5880835 Step 3 2026-05-11 20:49:36 +10:00
Aiden
52eaf16a8c Phase 7 step 2
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m44s
CI / Windows Release Package (push) Successful in 2m57s
2026-05-11 20:45:58 +10:00
Aiden
6b0638336a Phase 7 step 1
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m47s
CI / Windows Release Package (push) Successful in 2m53s
2026-05-11 20:39:01 +10:00
Aiden
0da6ad6802 Docs update
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m44s
CI / Windows Release Package (push) Successful in 2m47s
2026-05-11 20:14:10 +10:00
Aiden
dd3cd6b66c Clean up
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-11 20:11:20 +10:00
Aiden
1d08dec5fe step 6
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m44s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 20:06:14 +10:00
Aiden
0d57920bc1 step 5 2026-05-11 20:02:26 +10:00
Aiden
1629dbc77a step 4 2026-05-11 19:58:14 +10:00
Aiden
205c90e52e Step 3 2026-05-11 19:53:31 +10:00
Aiden
ab38bfad24 step 2
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m41s
CI / Windows Release Package (push) Successful in 2m46s
2026-05-11 19:49:05 +10:00
Aiden
68503256dc Phase 6 step 1
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Successful in 2m47s
2026-05-11 19:44:35 +10:00
Aiden
a91cc91a21 Clean up shape
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m41s
CI / Windows Release Package (push) Successful in 2m48s
2026-05-11 19:37:44 +10:00
Aiden
a530325fa1 Organisation
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m42s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 19:31:06 +10:00
Aiden
d332dceb5b Step 6
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m43s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 19:25:29 +10:00
Aiden
79855d788c Step 5 storng option
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m41s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 19:20:23 +10:00
Aiden
ff10b66d1d Phase 5 step 5
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m42s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 19:14:59 +10:00
Aiden
fdcc38c6ae Step 4 2026-05-11 19:09:01 +10:00
Aiden
718e4dcadd step 3
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Successful in 2m44s
2026-05-11 19:05:29 +10:00
Aiden
7740fe209c Phase 5 step 2
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m54s
2026-05-11 18:56:39 +10:00
Aiden
77590f4a62 Phase 5 step 1
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-11 18:53:59 +10:00
Aiden
e8a3805fff Doc update again
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 18:48:55 +10:00
Aiden
99fd903144 docs update
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 18:45:03 +10:00
Aiden
761df3b2d0 Phase 4 complete
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m45s
2026-05-11 18:39:02 +10:00
Aiden
f141d20026 Sanity pass
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m42s
CI / Windows Release Package (push) Successful in 2m45s
2026-05-11 18:31:36 +10:00
Aiden
bfc32c4a1e Phase 4
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m43s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 18:25:47 +10:00
Aiden
20476bdf63 Step 3
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Successful in 2m46s
2026-05-11 17:41:59 +10:00
Aiden
0ec5a4cfed Phase 4 step 2a
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m36s
CI / Windows Release Package (push) Successful in 2m42s
2026-05-11 17:26:24 +10:00
Aiden
539fcd3351 Phase 4 step 1
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m35s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 17:21:27 +10:00
Aiden
ebc10a9925 docs update
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m36s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 17:16:39 +10:00
Aiden
e5c5920ccd Launch debug change
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m35s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 17:11:20 +10:00
Aiden
3b641dd07a render thread
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-11 17:07:45 +10:00
Aiden
e00e2574ed render engine updates
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-11 17:06:42 +10:00
Aiden
e459155d51 Clean up pass
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m35s
CI / Windows Release Package (push) Successful in 2m38s
2026-05-11 16:52:53 +10:00
Aiden
06f3dd4942 Phase 3 refactor in progress
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m33s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 16:48:52 +10:00
Aiden
0808171677 GROUND WORK PHASE 3
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m32s
CI / Windows Release Package (push) Successful in 2m34s
2026-05-11 16:32:51 +10:00
Aiden
00b6ad4c36 Phase 3 docs
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m27s
CI / Windows Release Package (push) Successful in 2m46s
2026-05-11 16:24:52 +10:00
Aiden
d4f6a4a268 phase 2 progress
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m30s
2026-05-11 16:18:34 +10:00
Aiden
6e600be112 Tests
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m28s
2026-05-11 15:59:29 +10:00
Aiden
a9b08f7f27 dispatch event intergration
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m28s
CI / Windows Release Package (push) Successful in 2m24s
2026-05-11 15:42:14 +10:00
Aiden
ccfc0237fd RuntimeStateBroadcastRequested
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m21s
CI / Windows Release Package (push) Successful in 2m23s
2026-05-11 15:19:43 +10:00
Aiden
b3705d96cc event dispatcher
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m37s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 15:15:42 +10:00
Aiden
5503ce85a9 phase 2
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m17s
CI / Windows Release Package (push) Successful in 2m27s
2026-05-11 02:35:31 +10:00
Aiden
41677b71ec Finished phase 1
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m18s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 02:32:13 +10:00
Aiden
9cbb5d8004 phase 1 runtime complete
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m46s
CI / Windows Release Package (push) Successful in 2m59s
2026-05-11 02:23:01 +10:00
Aiden
cbf1b541dc re organisation
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m47s
CI / Windows Release Package (push) Successful in 2m52s
2026-05-11 02:11:51 +10:00
Aiden
5cbdbd6813 Pass 3
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m48s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 02:06:17 +10:00
Aiden
b2369c418b pass 2
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Successful in 2m39s
2026-05-11 01:29:44 +10:00
Aiden
c4883d3413 pass 1
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m45s
2026-05-11 01:12:24 +10:00
Aiden
53e78890a8 more
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m37s
CI / Windows Release Package (push) Successful in 2m40s
2026-05-11 00:55:19 +10:00
Aiden
36b398ea95 mini cleanup
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m38s
CI / Windows Release Package (push) Successful in 2m41s
2026-05-11 00:41:51 +10:00
Aiden
ba4643dfa3 further phase 1
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 00:38:49 +10:00
Aiden
27dbb55f7b OSC seperation
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m36s
CI / Windows Release Package (push) Successful in 2m48s
2026-05-11 00:26:59 +10:00
Aiden
f6b26bf28b runtime udates
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 00:22:55 +10:00
Aiden
861593123d Runtime snapshot provider changes
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m38s
CI / Windows Release Package (push) Has been cancelled
2026-05-11 00:18:01 +10:00
Aiden
34c145e80b Health
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m35s
CI / Windows Release Package (push) Successful in 2m46s
2026-05-11 00:08:12 +10:00
Aiden
a24cdc0630 Osc orchestration
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m38s
CI / Windows Release Package (push) Successful in 2m42s
2026-05-10 23:59:48 +10:00
Aiden
120f899b0d docs
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled
2026-05-10 23:57:05 +10:00
Aiden
41075bbc61 more seperation 2026-05-10 23:53:27 +10:00
Aiden
7f0f60c0e3 ore untangling
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m38s
CI / Windows Release Package (push) Successful in 2m36s
2026-05-10 23:31:45 +10:00
Aiden
739231d5a1 Phase 1 clean-up and separation of concerns
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m34s
CI / Windows Release Package (push) Successful in 2m42s
2026-05-10 23:21:13 +10:00
Aiden
3629227aa9 intial phase 1 subsytem split
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m31s
CI / Windows Release Package (push) Successful in 2m32s
2026-05-10 23:03:15 +10:00
Aiden
618831d578 Phase 1 goals
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m32s
2026-05-10 22:36:34 +10:00
Aiden
c38c22834d Preroll udpate
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m29s
CI / Windows Release Package (push) Successful in 2m30s
2026-05-10 22:30:47 +10:00
Aiden
c8a4bd4c7b adjustments to control and stack saving
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m28s
CI / Windows Release Package (push) Successful in 2m44s
2026-05-10 22:10:54 +10:00
Aiden
46129a6044 UI fix
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m28s
2026-05-10 21:27:13 +10:00
Aiden
8fcb51d140 example data store
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m24s
CI / Windows Release Package (push) Successful in 2m34s
2026-05-10 21:11:17 +10:00
Aiden
944773c248 added new layer input pass
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m25s
CI / Windows Release Package (push) Successful in 2m28s
2026-05-10 21:00:34 +10:00
Aiden
7777cfc194 data storage
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m23s
CI / Windows Release Package (push) Successful in 2m46s
2026-05-10 20:39:28 +10:00
Aiden
198639ae3f OSC sync back
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Successful in 2m29s
2026-05-10 18:58:26 +10:00
Aiden
d7ca42b51b OSC fixes
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Successful in 2m43s
2026-05-10 18:37:30 +10:00
Aiden
f11d531e0c OSC bind address
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m41s
CI / Windows Release Package (push) Successful in 2m30s
2026-05-10 17:23:28 +10:00
Aiden
a3635b5d31 Revert "preview changes"
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m23s
CI / Windows Release Package (push) Successful in 2m28s
This reverts commit 98f5cbe309.
2026-05-09 16:47:45 +10:00
Aiden
bc9aa6fbad Revert "Video backend"
This reverts commit 4ffbb97abf.
2026-05-09 16:47:43 +10:00
271 changed files with 39867 additions and 4611 deletions

View File

@@ -7,6 +7,9 @@ on:
- master - master
- develop - develop
pull_request: pull_request:
schedule:
# Nightly build at 14:00 UTC, roughly midnight in Australia/Sydney.
- cron: "0 14 * * *"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -57,11 +60,11 @@ jobs:
- name: Build Debug - name: Build Debug
shell: powershell shell: powershell
run: cmake --build --preset build-debug run: cmake --build --preset build-debug --parallel
- name: Run Native Tests And Shader Validation - name: Run Native Tests And Shader Validation
shell: powershell shell: powershell
run: cmake --build --preset build-debug --target RUN_TESTS run: cmake --build --preset build-debug --target RUN_TESTS --parallel
ui-ubuntu: ui-ubuntu:
name: React UI Build name: React UI Build
@@ -82,6 +85,7 @@ jobs:
package-windows: package-windows:
name: Windows Release Package name: Windows Release Package
runs-on: windows-2022 runs-on: windows-2022
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
needs: needs:
- native-windows - native-windows
- ui-ubuntu - ui-ubuntu
@@ -136,7 +140,7 @@ jobs:
- name: Build Release - name: Build Release
shell: powershell shell: powershell
run: cmake --build --preset build-release run: cmake --build --preset build-release --parallel
- name: Install Runtime Package - name: Install Runtime Package
shell: powershell shell: powershell

90
.vscode/launch.json vendored
View File

@@ -9,9 +9,97 @@
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug", "cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [], "environment": [
{
"name": "VST_DISABLE_INPUT_CAPTURE",
"value": "1"
}
],
"console": "internalConsole", "console": "internalConsole",
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"requireExactSource": true,
"logging": {
"moduleLoad": true
},
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64" "preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
},
{
"name": "Debug LoopThroughWithOpenGLCompositing - sync readback experiment",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [
{
"name": "VST_OUTPUT_READBACK_MODE",
"value": "sync"
}
],
"console": "internalConsole",
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"requireExactSource": true,
"logging": {
"moduleLoad": true
},
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
},
{
"name": "Debug LoopThroughWithOpenGLCompositing - cached output experiment",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [
{
"name": "VST_OUTPUT_READBACK_MODE",
"value": "cached_only"
}
],
"console": "internalConsole",
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"requireExactSource": true,
"logging": {
"moduleLoad": true
},
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
},
{
"name": "Debug DeckLinkRenderCadenceProbe",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\DeckLinkRenderCadenceProbe.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [],
"console": "externalTerminal",
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"requireExactSource": true,
"logging": {
"moduleLoad": true
},
"preLaunchTask": "Build DeckLinkRenderCadenceProbe Debug x64"
},
{
"name": "Debug RenderCadenceCompositor",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\RenderCadenceCompositor.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"environment": [],
"console": "externalTerminal",
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
"requireExactSource": true,
"logging": {
"moduleLoad": true
},
"preLaunchTask": "Build RenderCadenceCompositor Debug x64"
} }
] ]
} }

38
.vscode/tasks.json vendored
View File

@@ -11,7 +11,8 @@
"--config", "--config",
"Debug", "Debug",
"--target", "--target",
"LoopThroughWithOpenGLCompositing" "LoopThroughWithOpenGLCompositing",
"--parallel"
], ],
"group": { "group": {
"kind": "build", "kind": "build",
@@ -29,7 +30,40 @@
"--config", "--config",
"Release", "Release",
"--target", "--target",
"LoopThroughWithOpenGLCompositing" "LoopThroughWithOpenGLCompositing",
"--parallel"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Build DeckLinkRenderCadenceProbe Debug x64",
"type": "process",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
"args": [
"--build",
"${workspaceFolder}\\build\\vs2022-x64-debug",
"--config",
"Debug",
"--target",
"DeckLinkRenderCadenceProbe",
"--parallel"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Build RenderCadenceCompositor Debug x64",
"type": "process",
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
"args": [
"--build",
"${workspaceFolder}\\build\\vs2022-x64-debug",
"--config",
"Debug",
"--target",
"RenderCadenceCompositor",
"--parallel"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@@ -34,10 +34,14 @@ set(APP_SOURCES
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c" "${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/control/ControlServer.cpp" "${APP_DIR}/control/ControlServer.cpp"
"${APP_DIR}/control/ControlServer.h" "${APP_DIR}/control/ControlServer.h"
"${APP_DIR}/control/ControlServices.cpp"
"${APP_DIR}/control/ControlServices.h"
"${APP_DIR}/control/OscServer.cpp" "${APP_DIR}/control/OscServer.cpp"
"${APP_DIR}/control/OscServer.h" "${APP_DIR}/control/OscServer.h"
"${APP_DIR}/control/RuntimeControlBridge.cpp" "${APP_DIR}/control/RuntimeControlBridge.cpp"
"${APP_DIR}/control/RuntimeControlBridge.h" "${APP_DIR}/control/RuntimeControlBridge.h"
"${APP_DIR}/control/RuntimeServiceLiveBridge.cpp"
"${APP_DIR}/control/RuntimeServiceLiveBridge.h"
"${APP_DIR}/control/RuntimeServices.cpp" "${APP_DIR}/control/RuntimeServices.cpp"
"${APP_DIR}/control/RuntimeServices.h" "${APP_DIR}/control/RuntimeServices.h"
"${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h" "${APP_DIR}/videoio/decklink/DeckLinkAPI_h.h"
@@ -49,10 +53,6 @@ set(APP_SOURCES
"${APP_DIR}/videoio/decklink/DeckLinkSession.h" "${APP_DIR}/videoio/decklink/DeckLinkSession.h"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp" "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h" "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
"${APP_DIR}/videoio/VideoIOBackendFactory.cpp"
"${APP_DIR}/videoio/VideoIOBackendFactory.h"
"${APP_DIR}/videoio/VideoIOConfig.cpp"
"${APP_DIR}/videoio/VideoIOConfig.h"
"${APP_DIR}/gl/renderer/GLExtensions.cpp" "${APP_DIR}/gl/renderer/GLExtensions.cpp"
"${APP_DIR}/gl/renderer/GLExtensions.h" "${APP_DIR}/gl/renderer/GLExtensions.h"
"${APP_DIR}/gl/shader/GlobalParamsBuffer.cpp" "${APP_DIR}/gl/shader/GlobalParamsBuffer.cpp"
@@ -61,14 +61,25 @@ set(APP_SOURCES
"${APP_DIR}/gl/renderer/GlScopedObjects.h" "${APP_DIR}/gl/renderer/GlScopedObjects.h"
"${APP_DIR}/gl/shader/GlShaderSources.cpp" "${APP_DIR}/gl/shader/GlShaderSources.cpp"
"${APP_DIR}/gl/shader/GlShaderSources.h" "${APP_DIR}/gl/shader/GlShaderSources.h"
"${APP_DIR}/gl/OpenGLComposite.cpp" "${APP_DIR}/gl/composite/OpenGLComposite.cpp"
"${APP_DIR}/gl/OpenGLComposite.h" "${APP_DIR}/gl/composite/OpenGLComposite.h"
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" "${APP_DIR}/gl/composite/OpenGLCompositeRuntimeControls.cpp"
"${APP_DIR}/gl/threading/RenderCommandQueue.cpp"
"${APP_DIR}/gl/threading/RenderCommandQueue.h"
"${APP_DIR}/gl/RenderEngine.cpp"
"${APP_DIR}/gl/RenderEngine.h"
"${APP_DIR}/gl/frame/RenderFrameState.h"
"${APP_DIR}/gl/frame/RenderFrameStateResolver.cpp"
"${APP_DIR}/gl/frame/RenderFrameStateResolver.h"
"${APP_DIR}/gl/frame/RuntimeUpdateController.cpp"
"${APP_DIR}/gl/frame/RuntimeUpdateController.h"
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp"
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp" "${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.h"
"${APP_DIR}/gl/pipeline/RenderPassDescriptor.h" "${APP_DIR}/gl/pipeline/RenderPassDescriptor.h"
"${APP_DIR}/gl/pipeline/ShaderFeedbackBuffers.cpp"
"${APP_DIR}/gl/pipeline/ShaderFeedbackBuffers.h"
"${APP_DIR}/gl/renderer/OpenGLRenderer.cpp" "${APP_DIR}/gl/renderer/OpenGLRenderer.cpp"
"${APP_DIR}/gl/renderer/OpenGLRenderer.h" "${APP_DIR}/gl/renderer/OpenGLRenderer.h"
"${APP_DIR}/gl/renderer/RenderTargetPool.cpp" "${APP_DIR}/gl/renderer/RenderTargetPool.cpp"
@@ -98,14 +109,50 @@ set(APP_SOURCES
"${APP_DIR}/platform/NativeHandles.h" "${APP_DIR}/platform/NativeHandles.h"
"${APP_DIR}/platform/NativeSockets.h" "${APP_DIR}/platform/NativeSockets.h"
"${APP_DIR}/resource.h" "${APP_DIR}/resource.h"
"${APP_DIR}/runtime/RuntimeHost.cpp" "${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
"${APP_DIR}/runtime/RuntimeHost.h" "${APP_DIR}/runtime/coordination/RuntimeCoordinator.h"
"${APP_DIR}/runtime/RuntimeClock.cpp" "${APP_DIR}/runtime/events/RuntimeEventCoalescingQueue.h"
"${APP_DIR}/runtime/RuntimeClock.h" "${APP_DIR}/runtime/events/RuntimeEventDispatcher.h"
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/events/RuntimeEvent.h"
"${APP_DIR}/runtime/RuntimeJson.h" "${APP_DIR}/runtime/events/RuntimeEventPayloads.h"
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp" "${APP_DIR}/runtime/events/RuntimeEventQueue.h"
"${APP_DIR}/runtime/RuntimeParameterUtils.h" "${APP_DIR}/runtime/events/RuntimeEventType.h"
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
"${APP_DIR}/runtime/live/CommittedLiveState.h"
"${APP_DIR}/runtime/live/RenderStateComposer.cpp"
"${APP_DIR}/runtime/live/RenderStateComposer.h"
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp"
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.h"
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
"${APP_DIR}/runtime/live/RuntimeLiveState.h"
"${APP_DIR}/runtime/persistence/PersistenceRequest.h"
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
"${APP_DIR}/runtime/persistence/PersistenceWriter.h"
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
"${APP_DIR}/runtime/presentation/RuntimeStateJson.h"
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.h"
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.h"
"${APP_DIR}/runtime/snapshot/RuntimeSnapshotProvider.cpp"
"${APP_DIR}/runtime/snapshot/RuntimeSnapshotProvider.h"
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
"${APP_DIR}/runtime/store/LayerStackStore.h"
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
"${APP_DIR}/runtime/store/RuntimeConfigStore.h"
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
"${APP_DIR}/runtime/store/RuntimeStore.h"
"${APP_DIR}/runtime/store/RuntimeStoreReadModels.h"
"${APP_DIR}/runtime/store/ShaderPackageCatalog.cpp"
"${APP_DIR}/runtime/store/ShaderPackageCatalog.h"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.h"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.h"
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
"${APP_DIR}/runtime/telemetry/HealthTelemetry.h"
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
"${APP_DIR}/runtime/telemetry/RuntimeClock.h"
"${APP_DIR}/shader/ShaderCompiler.cpp" "${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderCompiler.h" "${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
@@ -116,7 +163,20 @@ set(APP_SOURCES
"${APP_DIR}/targetver.h" "${APP_DIR}/targetver.h"
"${APP_DIR}/videoio/VideoIOFormat.cpp" "${APP_DIR}/videoio/VideoIOFormat.cpp"
"${APP_DIR}/videoio/VideoIOFormat.h" "${APP_DIR}/videoio/VideoIOFormat.h"
"${APP_DIR}/videoio/VideoBackend.cpp"
"${APP_DIR}/videoio/VideoBackend.h"
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
"${APP_DIR}/videoio/VideoIOTypes.h" "${APP_DIR}/videoio/VideoIOTypes.h"
"${APP_DIR}/videoio/OutputProductionController.cpp"
"${APP_DIR}/videoio/OutputProductionController.h"
"${APP_DIR}/videoio/RenderCadenceController.cpp"
"${APP_DIR}/videoio/RenderCadenceController.h"
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
"${APP_DIR}/videoio/RenderOutputQueue.h"
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
"${APP_DIR}/videoio/SystemOutputFramePool.h"
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp" "${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.h" "${APP_DIR}/videoio/VideoPlayoutScheduler.h"
) )
@@ -127,11 +187,23 @@ target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/control" "${APP_DIR}/control"
"${APP_DIR}/gl" "${APP_DIR}/gl"
"${APP_DIR}/gl/composite"
"${APP_DIR}/gl/frame"
"${APP_DIR}/gl/pipeline" "${APP_DIR}/gl/pipeline"
"${APP_DIR}/gl/renderer" "${APP_DIR}/gl/renderer"
"${APP_DIR}/gl/shader" "${APP_DIR}/gl/shader"
"${APP_DIR}/gl/threading"
"${APP_DIR}/platform" "${APP_DIR}/platform"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/coordination"
"${APP_DIR}/runtime/events"
"${APP_DIR}/runtime/live"
"${APP_DIR}/runtime/persistence"
"${APP_DIR}/runtime/presentation"
"${APP_DIR}/runtime/snapshot"
"${APP_DIR}/runtime/store"
"${APP_DIR}/runtime/support"
"${APP_DIR}/runtime/telemetry"
"${APP_DIR}/shader" "${APP_DIR}/shader"
"${APP_DIR}/videoio" "${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink" "${APP_DIR}/videoio/decklink"
@@ -157,14 +229,201 @@ if(MSVC)
target_compile_options(LoopThroughWithOpenGLCompositing PRIVATE /W3) target_compile_options(LoopThroughWithOpenGLCompositing PRIVATE /W3)
endif() endif()
set(PROBE_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/apps/DeckLinkRenderCadenceProbe")
add_executable(DeckLinkRenderCadenceProbe
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.h"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.h"
"${APP_DIR}/videoio/decklink/DeckLinkSession.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkSession.h"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
"${APP_DIR}/gl/renderer/GLExtensions.cpp"
"${APP_DIR}/gl/renderer/GLExtensions.h"
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${APP_DIR}/videoio/VideoIOFormat.h"
"${APP_DIR}/videoio/VideoIOTypes.h"
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
"${PROBE_APP_DIR}/DeckLinkRenderCadenceProbe.cpp"
)
target_include_directories(DeckLinkRenderCadenceProbe PRIVATE
"${APP_DIR}"
"${APP_DIR}/gl/renderer"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
target_link_libraries(DeckLinkRenderCadenceProbe PRIVATE
opengl32
Ole32
)
target_compile_definitions(DeckLinkRenderCadenceProbe PRIVATE
_UNICODE
UNICODE
)
if(MSVC)
target_compile_options(DeckLinkRenderCadenceProbe PRIVATE /W3)
endif()
set(RENDER_CADENCE_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/apps/RenderCadenceCompositor")
set(RENDER_CADENCE_APP_SOURCES
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.h"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.h"
"${APP_DIR}/videoio/decklink/DeckLinkSession.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkSession.h"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
"${APP_DIR}/gl/renderer/GLExtensions.cpp"
"${APP_DIR}/gl/renderer/GLExtensions.h"
"${APP_DIR}/gl/shader/Std140Buffer.h"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.h"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.h"
"${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.h"
"${APP_DIR}/shader/ShaderTypes.h"
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${APP_DIR}/videoio/VideoIOFormat.h"
"${APP_DIR}/videoio/VideoIOTypes.h"
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
"${RENDER_CADENCE_APP_DIR}/RenderCadenceCompositor.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.h"
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.h"
"${RENDER_CADENCE_APP_DIR}/app/RenderCadenceApp.h"
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.cpp"
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerControllerBuild.cpp"
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerControllerControls.cpp"
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.h"
"${RENDER_CADENCE_APP_DIR}/control/ControlActionResult.h"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.cpp"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.h"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.h"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerRoutes.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeStateJson.h"
"${RENDER_CADENCE_APP_DIR}/frames/InputFrameMailbox.cpp"
"${RENDER_CADENCE_APP_DIR}/frames/InputFrameMailbox.h"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.h"
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
"${RENDER_CADENCE_APP_DIR}/logging/Logger.h"
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.cpp"
"${RENDER_CADENCE_APP_DIR}/platform/HiddenGlWindow.h"
"${RENDER_CADENCE_APP_DIR}/render/InputFrameTexture.cpp"
"${RENDER_CADENCE_APP_DIR}/render/InputFrameTexture.h"
"${RENDER_CADENCE_APP_DIR}/render/readback/Bgra8ReadbackPipeline.cpp"
"${RENDER_CADENCE_APP_DIR}/render/readback/Bgra8ReadbackPipeline.h"
"${RENDER_CADENCE_APP_DIR}/render/readback/PboReadbackRing.cpp"
"${RENDER_CADENCE_APP_DIR}/render/readback/PboReadbackRing.h"
"${RENDER_CADENCE_APP_DIR}/render/RenderCadenceClock.cpp"
"${RENDER_CADENCE_APP_DIR}/render/RenderCadenceClock.h"
"${RENDER_CADENCE_APP_DIR}/render/RenderThread.cpp"
"${RENDER_CADENCE_APP_DIR}/render/RenderThread.h"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderRenderer.cpp"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderRenderer.h"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderParams.cpp"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderParams.h"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeRenderScene.cpp"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeRenderScene.h"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeRenderSceneRender.cpp"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderPrepareWorker.cpp"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderPrepareWorker.h"
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderProgram.h"
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.cpp"
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.h"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.h"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h"
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.h"
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetryJson.h"
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h"
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryHealthMonitor.h"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkInput.cpp"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkInput.h"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkInputThread.h"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.h"
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutputThread.h"
)
add_executable(RenderCadenceCompositor ${RENDER_CADENCE_APP_SOURCES})
target_include_directories(RenderCadenceCompositor PRIVATE
"${APP_DIR}"
"${APP_DIR}/gl/renderer"
"${APP_DIR}/gl/shader"
"${APP_DIR}/platform"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
"${RENDER_CADENCE_APP_DIR}"
"${RENDER_CADENCE_APP_DIR}/app"
"${RENDER_CADENCE_APP_DIR}/control"
"${RENDER_CADENCE_APP_DIR}/control/http"
"${RENDER_CADENCE_APP_DIR}/frames"
"${RENDER_CADENCE_APP_DIR}/json"
"${RENDER_CADENCE_APP_DIR}/logging"
"${RENDER_CADENCE_APP_DIR}/platform"
"${RENDER_CADENCE_APP_DIR}/render"
"${RENDER_CADENCE_APP_DIR}/render/readback"
"${RENDER_CADENCE_APP_DIR}/render/runtime"
"${RENDER_CADENCE_APP_DIR}/runtime"
"${RENDER_CADENCE_APP_DIR}/telemetry"
"${RENDER_CADENCE_APP_DIR}/video"
)
target_link_libraries(RenderCadenceCompositor PRIVATE
opengl32
Ole32
Ws2_32
)
target_compile_definitions(RenderCadenceCompositor PRIVATE
_UNICODE
UNICODE
)
if(MSVC)
target_compile_options(RenderCadenceCompositor PRIVATE /W3)
endif()
add_executable(RuntimeJsonTests add_executable(RuntimeJsonTests
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeJsonTests.cpp"
) )
target_include_directories(RuntimeJsonTests PRIVATE target_include_directories(RuntimeJsonTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/support"
) )
if(MSVC) if(MSVC)
@@ -175,13 +434,14 @@ enable_testing()
add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests) add_test(NAME RuntimeJsonTests COMMAND RuntimeJsonTests)
add_executable(RuntimeClockTests add_executable(RuntimeClockTests
"${APP_DIR}/runtime/RuntimeClock.cpp" "${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeClockTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeClockTests.cpp"
) )
target_include_directories(RuntimeClockTests PRIVATE target_include_directories(RuntimeClockTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/telemetry"
) )
if(MSVC) if(MSVC)
@@ -190,15 +450,33 @@ endif()
add_test(NAME RuntimeClockTests COMMAND RuntimeClockTests) add_test(NAME RuntimeClockTests COMMAND RuntimeClockTests)
add_executable(HealthTelemetryTests
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/HealthTelemetryTests.cpp"
)
target_include_directories(HealthTelemetryTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/runtime/telemetry"
)
if(MSVC)
target_compile_options(HealthTelemetryTests PRIVATE /W3)
endif()
add_test(NAME HealthTelemetryTests COMMAND HealthTelemetryTests)
add_executable(RuntimeParameterUtilsTests add_executable(RuntimeParameterUtilsTests
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp" "${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeParameterUtilsTests.cpp"
) )
target_include_directories(RuntimeParameterUtilsTests PRIVATE target_include_directories(RuntimeParameterUtilsTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader" "${APP_DIR}/shader"
) )
@@ -208,34 +486,120 @@ endif()
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests) add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
add_executable(RuntimeHostVideoIOStateTests add_executable(RuntimeEventTypeTests
"${APP_DIR}/runtime/RuntimeHost.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeEventTypeTests.cpp"
"${APP_DIR}/runtime/RuntimeClock.cpp"
"${APP_DIR}/runtime/RuntimeJson.cpp"
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
"${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/videoio/VideoIOConfig.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeHostVideoIOStateTests.cpp"
) )
target_include_directories(RuntimeHostVideoIOStateTests PRIVATE target_include_directories(RuntimeEventTypeTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/platform"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/shader" "${APP_DIR}/runtime/events"
"${APP_DIR}/videoio" "${APP_DIR}/runtime/persistence"
)
target_link_libraries(RuntimeHostVideoIOStateTests PRIVATE
Advapi32
) )
if(MSVC) if(MSVC)
target_compile_options(RuntimeHostVideoIOStateTests PRIVATE /W3) target_compile_options(RuntimeEventTypeTests PRIVATE /W3)
endif() endif()
add_test(NAME RuntimeHostVideoIOStateTests COMMAND RuntimeHostVideoIOStateTests) add_test(NAME RuntimeEventTypeTests COMMAND RuntimeEventTypeTests)
add_executable(RuntimeLiveStateTests
"${APP_DIR}/runtime/live/RenderStateComposer.cpp"
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeLiveStateTests.cpp"
)
target_include_directories(RuntimeLiveStateTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/runtime/live"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader"
)
if(MSVC)
target_compile_options(RuntimeLiveStateTests PRIVATE /W3)
endif()
add_test(NAME RuntimeLiveStateTests COMMAND RuntimeLiveStateTests)
add_executable(RuntimeStateLayerModelTests
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeStateLayerModelTests.cpp"
)
target_include_directories(RuntimeStateLayerModelTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/runtime/live"
)
if(MSVC)
target_compile_options(RuntimeStateLayerModelTests PRIVATE /W3)
endif()
add_test(NAME RuntimeStateLayerModelTests COMMAND RuntimeStateLayerModelTests)
add_executable(PersistenceWriterTests
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/PersistenceWriterTests.cpp"
)
target_include_directories(PersistenceWriterTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime"
"${APP_DIR}/runtime/persistence"
)
if(MSVC)
target_compile_options(PersistenceWriterTests PRIVATE /W3)
endif()
add_test(NAME PersistenceWriterTests COMMAND PersistenceWriterTests)
add_executable(RuntimeSubsystemTests
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
"${APP_DIR}/runtime/store/ShaderPackageCatalog.cpp"
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/runtime/telemetry/HealthTelemetry.cpp"
"${APP_DIR}/runtime/telemetry/RuntimeClock.cpp"
"${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeSubsystemTests.cpp"
)
target_include_directories(RuntimeSubsystemTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/platform"
"${APP_DIR}/runtime"
"${APP_DIR}/runtime/coordination"
"${APP_DIR}/runtime/events"
"${APP_DIR}/runtime/live"
"${APP_DIR}/runtime/persistence"
"${APP_DIR}/runtime/presentation"
"${APP_DIR}/runtime/snapshot"
"${APP_DIR}/runtime/store"
"${APP_DIR}/runtime/support"
"${APP_DIR}/runtime/telemetry"
"${APP_DIR}/shader"
)
if(MSVC)
target_compile_options(RuntimeSubsystemTests PRIVATE /W3)
endif()
add_test(NAME RuntimeSubsystemTests COMMAND RuntimeSubsystemTests)
add_executable(Std140BufferTests add_executable(Std140BufferTests
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
@@ -253,8 +617,27 @@ endif()
add_test(NAME Std140BufferTests COMMAND Std140BufferTests) add_test(NAME Std140BufferTests COMMAND Std140BufferTests)
add_executable(RenderCommandQueueTests
"${APP_DIR}/gl/threading/RenderCommandQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCommandQueueTests.cpp"
)
target_include_directories(RenderCommandQueueTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/gl"
"${APP_DIR}/gl/threading"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
if(MSVC)
target_compile_options(RenderCommandQueueTests PRIVATE /W3)
endif()
add_test(NAME RenderCommandQueueTests COMMAND RenderCommandQueueTests)
add_executable(ShaderPackageRegistryTests add_executable(ShaderPackageRegistryTests
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderPackageRegistryTests.cpp"
) )
@@ -262,6 +645,7 @@ add_executable(ShaderPackageRegistryTests
target_include_directories(ShaderPackageRegistryTests PRIVATE target_include_directories(ShaderPackageRegistryTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader" "${APP_DIR}/shader"
) )
@@ -272,7 +656,7 @@ endif()
add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests) add_test(NAME ShaderPackageRegistryTests COMMAND ShaderPackageRegistryTests)
add_executable(ShaderSlangValidationTests add_executable(ShaderSlangValidationTests
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/shader/ShaderCompiler.cpp" "${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/ShaderSlangValidationTests.cpp"
@@ -282,6 +666,7 @@ target_include_directories(ShaderSlangValidationTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/platform" "${APP_DIR}/platform"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader" "${APP_DIR}/shader"
) )
@@ -350,8 +735,338 @@ endif()
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests) add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
add_executable(OutputProductionControllerTests
"${APP_DIR}/videoio/OutputProductionController.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/OutputProductionControllerTests.cpp"
)
target_include_directories(OutputProductionControllerTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/videoio"
)
if(MSVC)
target_compile_options(OutputProductionControllerTests PRIVATE /W3)
endif()
add_test(NAME OutputProductionControllerTests COMMAND OutputProductionControllerTests)
add_executable(RenderOutputQueueTests
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderOutputQueueTests.cpp"
)
target_include_directories(RenderOutputQueueTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
if(MSVC)
target_compile_options(RenderOutputQueueTests PRIVATE /W3)
endif()
add_test(NAME RenderOutputQueueTests COMMAND RenderOutputQueueTests)
add_executable(RenderCadenceControllerTests
"${APP_DIR}/videoio/RenderCadenceController.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceControllerTests.cpp"
)
target_include_directories(RenderCadenceControllerTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/videoio"
)
if(MSVC)
target_compile_options(RenderCadenceControllerTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceControllerTests COMMAND RenderCadenceControllerTests)
add_executable(RenderCadenceCompositorFrameExchangeTests
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorFrameExchangeTests.cpp"
)
target_include_directories(RenderCadenceCompositorFrameExchangeTests PRIVATE
"${APP_DIR}/videoio"
"${RENDER_CADENCE_APP_DIR}/frames"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorFrameExchangeTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorFrameExchangeTests COMMAND RenderCadenceCompositorFrameExchangeTests)
add_executable(RenderCadenceCompositorInputFrameMailboxTests
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${RENDER_CADENCE_APP_DIR}/frames/InputFrameMailbox.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorInputFrameMailboxTests.cpp"
)
target_include_directories(RenderCadenceCompositorInputFrameMailboxTests PRIVATE
"${APP_DIR}/videoio"
"${RENDER_CADENCE_APP_DIR}/frames"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorInputFrameMailboxTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorInputFrameMailboxTests COMMAND RenderCadenceCompositorInputFrameMailboxTests)
add_executable(RenderCadenceCompositorClockTests
"${RENDER_CADENCE_APP_DIR}/render/RenderCadenceClock.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorClockTests.cpp"
)
target_include_directories(RenderCadenceCompositorClockTests PRIVATE
"${RENDER_CADENCE_APP_DIR}/render"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorClockTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorClockTests COMMAND RenderCadenceCompositorClockTests)
add_executable(RenderCadenceCompositorTelemetryTests
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorTelemetryTests.cpp"
)
target_include_directories(RenderCadenceCompositorTelemetryTests PRIVATE
"${RENDER_CADENCE_APP_DIR}/json"
"${RENDER_CADENCE_APP_DIR}/telemetry"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorTelemetryTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorTelemetryTests COMMAND RenderCadenceCompositorTelemetryTests)
add_executable(RenderCadenceCompositorRuntimeShaderParamsTests
"${RENDER_CADENCE_APP_DIR}/render/runtime/RuntimeShaderParams.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeShaderParamsTests.cpp"
)
target_include_directories(RenderCadenceCompositorRuntimeShaderParamsTests PRIVATE
"${APP_DIR}/gl/shader"
"${APP_DIR}/shader"
"${RENDER_CADENCE_APP_DIR}/render"
"${RENDER_CADENCE_APP_DIR}/render/runtime"
"${RENDER_CADENCE_APP_DIR}/runtime"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorRuntimeShaderParamsTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
add_executable(RenderCadenceCompositorRuntimeLayerModelTests
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
)
target_include_directories(RenderCadenceCompositorRuntimeLayerModelTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader"
"${RENDER_CADENCE_APP_DIR}/runtime"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorRuntimeLayerModelTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorRuntimeLayerModelTests COMMAND RenderCadenceCompositorRuntimeLayerModelTests)
add_executable(RenderCadenceCompositorSupportedShaderCatalogTests
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorSupportedShaderCatalogTests.cpp"
)
target_include_directories(RenderCadenceCompositorSupportedShaderCatalogTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader"
"${RENDER_CADENCE_APP_DIR}/runtime"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorSupportedShaderCatalogTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorSupportedShaderCatalogTests COMMAND RenderCadenceCompositorSupportedShaderCatalogTests)
add_executable(RenderCadenceCompositorLoggerTests
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorLoggerTests.cpp"
)
target_include_directories(RenderCadenceCompositorLoggerTests PRIVATE
"${RENDER_CADENCE_APP_DIR}/logging"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorLoggerTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorLoggerTests COMMAND RenderCadenceCompositorLoggerTests)
add_executable(RenderCadenceCompositorJsonWriterTests
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorJsonWriterTests.cpp"
)
target_include_directories(RenderCadenceCompositorJsonWriterTests PRIVATE
"${RENDER_CADENCE_APP_DIR}/json"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorJsonWriterTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorJsonWriterTests COMMAND RenderCadenceCompositorJsonWriterTests)
add_executable(RenderCadenceCompositorRuntimeStateJsonTests
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
)
target_include_directories(RenderCadenceCompositorRuntimeStateJsonTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${APP_DIR}/shader"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
"${RENDER_CADENCE_APP_DIR}/app"
"${RENDER_CADENCE_APP_DIR}/control"
"${RENDER_CADENCE_APP_DIR}/json"
"${RENDER_CADENCE_APP_DIR}/logging"
"${RENDER_CADENCE_APP_DIR}/runtime"
"${RENDER_CADENCE_APP_DIR}/telemetry"
"${RENDER_CADENCE_APP_DIR}/video"
)
if(MSVC)
target_compile_options(RenderCadenceCompositorRuntimeStateJsonTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorRuntimeStateJsonTests COMMAND RenderCadenceCompositorRuntimeStateJsonTests)
add_executable(RenderCadenceCompositorHttpControlServerTests
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerRoutes.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp"
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorHttpControlServerTests.cpp"
)
target_include_directories(RenderCadenceCompositorHttpControlServerTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${RENDER_CADENCE_APP_DIR}/control"
"${RENDER_CADENCE_APP_DIR}/control/http"
"${RENDER_CADENCE_APP_DIR}/json"
"${RENDER_CADENCE_APP_DIR}/logging"
)
target_link_libraries(RenderCadenceCompositorHttpControlServerTests PRIVATE
Ws2_32
)
if(MSVC)
target_compile_options(RenderCadenceCompositorHttpControlServerTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorHttpControlServerTests COMMAND RenderCadenceCompositorHttpControlServerTests)
add_executable(RenderCadenceCompositorAppConfigProviderTests
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorAppConfigProviderTests.cpp"
)
target_include_directories(RenderCadenceCompositorAppConfigProviderTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${RENDER_CADENCE_APP_DIR}/app"
"${RENDER_CADENCE_APP_DIR}/control"
"${RENDER_CADENCE_APP_DIR}/logging"
"${RENDER_CADENCE_APP_DIR}/telemetry"
"${RENDER_CADENCE_APP_DIR}/video"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
target_link_libraries(RenderCadenceCompositorAppConfigProviderTests PRIVATE
Ws2_32
)
if(MSVC)
target_compile_options(RenderCadenceCompositorAppConfigProviderTests PRIVATE /W3)
endif()
add_test(NAME RenderCadenceCompositorAppConfigProviderTests COMMAND RenderCadenceCompositorAppConfigProviderTests)
add_executable(SystemOutputFramePoolTests
"${APP_DIR}/videoio/SystemOutputFramePool.cpp"
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/SystemOutputFramePoolTests.cpp"
)
target_include_directories(SystemOutputFramePoolTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
if(MSVC)
target_compile_options(SystemOutputFramePoolTests PRIVATE /W3)
endif()
add_test(NAME SystemOutputFramePoolTests COMMAND SystemOutputFramePoolTests)
add_executable(VideoBackendLifecycleTests
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoBackendLifecycleTests.cpp"
)
target_include_directories(VideoBackendLifecycleTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/videoio"
)
if(MSVC)
target_compile_options(VideoBackendLifecycleTests PRIVATE /W3)
endif()
add_test(NAME VideoBackendLifecycleTests COMMAND VideoBackendLifecycleTests)
add_executable(VideoIODeviceFakeTests add_executable(VideoIODeviceFakeTests
"${APP_DIR}/videoio/VideoIOConfig.cpp"
"${APP_DIR}/videoio/VideoIOFormat.cpp" "${APP_DIR}/videoio/VideoIOFormat.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
) )
@@ -368,43 +1083,6 @@ endif()
add_test(NAME VideoIODeviceFakeTests COMMAND VideoIODeviceFakeTests) add_test(NAME VideoIODeviceFakeTests COMMAND VideoIODeviceFakeTests)
add_executable(VideoIOBackendFactoryTests
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/videoio/decklink/DeckLinkSession.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkSession.h"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.h"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.cpp"
"${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.h"
"${APP_DIR}/videoio/VideoIOBackendFactory.cpp"
"${APP_DIR}/videoio/VideoIOBackendFactory.h"
"${APP_DIR}/videoio/VideoIOConfig.cpp"
"${APP_DIR}/videoio/VideoIOConfig.h"
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIOBackendFactoryTests.cpp"
)
target_include_directories(VideoIOBackendFactoryTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/gl/renderer"
"${APP_DIR}/videoio"
"${APP_DIR}/videoio/decklink"
)
target_link_libraries(VideoIOBackendFactoryTests PRIVATE
Ole32
)
if(MSVC)
target_compile_options(VideoIOBackendFactoryTests PRIVATE /W3)
endif()
add_test(NAME VideoIOBackendFactoryTests COMMAND VideoIOBackendFactoryTests)
install(TARGETS LoopThroughWithOpenGLCompositing install(TARGETS LoopThroughWithOpenGLCompositing
RUNTIME DESTINATION "." RUNTIME DESTINATION "."
) )
@@ -418,7 +1096,7 @@ install(FILES "${SLANG_LICENSE_FILE}"
RENAME "SLANG_LICENSE.txt" RENAME "SLANG_LICENSE.txt"
) )
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/SHADER_CONTRACT.md" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/SHADER_CONTRACT.md"
DESTINATION "." DESTINATION "."
) )

View File

@@ -36,7 +36,7 @@
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": 2, "decimals": 2,
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": false, "bypass": false,
"onCreate": "", "onCreate": "",
@@ -53,8 +53,8 @@
"visible": true, "visible": true,
"interaction": true, "interaction": true,
"comments": "XY control for Fisheye Reproject pan and tilt.", "comments": "XY control for Fisheye Reproject pan and tilt.",
"width": 420, "width": 460,
"height": 420, "height": 250,
"expand": false, "expand": false,
"colorText": "auto", "colorText": "auto",
"colorWidget": "auto", "colorWidget": "auto",
@@ -70,14 +70,14 @@
"css": "", "css": "",
"pips": true, "pips": true,
"snap": false, "snap": false,
"spring": false, "spring": true,
"rangeX": { "rangeX": {
"min": -60, "min": -1,
"max": 60 "max": 1
}, },
"rangeY": { "rangeY": {
"min": 45, "min": 1,
"max": -45 "max": -1
}, },
"logScaleX": false, "logScaleX": false,
"logScaleY": false, "logScaleY": false,
@@ -94,13 +94,13 @@
"address": "/VideoShaderToys/fisheye-reproject/xy", "address": "/VideoShaderToys/fisheye-reproject/xy",
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": "2f", "decimals": "3f",
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": true, "bypass": true,
"onCreate": "", "onCreate": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nstate.target = '192.168.1.46:9000';\nstate.panAddress = '/VideoShaderToys/fisheye-reproject/panDegrees';\nstate.tiltAddress = '/VideoShaderToys/fisheye-reproject/tiltDegrees';\nstate.minPan = -60;\nstate.maxPan = 60;\nstate.minTilt = -45;\nstate.maxTilt = 45;\nstate.pan = 0;\nstate.tilt = 0;\nstate.stickX = 0;\nstate.stickY = 0;\nstate.tickMs = 16;\nstate.stepPan = 0.75;\nstate.stepTilt = 0.75;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
"onValue": "var pan = Array.isArray(value) ? Number(value[0]) : 0;\nvar tilt = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/panDegrees', {type: 'f', value: pan});\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/tiltDegrees', {type: 'f', value: tilt});", "onValue": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nvar stickX = Array.isArray(value) ? Number(value[0]) : 0;\nvar stickY = Array.isArray(value) ? Number(value[1]) : 0;\nstate.stickX = isFinite(stickX) ? state.applyCurve(stickX) : 0;\nstate.stickY = isFinite(stickY) ? state.applyCurve(stickY) : 0;",
"onTouch": "", "onTouch": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stickX && !state.stickY) {\n return;\n }\n state.pan = Math.max(state.minPan, Math.min(state.maxPan, state.pan + (state.stickX * state.stepPan)));\n state.tilt = Math.max(state.minTilt, Math.min(state.maxTilt, state.tilt + (state.stickY * state.stepTilt)));\n send(state.target, state.panAddress, {type: 'f', value: state.pan});\n send(state.target, state.tiltAddress, {type: 'f', value: state.tilt});\n }, state.tickMs);\n }\n} else {\n state.stickX = 0;\n state.stickY = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}",
"pointSize": 20, "pointSize": 20,
"ephemeral": false, "ephemeral": false,
"label": "", "label": "",
@@ -121,7 +121,7 @@
"interaction": true, "interaction": true,
"comments": "", "comments": "",
"width": 90, "width": 90,
"height": 420, "height": 250,
"expand": false, "expand": false,
"colorText": "auto", "colorText": "auto",
"colorWidget": "auto", "colorWidget": "auto",
@@ -144,90 +144,29 @@
"gradient": [], "gradient": [],
"snap": false, "snap": false,
"touchZone": "all", "touchZone": "all",
"spring": false, "spring": true,
"doubleTap": false, "doubleTap": false,
"range": { "range": {
"min": 100, "min": -1,
"max": 10 "max": 1
}, },
"logScale": false, "logScale": false,
"sensitivity": 1, "sensitivity": 1,
"steps": "", "steps": "",
"origin": "auto", "origin": "auto",
"value": "", "value": 0,
"default": 90, "default": 0,
"linkId": "", "linkId": "",
"address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees", "address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees",
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": 2, "decimals": "3f",
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false,
"bypass": false,
"onCreate": "",
"onValue": "",
"onTouch": ""
},
{
"type": "xy",
"top": 700,
"left": 190,
"lock": false,
"id": "Pan Pad",
"visible": true,
"interaction": true,
"comments": "",
"width": "auto",
"height": "auto",
"expand": false,
"colorText": "auto",
"colorWidget": "auto",
"colorStroke": "auto",
"colorFill": "auto",
"alphaStroke": "auto",
"alphaFillOff": "auto",
"alphaFillOn": "auto",
"lineWidth": "auto",
"borderRadius": "auto",
"padding": "auto",
"html": "",
"css": "",
"pointSize": 20,
"ephemeral": false,
"pips": true,
"label": "",
"snap": false,
"spring": false,
"rangeX": {
"min": -1,
"max": 1
},
"rangeY": {
"min": -1,
"max": 1
},
"logScaleX": false,
"logScaleY": false,
"stepsX": false,
"stepsY": false,
"clipX": "",
"clipY": "",
"axisLock": "",
"doubleTap": false,
"sensitivity": 1,
"value": "",
"default": "",
"linkId": "",
"address": "/VideoShaderToys/video-transform/pan",
"preArgs": "",
"typeTags": "",
"decimals": 2,
"target": "",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": true, "bypass": true,
"onCreate": "", "onCreate": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nstate.target = '192.168.1.46:9000';\nstate.address = '/VideoShaderToys/fisheye-reproject/virtualFovDegrees';\nstate.minFov = 10;\nstate.maxFov = 100;\nstate.fov = 90;\nstate.stick = 0;\nstate.tickMs = 16;\nstate.stepFov = 0.6;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
"onValue": "var x = Array.isArray(value) ? Number(value[0]) : 0;\nvar y = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/video-transform/pan', {type: 'f', value: x}, {type: 'f', value: y});", "onValue": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nvar stick = Number(value);\nstate.stick = isFinite(stick) ? state.applyCurve(stick) : 0;",
"onTouch": "" "onTouch": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stick) {\n return;\n }\n state.fov = Math.max(state.minFov, Math.min(state.maxFov, state.fov - (state.stick * state.stepFov)));\n send(state.target, state.address, {type: 'f', value: state.fov});\n }, state.tickMs);\n }\n} else {\n state.stick = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}"
} }
], ],
"tabs": [] "tabs": []

View File

@@ -49,7 +49,7 @@ Configure and build the native app:
```powershell ```powershell
cmake --preset vs2022-x64-debug cmake --preset vs2022-x64-debug
cmake --build --preset build-debug cmake --build --preset build-debug --parallel
``` ```
Build the React control UI: Build the React control UI:
@@ -80,7 +80,7 @@ npm ci
npm run build npm run build
cd .. cd ..
cmake --preset vs2022-x64-release cmake --preset vs2022-x64-release
cmake --build --preset build-release cmake --build --preset build-release --parallel
cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader
``` ```
@@ -114,7 +114,7 @@ Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip
Run native tests: Run native tests:
```powershell ```powershell
cmake --build --preset build-debug --target RUN_TESTS cmake --build --preset build-debug --target RUN_TESTS --parallel
``` ```
Run the UI production build check: Run the UI production build check:
@@ -141,7 +141,9 @@ Current native test coverage includes:
{ {
"shaderLibrary": "shaders", "shaderLibrary": "shaders",
"serverPort": 8080, "serverPort": 8080,
"oscBindAddress": "127.0.0.1",
"oscPort": 9000, "oscPort": 9000,
"oscSmoothing": 0.18,
"inputVideoFormat": "1080p", "inputVideoFormat": "1080p",
"inputFrameRate": "59.94", "inputFrameRate": "59.94",
"outputVideoFormat": "1080p", "outputVideoFormat": "1080p",
@@ -203,13 +205,13 @@ runtime/screenshots/
## OSC Control ## OSC Control
The native host also listens for local OSC parameter control on the configured `oscPort`: The native host also listens for OSC parameter control on the configured `oscBindAddress` and `oscPort`:
```text ```text
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID} /VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
``` ```
For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. See `docs/OSC_CONTROL.md` for details. For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. OSC updates are coalesced and applied once per render tick, UI state broadcasts are throttled, and OSC-driven parameter changes are not autosaved to `runtime/runtime_state.json`. `oscSmoothing` adds a small per-frame easing amount for numeric OSC controls such as floats, `vec2`, and `color`, while booleans, enums, text, and triggers stay immediate. The default bind address is `127.0.0.1`; set `oscBindAddress` to `0.0.0.0` to accept OSC on all IPv4 interfaces. See `docs/OSC_CONTROL.md` for details.
## Shader Packages ## Shader Packages
@@ -275,3 +277,4 @@ If `SLANG_ROOT` is not set, the workflow falls back to the repo-local default un
- Mipmapping for shader-declared textures - Mipmapping for shader-declared textures
- Anotate included shaders - Anotate included shaders
- allow 3 vector exposed controls - allow 3 vector exposed controls
- add nearest sampling to the extra shader pass

View File

@@ -0,0 +1,920 @@
#include "DeckLinkSession.h"
#include "GLExtensions.h"
#include "VideoIOFormat.h"
#include "VideoPlayoutPolicy.h"
#include <windows.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
namespace
{
constexpr unsigned kDefaultWidth = 1920;
constexpr unsigned kDefaultHeight = 1080;
constexpr std::size_t kSystemFrameSlots = 12;
constexpr std::size_t kPboDepth = 6;
constexpr std::size_t kWarmupFrames = 4;
constexpr std::size_t kDeckLinkTargetBufferedFrames = 4;
enum class ProbeSlotState
{
Free,
Rendering,
Completed,
Scheduled
};
struct ProbeFrame
{
void* bytes = nullptr;
long rowBytes = 0;
unsigned width = 0;
unsigned height = 0;
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
std::size_t index = 0;
uint64_t generation = 0;
uint64_t frameIndex = 0;
};
struct ProbeMetrics
{
uint64_t renderedFrames = 0;
uint64_t completedFrames = 0;
uint64_t scheduledFrames = 0;
uint64_t completedDrops = 0;
uint64_t acquireMisses = 0;
uint64_t scheduleUnderruns = 0;
uint64_t pboQueueMisses = 0;
std::size_t freeCount = 0;
std::size_t renderingCount = 0;
std::size_t completedCount = 0;
std::size_t scheduledCount = 0;
};
class LatestFrameStore
{
public:
LatestFrameStore(unsigned width, unsigned height, std::size_t capacity) :
mWidth(width),
mHeight(height),
mRowBytes(VideoIORowBytes(VideoIOPixelFormat::Bgra8, width))
{
mSlots.resize(capacity);
const std::size_t byteCount = static_cast<std::size_t>(mRowBytes) * static_cast<std::size_t>(mHeight);
for (Slot& slot : mSlots)
{
slot.bytes.resize(byteCount);
slot.generation = 1;
}
}
bool AcquireForRender(ProbeFrame& frame)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!AcquireFreeLocked(frame))
{
if (!DropOldestCompletedLocked() || !AcquireFreeLocked(frame))
{
++mMetrics.acquireMisses;
return false;
}
}
return true;
}
bool PublishCompleted(const ProbeFrame& frame)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!IsValidLocked(frame))
return false;
Slot& slot = mSlots[frame.index];
if (slot.state != ProbeSlotState::Rendering)
return false;
slot.state = ProbeSlotState::Completed;
slot.frameIndex = frame.frameIndex;
mCompletedIndices.push_back(frame.index);
++mMetrics.completedFrames;
mCondition.notify_all();
return true;
}
bool ConsumeCompleted(ProbeFrame& frame)
{
std::lock_guard<std::mutex> lock(mMutex);
while (!mCompletedIndices.empty())
{
const std::size_t index = mCompletedIndices.front();
mCompletedIndices.pop_front();
if (index >= mSlots.size() || mSlots[index].state != ProbeSlotState::Completed)
continue;
mSlots[index].state = ProbeSlotState::Scheduled;
FillFrameLocked(index, frame);
++mMetrics.scheduledFrames;
return true;
}
++mMetrics.scheduleUnderruns;
return false;
}
bool ReleaseByBytes(void* bytes)
{
if (bytes == nullptr)
return false;
std::lock_guard<std::mutex> lock(mMutex);
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
if (mSlots[index].bytes.data() != bytes)
continue;
mSlots[index].state = ProbeSlotState::Free;
++mSlots[index].generation;
RemoveCompletedIndexLocked(index);
mCondition.notify_all();
return true;
}
return false;
}
bool WaitForCompletedDepth(std::size_t targetDepth, std::chrono::milliseconds timeout)
{
std::unique_lock<std::mutex> lock(mMutex);
return mCondition.wait_for(lock, timeout, [&]() {
return CompletedCountLocked() >= targetDepth;
});
}
ProbeMetrics Metrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
ProbeMetrics metrics = mMetrics;
for (const Slot& slot : mSlots)
{
switch (slot.state)
{
case ProbeSlotState::Free:
++metrics.freeCount;
break;
case ProbeSlotState::Rendering:
++metrics.renderingCount;
break;
case ProbeSlotState::Completed:
++metrics.completedCount;
break;
case ProbeSlotState::Scheduled:
++metrics.scheduledCount;
break;
}
}
return metrics;
}
void CountRenderedFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
++mMetrics.renderedFrames;
}
void CountPboQueueMiss()
{
std::lock_guard<std::mutex> lock(mMutex);
++mMetrics.pboQueueMisses;
}
private:
struct Slot
{
std::vector<unsigned char> bytes;
ProbeSlotState state = ProbeSlotState::Free;
uint64_t generation = 1;
uint64_t frameIndex = 0;
};
bool AcquireFreeLocked(ProbeFrame& frame)
{
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
if (mSlots[index].state != ProbeSlotState::Free)
continue;
mSlots[index].state = ProbeSlotState::Rendering;
++mSlots[index].generation;
FillFrameLocked(index, frame);
return true;
}
return false;
}
bool DropOldestCompletedLocked()
{
while (!mCompletedIndices.empty())
{
const std::size_t index = mCompletedIndices.front();
mCompletedIndices.pop_front();
if (index >= mSlots.size() || mSlots[index].state != ProbeSlotState::Completed)
continue;
mSlots[index].state = ProbeSlotState::Free;
++mSlots[index].generation;
++mMetrics.completedDrops;
return true;
}
return false;
}
void FillFrameLocked(std::size_t index, ProbeFrame& frame) const
{
const Slot& slot = mSlots[index];
frame.bytes = const_cast<unsigned char*>(slot.bytes.data());
frame.rowBytes = static_cast<long>(mRowBytes);
frame.width = mWidth;
frame.height = mHeight;
frame.pixelFormat = VideoIOPixelFormat::Bgra8;
frame.index = index;
frame.generation = slot.generation;
frame.frameIndex = slot.frameIndex;
}
bool IsValidLocked(const ProbeFrame& frame) const
{
return frame.index < mSlots.size() && mSlots[frame.index].generation == frame.generation;
}
void RemoveCompletedIndexLocked(std::size_t index)
{
mCompletedIndices.erase(std::remove(mCompletedIndices.begin(), mCompletedIndices.end(), index), mCompletedIndices.end());
}
std::size_t CompletedCountLocked() const
{
std::size_t count = 0;
for (const Slot& slot : mSlots)
{
if (slot.state == ProbeSlotState::Completed)
++count;
}
return count;
}
unsigned mWidth = 0;
unsigned mHeight = 0;
unsigned mRowBytes = 0;
std::vector<Slot> mSlots;
std::deque<std::size_t> mCompletedIndices;
mutable std::mutex mMutex;
std::condition_variable mCondition;
ProbeMetrics mMetrics;
};
LRESULT CALLBACK ProbeWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
class HiddenOpenGLContext
{
public:
~HiddenOpenGLContext()
{
Destroy();
}
bool Create(unsigned width, unsigned height, std::string& error)
{
mInstance = GetModuleHandle(nullptr);
WNDCLASSA wc = {};
wc.style = CS_OWNDC;
wc.lpfnWndProc = ProbeWindowProc;
wc.hInstance = mInstance;
wc.lpszClassName = "DeckLinkRenderCadenceProbeWindow";
RegisterClassA(&wc);
mWindow = CreateWindowA(
wc.lpszClassName,
"DeckLink Render Cadence Probe",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
static_cast<int>(width),
static_cast<int>(height),
nullptr,
nullptr,
mInstance,
nullptr);
if (!mWindow)
{
error = "CreateWindowA failed.";
return false;
}
mDc = GetDC(mWindow);
if (!mDc)
{
error = "GetDC failed.";
return false;
}
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 0;
pfd.iLayerType = PFD_MAIN_PLANE;
const int pixelFormat = ChoosePixelFormat(mDc, &pfd);
if (pixelFormat == 0 || !SetPixelFormat(mDc, pixelFormat, &pfd))
{
error = "Could not choose/set a pixel format.";
return false;
}
mGlrc = wglCreateContext(mDc);
if (!mGlrc)
{
error = "wglCreateContext failed.";
return false;
}
return true;
}
bool MakeCurrent()
{
return mDc && mGlrc && wglMakeCurrent(mDc, mGlrc);
}
void ClearCurrent()
{
wglMakeCurrent(nullptr, nullptr);
}
void Destroy()
{
ClearCurrent();
if (mGlrc)
{
wglDeleteContext(mGlrc);
mGlrc = nullptr;
}
if (mWindow && mDc)
{
ReleaseDC(mWindow, mDc);
mDc = nullptr;
}
if (mWindow)
{
DestroyWindow(mWindow);
mWindow = nullptr;
}
}
private:
HINSTANCE mInstance = nullptr;
HWND mWindow = nullptr;
HDC mDc = nullptr;
HGLRC mGlrc = nullptr;
};
class RenderCadenceProbe
{
public:
RenderCadenceProbe(LatestFrameStore& frameStore, unsigned width, unsigned height, double frameDurationMs) :
mFrameStore(frameStore),
mWidth(width),
mHeight(height),
mFrameDuration(std::chrono::duration_cast<Clock::duration>(std::chrono::duration<double, std::milli>(frameDurationMs)))
{
if (mFrameDuration <= Clock::duration::zero())
mFrameDuration = std::chrono::milliseconds(16);
}
bool Start(std::string& error)
{
mStopping = false;
mThread = std::thread([this]() { ThreadMain(); });
std::unique_lock<std::mutex> lock(mStartupMutex);
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() { return mStarted || !mStartupError.empty(); }))
{
error = "Timed out starting render thread.";
return false;
}
if (!mStartupError.empty())
{
error = mStartupError;
return false;
}
return true;
}
void Stop()
{
mStopping = true;
if (mThread.joinable())
mThread.join();
}
private:
struct PboSlot
{
GLuint pbo = 0;
GLsync fence = nullptr;
bool inFlight = false;
uint64_t frameIndex = 0;
};
using Clock = std::chrono::steady_clock;
void ThreadMain()
{
std::string error;
HiddenOpenGLContext context;
if (!context.Create(mWidth, mHeight, error) || !context.MakeCurrent())
{
SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
return;
}
if (!ResolveGLExtensions())
{
SignalStartupFailure("OpenGL extension resolution failed.");
return;
}
if (!CreateRenderTargets())
{
SignalStartupFailure("OpenGL render target creation failed.");
return;
}
CreatePbos();
SignalStarted();
auto nextRenderTime = Clock::now();
while (!mStopping)
{
ConsumeCompletedPbos();
const auto now = Clock::now();
if (now < nextRenderTime)
{
std::this_thread::sleep_for((std::min)(std::chrono::milliseconds(1), std::chrono::duration_cast<std::chrono::milliseconds>(nextRenderTime - now)));
continue;
}
RenderPattern(mFrameIndex);
if (!QueueReadback(mFrameIndex))
mFrameStore.CountPboQueueMiss();
mFrameStore.CountRenderedFrame();
++mFrameIndex;
nextRenderTime += mFrameDuration;
if (Clock::now() - nextRenderTime > mFrameDuration * 4)
nextRenderTime = Clock::now() + mFrameDuration;
}
FlushPbos();
DestroyPbos();
DestroyRenderTargets();
context.ClearCurrent();
}
bool CreateRenderTargets()
{
glGenFramebuffers(1, &mFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
const bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return complete;
}
void DestroyRenderTargets()
{
if (mFramebuffer != 0)
glDeleteFramebuffers(1, &mFramebuffer);
if (mTexture != 0)
glDeleteTextures(1, &mTexture);
mFramebuffer = 0;
mTexture = 0;
}
void CreatePbos()
{
mPbos.resize(kPboDepth);
const std::size_t byteCount = static_cast<std::size_t>(VideoIORowBytes(VideoIOPixelFormat::Bgra8, mWidth)) * mHeight;
for (PboSlot& slot : mPbos)
{
glGenBuffers(1, &slot.pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(byteCount), nullptr, GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
void DestroyPbos()
{
for (PboSlot& slot : mPbos)
{
if (slot.fence)
glDeleteSync(slot.fence);
if (slot.pbo != 0)
glDeleteBuffers(1, &slot.pbo);
slot = {};
}
mPbos.clear();
}
void FlushPbos()
{
for (std::size_t i = 0; i < mPbos.size() * 2; ++i)
ConsumeCompletedPbos();
}
void RenderPattern(uint64_t frameIndex)
{
const float t = static_cast<float>(frameIndex) / 60.0f;
const float red = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t));
const float green = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t * 0.73f + 1.0f));
const float blue = 0.15f + 0.3f * (0.5f + 0.5f * std::sin(t * 0.41f + 2.0f));
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
glDisable(GL_SCISSOR_TEST);
glClearColor(red, green, blue, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
const int boxWidth = static_cast<int>(mWidth / 6);
const int boxHeight = static_cast<int>(mHeight / 5);
const float phase = 0.5f + 0.5f * std::sin(t * 1.7f);
const int x = static_cast<int>(phase * static_cast<float>(mWidth - boxWidth));
const int y = static_cast<int>((0.5f + 0.5f * std::sin(t * 1.1f + 0.8f)) * static_cast<float>(mHeight - boxHeight));
glEnable(GL_SCISSOR_TEST);
glScissor(x, y, boxWidth, boxHeight);
glClearColor(1.0f - red, 0.85f, 0.15f + blue, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_SCISSOR_TEST);
}
bool QueueReadback(uint64_t frameIndex)
{
if (mPbos.empty())
return false;
PboSlot& slot = mPbos[mWriteIndex];
if (slot.inFlight)
return false;
const std::size_t byteCount = static_cast<std::size_t>(VideoIORowBytes(VideoIOPixelFormat::Bgra8, mWidth)) * mHeight;
glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(byteCount), nullptr, GL_STREAM_READ);
glReadPixels(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
slot.inFlight = slot.fence != nullptr;
slot.frameIndex = frameIndex;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mWriteIndex = (mWriteIndex + 1) % mPbos.size();
return slot.inFlight;
}
void ConsumeCompletedPbos()
{
for (std::size_t checked = 0; checked < mPbos.size(); ++checked)
{
PboSlot& slot = mPbos[mReadIndex];
if (!slot.inFlight || slot.fence == nullptr)
{
mReadIndex = (mReadIndex + 1) % mPbos.size();
continue;
}
const GLenum waitResult = glClientWaitSync(slot.fence, 0, 0);
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
return;
ProbeFrame frame;
if (mFrameStore.AcquireForRender(frame))
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
void* mapped = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (mapped)
{
const std::size_t byteCount = static_cast<std::size_t>(frame.rowBytes) * frame.height;
std::memcpy(frame.bytes, mapped, byteCount);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
frame.frameIndex = slot.frameIndex;
mFrameStore.PublishCompleted(frame);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
glDeleteSync(slot.fence);
slot.fence = nullptr;
slot.inFlight = false;
mReadIndex = (mReadIndex + 1) % mPbos.size();
}
}
void SignalStarted()
{
std::lock_guard<std::mutex> lock(mStartupMutex);
mStarted = true;
mStartupCondition.notify_all();
}
void SignalStartupFailure(const std::string& error)
{
std::lock_guard<std::mutex> lock(mStartupMutex);
mStartupError = error;
mStartupCondition.notify_all();
}
LatestFrameStore& mFrameStore;
unsigned mWidth = 0;
unsigned mHeight = 0;
Clock::duration mFrameDuration;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::mutex mStartupMutex;
std::condition_variable mStartupCondition;
bool mStarted = false;
std::string mStartupError;
GLuint mFramebuffer = 0;
GLuint mTexture = 0;
std::vector<PboSlot> mPbos;
std::size_t mWriteIndex = 0;
std::size_t mReadIndex = 0;
uint64_t mFrameIndex = 0;
};
class DeckLinkProbePlayout
{
public:
DeckLinkProbePlayout(DeckLinkSession& session, LatestFrameStore& frameStore) :
mSession(session),
mFrameStore(frameStore)
{
}
bool Start()
{
mStopping = false;
mThread = std::thread([this]() { ThreadMain(); });
return true;
}
void Stop()
{
mStopping = true;
if (mThread.joinable())
mThread.join();
}
void ThreadMain()
{
while (!mStopping)
{
const ProbeMetrics metrics = mFrameStore.Metrics();
if (metrics.scheduledCount >= kDeckLinkTargetBufferedFrames)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
ProbeFrame frame;
if (!mFrameStore.ConsumeCompleted(frame))
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
VideoIOOutputFrame outputFrame;
outputFrame.bytes = frame.bytes;
outputFrame.nativeBuffer = frame.bytes;
outputFrame.rowBytes = frame.rowBytes;
outputFrame.width = frame.width;
outputFrame.height = frame.height;
outputFrame.pixelFormat = frame.pixelFormat;
if (!mSession.ScheduleOutputFrame(outputFrame))
{
mFrameStore.ReleaseByBytes(frame.bytes);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
private:
DeckLinkSession& mSession;
LatestFrameStore& mFrameStore;
std::thread mThread;
std::atomic<bool> mStopping{ false };
};
std::string CompletionResultToString(VideoIOCompletionResult result)
{
switch (result)
{
case VideoIOCompletionResult::Completed:
return "completed";
case VideoIOCompletionResult::DisplayedLate:
return "late";
case VideoIOCompletionResult::Dropped:
return "dropped";
case VideoIOCompletionResult::Flushed:
return "flushed";
case VideoIOCompletionResult::Unknown:
default:
return "unknown";
}
}
void PrintUsage()
{
std::cout << "DeckLinkRenderCadenceProbe\n"
<< " Renders a simple OpenGL BGRA8 motion pattern on one GL thread,\n"
<< " copies completed PBO readbacks into latest-N system memory slots,\n"
<< " warms up rendered frames, then feeds DeckLink scheduled playback.\n\n"
<< "Press Enter to stop.\n";
}
class ComInitGuard
{
public:
~ComInitGuard()
{
if (mInitialized)
CoUninitialize();
}
bool Initialize()
{
const HRESULT result = CoInitialize(nullptr);
mInitialized = SUCCEEDED(result);
mResult = result;
return mInitialized;
}
HRESULT Result() const { return mResult; }
private:
bool mInitialized = false;
HRESULT mResult = S_OK;
};
}
int main()
{
PrintUsage();
ComInitGuard com;
if (!com.Initialize())
{
std::cerr << "COM initialization failed: 0x" << std::hex << com.Result() << std::dec << "\n";
return 1;
}
LatestFrameStore frameStore(kDefaultWidth, kDefaultHeight, kSystemFrameSlots);
DeckLinkSession deckLink;
std::atomic<uint64_t> completions{ 0 };
std::atomic<uint64_t> late{ 0 };
std::atomic<uint64_t> dropped{ 0 };
VideoFormatSelection formats;
std::string error;
if (!deckLink.DiscoverDevicesAndModes(formats, error))
{
std::cerr << "DeckLink discovery failed: " << error << "\n";
return 1;
}
if (!deckLink.SelectPreferredFormats(formats, false, error))
{
std::cerr << "DeckLink format selection failed: " << error << "\n";
return 1;
}
if (!deckLink.ConfigureOutput(
[&](const VideoIOCompletion& completion) {
frameStore.ReleaseByBytes(completion.outputFrameBuffer);
++completions;
if (completion.result == VideoIOCompletionResult::DisplayedLate)
++late;
else if (completion.result == VideoIOCompletionResult::Dropped)
++dropped;
},
formats.output,
false,
error))
{
std::cerr << "DeckLink output configuration failed: " << error << "\n";
return 1;
}
if (!deckLink.PrepareOutputSchedule())
{
std::cerr << "DeckLink schedule preparation failed.\n";
return 1;
}
const VideoIOState& state = deckLink.State();
if (state.outputFrameSize.width != kDefaultWidth || state.outputFrameSize.height != kDefaultHeight)
{
std::cerr << "This probe currently expects 1920x1080 output. Selected mode is "
<< state.outputFrameSize.width << "x" << state.outputFrameSize.height << ".\n";
return 1;
}
RenderCadenceProbe renderer(frameStore, state.outputFrameSize.width, state.outputFrameSize.height, state.frameBudgetMilliseconds);
if (!renderer.Start(error))
{
std::cerr << "Render thread start failed: " << error << "\n";
return 1;
}
std::cout << "Warming up " << kWarmupFrames << " rendered frames at cadence...\n";
if (!frameStore.WaitForCompletedDepth(kWarmupFrames, std::chrono::seconds(3)))
{
std::cerr << "Timed out waiting for rendered warmup frames.\n";
renderer.Stop();
return 1;
}
DeckLinkProbePlayout playout(deckLink, frameStore);
playout.Start();
const auto prerollDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
while (std::chrono::steady_clock::now() < prerollDeadline)
{
if (frameStore.Metrics().scheduledCount >= kDeckLinkTargetBufferedFrames)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
if (!deckLink.StartScheduledPlayback())
{
std::cerr << "DeckLink scheduled playback failed to start.\n";
playout.Stop();
renderer.Stop();
return 1;
}
std::atomic<bool> metricsStopping{ false };
std::thread metricsThread([&]() {
uint64_t lastRendered = 0;
uint64_t lastScheduled = 0;
auto lastTime = std::chrono::steady_clock::now();
while (!metricsStopping)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
const auto now = std::chrono::steady_clock::now();
const double seconds = std::chrono::duration_cast<std::chrono::duration<double>>(now - lastTime).count();
const ProbeMetrics metrics = frameStore.Metrics();
const double renderFps = seconds > 0.0 ? static_cast<double>(metrics.renderedFrames - lastRendered) / seconds : 0.0;
const double scheduleFps = seconds > 0.0 ? static_cast<double>(metrics.scheduledFrames - lastScheduled) / seconds : 0.0;
lastRendered = metrics.renderedFrames;
lastScheduled = metrics.scheduledFrames;
lastTime = now;
std::cout << std::fixed << std::setprecision(1)
<< "renderFps=" << renderFps
<< " scheduleFps=" << scheduleFps
<< " free=" << metrics.freeCount
<< " completed=" << metrics.completedCount
<< " scheduled=" << metrics.scheduledCount
<< " drops=" << metrics.completedDrops
<< " pboMiss=" << metrics.pboQueueMisses
<< " completions=" << completions.load()
<< " late=" << late.load()
<< " dropped=" << dropped.load()
<< " decklinkBuffered=" << deckLink.State().actualDeckLinkBufferedFrames
<< "\n";
}
});
std::string line;
std::getline(std::cin, line);
metricsStopping = true;
if (metricsThread.joinable())
metricsThread.join();
playout.Stop();
deckLink.Stop();
renderer.Stop();
deckLink.ReleaseResources();
return 0;
}

View File

@@ -0,0 +1,113 @@
# DeckLink Render Cadence Probe
This is a deliberately small architecture probe for the Phase 7.7 playout model.
It is not the main app and does not use the main runtime, shader stack, preview path, input upload path, or render engine.
## What It Tests
The probe validates the clean playout spine:
```text
single OpenGL render thread
owns its own hidden GL context
renders a simple moving BGRA8 pattern at output cadence
queues GPU readback through a PBO ring
copies completed readbacks into latest-N system-memory slots
system-memory frame store
owns free / rendering / completed / scheduled slots
drops old completed unscheduled frames when render cadence needs space
protects scheduled frames until DeckLink completion
DeckLink playout thread
consumes completed system-memory frames
keeps a small scheduled buffer filled
does not render
```
Startup warms up rendered frames before starting DeckLink scheduled playback.
## How To Build
```powershell
cmake --build --preset build-debug --target DeckLinkRenderCadenceProbe -- /m:1
```
The executable is:
```text
build\vs2022-x64-debug\Debug\DeckLinkRenderCadenceProbe.exe
```
## How To Run
Run it from a terminal so you can see the telemetry:
```powershell
build\vs2022-x64-debug\Debug\DeckLinkRenderCadenceProbe.exe
```
Press Enter to stop.
The first version assumes `1080p59.94` / `1920x1080` output and BGRA8 system-memory frames.
## What To Watch
The probe prints one line per second:
- `renderFps`: cadence render throughput
- `scheduleFps`: DeckLink scheduling throughput
- `free`: free system-memory slots
- `completed`: rendered, unscheduled slots
- `scheduled`: slots currently owned by DeckLink
- `drops`: old completed unscheduled frames recycled by the latest-N cache
- `pboMiss`: PBO ring was full when trying to queue readback
- `late`: DeckLink displayed-late completions
- `dropped`: DeckLink dropped completions
- `decklinkBuffered`: actual DeckLink buffered-frame count when available
For a healthy architecture proof, expect:
- `renderFps` close to the selected output cadence
- `scheduleFps` close to the selected output cadence after warmup
- `scheduled` hovering near the target buffer depth
- `late` and `dropped` not increasing continuously
- visible motion that is smooth on the DeckLink output
## Interpretation
If this probe is smooth at 59.94/60, the broad architecture is viable and the main app's remaining stutters are likely caused by integration details such as input upload, shared render-thread work, preview/screenshot work, or runtime/render-state coupling.
If this probe is not smooth, the problem is lower level: DeckLink scheduling, OpenGL readback, Windows scheduling, or hardware/driver behavior.
## Initial Result
Date: 2026-05-12
User-visible result:
- output looked smooth
Representative telemetry:
```text
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=119 late=0 dropped=0 decklinkBuffered=4
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=179 late=0 dropped=0 decklinkBuffered=4
renderFps=59.8 scheduleFps=59.8 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=239 late=0 dropped=0 decklinkBuffered=4
renderFps=60.8 scheduleFps=59.8 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=299 late=0 dropped=0 decklinkBuffered=4
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=360 late=0 dropped=0 decklinkBuffered=4
renderFps=59.8 scheduleFps=60.8 free=8 completed=0 scheduled=4 drops=0 pboMiss=0 completions=420 late=0 dropped=0 decklinkBuffered=4
```
Read:
- the clean architecture can sustain the selected output cadence on the test machine
- BGRA8 PBO readback is viable when isolated from the main app's other render-thread work
- latest-N system-memory buffering stayed stable
- DeckLink actual buffered depth stayed at 4
- there were no late frames, dropped frames, completed-frame drops, or PBO misses in the sampled output
Implication:
The main app's remaining stutters are likely integration/ownership issues rather than a fundamental DeckLink/OpenGL/BGRA8 readback limit. The highest-value suspects are input upload before output render, shared render-thread queue contention, preview/screenshot work, and runtime/render-state work on the output path.

View File

@@ -1,47 +1,3 @@
/* -LICENSE-START-
** Copyright (c) 2012 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation (the
** "Software") to use, reproduce, display, distribute, sub-license, execute,
** and transmit the Software, and to prepare derivative works of the Software,
** and to permit third-parties to whom the Software is furnished to do so, in
** accordance with:
**
** (1) if the Software is obtained from Blackmagic Design, the End User License
** Agreement for the Software Development Kit ("EULA") available at
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
**
** (2) if the Software is obtained from any third party, such licensing terms
** as notified by that third party,
**
** and all subject to the following:
**
** (3) the copyright notices in the Software and this entire statement,
** including the above license grant, this restriction and the following
** disclaimer, must be included in all copies of the Software, in whole or in
** part, and all derivative works of the Software, unless such copies or
** derivative works are solely in the form of machine-executable object code
** generated by a source language processor.
**
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
**
** A copy of the Software is available free of charge at
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
**
** -LICENSE-END-
*/
//
// LoopThroughWithOpenGLCompositing.cpp
// LoopThroughWithOpenGLCompositing
//
#include "stdafx.h" #include "stdafx.h"
#include "resource.h" #include "resource.h"
#include "OpenGLComposite.h" #include "OpenGLComposite.h"
@@ -412,10 +368,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
break; break;
} }
// Setup OpenGL and video I/O capture/playout object // Setup OpenGL and DeckLink capture and playout object
pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC); pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC);
if (pOpenGLComposite->InitializeVideoIO()) if (pOpenGLComposite->InitDeckLink())
{ {
wglMakeCurrent( NULL, NULL ); wglMakeCurrent( NULL, NULL );
if (pOpenGLComposite->Start()) if (pOpenGLComposite->Start())
@@ -423,11 +379,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
PostMessage(hWnd, kCreateStatusStripMessage, 0, 0); PostMessage(hWnd, kCreateStatusStripMessage, 0, 0);
break; // success break; // success
} }
MessageBoxA(NULL, "The OpenGL/video I/O runtime initialized, but playout failed to start. See the previous start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR); MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
} }
else else
{ {
MessageBoxA(NULL, "The OpenGL/video I/O runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR); MessageBoxA(NULL, "The OpenGL/DeckLink runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
} }
// Failed to initialize - cleanup // Failed to initialize - cleanup
@@ -438,7 +394,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
} }
catch (...) catch (...)
{ {
ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/video I/O runtime."); ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime.");
PostMessage(hWnd, WM_CLOSE, 0, 0); PostMessage(hWnd, WM_CLOSE, 0, 0);
break; break;
} }
@@ -474,11 +430,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
} }
catch (...) catch (...)
{ {
ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/video I/O runtime."); ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime.");
} }
// Deselect the current rendering context and delete it // Deselect the current rendering context and delete it
wglMakeCurrent(hDC, NULL); wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC); wglDeleteContext(hRC);
// Tell the application to terminate after the window is gone // Tell the application to terminate after the window is gone
@@ -530,15 +486,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
if (!sInteractiveResize && pOpenGLComposite) if (!sInteractiveResize && pOpenGLComposite)
{ {
wglMakeCurrent(hDC, hRC); pOpenGLComposite->paintGL(true);
pOpenGLComposite->paintGL();
wglMakeCurrent( NULL, NULL );
RaiseStatusControls(sStatusStrip); RaiseStatusControls(sStatusStrip);
} }
} }
catch (...) catch (...)
{ {
wglMakeCurrent( NULL, NULL );
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime."); ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
} }
break; break;

View File

@@ -1,47 +1,3 @@
/* -LICENSE-START-
** Copyright (c) 2012 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation (the
** "Software") to use, reproduce, display, distribute, sub-license, execute,
** and transmit the Software, and to prepare derivative works of the Software,
** and to permit third-parties to whom the Software is furnished to do so, in
** accordance with:
**
** (1) if the Software is obtained from Blackmagic Design, the End User License
** Agreement for the Software Development Kit ("EULA") available at
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
**
** (2) if the Software is obtained from any third party, such licensing terms
** as notified by that third party,
**
** and all subject to the following:
**
** (3) the copyright notices in the Software and this entire statement,
** including the above license grant, this restriction and the following
** disclaimer, must be included in all copies of the Software, in whole or in
** part, and all derivative works of the Software, unless such copies or
** derivative works are solely in the form of machine-executable object code
** generated by a source language processor.
**
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
**
** A copy of the Software is available free of charge at
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
**
** -LICENSE-END-
*/
//
// LoopThroughWithOpenGLCompositing.h
// LoopThroughWithOpenGLCompositing
//
#pragma once #pragma once
#include "resource.h" #include "resource.h"

View File

@@ -1,28 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LoopThroughWithOpenGLCompositing", "LoopThroughWithOpenGLCompositing.vcxproj", "{92C79085-CA51-4008-95DB-5403D2E19885}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.ActiveCfg = Debug|Win32
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.Build.0 = Debug|Win32
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.ActiveCfg = Debug|x64
{92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.Build.0 = Debug|x64
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.ActiveCfg = Release|Win32
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.Build.0 = Release|Win32
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.ActiveCfg = Release|x64
{92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,245 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{92C79085-CA51-4008-95DB-5403D2E19885}</ProjectGuid>
<RootNamespace>LoopThroughWithOpenGLCompositing</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>12.0.21005.1</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="gl\renderer\GLExtensions.cpp" />
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
<ClCompile Include="gl\OpenGLComposite.cpp" />
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp" />
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp" />
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp" />
<ClCompile Include="gl\renderer\RenderTargetPool.cpp" />
<ClCompile Include="gl\shader\OpenGLShaderPrograms.cpp" />
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp" />
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp" />
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp" />
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkAPI_i.c" />
<ClCompile Include="control\RuntimeServices.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp" />
<ClCompile Include="runtime\RuntimeClock.cpp" />
<ClCompile Include="videoio\VideoIOFormat.cpp" />
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="gl\renderer\GLExtensions.h" />
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
<ClInclude Include="gl\OpenGLComposite.h" />
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h" />
<ClInclude Include="gl\pipeline\OpenGLRenderPipeline.h" />
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h" />
<ClInclude Include="gl\renderer\OpenGLRenderer.h" />
<ClInclude Include="gl\renderer\RenderTargetPool.h" />
<ClInclude Include="gl\shader\OpenGLShaderPrograms.h" />
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h" />
<ClInclude Include="gl\shader\ShaderBuildQueue.h" />
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h" />
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="control\RuntimeServices.h" />
<ClInclude Include="videoio\decklink\DeckLinkSession.h" />
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h" />
<ClInclude Include="runtime\RuntimeClock.h" />
<ClInclude Include="videoio\VideoIOFormat.h" />
<ClInclude Include="videoio\VideoIOTypes.h" />
<ClInclude Include="videoio\VideoPlayoutScheduler.h" />
</ItemGroup>
<ItemGroup>
<Image Include="LoopThroughWithOpenGLCompositing.ico" />
<Image Include="small.ico" />
</ItemGroup>
<ItemGroup>
<None Include="video_effect.slang" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="LoopThroughWithOpenGLCompositing.rc" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\include\DeckLinkAPI.idl" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
</Filter>
<Filter Include="DeckLink API">
<UniqueIdentifier>{1eab21d6-58f8-49e0-929b-8a4482e04756}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gl\renderer\GLExtensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\OpenGLComposite.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\renderer\RenderTargetPool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\OpenGLShaderPrograms.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkAPI_i.c">
<Filter>DeckLink API</Filter>
</ClCompile>
<ClCompile Include="control\RuntimeServices.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeClock.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\VideoIOFormat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="gl\renderer\GLExtensions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LoopThroughWithOpenGLCompositing.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\OpenGLComposite.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\OpenGLRenderPipeline.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\OpenGLRenderer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\RenderTargetPool.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\OpenGLShaderPrograms.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\ShaderBuildQueue.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="control\RuntimeServices.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\decklink\DeckLinkSession.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeClock.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\VideoIOFormat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\VideoIOTypes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\VideoPlayoutScheduler.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="LoopThroughWithOpenGLCompositing.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="small.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="LoopThroughWithOpenGLCompositing.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Midl Include="..\..\include\DeckLinkAPI.idl">
<Filter>DeckLink API</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<None Include="video_effect.slang">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@@ -17,6 +17,7 @@
namespace namespace
{ {
constexpr DWORD kStateBroadcastIntervalMs = 250; constexpr DWORD kStateBroadcastIntervalMs = 250;
constexpr DWORD kStateBroadcastThrottleMs = 50;
bool InitializeWinsock(std::string& error) bool InitializeWinsock(std::string& error)
{ {
@@ -75,7 +76,7 @@ std::string GuessContentType(const std::filesystem::path& assetPath)
} }
ControlServer::ControlServer() ControlServer::ControlServer()
: mPort(0), mRunning(false) : mPort(0), mRunning(false), mBroadcastPending(false)
{ {
} }
@@ -161,10 +162,16 @@ void ControlServer::Stop()
void ControlServer::BroadcastState() void ControlServer::BroadcastState()
{ {
mBroadcastPending = false;
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
BroadcastStateLocked(); BroadcastStateLocked();
} }
void ControlServer::RequestBroadcastState()
{
mBroadcastPending = true;
}
void ControlServer::ServerLoop() void ControlServer::ServerLoop()
{ {
DWORD lastStateBroadcastMs = GetTickCount(); DWORD lastStateBroadcastMs = GetTickCount();
@@ -173,7 +180,12 @@ void ControlServer::ServerLoop()
TryAcceptClient(); TryAcceptClient();
const DWORD nowMs = GetTickCount(); const DWORD nowMs = GetTickCount();
if (nowMs - lastStateBroadcastMs >= kStateBroadcastIntervalMs) if (mBroadcastPending && nowMs - lastStateBroadcastMs >= kStateBroadcastThrottleMs)
{
BroadcastState();
lastStateBroadcastMs = nowMs;
}
else if (nowMs - lastStateBroadcastMs >= kStateBroadcastIntervalMs)
{ {
BroadcastState(); BroadcastState();
lastStateBroadcastMs = nowMs; lastStateBroadcastMs = nowMs;
@@ -469,6 +481,7 @@ bool ControlServer::HandleWebSocketUpgrade(UniqueSocket clientSocket, const Http
client.socket.reset(clientSocket.release()); client.socket.reset(clientSocket.release());
client.websocket = true; client.websocket = true;
mClients.push_back(std::move(client)); mClients.push_back(std::move(client));
mBroadcastPending = false;
BroadcastStateLocked(); BroadcastStateLocked();
} }
return true; return true;

View File

@@ -41,6 +41,7 @@ public:
bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error); bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
void RequestBroadcastState();
unsigned short GetPort() const { return mPort; } unsigned short GetPort() const { return mPort; }
@@ -100,6 +101,7 @@ private:
unsigned short mPort; unsigned short mPort;
std::thread mThread; std::thread mThread;
std::atomic<bool> mRunning; std::atomic<bool> mRunning;
std::atomic<bool> mBroadcastPending;
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::vector<ClientConnection> mClients; std::vector<ClientConnection> mClients;
}; };

View File

@@ -0,0 +1,343 @@
#include "ControlServices.h"
#include "ControlServer.h"
#include "OscServer.h"
#include "RuntimeControlBridge.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeStore.h"
#include <windows.h>
namespace
{
constexpr auto kCompatibilityPollFallbackInterval = std::chrono::milliseconds(250);
}
ControlServices::ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mPollRunning(false)
{
}
ControlServices::~ControlServices()
{
Stop();
}
bool ControlServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
{
Stop();
if (!StartControlServicesBoundary(composite, runtimeStore, *this, *mControlServer, *mOscServer, error))
{
Stop();
return false;
}
return true;
}
void ControlServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
{
StartPolling(runtimeCoordinator);
}
void ControlServices::Stop()
{
StopPolling();
if (mOscServer)
mOscServer->Stop();
if (mControlServer)
mControlServer->Stop();
}
void ControlServices::BroadcastState()
{
if (mControlServer)
mControlServer->BroadcastState();
}
void ControlServices::RequestBroadcastState()
{
PublishRuntimeStateBroadcastRequested("control-service-request");
if (mControlServer)
mControlServer->RequestBroadcastState();
}
bool ControlServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
(void)error;
PendingOscUpdate update;
update.layerKey = layerKey;
update.parameterKey = parameterKey;
update.valueJson = valueJson;
const std::string routeKey = layerKey + "\n" + parameterKey;
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates[routeKey] = std::move(update);
}
return true;
}
bool ControlServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
{
appliedUpdates.clear();
std::map<std::string, PendingOscUpdate> pending;
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
if (mPendingOscUpdates.empty())
return true;
pending.swap(mPendingOscUpdates);
}
for (const auto& entry : pending)
{
JsonValue targetValue;
std::string parseError;
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
{
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
continue;
}
AppliedOscUpdate appliedUpdate;
appliedUpdate.routeKey = entry.first;
appliedUpdate.layerKey = entry.second.layerKey;
appliedUpdate.parameterKey = entry.second.parameterKey;
appliedUpdate.targetValue = targetValue;
appliedUpdates.push_back(std::move(appliedUpdate));
PublishOscValueReceived(entry.second, entry.first);
}
(void)error;
return true;
}
bool ControlServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
{
(void)error;
PendingOscCommit commit;
commit.routeKey = routeKey;
commit.layerKey = layerKey;
commit.parameterKey = parameterKey;
commit.value = value;
commit.generation = generation;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits[routeKey] = std::move(commit);
}
WakePolling();
return true;
}
void ControlServices::ClearOscState()
{
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates.clear();
}
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits.clear();
}
{
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.clear();
}
}
void ControlServices::ClearOscStateForLayerKey(const std::string& layerKey)
{
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
for (auto it = mPendingOscUpdates.begin(); it != mPendingOscUpdates.end();)
{
if (it->second.layerKey == layerKey)
it = mPendingOscUpdates.erase(it);
else
++it;
}
}
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
for (auto it = mPendingOscCommits.begin(); it != mPendingOscCommits.end();)
{
if (it->second.layerKey == layerKey)
it = mPendingOscCommits.erase(it);
else
++it;
}
}
{
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
for (auto it = mCompletedOscCommits.begin(); it != mCompletedOscCommits.end();)
{
if (it->routeKey.rfind(layerKey + "\n", 0) == 0)
it = mCompletedOscCommits.erase(it);
else
++it;
}
}
}
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
{
completedCommits.clear();
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
if (mCompletedOscCommits.empty())
return;
completedCommits.swap(mCompletedOscCommits);
}
void ControlServices::StartPolling(RuntimeCoordinator& runtimeCoordinator)
{
if (mPollRunning.exchange(true))
return;
mPollThread = std::thread([this, &runtimeCoordinator]() { PollLoop(runtimeCoordinator); });
}
void ControlServices::StopPolling()
{
if (!mPollRunning.exchange(false))
return;
WakePolling();
if (mPollThread.joinable())
mPollThread.join();
}
void ControlServices::PollLoop(RuntimeCoordinator& runtimeCoordinator)
{
while (mPollRunning)
{
std::map<std::string, PendingOscCommit> pendingCommits;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
pendingCommits.swap(mPendingOscCommits);
}
for (const auto& entry : pendingCommits)
{
PublishOscCommitRequested(entry.second);
const RuntimeCoordinatorResult result = runtimeCoordinator.CommitOscParameterByControlKey(
entry.second.layerKey,
entry.second.parameterKey,
entry.second.value);
if (result.accepted)
{
CompletedOscCommit completedCommit;
completedCommit.routeKey = entry.second.routeKey;
completedCommit.generation = entry.second.generation;
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.push_back(std::move(completedCommit));
PublishOscOverlaySettled(entry.second);
}
else if (!result.errorMessage.empty())
{
OutputDebugStringA(("OSC commit failed: " + result.errorMessage + "\n").c_str());
}
}
bool registryChanged = false;
const RuntimeCoordinatorResult pollResult = runtimeCoordinator.PollRuntimeStoreChanges(registryChanged);
if (pollResult.compileStatusChanged && !pollResult.compileStatusSucceeded && !pollResult.compileStatusMessage.empty())
OutputDebugStringA(("Runtime poll failed: " + pollResult.compileStatusMessage + "\n").c_str());
std::unique_lock<std::mutex> wakeLock(mPollWakeMutex);
mPollWakeCondition.wait_for(wakeLock, kCompatibilityPollFallbackInterval, [this]() {
return !mPollRunning.load() || mPollWakeRequested;
});
mPollWakeRequested = false;
}
}
void ControlServices::WakePolling()
{
{
std::lock_guard<std::mutex> lock(mPollWakeMutex);
mPollWakeRequested = true;
}
mPollWakeCondition.notify_one();
}
void ControlServices::PublishRuntimeStateBroadcastRequested(const std::string& reason)
{
try
{
RuntimeStateBroadcastRequestedEvent event;
event.reason = reason;
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
OutputDebugStringA("RuntimeStateBroadcastRequested event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("RuntimeStateBroadcastRequested event publish threw.\n");
}
}
void ControlServices::PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey)
{
try
{
OscValueReceivedEvent event;
event.routeKey = routeKey;
event.layerKey = update.layerKey;
event.parameterKey = update.parameterKey;
event.valueJson = update.valueJson;
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
OutputDebugStringA("OscValueReceived event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("OscValueReceived event publish threw.\n");
}
}
void ControlServices::PublishOscCommitRequested(const PendingOscCommit& commit)
{
try
{
OscCommitRequestedEvent event;
event.routeKey = commit.routeKey;
event.layerKey = commit.layerKey;
event.parameterKey = commit.parameterKey;
event.valueJson = SerializeJson(commit.value, false);
event.generation = commit.generation;
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
OutputDebugStringA("OscCommitRequested event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("OscCommitRequested event publish threw.\n");
}
}
void ControlServices::PublishOscOverlaySettled(const PendingOscCommit& commit)
{
try
{
OscOverlayEvent event;
event.routeKey = commit.routeKey;
event.layerKey = commit.layerKey;
event.parameterKey = commit.parameterKey;
event.generation = commit.generation;
event.settled = true;
if (!mRuntimeEventDispatcher.PublishPayload(event, "ControlServices"))
OutputDebugStringA("OscOverlaySettled event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("OscOverlaySettled event publish threw.\n");
}
}

View File

@@ -0,0 +1,95 @@
#pragma once
#include "RuntimeJson.h"
#include "RuntimeCoordinator.h"
#include "ShaderTypes.h"
#include <atomic>
#include <condition_variable>
#include <chrono>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class ControlServer;
class OpenGLComposite;
class OscServer;
class RuntimeEventDispatcher;
class RuntimeStore;
class ControlServices
{
public:
struct AppliedOscUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct CompletedOscCommit
{
std::string routeKey;
uint64_t generation = 0;
};
explicit ControlServices(RuntimeEventDispatcher& runtimeEventDispatcher);
~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
void Stop();
void BroadcastState();
void RequestBroadcastState();
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
void ClearOscState();
void ClearOscStateForLayerKey(const std::string& layerKey);
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
private:
struct PendingOscUpdate
{
std::string layerKey;
std::string parameterKey;
std::string valueJson;
};
struct PendingOscCommit
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
void StartPolling(RuntimeCoordinator& runtimeCoordinator);
void StopPolling();
void PollLoop(RuntimeCoordinator& runtimeCoordinator);
void WakePolling();
void PublishRuntimeStateBroadcastRequested(const std::string& reason);
void PublishOscValueReceived(const PendingOscUpdate& update, const std::string& routeKey);
void PublishOscCommitRequested(const PendingOscCommit& commit);
void PublishOscOverlaySettled(const PendingOscCommit& commit);
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
std::thread mPollThread;
std::atomic<bool> mPollRunning;
std::mutex mPollWakeMutex;
std::condition_variable mPollWakeCondition;
bool mPollWakeRequested = false;
std::mutex mPendingOscMutex;
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
std::mutex mPendingOscCommitMutex;
std::map<std::string, PendingOscCommit> mPendingOscCommits;
std::mutex mCompletedOscCommitMutex;
std::vector<CompletedOscCommit> mCompletedOscCommits;
};

View File

@@ -55,7 +55,7 @@ OscServer::~OscServer()
Stop(); Stop();
} }
bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::string& error) bool OscServer::Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error)
{ {
if (port == 0) if (port == 0)
return true; return true;
@@ -78,11 +78,15 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
sockaddr_in address = {}; sockaddr_in address = {};
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (!TryParseBindAddress(bindAddress, address.sin_addr, error))
{
mSocket.reset();
return false;
}
address.sin_port = htons(static_cast<u_short>(port)); address.sin_port = htons(static_cast<u_short>(port));
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0) if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
{ {
error = "Could not bind OSC listener to UDP port " + std::to_string(port) + "."; error = "Could not bind OSC listener to " + bindAddress + ":" + std::to_string(port) + ".";
mSocket.reset(); mSocket.reset();
return false; return false;
} }
@@ -92,6 +96,24 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
return true; return true;
} }
bool OscServer::TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
{
if (bindAddress.empty())
{
error = "OSC bind address must not be empty.";
return false;
}
address = {};
if (InetPtonA(AF_INET, bindAddress.c_str(), &address) != 1)
{
error = "Invalid OSC bind address '" + bindAddress + "'. Use an IPv4 address such as 127.0.0.1 or 0.0.0.0.";
return false;
}
return true;
}
void OscServer::Stop() void OscServer::Stop()
{ {
mRunning = false; mRunning = false;

View File

@@ -20,7 +20,7 @@ public:
OscServer(); OscServer();
~OscServer(); ~OscServer();
bool Start(unsigned short port, const Callbacks& callbacks, std::string& error); bool Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error);
void Stop(); void Stop();
unsigned short GetPort() const { return mPort; } unsigned short GetPort() const { return mPort; }
@@ -37,6 +37,7 @@ private:
void ServerLoop(); void ServerLoop();
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const; bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
bool DispatchMessage(const OscMessage& message, std::string& error) const; bool DispatchMessage(const OscMessage& message, std::string& error) const;
static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error);
static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson); static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson);
static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value); static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value); static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);

View File

@@ -1,13 +1,15 @@
#include "RuntimeControlBridge.h" #include "RuntimeControlBridge.h"
#include "ControlServices.h"
#include "ControlServer.h" #include "ControlServer.h"
#include "OpenGLComposite.h" #include "OpenGLComposite.h"
#include "OscServer.h" #include "OscServer.h"
#include "RuntimeHost.h" #include "RuntimeStore.h"
bool StartRuntimeControlServices( bool StartControlServicesBoundary(
OpenGLComposite& composite, OpenGLComposite& composite,
RuntimeHost& runtimeHost, RuntimeStore& runtimeStore,
ControlServices& controlServices,
ControlServer& controlServer, ControlServer& controlServer,
OscServer& oscServer, OscServer& oscServer,
std::string& error) std::string& error)
@@ -36,15 +38,16 @@ bool StartRuntimeControlServices(
return true; return true;
}; };
if (!controlServer.Start(runtimeHost.GetUiRoot(), runtimeHost.GetDocsRoot(), runtimeHost.GetServerPort(), callbacks, error)) if (!controlServer.Start(runtimeStore.GetRuntimeUiRoot(), runtimeStore.GetRuntimeDocsRoot(), runtimeStore.GetConfiguredControlServerPort(), callbacks, error))
return false; return false;
runtimeHost.SetServerPort(controlServer.GetPort()); runtimeStore.SetBoundControlServerPort(controlServer.GetPort());
OscServer::Callbacks oscCallbacks; OscServer::Callbacks oscCallbacks;
oscCallbacks.updateParameter = [&composite](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) { oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
return composite.UpdateLayerParameterByControlKeyJson(layerKey, parameterKey, valueJson, actionError); return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
}; };
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscPort(), oscCallbacks, error)) if (runtimeStore.GetConfiguredOscPort() > 0 &&
!oscServer.Start(runtimeStore.GetConfiguredOscBindAddress(), runtimeStore.GetConfiguredOscPort(), oscCallbacks, error))
return false; return false;
return true; return true;

View File

@@ -3,13 +3,15 @@
#include <string> #include <string>
class ControlServer; class ControlServer;
class ControlServices;
class OpenGLComposite; class OpenGLComposite;
class OscServer; class OscServer;
class RuntimeHost; class RuntimeStore;
bool StartRuntimeControlServices( bool StartControlServicesBoundary(
OpenGLComposite& composite, OpenGLComposite& composite,
RuntimeHost& runtimeHost, RuntimeStore& runtimeStore,
ControlServices& controlServices,
ControlServer& controlServer, ControlServer& controlServer,
OscServer& oscServer, OscServer& oscServer,
std::string& error); std::string& error);

View File

@@ -0,0 +1,72 @@
#include "RuntimeServiceLiveBridge.h"
#include "RenderEngine.h"
#include "RuntimeServices.h"
#include <windows.h>
namespace
{
void DrainServiceEvents(RuntimeServices& runtimeServices, RenderEngine& renderEngine)
{
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
std::string oscError;
if (!runtimeServices.ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
runtimeServices.ConsumeCompletedOscCommits(completedOscCommits);
std::vector<RenderEngine::OscOverlayUpdate> overlayUpdates;
overlayUpdates.reserve(appliedOscUpdates.size());
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
{
overlayUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
}
std::vector<RenderEngine::OscOverlayCommitCompletion> overlayCommitCompletions;
overlayCommitCompletions.reserve(completedOscCommits.size());
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
{
overlayCommitCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
}
renderEngine.UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions);
}
void QueueServiceCommitRequests(
RuntimeServices& runtimeServices,
const std::vector<RenderEngine::OscOverlayCommitRequest>& commitRequests)
{
for (const RenderEngine::OscOverlayCommitRequest& commitRequest : commitRequests)
{
std::string commitError;
if (!runtimeServices.QueueOscCommit(
commitRequest.routeKey,
commitRequest.layerKey,
commitRequest.parameterKey,
commitRequest.value,
commitRequest.generation,
commitError) &&
!commitError.empty())
{
OutputDebugStringA(("OSC commit queue failed: " + commitError + "\n").c_str());
}
}
}
}
bool RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
const RenderFrameInput& input,
RenderFrameState& frameState)
{
DrainServiceEvents(runtimeServices, renderEngine);
std::vector<RenderEngine::OscOverlayCommitRequest> commitRequests;
const bool resolved = renderEngine.ResolveRenderFrameState(input, &commitRequests, frameState);
QueueServiceCommitRequests(runtimeServices, commitRequests);
return resolved;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "RenderFrameState.h"
#include <vector>
class RenderEngine;
class RuntimeServices;
class RuntimeServiceLiveBridge
{
public:
static bool PrepareLiveRenderFrameState(
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
const RenderFrameInput& input,
RenderFrameState& frameState);
};

View File

@@ -1,19 +1,9 @@
#include "RuntimeServices.h" #include "RuntimeServices.h"
#include "ControlServer.h" #include "RuntimeStore.h"
#include "OscServer.h"
#include "RuntimeControlBridge.h"
#include "RuntimeHost.h"
#include <windows.h> RuntimeServices::RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher) :
mControlServices(std::make_unique<ControlServices>(runtimeEventDispatcher))
RuntimeServices::RuntimeServices() :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mPollRunning(false),
mRegistryChanged(false),
mReloadRequested(false),
mPollFailed(false)
{ {
} }
@@ -22,98 +12,75 @@ RuntimeServices::~RuntimeServices()
Stop(); Stop();
} }
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error) bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
{ {
Stop(); return mControlServices && mControlServices->Start(composite, runtimeStore, error);
if (!StartRuntimeControlServices(composite, runtimeHost, *mControlServer, *mOscServer, error))
{
Stop();
return false;
}
return true;
} }
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost) void RuntimeServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
{ {
StartPolling(runtimeHost); if (mControlServices)
mControlServices->BeginPolling(runtimeCoordinator);
} }
void RuntimeServices::Stop() void RuntimeServices::Stop()
{ {
StopPolling(); if (mControlServices)
mControlServices->Stop();
if (mOscServer)
mOscServer->Stop();
if (mControlServer)
mControlServer->Stop();
} }
void RuntimeServices::BroadcastState() void RuntimeServices::BroadcastState()
{ {
if (mControlServer) if (mControlServices)
mControlServer->BroadcastState(); mControlServices->BroadcastState();
} }
RuntimePollEvents RuntimeServices::ConsumePollEvents() void RuntimeServices::RequestBroadcastState()
{ {
RuntimePollEvents events; if (mControlServices)
events.registryChanged = mRegistryChanged.exchange(false); mControlServices->RequestBroadcastState();
events.reloadRequested = mReloadRequested.exchange(false); }
events.failed = mPollFailed.exchange(false);
if (events.failed) bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, error);
}
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
{
if (!mControlServices)
{ {
std::lock_guard<std::mutex> lock(mPollErrorMutex); appliedUpdates.clear();
events.error = mPollError; return true;
} }
return events; return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
} }
void RuntimeServices::StartPolling(RuntimeHost& runtimeHost) bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
{ {
if (mPollRunning.exchange(true)) return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, error);
}
void RuntimeServices::ClearOscState()
{
if (mControlServices)
mControlServices->ClearOscState();
}
void RuntimeServices::ClearOscStateForLayerKey(const std::string& layerKey)
{
if (mControlServices)
mControlServices->ClearOscStateForLayerKey(layerKey);
}
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
{
if (!mControlServices)
{
completedCommits.clear();
return; return;
}
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); }); mControlServices->ConsumeCompletedOscCommits(completedCommits);
}
void RuntimeServices::StopPolling()
{
if (!mPollRunning.exchange(false))
return;
if (mPollThread.joinable())
mPollThread.join();
}
void RuntimeServices::PollLoop(RuntimeHost& runtimeHost)
{
while (mPollRunning)
{
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError))
{
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
mPollError = runtimeError;
}
mPollFailed = true;
}
else
{
if (registryChanged)
mRegistryChanged = true;
if (reloadRequested)
mReloadRequested = true;
}
for (int i = 0; i < 25 && mPollRunning; ++i)
Sleep(10);
}
} }

View File

@@ -1,48 +1,35 @@
#pragma once #pragma once
#include <atomic> #include "ControlServices.h"
#include <memory> #include <memory>
#include <mutex>
#include <string> #include <string>
#include <thread>
class ControlServer;
class OpenGLComposite; class OpenGLComposite;
class OscServer; class RuntimeCoordinator;
class RuntimeHost; class RuntimeEventDispatcher;
class RuntimeStore;
struct RuntimePollEvents
{
bool registryChanged = false;
bool reloadRequested = false;
bool failed = false;
std::string error;
};
class RuntimeServices class RuntimeServices
{ {
public: public:
RuntimeServices(); using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
using CompletedOscCommit = ControlServices::CompletedOscCommit;
explicit RuntimeServices(RuntimeEventDispatcher& runtimeEventDispatcher);
~RuntimeServices(); ~RuntimeServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost); void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
RuntimePollEvents ConsumePollEvents(); void RequestBroadcastState();
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
void ClearOscState();
void ClearOscStateForLayerKey(const std::string& layerKey);
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
private: private:
void StartPolling(RuntimeHost& runtimeHost); std::unique_ptr<ControlServices> mControlServices;
void StopPolling();
void PollLoop(RuntimeHost& runtimeHost);
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
std::thread mPollThread;
std::atomic<bool> mPollRunning;
std::atomic<bool> mRegistryChanged;
std::atomic<bool> mReloadRequested;
std::atomic<bool> mPollFailed;
std::mutex mPollErrorMutex;
std::string mPollError;
}; };

View File

@@ -1,488 +0,0 @@
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include "GlRenderConstants.h"
#include "OpenGLRenderPass.h"
#include "OpenGLRenderPipeline.h"
#include "OpenGLShaderPrograms.h"
#include "OpenGLVideoIOBridge.h"
#include "PngScreenshotWriter.h"
#include "RuntimeServices.h"
#include "ShaderBuildQueue.h"
#include "VideoIOBackendFactory.h"
#include <algorithm>
#include <chrono>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
mRenderer(std::make_unique<OpenGLRenderer>()),
mUseCommittedLayerStates(false),
mScreenshotRequested(false)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
mRenderPipeline = std::make_unique<OpenGLRenderPipeline>(
*mRenderer,
*mRuntimeHost,
[this]() { renderEffect(); },
[this]() { ProcessScreenshotRequest(); },
[this]() { paintGL(); });
mVideoIOBridge = std::make_unique<OpenGLVideoIOBridge>(
nullptr,
*mRenderer,
*mRenderPipeline,
*mRuntimeHost,
pMutex,
hGLDC,
hGLRC);
mRenderPass = std::make_unique<OpenGLRenderPass>(*mRenderer);
mShaderPrograms = std::make_unique<OpenGLShaderPrograms>(*mRenderer, *mRuntimeHost);
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeHost);
mRuntimeServices = std::make_unique<RuntimeServices>();
}
OpenGLComposite::~OpenGLComposite()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
if (mShaderBuildQueue)
mShaderBuildQueue->Stop();
if (mVideoIO)
mVideoIO->ReleaseResources();
mRenderer->DestroyResources();
DeleteCriticalSection(&pMutex);
}
bool OpenGLComposite::InitializeVideoIO()
{
std::string initFailureReason;
if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty())
{
std::string runtimeError;
if (!mRuntimeHost->Initialize(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
}
if (!mRuntimeHost)
{
initFailureReason = "Runtime host is not available.";
MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR);
return false;
}
const VideoIOConfiguration videoIOConfig = mRuntimeHost->GetVideoIOConfiguration();
mVideoIO = CreateVideoIODevice(videoIOConfig.backendId, initFailureReason);
if (!mVideoIO)
{
MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR);
return false;
}
mVideoIOBridge->SetVideoIODevice(mVideoIO.get());
if (!mVideoIO->DiscoverDevicesAndModes(videoIOConfig, initFailureReason))
{
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
? "This application requires the selected video I/O drivers installed."
: "Video I/O initialization failed";
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
if (!mVideoIO->SelectPreferredFormats(videoIOConfig, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())
{
initFailureReason = "OpenGL extension checks failed.";
goto error;
}
if (! InitOpenGLState())
{
initFailureReason = "OpenGL state initialization failed.";
goto error;
}
PublishVideoIOStatus(mVideoIO->DeviceName().empty()
? "Video I/O output device selected."
: ("Selected output device: " + mVideoIO->DeviceName()));
// Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoIO->OutputFrameWidth() < 1920)
resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
else
resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2);
if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, initFailureReason))
{
goto error;
}
if (!mVideoIO->HasInputDevice() && mRuntimeHost)
{
mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName());
}
if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, initFailureReason))
{
goto error;
}
PublishVideoIOStatus(mVideoIO->StatusMessage());
return true;
error:
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR);
mVideoIO->ReleaseResources();
return false;
}
void OpenGLComposite::paintGL()
{
if (!mVideoIO)
return;
if (!TryEnterCriticalSection(&pMutex))
{
ValidateRect(hGLWnd, NULL);
return;
}
mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
ValidateRect(hGLWnd, NULL);
LeaveCriticalSection(&pMutex);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
mRenderer->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
{
RECT r;
if (GetWindowRect(hGLWnd, &r))
{
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
}
}
void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
{
if (!mRuntimeHost || !mVideoIO)
return;
if (!statusMessage.empty())
mVideoIO->SetStatusMessage(statusMessage);
mRuntimeHost->SetVideoIOStatus(mVideoIO->State());
}
bool OpenGLComposite::InitOpenGLState()
{
if (! ResolveGLExtensions())
return false;
std::string runtimeError;
if (mRuntimeHost->GetRepoRoot().empty() && !mRuntimeHost->Initialize(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
if (!mRuntimeServices->Start(*this, *mRuntimeHost, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
return false;
}
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
return false;
}
if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
}
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
mUseCommittedLayerStates = false;
mShaderPrograms->ResetTemporalHistoryState();
std::string rendererError;
if (!mRenderer->InitializeResources(
mVideoIO->InputFrameWidth(),
mVideoIO->InputFrameHeight(),
mVideoIO->CaptureTextureWidth(),
mVideoIO->OutputFrameWidth(),
mVideoIO->OutputFrameHeight(),
mVideoIO->OutputPackTextureWidth(),
rendererError))
{
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
broadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeHost);
return true;
}
bool OpenGLComposite::Start()
{
return mVideoIO->Start();
}
bool OpenGLComposite::Stop()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive();
mVideoIO->Stop();
if (wasExternalKeyingActive)
PublishVideoIOStatus("External keying has been disabled.");
return true;
}
bool OpenGLComposite::ReloadShader()
{
if (mRuntimeHost)
{
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
mRuntimeHost->ClearReloadRequest();
}
RequestShaderBuild();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::RequestScreenshot(std::string& error)
{
(void)error;
mScreenshotRequested.store(true);
return true;
}
void OpenGLComposite::renderEffect()
{
ProcessRuntimePollResults();
const bool hasInputSource = mVideoIO->HasInputSource();
std::vector<RuntimeRenderState> layerStates;
if (mUseCommittedLayerStates)
{
layerStates = mShaderPrograms->CommittedLayerStates();
if (mRuntimeHost)
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
}
else if (mRuntimeHost)
{
const unsigned renderWidth = mVideoIO->InputFrameWidth();
const unsigned renderHeight = mVideoIO->InputFrameHeight();
const uint64_t renderStateVersion = mRuntimeHost->GetRenderStateVersion();
const bool renderStateCacheValid =
!mCachedLayerRenderStates.empty() &&
mCachedRenderStateVersion == renderStateVersion &&
mCachedRenderStateWidth == renderWidth &&
mCachedRenderStateHeight == renderHeight;
if (renderStateCacheValid)
{
layerStates = mCachedLayerRenderStates;
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
}
else
{
if (mRuntimeHost->TryGetLayerRenderStates(renderWidth, renderHeight, layerStates))
{
mCachedLayerRenderStates = layerStates;
mCachedRenderStateVersion = renderStateVersion;
mCachedRenderStateWidth = renderWidth;
mCachedRenderStateHeight = renderHeight;
}
else
{
layerStates = mCachedLayerRenderStates;
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
}
}
}
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
mRenderPass->Render(
hasInputSource,
layerStates,
mVideoIO->InputFrameWidth(),
mVideoIO->InputFrameHeight(),
mVideoIO->CaptureTextureWidth(),
mVideoIO->InputPixelFormat(),
historyCap,
[this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) {
return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error);
},
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength) {
return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength);
});
}
void OpenGLComposite::ProcessScreenshotRequest()
{
if (!mScreenshotRequested.exchange(false))
return;
const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0;
const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0;
if (width == 0 || height == 0)
return;
std::vector<unsigned char> bottomUpPixels(static_cast<std::size_t>(width) * height * 4);
std::vector<unsigned char> topDownPixels(bottomUpPixels.size());
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->OutputFramebuffer());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const unsigned sourceY = height - 1 - y;
std::copy(
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
}
try
{
const std::filesystem::path outputPath = BuildScreenshotPath();
std::filesystem::create_directories(outputPath.parent_path());
WritePngFileAsync(outputPath, width, height, std::move(topDownPixels));
}
catch (const std::exception& exception)
{
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
}
}
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
{
const std::filesystem::path root = mRuntimeHost && !mRuntimeHost->GetRuntimeRoot().empty()
? mRuntimeHost->GetRuntimeRoot()
: std::filesystem::current_path();
const auto now = std::chrono::system_clock::now();
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &nowTime);
std::ostringstream filename;
filename << "video-shader-toys-"
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
<< ".png";
return root / "screenshots" / filename.str();
}
bool OpenGLComposite::ProcessRuntimePollResults()
{
if (!mRuntimeHost || !mRuntimeServices)
return true;
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
if (events.failed)
{
mRuntimeHost->SetCompileStatus(false, events.error);
broadcastRuntimeState();
return false;
}
if (events.registryChanged)
broadcastRuntimeState();
if (!events.reloadRequested)
{
PreparedShaderBuild readyBuild;
if (!mShaderBuildQueue || !mShaderBuildQueue->TryConsumeReadyBuild(readyBuild))
return true;
char compilerErrorMessage[1024] = {};
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
mUseCommittedLayerStates = true;
broadcastRuntimeState();
return false;
}
mUseCommittedLayerStates = false;
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
mShaderPrograms->ResetTemporalHistoryState();
broadcastRuntimeState();
return true;
}
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
RequestShaderBuild();
broadcastRuntimeState();
return true;
}
void OpenGLComposite::RequestShaderBuild()
{
if (!mShaderBuildQueue || !mVideoIO)
return;
mUseCommittedLayerStates = true;
if (mRuntimeHost)
mRuntimeHost->ClearReloadRequest();
mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight());
}
void OpenGLComposite::broadcastRuntimeState()
{
if (mRuntimeServices)
mRuntimeServices->BroadcastState();
}
void OpenGLComposite::resetTemporalHistoryState()
{
mShaderPrograms->ResetTemporalHistoryState();
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
return true;
}
////////////////////////////////////////////

View File

@@ -1,145 +0,0 @@
#include "OpenGLComposite.h"
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
}
unsigned short OpenGLComposite::GetControlServerPort() const
{
return mRuntimeHost ? mRuntimeHost->GetServerPort() : 0;
}
unsigned short OpenGLComposite::GetOscPort() const
{
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
}
std::string OpenGLComposite::GetControlUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
}
std::string OpenGLComposite::GetDocsUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
}
std::string OpenGLComposite::GetOscAddress() const
{
return "udp://127.0.0.1:" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
}
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->AddLayer(shaderId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->RemoveLayer(layerId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
if (!mRuntimeHost->MoveLayer(layerId, direction, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->ResetLayerParameters(layerId, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->SaveStackPreset(presetName, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->LoadStackPreset(presetName, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}

View File

@@ -0,0 +1,668 @@
#include "RenderEngine.h"
#include <gl/gl.h>
#include <algorithm>
#include <cstring>
#include <sstream>
RenderEngine::RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint) :
mRenderer(),
mRenderPass(mRenderer),
mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)),
mShaderPrograms(mRenderer, runtimeSnapshotProvider),
mHealthTelemetry(healthTelemetry),
mHdc(hdc),
mHglrc(hglrc),
mFrameStateResolver(runtimeSnapshotProvider)
{
}
RenderEngine::~RenderEngine()
{
StopRenderThread();
if (!mResourcesDestroyed)
{
mRenderer.DestroyResources();
mResourcesDestroyed = true;
}
}
bool RenderEngine::StartRenderThread()
{
if (mRenderThreadRunning)
return true;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadStopping = false;
}
std::promise<bool> ready;
std::future<bool> readyResult = ready.get_future();
mRenderThread = std::thread(&RenderEngine::RenderThreadMain, this, std::move(ready));
if (!readyResult.get())
{
if (mRenderThread.joinable())
mRenderThread.join();
return false;
}
return true;
}
void RenderEngine::StopRenderThread()
{
if (mRenderThreadRunning)
{
InvokeOnRenderThread([this]() {
if (!mResourcesDestroyed)
{
mRenderer.DestroyResources();
mResourcesDestroyed = true;
}
});
}
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadStopping = true;
}
mRenderThreadCondition.notify_one();
if (mRenderThread.joinable())
mRenderThread.join();
}
void RenderEngine::RenderThreadMain(std::promise<bool> ready)
{
mRenderThreadId = GetCurrentThreadId();
if (!wglMakeCurrent(mHdc, mHglrc))
{
mRenderThreadId = 0;
ready.set_value(false);
return;
}
mRenderThreadRunning = true;
ready.set_value(true);
for (;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mRenderThreadMutex);
mRenderThreadCondition.wait(lock, [this]() {
return mRenderThreadStopping || !mRenderThreadTasks.empty();
});
if (mRenderThreadStopping && mRenderThreadTasks.empty())
break;
task = std::move(mRenderThreadTasks.front());
mRenderThreadTasks.pop();
}
try
{
task();
}
catch (...)
{
OutputDebugStringA("Render thread task failed with an unhandled exception.\n");
}
}
wglMakeCurrent(NULL, NULL);
mRenderThreadRunning = false;
mRenderThreadId = 0;
}
void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, const char* reason)
{
std::ostringstream message;
message << "Render thread request failed";
if (operationName && operationName[0] != '\0')
message << " [" << operationName << "]";
if (reason && reason[0] != '\0')
message << ": " << reason;
message << ".\n";
OutputDebugStringA(message.str().c_str());
}
bool RenderEngine::IsRenderThreadAccessExpected() const
{
return !mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId;
}
void RenderEngine::ReportWrongThreadRenderAccess(const char* operationName) const
{
if (IsRenderThreadAccessExpected())
return;
std::ostringstream message;
message << "Wrong-thread render access detected";
if (operationName && operationName[0] != '\0')
message << " [" << operationName << "]";
message << ".\n";
OutputDebugStringA(message.str().c_str());
}
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
});
}
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage);
});
}
bool RenderEngine::InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
std::string& error)
{
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, &error]() {
return mRenderer.InitializeResources(
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
outputFrameWidth,
outputFrameHeight,
outputPackTextureWidth,
error);
});
}
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
});
}
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, &preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
});
}
bool RenderEngine::ApplyPreparedShaderBuild(
const PreparedShaderBuild& preparedBuild,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState,
int errorMessageSize,
char* errorMessage)
{
if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage))
return false;
mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates());
ResetTemporalHistoryState();
if (!preserveFeedbackState)
ResetShaderFeedbackState();
return true;
}
void RenderEngine::ResetTemporalHistoryState()
{
InvokeOnRenderThread([this]() {
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
});
}
void RenderEngine::ResetShaderFeedbackState()
{
InvokeOnRenderThread([this]() {
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
ProcessRenderResetCommandsOnRenderThread();
});
}
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
{
InvokeOnRenderThread([this, resetScope]() {
switch (resetScope)
{
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
break;
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback);
ProcessRenderResetCommandsOnRenderThread();
break;
case RuntimeCoordinatorRenderResetScope::None:
default:
break;
}
});
}
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-temporal-history");
mShaderPrograms.ResetTemporalHistoryState();
}
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-shader-feedback");
mShaderPrograms.ResetShaderFeedbackState();
}
void RenderEngine::ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope)
{
switch (resetScope)
{
case RenderCommandResetScope::ShaderFeedbackOnly:
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryOnly:
ResetTemporalHistoryStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryAndFeedback:
ResetTemporalHistoryStateOnRenderThread();
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::None:
default:
break;
}
}
void RenderEngine::ProcessRenderResetCommandsOnRenderThread()
{
RenderCommandResetScope resetScope = RenderCommandResetScope::None;
while (mRenderCommandQueue.TryTakeRenderReset(resetScope))
ApplyRenderResetOnRenderThread(resetScope);
}
void RenderEngine::EnqueuePreviewPresentWake()
{
if (!mRenderThreadRunning)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mPreviewPresentWakePending)
{
mPreviewPresentWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mPreviewPresentWakePending = false;
}
ProcessPreviewPresentCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessPreviewPresentCommandsOnRenderThread()
{
RenderPreviewPresentRequest request;
if (mRenderCommandQueue.TryTakePreviewPresent(request))
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
}
void RenderEngine::EnqueueInputUploadWake()
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mInputUploadWakePending)
{
mInputUploadWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mInputUploadWakePending = false;
}
ProcessInputUploadCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessInputUploadCommandsOnRenderThread()
{
RenderInputUploadRequest request;
while (mRenderCommandQueue.TryTakeInputUpload(request))
{
if (request.ownedBytes.empty())
continue;
request.inputFrame.bytes = request.ownedBytes.data();
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
}
}
void RenderEngine::EnqueueScreenshotCaptureWake()
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mScreenshotCaptureWakePending)
{
mScreenshotCaptureWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mScreenshotCaptureWakePending = false;
}
ProcessScreenshotCaptureCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
{
RenderScreenshotCaptureRequest request;
ScreenshotCaptureCallback completion;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
completion = mScreenshotCaptureCompletion;
}
while (mRenderCommandQueue.TryTakeScreenshotCapture(request))
{
if (!completion)
continue;
std::vector<unsigned char> topDownPixels;
if (CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels))
completion(request.width, request.height, std::move(topDownPixels));
}
}
void RenderEngine::ClearOscOverlayState()
{
InvokeOnRenderThread([this]() {
mRuntimeLiveState.Clear();
});
}
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
{
InvokeOnRenderThread([this, layerKey]() {
mRuntimeLiveState.ClearForLayerKey(layerKey);
});
}
void RenderEngine::UpdateOscOverlayState(
const std::vector<OscOverlayUpdate>& updates,
const std::vector<OscOverlayCommitCompletion>& completedCommits)
{
std::vector<RuntimeLiveOscCommitCompletion> liveCompletions;
liveCompletions.reserve(completedCommits.size());
for (const OscOverlayCommitCompletion& completedCommit : completedCommits)
liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions);
std::vector<RuntimeLiveOscUpdate> liveUpdates;
liveUpdates.reserve(updates.size());
for (const OscOverlayUpdate& update : updates)
liveUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
mRuntimeLiveState.ApplyOscUpdates(liveUpdates);
}
void RenderEngine::ResizeView(int width, int height)
{
InvokeOnRenderThread([this, width, height]() {
mRenderer.ResizeView(width, height);
});
}
bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight)
{
if (!force)
{
if (previewFps == 0)
return false;
const auto now = std::chrono::steady_clock::now();
const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps));
if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() &&
now - mLastPreviewPresentTime < minimumInterval)
{
return false;
}
}
if (mRenderThreadRunning)
{
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
EnqueuePreviewPresentWake();
return true;
}
ReportRenderThreadRequestFailure("preview-present", "render thread is not running");
return false;
}
bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight)
{
ReportWrongThreadRenderAccess("preview-present");
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
mLastPreviewPresentTime = std::chrono::steady_clock::now();
return true;
}
bool RenderEngine::RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion)
{
if (width == 0 || height == 0 || !completion)
return false;
if (!mRenderThreadRunning)
return false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mScreenshotCaptureCompletion = std::move(completion);
}
mRenderCommandQueue.RequestScreenshotCapture({ width, height });
EnqueueScreenshotCaptureWake();
return true;
}
bool RenderEngine::QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
return true;
if (inputFrame.rowBytes <= 0 || inputFrame.height == 0)
return false;
const std::size_t byteCount = static_cast<std::size_t>(inputFrame.rowBytes) * inputFrame.height;
RenderInputUploadRequest request;
request.inputFrame = inputFrame;
request.videoState = videoState;
request.ownedBytes.resize(byteCount);
std::memcpy(request.ownedBytes.data(), inputFrame.bytes, byteCount);
request.inputFrame.bytes = nullptr;
mRenderCommandQueue.RequestInputUpload(request);
EnqueueInputUploadWake();
return true;
}
bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
ReportWrongThreadRenderAccess("input-upload");
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
return true;
}
bool RenderEngine::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
if (mRenderThreadRunning)
{
const auto queuedAt = std::chrono::steady_clock::now();
return TryInvokeOnRenderThread("output-render", [this, &context, &outputFrame, queuedAt]() {
const auto startedAt = std::chrono::steady_clock::now();
const double queueWaitMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(startedAt - queuedAt).count();
mHealthTelemetry.TryRecordOutputRenderQueueWait(queueWaitMilliseconds);
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
RenderOutputFrameRequest request;
return mRenderCommandQueue.TryTakeOutputFrame(request) &&
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
});
}
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
return false;
}
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
ReportWrongThreadRenderAccess("output-render");
ProcessRenderResetCommandsOnRenderThread();
ProcessInputUploadCommandsOnRenderThread();
return mRenderPipeline.RenderFrame(context, outputFrame);
}
bool RenderEngine::ResolveRenderFrameState(
const RenderFrameInput& input,
std::vector<OscOverlayCommitRequest>* commitRequests,
RenderFrameState& frameState)
{
std::vector<RuntimeLiveOscCommitRequest> liveCommitRequests;
const bool resolved = mFrameStateResolver.Resolve(
input,
mShaderPrograms.CommittedLayerStates(),
mRuntimeLiveState,
commitRequests ? &liveCommitRequests : nullptr,
frameState);
if (commitRequests)
{
for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests)
commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation });
}
return resolved;
}
void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState)
{
RenderLayerStack(
frameState.hasInputSource,
frameState.layerStates,
frameState.inputFrameWidth,
frameState.inputFrameHeight,
frameState.captureTextureWidth,
frameState.inputPixelFormat,
frameState.historyCap);
}
void RenderEngine::RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap)
{
ReportWrongThreadRenderAccess("render-layer-stack");
mRenderPass.Render(
hasInputSource,
layerStates,
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
inputPixelFormat,
historyCap,
[this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) {
return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error);
},
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
});
}
bool RenderEngine::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
{
ReportWrongThreadRenderAccess("read-output-frame-rgba");
if (width == 0 || height == 0)
return false;
bottomUpPixels.resize(static_cast<std::size_t>(width) * height * 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
return true;
}
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
{
std::vector<unsigned char> bottomUpPixels;
if (!ReadOutputFrameRgbaOnRenderThread(width, height, bottomUpPixels))
return false;
topDownPixels.resize(bottomUpPixels.size());
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const unsigned sourceY = height - 1 - y;
std::copy(
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
}
return true;
}

View File

@@ -0,0 +1,232 @@
#pragma once
#include "OpenGLRenderPass.h"
#include "OpenGLRenderPipeline.h"
#include "OpenGLRenderer.h"
#include "OpenGLShaderPrograms.h"
#include "RenderCommandQueue.h"
#include "RenderFrameState.h"
#include "RenderFrameStateResolver.h"
#include "HealthTelemetry.h"
#include "RuntimeCoordinator.h"
#include "RuntimeSnapshotProvider.h"
#include <windows.h>
#include <atomic>
#include <cstdint>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <utility>
#include <vector>
class RenderEngine
{
public:
using RenderEffectCallback = std::function<void()>;
using ScreenshotCallback = std::function<void()>;
using ScreenshotCaptureCallback = std::function<void(unsigned, unsigned, std::vector<unsigned char>)>;
using PreviewPaintCallback = std::function<void()>;
struct OscOverlayUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct OscOverlayCommitCompletion
{
std::string routeKey;
uint64_t generation = 0;
};
struct OscOverlayCommitRequest
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint);
~RenderEngine();
bool StartRenderThread();
void StopRenderThread();
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
bool InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
std::string& error);
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool ApplyPreparedShaderBuild(
const PreparedShaderBuild& preparedBuild,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState,
int errorMessageSize,
char* errorMessage);
void ResetTemporalHistoryState();
void ResetShaderFeedbackState();
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
void ClearOscOverlayState();
void ClearOscOverlayStateForLayerKey(const std::string& layerKey);
void UpdateOscOverlayState(
const std::vector<OscOverlayUpdate>& updates,
const std::vector<OscOverlayCommitCompletion>& completedCommits);
void ResizeView(int width, int height);
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
bool RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion);
bool QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
bool ResolveRenderFrameState(
const RenderFrameInput& input,
std::vector<OscOverlayCommitRequest>* commitRequests,
RenderFrameState& frameState);
void RenderPreparedFrame(const RenderFrameState& frameState);
private:
static constexpr std::chrono::milliseconds kRenderThreadRequestTimeout{ 250 };
struct RenderThreadTaskState
{
std::atomic<bool> started = false;
std::atomic<bool> cancelled = false;
};
template<typename Func>
auto InvokeOnRenderThread(Func&& func) -> decltype(func())
{
using Result = decltype(func());
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return func();
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Func>(func));
std::future<Result> result = task->get_future();
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadTasks.push([task]() { (*task)(); });
}
mRenderThreadCondition.notify_one();
return result.get();
}
template<typename Func>
bool TryInvokeOnRenderThread(const char* operationName, Func&& func)
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return func();
auto state = std::make_shared<RenderThreadTaskState>();
auto task = std::make_shared<std::packaged_task<bool()>>(
[state, func = std::forward<Func>(func)]() mutable {
state->started = true;
if (state->cancelled)
return false;
return func();
});
std::future<bool> result = task->get_future();
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (mRenderThreadStopping)
{
ReportRenderThreadRequestFailure(operationName, "render thread is stopping");
return false;
}
mRenderThreadTasks.push([task]() { (*task)(); });
}
mRenderThreadCondition.notify_one();
if (result.wait_for(kRenderThreadRequestTimeout) == std::future_status::ready)
return result.get();
if (!state->started)
{
state->cancelled = true;
ReportRenderThreadRequestFailure(operationName, "timed out before execution");
return false;
}
ReportRenderThreadRequestFailure(operationName, "exceeded timeout while executing; waiting for safe completion");
return result.get();
}
void RenderThreadMain(std::promise<bool> ready);
void ReportRenderThreadRequestFailure(const char* operationName, const char* reason);
bool IsRenderThreadAccessExpected() const;
void ReportWrongThreadRenderAccess(const char* operationName) const;
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
void RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap);
void ResetTemporalHistoryStateOnRenderThread();
void ResetShaderFeedbackStateOnRenderThread();
void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope);
void ProcessRenderResetCommandsOnRenderThread();
void EnqueuePreviewPresentWake();
void ProcessPreviewPresentCommandsOnRenderThread();
void EnqueueInputUploadWake();
void ProcessInputUploadCommandsOnRenderThread();
void EnqueueScreenshotCaptureWake();
void ProcessScreenshotCaptureCommandsOnRenderThread();
bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight);
bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
bool ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
bool CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
OpenGLRenderer mRenderer;
OpenGLRenderPass mRenderPass;
OpenGLRenderPipeline mRenderPipeline;
OpenGLShaderPrograms mShaderPrograms;
HealthTelemetry& mHealthTelemetry;
HDC mHdc;
HGLRC mHglrc;
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
RenderCommandQueue mRenderCommandQueue;
RenderFrameStateResolver mFrameStateResolver;
RuntimeLiveState mRuntimeLiveState;
std::thread mRenderThread;
std::atomic<DWORD> mRenderThreadId = 0;
std::mutex mRenderThreadMutex;
std::condition_variable mRenderThreadCondition;
std::queue<std::function<void()>> mRenderThreadTasks;
std::atomic<bool> mRenderThreadRunning = false;
bool mRenderThreadStopping = false;
bool mPreviewPresentWakePending = false;
bool mInputUploadWakePending = false;
bool mScreenshotCaptureWakePending = false;
ScreenshotCaptureCallback mScreenshotCaptureCompletion;
bool mResourcesDestroyed = false;
};

View File

@@ -0,0 +1,428 @@
#include "DeckLinkDisplayMode.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include "PngScreenshotWriter.h"
#include "RenderEngine.h"
#include "RuntimeCoordinator.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServiceLiveBridge.h"
#include "RuntimeServices.h"
#include "RuntimeSnapshotProvider.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <chrono>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC)
{
mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder(), *mRuntimeEventDispatcher);
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider,
mRuntimeStore->GetHealthTelemetry(),
hGLDC,
hGLRC,
[this]() { renderEffect(); },
[]() {},
[this]() { paintGL(false); });
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry(), *mRuntimeEventDispatcher);
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher);
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
*mRuntimeStore,
*mRuntimeCoordinator,
*mRuntimeEventDispatcher,
*mRuntimeServices,
*mRenderEngine,
*mShaderBuildQueue,
*mVideoBackend);
}
OpenGLComposite::~OpenGLComposite()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
if (mShaderBuildQueue)
mShaderBuildQueue->Stop();
if (mVideoBackend)
mVideoBackend->ReleaseResources();
if (mRuntimeStore)
{
std::string persistenceError;
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
}
}
bool OpenGLComposite::InitDeckLink()
{
return InitVideoIO();
}
bool OpenGLComposite::InitVideoIO()
{
VideoFormatSelection videoModes;
std::string initFailureReason;
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
{
std::string runtimeError;
if (!mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
}
if (mRuntimeStore)
{
if (!ResolveConfiguredVideoFormats(
mRuntimeStore->GetConfiguredInputVideoFormat(),
mRuntimeStore->GetConfiguredInputFrameRate(),
mRuntimeStore->GetConfiguredOutputVideoFormat(),
mRuntimeStore->GetConfiguredOutputFrameRate(),
videoModes,
initFailureReason))
{
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
return false;
}
}
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
{
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
? "This application requires the DeckLink drivers installed."
: "DeckLink initialization failed";
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())
{
initFailureReason = "OpenGL extension checks failed.";
goto error;
}
if (! InitOpenGLState())
{
initFailureReason = "OpenGL state initialization failed.";
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->OutputModelName().empty()
? "DeckLink output device selected."
: ("Selected output device: " + mVideoBackend->OutputModelName()));
// Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoBackend->OutputFrameWidth() < 1920)
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
else
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
{
goto error;
}
if (!mVideoBackend->HasInputDevice())
mVideoBackend->ReportNoInputDeviceSignalStatus();
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
{
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->StatusMessage());
return true;
error:
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
mVideoBackend->ReleaseResources();
return false;
}
void OpenGLComposite::paintGL(bool force)
{
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
if (!force)
{
if (IsIconic(hGLWnd))
return;
}
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
if (!force && mVideoBackend && mVideoBackend->ShouldPrioritizeOutputOverPreview())
{
ValidateRect(hGLWnd, NULL);
return;
}
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
{
ValidateRect(hGLWnd, NULL);
return;
}
ValidateRect(hGLWnd, NULL);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
mRenderEngine->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
{
RECT r;
if (GetWindowRect(hGLWnd, &r))
{
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
}
}
bool OpenGLComposite::InitOpenGLState()
{
if (! ResolveGLExtensions())
return false;
std::string runtimeError;
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
return false;
}
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
return false;
}
std::string rendererError;
if (!mRenderEngine->InitializeResources(
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->CaptureTextureWidth(),
mVideoBackend->OutputFrameWidth(),
mVideoBackend->OutputFrameHeight(),
mVideoBackend->OutputPackTextureWidth(),
rendererError))
{
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
}
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState();
mRuntimeUpdateController->BroadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
return true;
}
bool OpenGLComposite::Start()
{
if (!mRenderEngine->StartRenderThread())
return false;
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
if (mVideoBackend->Start())
return true;
mRenderEngine->StopRenderThread();
return false;
}
bool OpenGLComposite::Stop()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
mVideoBackend->Stop();
if (wasExternalKeyingActive)
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
"External keying has been disabled.");
if (mRenderEngine)
mRenderEngine->StopRenderThread();
if (mRuntimeStore)
{
std::string persistenceError;
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
}
return true;
}
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
}
bool OpenGLComposite::RequestScreenshot(std::string& error)
{
if (!mRenderEngine || !mVideoBackend)
{
error = "The render engine is not ready.";
return false;
}
const unsigned width = mVideoBackend->OutputFrameWidth();
const unsigned height = mVideoBackend->OutputFrameHeight();
if (width == 0 || height == 0)
{
error = "The output frame size is not available.";
return false;
}
std::filesystem::path outputPath;
try
{
outputPath = BuildScreenshotPath();
std::filesystem::create_directories(outputPath.parent_path());
}
catch (const std::exception& exception)
{
error = exception.what();
return false;
}
if (!mRenderEngine->RequestScreenshotCapture(
width,
height,
[outputPath](unsigned captureWidth, unsigned captureHeight, std::vector<unsigned char> topDownPixels) {
try
{
WritePngFileAsync(outputPath, captureWidth, captureHeight, std::move(topDownPixels));
}
catch (const std::exception& exception)
{
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
}
}))
{
error = "Screenshot capture request failed.";
return false;
}
return true;
}
void OpenGLComposite::renderEffect()
{
const RenderFrameInput frameInput = BuildRenderFrameInput();
RenderFrame(frameInput);
}
RenderFrameInput OpenGLComposite::BuildRenderFrameInput() const
{
RenderFrameInput frameInput;
frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates();
frameInput.hasInputSource = mVideoBackend->HasInputSource();
frameInput.renderWidth = mVideoBackend->InputFrameWidth();
frameInput.renderHeight = mVideoBackend->InputFrameHeight();
frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth();
frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight();
frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth();
frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat();
frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
return frameInput;
}
void OpenGLComposite::RenderFrame(const RenderFrameInput& frameInput)
{
RenderFrameState frameState;
if (mRuntimeServices)
{
RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
*mRuntimeServices,
*mRenderEngine,
frameInput,
frameState);
}
else
{
mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState);
}
mRenderEngine->RenderPreparedFrame(frameState);
}
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
{
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
? mRuntimeStore->GetRuntimeDataRoot()
: std::filesystem::current_path();
const auto now = std::chrono::system_clock::now();
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &nowTime);
std::ostringstream filename;
filename << "video-shader-toys-"
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
<< ".png";
return root / "screenshots" / filename.str();
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
return true;
}

View File

@@ -2,35 +2,23 @@
#define __OPENGL_COMPOSITE_H__ #define __OPENGL_COMPOSITE_H__
#include <windows.h> #include <windows.h>
#include <process.h>
#include <tchar.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <objbase.h> #include <objbase.h>
#include <atlbase.h>
#include <comutil.h>
#include "GLExtensions.h" #include "RenderFrameState.h"
#include "OpenGLRenderer.h"
#include "RuntimeHost.h"
#include <functional>
#include <atomic>
#include <filesystem> #include <filesystem>
#include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include <deque>
class VideoIODevice; class RenderEngine;
class OpenGLVideoIOBridge; class RuntimeCoordinator;
class OpenGLRenderPass; class RuntimeEventDispatcher;
class OpenGLRenderPipeline; class RuntimeSnapshotProvider;
class OpenGLShaderPrograms;
class RuntimeServices; class RuntimeServices;
class RuntimeStore;
class RuntimeUpdateController;
class ShaderBuildQueue; class ShaderBuildQueue;
class VideoBackend;
class OpenGLComposite class OpenGLComposite
@@ -39,10 +27,11 @@ public:
OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC); OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC);
~OpenGLComposite(); ~OpenGLComposite();
bool InitializeVideoIO(); bool InitDeckLink();
bool InitVideoIO();
bool Start(); bool Start();
bool Stop(); bool Stop();
bool ReloadShader(); bool ReloadShader(bool preserveFeedbackState = false);
std::string GetRuntimeStateJson() const; std::string GetRuntimeStateJson() const;
bool AddLayer(const std::string& shaderId, std::string& error); bool AddLayer(const std::string& shaderId, std::string& error);
bool RemoveLayer(const std::string& layerId, std::string& error); bool RemoveLayer(const std::string& layerId, std::string& error);
@@ -58,48 +47,37 @@ public:
bool RequestScreenshot(std::string& error); bool RequestScreenshot(std::string& error);
unsigned short GetControlServerPort() const; unsigned short GetControlServerPort() const;
unsigned short GetOscPort() const; unsigned short GetOscPort() const;
std::string GetOscBindAddress() const;
std::string GetControlUrl() const; std::string GetControlUrl() const;
std::string GetDocsUrl() const; std::string GetDocsUrl() const;
std::string GetOscAddress() const; std::string GetOscAddress() const;
void resizeGL(WORD width, WORD height); void resizeGL(WORD width, WORD height);
void paintGL(); void paintGL(bool force = false);
private: private:
void resizeWindow(int width, int height); void resizeWindow(int width, int height);
bool CheckOpenGLExtensions(); bool CheckOpenGLExtensions();
void PublishVideoIOStatus(const std::string& statusMessage);
using LayerProgram = OpenGLRenderer::LayerProgram;
HWND hGLWnd; HWND hGLWnd;
HDC hGLDC; HDC hGLDC;
HGLRC hGLRC; HGLRC hGLRC;
CRITICAL_SECTION pMutex;
std::unique_ptr<VideoIODevice> mVideoIO; std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<OpenGLRenderer> mRenderer; std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
std::unique_ptr<RuntimeHost> mRuntimeHost; std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
std::unique_ptr<OpenGLVideoIOBridge> mVideoIOBridge; std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
std::unique_ptr<OpenGLRenderPass> mRenderPass; std::unique_ptr<RenderEngine> mRenderEngine;
std::unique_ptr<OpenGLRenderPipeline> mRenderPipeline;
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue; std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices; std::unique_ptr<RuntimeServices> mRuntimeServices;
std::vector<RuntimeRenderState> mCachedLayerRenderStates; std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
uint64_t mCachedRenderStateVersion = 0; std::unique_ptr<VideoBackend> mVideoBackend;
unsigned mCachedRenderStateWidth = 0;
unsigned mCachedRenderStateHeight = 0;
std::atomic<bool> mUseCommittedLayerStates;
std::atomic<bool> mScreenshotRequested;
bool InitOpenGLState(); bool InitOpenGLState();
void renderEffect(); void renderEffect();
bool ProcessRuntimePollResults(); RenderFrameInput BuildRenderFrameInput() const;
void RequestShaderBuild(); void RenderFrame(const RenderFrameInput& frameInput);
void ProcessScreenshotRequest();
std::filesystem::path BuildScreenshotPath() const; std::filesystem::path BuildScreenshotPath() const;
void broadcastRuntimeState();
void resetTemporalHistoryState();
}; };
#endif // __OPENGL_COMPOSITE_H__ #endif // __OPENGL_COMPOSITE_H__

View File

@@ -0,0 +1,126 @@
#include "OpenGLComposite.h"
#include "RuntimeCoordinator.h"
#include "RuntimeJson.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
}
unsigned short OpenGLComposite::GetControlServerPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
}
unsigned short OpenGLComposite::GetOscPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
}
std::string OpenGLComposite::GetOscBindAddress() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
}
std::string OpenGLComposite::GetControlUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
}
std::string OpenGLComposite::GetDocsUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
}
std::string OpenGLComposite::GetOscAddress() const
{
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
}
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
}
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
}
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
}
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
}
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
}
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
}
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
}
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
}
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
}
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
}
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "ShaderTypes.h"
#include "VideoIOTypes.h"
#include <vector>
struct RenderFrameInput
{
bool useCommittedLayerStates = false;
bool hasInputSource = false;
unsigned renderWidth = 0;
unsigned renderHeight = 0;
unsigned inputFrameWidth = 0;
unsigned inputFrameHeight = 0;
unsigned captureTextureWidth = 0;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
unsigned historyCap = 0;
double oscSmoothing = 0.0;
};
struct RenderFrameState
{
bool hasInputSource = false;
unsigned inputFrameWidth = 0;
unsigned inputFrameHeight = 0;
unsigned captureTextureWidth = 0;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
unsigned historyCap = 0;
std::vector<RuntimeRenderState> layerStates;
};

View File

@@ -0,0 +1,119 @@
#include "RenderFrameStateResolver.h"
#include <chrono>
namespace
{
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
}
RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRuntimeSnapshotProvider(runtimeSnapshotProvider)
{
}
void RenderFrameStateResolver::StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& committedLayerStates)
{
mCachedLayerRenderStates = committedLayerStates;
mCachedRenderStateVersion = snapshot.versions.renderStateVersion;
mCachedParameterStateVersion = snapshot.versions.parameterStateVersion;
mCachedRenderStateWidth = snapshot.outputWidth;
mCachedRenderStateHeight = snapshot.outputHeight;
}
bool RenderFrameStateResolver::Resolve(
const RenderFrameInput& input,
const std::vector<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
RenderFrameState& frameState)
{
frameState.hasInputSource = input.hasInputSource;
frameState.inputFrameWidth = input.inputFrameWidth;
frameState.inputFrameHeight = input.inputFrameHeight;
frameState.captureTextureWidth = input.captureTextureWidth;
frameState.inputPixelFormat = input.inputPixelFormat;
frameState.historyCap = input.historyCap;
frameState.layerStates.clear();
if (input.useCommittedLayerStates)
{
frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests);
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return true;
}
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
const bool renderStateCacheValid =
!mCachedLayerRenderStates.empty() &&
mCachedRenderStateVersion == versions.renderStateVersion &&
mCachedRenderStateWidth == input.renderWidth &&
mCachedRenderStateHeight == input.renderHeight;
if (renderStateCacheValid)
{
RuntimeRenderStateSnapshot renderSnapshot;
renderSnapshot.outputWidth = input.renderWidth;
renderSnapshot.outputHeight = input.renderHeight;
renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
renderSnapshot.states = mCachedLayerRenderStates;
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot))
{
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
}
mCachedLayerRenderStates = renderSnapshot.states;
frameState.layerStates = renderSnapshot.states;
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return true;
}
RuntimeRenderStateSnapshot renderSnapshot;
if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot))
{
mCachedLayerRenderStates = renderSnapshot.states;
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
mCachedRenderStateWidth = renderSnapshot.outputWidth;
mCachedRenderStateHeight = renderSnapshot.outputHeight;
mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
frameState.layerStates = mCachedLayerRenderStates;
return true;
}
frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return !frameState.layerStates.empty();
}
std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const
{
LayeredRenderStateInput input;
input.committedLiveLayerStates = &baseStates;
input.transientAutomationOverlay = &liveState;
input.allowTransientAutomationCommits = allowCommit;
input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
input.transientAutomationSmoothing = smoothing;
input.transientAutomationCommitDelay = kOscOverlayCommitDelay;
input.now = std::chrono::steady_clock::now();
const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input);
if (commitRequests)
{
for (const RuntimeLiveOscCommitRequest& request : result.commitRequests)
commitRequests->push_back(request);
}
return result.layerStates;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "RenderFrameState.h"
#include "RenderStateComposer.h"
#include "RuntimeSnapshotProvider.h"
#include <cstdint>
#include <vector>
class RenderFrameStateResolver
{
public:
explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider);
void StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& committedLayerStates);
bool Resolve(
const RenderFrameInput& input,
const std::vector<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
RenderFrameState& frameState);
private:
std::vector<RuntimeRenderState> ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
RenderStateComposer mRenderStateComposer;
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
uint64_t mCachedRenderStateVersion = 0;
uint64_t mCachedParameterStateVersion = 0;
unsigned mCachedRenderStateWidth = 0;
unsigned mCachedRenderStateHeight = 0;
};

View File

@@ -0,0 +1,378 @@
#include "RuntimeUpdateController.h"
#include "RenderEngine.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <variant>
namespace
{
RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope)
{
switch (scope)
{
case RuntimeEventRenderResetScope::TemporalHistoryOnly:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly;
case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
case RuntimeEventRenderResetScope::None:
default:
return RuntimeCoordinatorRenderResetScope::None;
}
}
}
RuntimeUpdateController::RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend) :
mRuntimeStore(runtimeStore),
mRuntimeCoordinator(runtimeCoordinator),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mRuntimeServices(runtimeServices),
mRenderEngine(renderEngine),
mShaderBuildQueue(shaderBuildQueue),
mVideoBackend(videoBackend)
{
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeStateBroadcastRequested,
[this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeReloadRequested,
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimePersistenceRequested,
[this](const RuntimeEvent& event) { HandleRuntimePersistenceRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildRequested,
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildPrepared,
[this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildFailed,
[this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::CompileStatusChanged,
[this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RenderResetRequested,
[this](const RuntimeEvent& event) { HandleRenderResetRequested(event); });
}
bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
{
if (!result.accepted)
{
if (error)
*error = result.errorMessage;
return false;
}
if (result.compileStatusChanged)
{
mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
++mPendingCoordinatorCompileStatusEvents;
}
if (result.clearReloadRequest)
mRuntimeStore.ClearReloadRequest();
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
switch (result.transientOscInvalidation)
{
case RuntimeCoordinatorTransientOscInvalidation::All:
mRenderEngine.ClearOscOverlayState();
mRuntimeServices.ClearOscState();
break;
case RuntimeCoordinatorTransientOscInvalidation::Layer:
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
break;
case RuntimeCoordinatorTransientOscInvalidation::None:
default:
break;
}
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None)
++mPendingCoordinatorRenderResetEvents;
if (result.shaderBuildRequested)
{
RequestShaderBuild();
++mPendingCoordinatorShaderBuildEvents;
}
if (result.runtimeStateBroadcastRequired)
BroadcastRuntimeState();
return true;
}
bool RuntimeUpdateController::ProcessRuntimeWork()
{
DispatchRuntimeEvents();
return ConsumeReadyShaderBuild(0, true, true);
}
void RuntimeUpdateController::RequestShaderBuild()
{
mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight());
}
void RuntimeUpdateController::BroadcastRuntimeState()
{
RuntimeStateBroadcastRequestedEvent event;
event.reason = "runtime-state-changed";
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"))
{
mRuntimeServices.BroadcastState();
return;
}
DispatchRuntimeEvents();
}
void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event)
{
if (event.source == "ControlServices")
return;
mRuntimeServices.BroadcastState();
}
void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& event)
{
const RuntimeReloadRequestedEvent* payload = std::get_if<RuntimeReloadRequestedEvent>(&event.payload);
if (!payload)
return;
mRuntimeStore.ClearReloadRequest();
}
void RuntimeUpdateController::HandleRuntimePersistenceRequested(const RuntimeEvent& event)
{
const RuntimePersistenceRequestedEvent* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
if (!payload)
return;
std::string error;
mRuntimeStore.RequestPersistence(payload->request, error);
}
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents))
return;
RequestShaderBuild();
}
void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared)
return;
ConsumeReadyShaderBuild(payload->generation, false, true);
}
void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed)
return;
ConsumeReadyShaderBuild(payload->generation, false, false);
}
void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event)
{
const CompileStatusChangedEvent* payload = std::get_if<CompileStatusChangedEvent>(&event.payload);
if (!payload)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents))
return;
mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message);
}
void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event)
{
const RenderResetEvent* payload = std::get_if<RenderResetEvent>(&event.payload);
if (!payload || payload->applied)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents))
return;
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope));
}
bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent)
{
PreparedShaderBuild readyBuild;
const bool consumed = expectedGeneration == 0
? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild)
: mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild);
if (!consumed)
return true;
const unsigned inputWidth = mVideoBackend.InputFrameWidth();
const unsigned inputHeight = mVideoBackend.InputFrameHeight();
if (!readyBuild.succeeded)
{
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
readyBuild.message);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message));
return false;
}
if (publishPreparedEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Prepared,
readyBuild.generation,
inputWidth,
inputHeight,
true,
readyBuild.message);
DispatchRuntimeEvents();
}
char compilerErrorMessage[1024] = {};
if (!mRenderEngine.ApplyPreparedShaderBuild(
readyBuild,
inputWidth,
inputHeight,
mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(),
sizeof(compilerErrorMessage),
compilerErrorMessage))
{
const std::string errorMessage = compilerErrorMessage;
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
errorMessage);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage));
return false;
}
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Applied,
readyBuild.generation,
inputWidth,
inputHeight,
true,
"Shader layers applied successfully.");
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess());
return true;
}
void RuntimeUpdateController::PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message)
{
ShaderBuildEvent event;
event.phase = phase;
event.generation = generation;
event.inputWidth = inputWidth;
event.inputHeight = inputHeight;
event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild();
event.succeeded = succeeded;
event.message = message;
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController");
}
bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions)
{
if (event.source != "RuntimeCoordinator")
return false;
if (pendingSuppressions > 0)
--pendingSuppressions;
return true;
}
RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents)
{
RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents);
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry();
telemetry.TryRecordRuntimeEventDispatchStats(
result.dispatchedEvents,
result.handlerInvocations,
result.handlerFailures,
result.dispatchDurationMilliseconds);
telemetry.TryRecordRuntimeEventQueueMetrics(
"runtime-events",
queueMetrics.depth,
queueMetrics.capacity,
static_cast<uint64_t>(queueMetrics.droppedCount),
queueMetrics.oldestEventAgeMilliseconds);
PublishRuntimeEventHealthObservations(result);
return result;
}
void RuntimeUpdateController::PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result)
{
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
if (queueMetrics.depth != mLastReportedRuntimeEventQueueDepth ||
queueMetrics.droppedCount != mLastReportedRuntimeEventDroppedCount ||
queueMetrics.coalescedCount != mLastReportedRuntimeEventCoalescedCount)
{
QueueDepthChangedEvent queueDepth;
queueDepth.queueName = "runtime-events";
queueDepth.depth = queueMetrics.depth;
queueDepth.capacity = queueMetrics.capacity;
queueDepth.droppedCount = queueMetrics.droppedCount;
queueDepth.coalescedCount = queueMetrics.coalescedCount;
mRuntimeEventDispatcher.PublishPayload(queueDepth, "HealthTelemetry");
mLastReportedRuntimeEventQueueDepth = queueMetrics.depth;
mLastReportedRuntimeEventDroppedCount = queueMetrics.droppedCount;
mLastReportedRuntimeEventCoalescedCount = queueMetrics.coalescedCount;
}
if (result.handlerInvocations == 0 && result.handlerFailures == 0)
return;
TimingSampleRecordedEvent timing;
timing.subsystem = "RuntimeEventDispatcher";
timing.metric = "dispatchDuration";
timing.value = result.dispatchDurationMilliseconds;
timing.unit = "ms";
mRuntimeEventDispatcher.PublishPayload(timing, "HealthTelemetry");
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include "RuntimeCoordinator.h"
#include "RuntimeEventPayloads.h"
#include <cstddef>
#include <cstdint>
#include <string>
class RenderEngine;
struct RuntimeEvent;
struct RuntimeEventDispatchResult;
class RuntimeEventDispatcher;
class RuntimeServices;
class RuntimeStore;
class ShaderBuildQueue;
class VideoBackend;
class RuntimeUpdateController
{
public:
RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend);
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
bool ProcessRuntimeWork();
void RequestShaderBuild();
void BroadcastRuntimeState();
private:
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
void HandleRuntimePersistenceRequested(const RuntimeEvent& event);
void HandleShaderBuildRequested(const RuntimeEvent& event);
void HandleShaderBuildPrepared(const RuntimeEvent& event);
void HandleShaderBuildFailed(const RuntimeEvent& event);
void HandleCompileStatusChanged(const RuntimeEvent& event);
void HandleRenderResetRequested(const RuntimeEvent& event);
bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent);
void PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message);
bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions);
RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0);
void PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result);
RuntimeStore& mRuntimeStore;
RuntimeCoordinator& mRuntimeCoordinator;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
RuntimeServices& mRuntimeServices;
RenderEngine& mRenderEngine;
ShaderBuildQueue& mShaderBuildQueue;
VideoBackend& mVideoBackend;
std::size_t mPendingCoordinatorShaderBuildEvents = 0;
std::size_t mPendingCoordinatorCompileStatusEvents = 0;
std::size_t mPendingCoordinatorRenderResetEvents = 0;
std::size_t mLastReportedRuntimeEventQueueDepth = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventDroppedCount = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventCoalescedCount = static_cast<std::size_t>(-1);
};

View File

@@ -59,6 +59,7 @@ void OpenGLRenderPass::Render(
} }
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight); mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
mRenderer.FeedbackBuffers().FinalizeFrame();
} }
void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat) void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
@@ -187,6 +188,7 @@ std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
pass.passId = passProgram.passId; pass.passId = passProgram.passId;
pass.layerId = state.layerId; pass.layerId = state.layerId;
pass.shaderId = state.shaderId; pass.shaderId = state.shaderId;
pass.layerInputTexture = layerInputTexture;
pass.sourceTexture = passSourceTexture; pass.sourceTexture = passSourceTexture;
pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer; pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
pass.destinationTexture = passDestinationTexture; pass.destinationTexture = passDestinationTexture;
@@ -195,6 +197,7 @@ std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
pass.passProgram = &passProgram; pass.passProgram = &passProgram;
pass.layerState = &state; pass.layerState = &state;
pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput; pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput;
pass.captureFeedbackWrite = state.feedback.enabled && passProgram.passId == state.feedback.writePassId;
passes.push_back(pass); passes.push_back(pass);
// A later pass can reference either the explicit output name or the // A later pass can reference either the explicit output name or the
@@ -224,6 +227,7 @@ void OpenGLRenderPass::RenderLayerPass(
return; return;
RenderShaderProgram( RenderShaderProgram(
pass.layerInputTexture,
pass.sourceTexture, pass.sourceTexture,
pass.destinationFramebuffer, pass.destinationFramebuffer,
*pass.passProgram, *pass.passProgram,
@@ -236,9 +240,12 @@ void OpenGLRenderPass::RenderLayerPass(
if (pass.capturePreLayerHistory) if (pass.capturePreLayerHistory)
mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight); mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight);
if (pass.captureFeedbackWrite)
mRenderer.FeedbackBuffers().CaptureFeedbackFramebuffer(pass.layerId, pass.destinationFramebuffer, inputFrameWidth, inputFrameHeight);
} }
void OpenGLRenderPass::RenderShaderProgram( void OpenGLRenderPass::RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture, GLuint sourceTexture,
GLuint destinationFrameBuffer, GLuint destinationFrameBuffer,
PassProgram& passProgram, PassProgram& passProgram,
@@ -261,14 +268,19 @@ void OpenGLRenderPass::RenderShaderProgram(
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0); const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0); const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan = const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, sourceHistoryTextures, temporalHistoryTextures); mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
mTextureBindings.BindRuntimeTexturePlan(texturePlan); mTextureBindings.BindRuntimeTexturePlan(texturePlan);
glBindVertexArray(mRenderer.FullscreenVertexArray()); glBindVertexArray(mRenderer.FullscreenVertexArray());
glUseProgram(passProgram.program); glUseProgram(passProgram.program);
// The UBO is shared by every pass in a layer; texture routing is what // The UBO is shared by every pass in a layer; texture routing is what
// changes from pass to pass. // changes from pass to pass.
updateGlobalParams(state, mRenderer.TemporalHistory().SourceAvailableCount(), mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId)); updateGlobalParams(
state,
mRenderer.TemporalHistory().SourceAvailableCount(),
mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId),
mRenderer.FeedbackBuffers().FeedbackAvailable(state));
glDrawArrays(GL_TRIANGLES, 0, 3); glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0); glUseProgram(0);
glBindVertexArray(0); glBindVertexArray(0);

View File

@@ -16,7 +16,7 @@ public:
using LayerProgram = OpenGLRenderer::LayerProgram; using LayerProgram = OpenGLRenderer::LayerProgram;
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram; using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>; using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned)>; using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned, bool)>;
explicit OpenGLRenderPass(OpenGLRenderer& renderer); explicit OpenGLRenderPass(OpenGLRenderer& renderer);
@@ -44,6 +44,7 @@ private:
const TextBindingUpdater& updateTextBinding, const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams); const GlobalParamsUpdater& updateGlobalParams);
void RenderShaderProgram( void RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture, GLuint sourceTexture,
GLuint destinationFrameBuffer, GLuint destinationFrameBuffer,
PassProgram& passProgram, PassProgram& passProgram,

View File

@@ -1,25 +1,32 @@
#include "OpenGLRenderPipeline.h" #include "OpenGLRenderPipeline.h"
#include "HealthTelemetry.h"
#include "OpenGLRenderer.h" #include "OpenGLRenderer.h"
#include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h"
#include "VideoIOFormat.h" #include "VideoIOFormat.h"
#include <cstring> #include <cstring>
#include <chrono> #include <chrono>
#include <cstdlib>
#include <gl/gl.h> #include <gl/gl.h>
#include <string>
OpenGLRenderPipeline::OpenGLRenderPipeline( OpenGLRenderPipeline::OpenGLRenderPipeline(
OpenGLRenderer& renderer, OpenGLRenderer& renderer,
RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
RenderEffectCallback renderEffect, RenderEffectCallback renderEffect,
OutputReadyCallback outputReady, OutputReadyCallback outputReady,
PaintCallback paint) : PaintCallback paint) :
mRenderer(renderer), mRenderer(renderer),
mRuntimeHost(runtimeHost), mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mHealthTelemetry(healthTelemetry),
mRenderEffect(renderEffect), mRenderEffect(renderEffect),
mOutputReady(outputReady), mOutputReady(outputReady),
mPaint(paint) mPaint(paint),
mOutputReadbackMode(ReadOutputReadbackModeFromEnvironment()),
mAsyncReadbackDepth(ReadAsyncReadbackDepthFromEnvironment())
{ {
} }
@@ -41,22 +48,55 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer()); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
if (mOutputReady) if (mOutputReady)
mOutputReady(); mOutputReady();
if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10) if (state.outputPixelFormat == VideoIOPixelFormat::Bgra8)
PackOutputForBgra8(state);
else if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
PackOutputFor10Bit(state); PackOutputFor10Bit(state);
glFlush(); glFlush();
const auto renderEndTime = std::chrono::steady_clock::now(); const auto renderEndTime = std::chrono::steady_clock::now();
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count(); const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
mRuntimeHost.TrySetPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds); mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
mRuntimeHost.TryAdvanceFrame(); mRuntimeSnapshotProvider.AdvanceFrame();
ReadOutputFrame(state, outputFrame); OutputReadbackTiming readbackTiming = ReadOutputFrame(state, outputFrame);
if (mPaint) mHealthTelemetry.TryRecordOutputRenderPipelineTiming(
mPaint(); renderMilliseconds,
readbackTiming.fenceWaitMilliseconds,
readbackTiming.mapMilliseconds,
readbackTiming.copyMilliseconds,
readbackTiming.cachedCopyMilliseconds,
readbackTiming.asyncQueueMilliseconds,
readbackTiming.asyncQueueBufferMilliseconds,
readbackTiming.asyncQueueSetupMilliseconds,
readbackTiming.asyncQueueReadPixelsMilliseconds,
readbackTiming.asyncQueueFenceMilliseconds,
readbackTiming.syncReadMilliseconds,
readbackTiming.asyncReadbackMissed,
readbackTiming.cachedFallbackUsed,
readbackTiming.syncFallbackUsed);
return true; return true;
} }
void OpenGLRenderPipeline::PackOutputForBgra8(const VideoIOState& state)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
glBlitFramebuffer(
0,
0,
state.outputFrameSize.width,
state.outputFrameSize.height,
0,
0,
state.outputFrameSize.width,
state.outputFrameSize.height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
}
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state) void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
{ {
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
@@ -90,11 +130,17 @@ bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
if (requiredBytes == 0) if (requiredBytes == 0)
return false; return false;
if (mAsyncReadbackBytes == requiredBytes && mAsyncReadbackSlots[0].pixelPackBuffer != 0) if (mAsyncReadbackBytes == requiredBytes &&
mAsyncReadbackSlots.size() == mAsyncReadbackDepth &&
!mAsyncReadbackSlots.empty() &&
mAsyncReadbackSlots[0].pixelPackBuffer != 0)
{
return true; return true;
}
ResetAsyncReadbackState(); ResetAsyncReadbackState();
mAsyncReadbackBytes = requiredBytes; mAsyncReadbackBytes = requiredBytes;
mAsyncReadbackSlots.resize(mAsyncReadbackDepth);
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots) for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
{ {
glGenBuffers(1, &slot.pixelPackBuffer); glGenBuffers(1, &slot.pixelPackBuffer);
@@ -115,7 +161,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots) for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
slot.sizeBytes = 0; slot.sizeBytes = 0;
if (mAsyncReadbackSlots[0].pixelPackBuffer != 0) if (!mAsyncReadbackSlots.empty() && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
{ {
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots) for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
{ {
@@ -130,6 +176,7 @@ void OpenGLRenderPipeline::ResetAsyncReadbackState()
mAsyncReadbackWriteIndex = 0; mAsyncReadbackWriteIndex = 0;
mAsyncReadbackReadIndex = 0; mAsyncReadbackReadIndex = 0;
mAsyncReadbackBytes = 0; mAsyncReadbackBytes = 0;
mAsyncReadbackSlots.clear();
} }
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline() void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
@@ -148,18 +195,29 @@ void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
mAsyncReadbackReadIndex = 0; mAsyncReadbackReadIndex = 0;
} }
void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state) bool OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing)
{ {
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10; const auto queueStartTime = std::chrono::steady_clock::now();
const bool useTenBitPackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 ||
state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || useTenBitPackedOutput;
const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height; const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
const GLenum format = usePackedOutput ? GL_RGBA : GL_BGRA; const GLenum format = useTenBitPackedOutput ? GL_RGBA : GL_BGRA;
const GLenum type = usePackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV; const GLenum type = useTenBitPackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
const GLuint framebuffer = usePackedOutput ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer(); const GLuint framebuffer = usePackFramebuffer ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
const GLsizei readWidth = static_cast<GLsizei>(usePackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width); const GLsizei readWidth = static_cast<GLsizei>(useTenBitPackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height); const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
const auto finishTiming = [&timing, queueStartTime]() {
const auto queueEndTime = std::chrono::steady_clock::now();
timing.asyncQueueMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(queueEndTime - queueStartTime).count();
};
if (requiredBytes == 0) if (requiredBytes == 0)
return; {
finishTiming();
return false;
}
if (mAsyncReadbackBytes != requiredBytes if (mAsyncReadbackBytes != requiredBytes
|| mAsyncReadbackFormat != format || mAsyncReadbackFormat != format
@@ -170,30 +228,56 @@ void OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state)
mAsyncReadbackType = type; mAsyncReadbackType = type;
mAsyncReadbackFramebuffer = framebuffer; mAsyncReadbackFramebuffer = framebuffer;
if (!EnsureAsyncReadbackBuffers(requiredBytes)) if (!EnsureAsyncReadbackBuffers(requiredBytes))
return; {
finishTiming();
return false;
}
}
if (mAsyncReadbackSlots.empty())
{
finishTiming();
return false;
} }
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex]; AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
if (slot.fence != nullptr) if (slot.inFlight)
{ {
glDeleteSync(slot.fence); finishTiming();
slot.fence = nullptr; return false;
} }
auto stageStartTime = std::chrono::steady_clock::now();
glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer); glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
auto stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueSetupMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ); glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueBufferMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr); glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueReadPixelsMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueFenceMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
slot.inFlight = slot.fence != nullptr; slot.inFlight = slot.fence != nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size(); mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
finishTiming();
return slot.inFlight;
} }
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds) bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing)
{ {
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr) if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
return false; return false;
@@ -203,15 +287,24 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
return false; return false;
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0; const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
const auto waitStartTime = std::chrono::steady_clock::now();
const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds); const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds);
const auto waitEndTime = std::chrono::steady_clock::now();
timing.fenceWaitMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(waitEndTime - waitStartTime).count();
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED) if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
{
timing.asyncReadbackMissed = true;
return false; return false;
}
glDeleteSync(slot.fence); glDeleteSync(slot.fence);
slot.fence = nullptr; slot.fence = nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer); glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
const auto mapStartTime = std::chrono::steady_clock::now();
void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
const auto mapEndTime = std::chrono::steady_clock::now();
timing.mapMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(mapEndTime - mapStartTime).count();
if (mappedBytes == nullptr) if (mappedBytes == nullptr)
{ {
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -220,7 +313,10 @@ bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFra
return false; return false;
} }
const auto copyStartTime = std::chrono::steady_clock::now();
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes); std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
const auto copyEndTime = std::chrono::steady_clock::now();
timing.copyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -240,40 +336,144 @@ void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFram
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount); std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
} }
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes) bool OpenGLRenderPipeline::TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const
{ {
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
return false;
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
if (mCachedOutputFrame.size() != byteCount)
return false;
const auto copyStartTime = std::chrono::steady_clock::now();
std::memcpy(outputFrame.bytes, mCachedOutputFrame.data(), byteCount);
const auto copyEndTime = std::chrono::steady_clock::now();
timing.cachedCopyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
timing.cachedFallbackUsed = true;
return true;
}
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing)
{
const auto readStartTime = std::chrono::steady_clock::now();
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10; const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || usePackedOutput;
glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_ROW_LENGTH, 0);
if (usePackedOutput) if (usePackFramebuffer)
{ {
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
if (usePackedOutput)
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes); glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
else
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
} }
else else
{ {
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer()); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes); glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
} }
const auto readEndTime = std::chrono::steady_clock::now();
timing.syncReadMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(readEndTime - readStartTime).count();
timing.syncFallbackUsed = true;
} }
void OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame) OpenGLRenderPipeline::OutputReadbackTiming OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
{ {
if (TryConsumeAsyncReadback(outputFrame, 500000)) OutputReadbackTiming timing;
{
QueueAsyncReadback(state);
return;
}
// If async readback misses the playout deadline, prefer a fresh synchronous if (mOutputReadbackMode == OutputReadbackMode::Synchronous)
// frame over reusing stale cached output, then restart the async pipeline. {
if (outputFrame.bytes != nullptr) if (outputFrame.bytes != nullptr)
{ {
ReadOutputFrameSynchronously(state, outputFrame.bytes); ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame);
}
return timing;
}
if (mOutputReadbackMode == OutputReadbackMode::CachedOnly)
{
if (TryCopyCachedOutputFrame(outputFrame, timing))
return timing;
if (outputFrame.bytes != nullptr)
{
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame);
}
return timing;
}
if (TryConsumeAsyncReadback(outputFrame, 0, timing))
{
(void)QueueAsyncReadback(state, timing);
return timing;
}
const bool queued = QueueAsyncReadback(state, timing);
if (queued && TryConsumeAsyncReadback(outputFrame, 0, timing))
return timing;
if (TryCopyCachedOutputFrame(outputFrame, timing))
{
return timing;
}
// Bootstrap only: until the first async readback has produced cached output,
// use one synchronous readback so DeckLink has a valid frame to schedule.
if (outputFrame.bytes != nullptr && mCachedOutputFrame.empty())
{
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame); CacheOutputFrame(outputFrame);
} }
FlushAsyncReadbackPipeline(); if (!queued)
QueueAsyncReadback(state); (void)QueueAsyncReadback(state, timing);
return timing;
}
OpenGLRenderPipeline::OutputReadbackMode OpenGLRenderPipeline::ReadOutputReadbackModeFromEnvironment()
{
char* mode = nullptr;
std::size_t modeSize = 0;
if (_dupenv_s(&mode, &modeSize, "VST_OUTPUT_READBACK_MODE") != 0 || mode == nullptr)
return OutputReadbackMode::AsyncPbo;
const std::string modeValue(mode);
std::free(mode);
if (modeValue == "async_pbo")
return OutputReadbackMode::AsyncPbo;
if (modeValue == "sync")
return OutputReadbackMode::Synchronous;
if (modeValue == "cached_only")
return OutputReadbackMode::CachedOnly;
return OutputReadbackMode::AsyncPbo;
}
std::size_t OpenGLRenderPipeline::ReadAsyncReadbackDepthFromEnvironment()
{
char* depthValue = nullptr;
std::size_t depthValueSize = 0;
if (_dupenv_s(&depthValue, &depthValueSize, "VST_OUTPUT_READBACK_DEPTH") != 0 || depthValue == nullptr)
return 6;
const std::string value(depthValue);
std::free(depthValue);
try
{
const unsigned long requestedDepth = std::stoul(value);
if (requestedDepth < 3)
return 3;
if (requestedDepth > 12)
return 12;
return static_cast<std::size_t>(requestedDepth);
}
catch (...)
{
return 6;
}
} }

View File

@@ -3,12 +3,12 @@
#include "GLExtensions.h" #include "GLExtensions.h"
#include "VideoIOTypes.h" #include "VideoIOTypes.h"
#include <array>
#include <functional> #include <functional>
#include <vector> #include <vector>
class OpenGLRenderer; class OpenGLRenderer;
class RuntimeHost; class HealthTelemetry;
class RuntimeSnapshotProvider;
struct RenderPipelineFrameContext struct RenderPipelineFrameContext
{ {
@@ -25,7 +25,8 @@ public:
OpenGLRenderPipeline( OpenGLRenderPipeline(
OpenGLRenderer& renderer, OpenGLRenderer& renderer,
RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
RenderEffectCallback renderEffect, RenderEffectCallback renderEffect,
OutputReadyCallback outputReady, OutputReadyCallback outputReady,
PaintCallback paint); PaintCallback paint);
@@ -34,6 +35,13 @@ public:
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
private: private:
enum class OutputReadbackMode
{
AsyncPbo,
Synchronous,
CachedOnly
};
struct AsyncReadbackSlot struct AsyncReadbackSlot
{ {
GLuint pixelPackBuffer = 0; GLuint pixelPackBuffer = 0;
@@ -42,22 +50,46 @@ private:
bool inFlight = false; bool inFlight = false;
}; };
struct OutputReadbackTiming
{
double fenceWaitMilliseconds = 0.0;
double mapMilliseconds = 0.0;
double copyMilliseconds = 0.0;
double cachedCopyMilliseconds = 0.0;
double asyncQueueMilliseconds = 0.0;
double asyncQueueBufferMilliseconds = 0.0;
double asyncQueueSetupMilliseconds = 0.0;
double asyncQueueReadPixelsMilliseconds = 0.0;
double asyncQueueFenceMilliseconds = 0.0;
double syncReadMilliseconds = 0.0;
bool asyncReadbackMissed = false;
bool cachedFallbackUsed = false;
bool syncFallbackUsed = false;
};
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes); bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
void ResetAsyncReadbackState(); void ResetAsyncReadbackState();
void FlushAsyncReadbackPipeline(); void FlushAsyncReadbackPipeline();
void QueueAsyncReadback(const VideoIOState& state); bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds); bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame); void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes); bool TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const;
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing);
void PackOutputForBgra8(const VideoIOState& state);
void PackOutputFor10Bit(const VideoIOState& state); void PackOutputFor10Bit(const VideoIOState& state);
void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame); OutputReadbackTiming ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
static OutputReadbackMode ReadOutputReadbackModeFromEnvironment();
static std::size_t ReadAsyncReadbackDepthFromEnvironment();
OpenGLRenderer& mRenderer; OpenGLRenderer& mRenderer;
RuntimeHost& mRuntimeHost; RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
HealthTelemetry& mHealthTelemetry;
RenderEffectCallback mRenderEffect; RenderEffectCallback mRenderEffect;
OutputReadyCallback mOutputReady; OutputReadyCallback mOutputReady;
PaintCallback mPaint; PaintCallback mPaint;
std::array<AsyncReadbackSlot, 3> mAsyncReadbackSlots; OutputReadbackMode mOutputReadbackMode = OutputReadbackMode::AsyncPbo;
std::vector<AsyncReadbackSlot> mAsyncReadbackSlots;
std::size_t mAsyncReadbackDepth = 0;
std::size_t mAsyncReadbackWriteIndex = 0; std::size_t mAsyncReadbackWriteIndex = 0;
std::size_t mAsyncReadbackReadIndex = 0; std::size_t mAsyncReadbackReadIndex = 0;
std::size_t mAsyncReadbackBytes = 0; std::size_t mAsyncReadbackBytes = 0;

View File

@@ -1,135 +1,25 @@
#include "OpenGLVideoIOBridge.h" #include "OpenGLVideoIOBridge.h"
#include "OpenGLRenderer.h" #include "RenderEngine.h"
#include "RuntimeHost.h"
#include <chrono> OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
#include <gl/gl.h> mRenderEngine(renderEngine)
OpenGLVideoIOBridge::OpenGLVideoIOBridge(
VideoIODevice* videoIO,
OpenGLRenderer& renderer,
OpenGLRenderPipeline& renderPipeline,
RuntimeHost& runtimeHost,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc) :
mVideoIO(videoIO),
mRenderer(renderer),
mRenderPipeline(renderPipeline),
mRuntimeHost(runtimeHost),
mMutex(mutex),
mHdc(hdc),
mHglrc(hglrc)
{ {
} }
void OpenGLVideoIOBridge::SetVideoIODevice(VideoIODevice* videoIO) void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state)
{ {
mVideoIO = videoIO;
}
void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult)
{
const auto now = std::chrono::steady_clock::now();
if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point())
{
mCompletionIntervalMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(now - mLastPlayoutCompletionTime).count();
if (mSmoothedCompletionIntervalMilliseconds <= 0.0)
mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
else
mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1;
if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds)
mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
}
mLastPlayoutCompletionTime = now;
if (completionResult == VideoIOCompletionResult::DisplayedLate)
++mLateFrameCount;
else if (completionResult == VideoIOCompletionResult::Dropped)
++mDroppedFrameCount;
else if (completionResult == VideoIOCompletionResult::Flushed)
++mFlushedFrameCount;
mRuntimeHost.TrySetFramePacingStats(
mCompletionIntervalMilliseconds,
mSmoothedCompletionIntervalMilliseconds,
mMaxCompletionIntervalMilliseconds,
mLateFrameCount,
mDroppedFrameCount,
mFlushedFrameCount);
}
void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
{
if (mVideoIO == nullptr)
return;
const VideoIOState& state = mVideoIO->State();
mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
return; // don't transfer texture when there's no input return; // don't transfer texture when there's no input
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height); mRenderEngine.QueueInputFrame(inputFrame, state);
EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data.
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
} }
void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion) bool OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame)
{ {
if (mVideoIO == nullptr)
return;
RecordFramePacing(completion.result);
EnterCriticalSection(&mMutex);
VideoIOOutputFrame outputFrame;
if (!mVideoIO->BeginOutputFrame(outputFrame))
{
LeaveCriticalSection(&mMutex);
return;
}
const VideoIOState& state = mVideoIO->State();
RenderPipelineFrameContext frameContext; RenderPipelineFrameContext frameContext;
frameContext.videoState = state; frameContext.videoState = state;
frameContext.completion = completion; frameContext.completion = completion;
// make GL context current in this thread return mRenderEngine.RequestOutputFrame(frameContext, outputFrame);
wglMakeCurrent(mHdc, mHglrc);
mRenderPipeline.RenderFrame(frameContext, outputFrame);
mVideoIO->EndOutputFrame(outputFrame);
mVideoIO->AccountForCompletionResult(completion.result);
// Schedule the next frame for playout
mVideoIO->ScheduleOutputFrame(outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
} }

View File

@@ -2,45 +2,16 @@
#include "OpenGLRenderPipeline.h" #include "OpenGLRenderPipeline.h"
#include <windows.h> class RenderEngine;
#include <chrono>
#include <cstdint>
class RuntimeHost;
class OpenGLVideoIOBridge class OpenGLVideoIOBridge
{ {
public: public:
OpenGLVideoIOBridge( explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
VideoIODevice* videoIO,
OpenGLRenderer& renderer,
OpenGLRenderPipeline& renderPipeline,
RuntimeHost& runtimeHost,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc);
void SetVideoIODevice(VideoIODevice* videoIO); void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
bool RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
void VideoFrameArrived(const VideoIOFrame& inputFrame);
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
private: private:
void RecordFramePacing(VideoIOCompletionResult completionResult); RenderEngine& mRenderEngine;
VideoIODevice* mVideoIO;
OpenGLRenderer& mRenderer;
OpenGLRenderPipeline& mRenderPipeline;
RuntimeHost& mRuntimeHost;
CRITICAL_SECTION& mMutex;
HDC mHdc;
HGLRC mHglrc;
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
double mCompletionIntervalMilliseconds = 0.0;
double mSmoothedCompletionIntervalMilliseconds = 0.0;
double mMaxCompletionIntervalMilliseconds = 0.0;
uint64_t mLateFrameCount = 0;
uint64_t mDroppedFrameCount = 0;
uint64_t mFlushedFrameCount = 0;
}; };

View File

@@ -28,6 +28,7 @@ struct RenderPassDescriptor
std::string passId; std::string passId;
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
GLuint layerInputTexture = 0;
GLuint sourceTexture = 0; GLuint sourceTexture = 0;
GLuint sourceFramebuffer = 0; GLuint sourceFramebuffer = 0;
GLuint destinationTexture = 0; GLuint destinationTexture = 0;
@@ -36,4 +37,5 @@ struct RenderPassDescriptor
OpenGLRenderer::LayerProgram::PassProgram* passProgram = nullptr; OpenGLRenderer::LayerProgram::PassProgram* passProgram = nullptr;
const RuntimeRenderState* layerState = nullptr; const RuntimeRenderState* layerState = nullptr;
bool capturePreLayerHistory = false; bool capturePreLayerHistory = false;
bool captureFeedbackWrite = false;
}; };

View File

@@ -0,0 +1,202 @@
#include "ShaderFeedbackBuffers.h"
#include <set>
namespace
{
void ConfigureFeedbackTexture(unsigned frameWidth, unsigned frameHeight)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
}
}
bool ShaderFeedbackBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
if (!EnsureZeroTexture())
{
error = "Failed to initialize shader feedback fallback texture.";
return false;
}
std::set<std::string> requiredLayerIds;
for (const RuntimeRenderState& state : layerStates)
{
if (!state.feedback.enabled)
continue;
requiredLayerIds.insert(state.layerId);
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
if (surfaceIt == mSurfacesByLayerId.end() ||
surfaceIt->second.width != frameWidth ||
surfaceIt->second.height != frameHeight)
{
Surface replacement;
if (!CreateSurface(replacement, frameWidth, frameHeight, error))
return false;
mSurfacesByLayerId[state.layerId] = std::move(replacement);
}
}
for (auto it = mSurfacesByLayerId.begin(); it != mSurfacesByLayerId.end();)
{
if (requiredLayerIds.find(it->first) == requiredLayerIds.end())
{
DestroySurface(it->second);
it = mSurfacesByLayerId.erase(it);
}
else
{
++it;
}
}
return true;
}
void ShaderFeedbackBuffers::DestroyResources()
{
for (auto& entry : mSurfacesByLayerId)
DestroySurface(entry.second);
mSurfacesByLayerId.clear();
if (mZeroTexture != 0)
{
glDeleteTextures(1, &mZeroTexture);
mZeroTexture = 0;
}
}
void ShaderFeedbackBuffers::ResetState()
{
for (auto& entry : mSurfacesByLayerId)
ClearSurfaceState(entry.second);
}
GLuint ShaderFeedbackBuffers::ResolveReadTexture(const RuntimeRenderState& state) const
{
if (!state.feedback.enabled)
return mZeroTexture;
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
if (surfaceIt == mSurfacesByLayerId.end() || !surfaceIt->second.hasData)
return mZeroTexture;
return surfaceIt->second.slots[surfaceIt->second.readIndex].texture != 0
? surfaceIt->second.slots[surfaceIt->second.readIndex].texture
: mZeroTexture;
}
bool ShaderFeedbackBuffers::FeedbackAvailable(const RuntimeRenderState& state) const
{
if (!state.feedback.enabled)
return false;
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
return surfaceIt != mSurfacesByLayerId.end() && surfaceIt->second.hasData;
}
void ShaderFeedbackBuffers::CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
{
auto surfaceIt = mSurfacesByLayerId.find(layerId);
if (surfaceIt == mSurfacesByLayerId.end())
return;
Surface& surface = surfaceIt->second;
const unsigned writeIndex = 1u - surface.readIndex;
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, surface.slots[writeIndex].framebuffer);
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
surface.pendingWrite = true;
}
void ShaderFeedbackBuffers::FinalizeFrame()
{
for (auto& entry : mSurfacesByLayerId)
{
Surface& surface = entry.second;
if (!surface.pendingWrite)
continue;
surface.readIndex = 1u - surface.readIndex;
surface.hasData = true;
surface.pendingWrite = false;
}
}
bool ShaderFeedbackBuffers::EnsureZeroTexture()
{
if (mZeroTexture != 0)
return true;
glGenTextures(1, &mZeroTexture);
glBindTexture(GL_TEXTURE_2D, mZeroTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
const float zeroPixel[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, GL_FLOAT, zeroPixel);
glBindTexture(GL_TEXTURE_2D, 0);
return mZeroTexture != 0;
}
bool ShaderFeedbackBuffers::CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
DestroySurface(surface);
surface.width = frameWidth;
surface.height = frameHeight;
for (Slot& slot : surface.slots)
{
glGenTextures(1, &slot.texture);
glBindTexture(GL_TEXTURE_2D, slot.texture);
ConfigureFeedbackTexture(frameWidth, frameHeight);
glGenFramebuffers(1, &slot.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
error = "Failed to initialize a shader feedback framebuffer.";
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
DestroySurface(surface);
return false;
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
ClearSurfaceState(surface);
return true;
}
void ShaderFeedbackBuffers::DestroySurface(Surface& surface)
{
for (Slot& slot : surface.slots)
{
if (slot.framebuffer != 0)
glDeleteFramebuffers(1, &slot.framebuffer);
if (slot.texture != 0)
glDeleteTextures(1, &slot.texture);
slot.framebuffer = 0;
slot.texture = 0;
}
surface.width = 0;
surface.height = 0;
surface.readIndex = 0;
surface.hasData = false;
surface.pendingWrite = false;
}
void ShaderFeedbackBuffers::ClearSurfaceState(Surface& surface)
{
surface.readIndex = 0;
surface.hasData = false;
surface.pendingWrite = false;
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include "GLExtensions.h"
#include "ShaderTypes.h"
#include <map>
#include <string>
#include <vector>
class ShaderFeedbackBuffers
{
public:
struct Slot
{
GLuint texture = 0;
GLuint framebuffer = 0;
};
struct Surface
{
Slot slots[2];
unsigned width = 0;
unsigned height = 0;
unsigned readIndex = 0;
bool hasData = false;
bool pendingWrite = false;
};
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error);
void DestroyResources();
void ResetState();
GLuint ResolveReadTexture(const RuntimeRenderState& state) const;
bool FeedbackAvailable(const RuntimeRenderState& state) const;
void CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
void FinalizeFrame();
private:
bool EnsureZeroTexture();
bool CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error);
void DestroySurface(Surface& surface);
void ClearSurfaceState(Surface& surface);
private:
std::map<std::string, Surface> mSurfacesByLayerId;
GLuint mZeroTexture = 0;
};

View File

@@ -19,7 +19,8 @@ bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector<Runtime
++textTextureCount; ++textTextureCount;
} }
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount; const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures; const unsigned feedbackTextureCount = state.feedback.enabled ? 1u : 0u;
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + feedbackTextureCount + totalShaderTextures;
if (layerRequiredUnits > requiredUnits) if (layerRequiredUnits > requiredUnits)
requiredUnits = layerRequiredUnits; requiredUnits = layerRequiredUnits;
} }

View File

@@ -2,8 +2,8 @@
#include <gl/gl.h> #include <gl/gl.h>
constexpr GLuint kLayerInputTextureUnit = 0;
constexpr GLuint kDecodedVideoTextureUnit = 1; constexpr GLuint kDecodedVideoTextureUnit = 1;
constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kSourceHistoryTextureUnitBase = 2;
constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kPackedVideoTextureUnit = 2;
constexpr GLuint kGlobalParamsBindingPoint = 0; constexpr GLuint kGlobalParamsBindingPoint = 0;
constexpr unsigned kPrerollFrameCount = 8;

View File

@@ -63,6 +63,7 @@ bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inpu
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO); glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
mResourcesInitialized = true;
return true; return true;
} }
@@ -157,8 +158,10 @@ void OpenGLRenderer::DestroyResources()
mCaptureTexture = 0; mCaptureTexture = 0;
mTextureUploadBuffer = 0; mTextureUploadBuffer = 0;
mGlobalParamsUBOSize = 0; mGlobalParamsUBOSize = 0;
mResourcesInitialized = false;
mTemporalHistory.DestroyResources(); mTemporalHistory.DestroyResources();
mFeedbackBuffers.DestroyResources();
DestroyLayerPrograms(); DestroyLayerPrograms();
DestroyDecodeShaderProgram(); DestroyDecodeShaderProgram();
DestroyOutputPackShaderProgram(); DestroyOutputPackShaderProgram();

View File

@@ -2,6 +2,7 @@
#include "GLExtensions.h" #include "GLExtensions.h"
#include "RenderTargetPool.h" #include "RenderTargetPool.h"
#include "ShaderFeedbackBuffers.h"
#include "ShaderTypes.h" #include "ShaderTypes.h"
#include "TemporalHistoryBuffers.h" #include "TemporalHistoryBuffers.h"
@@ -78,6 +79,7 @@ public:
GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; } GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; }
GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; } GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; } void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
bool ResourcesInitialized() const { return mResourcesInitialized; }
void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); } void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; } std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; } const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
@@ -86,6 +88,8 @@ public:
std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); } std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); }
TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; } TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; } const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
ShaderFeedbackBuffers& FeedbackBuffers() { return mFeedbackBuffers; }
const ShaderFeedbackBuffers& FeedbackBuffers() const { return mFeedbackBuffers; }
void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader); void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader); void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error); bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
@@ -117,9 +121,11 @@ private:
GLint mOutputPackActiveWordsLocation = -1; GLint mOutputPackActiveWordsLocation = -1;
GLint mOutputPackFormatLocation = -1; GLint mOutputPackFormatLocation = -1;
GLsizeiptr mGlobalParamsUBOSize = 0; GLsizeiptr mGlobalParamsUBOSize = 0;
bool mResourcesInitialized = false;
int mViewWidth = 0; int mViewWidth = 0;
int mViewHeight = 0; int mViewHeight = 0;
std::vector<LayerProgram> mLayerPrograms; std::vector<LayerProgram> mLayerPrograms;
RenderTargetPool mRenderTargets; RenderTargetPool mRenderTargets;
TemporalHistoryBuffers mTemporalHistory; TemporalHistoryBuffers mTemporalHistory;
ShaderFeedbackBuffers mFeedbackBuffers;
}; };

View File

@@ -10,7 +10,7 @@ GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
{ {
} }
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength) bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{ {
std::vector<unsigned char>& buffer = mScratchBuffer; std::vector<unsigned char>& buffer = mScratchBuffer;
buffer.clear(); buffer.clear();
@@ -33,6 +33,7 @@ bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availa
: 0u; : 0u;
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength)); AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength)); AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
AppendStd140Int(buffer, feedbackAvailable ? 1 : 0);
for (const ShaderParameterDefinition& definition : state.parameterDefinitions) for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{ {

View File

@@ -10,7 +10,7 @@ class GlobalParamsBuffer
public: public:
explicit GlobalParamsBuffer(OpenGLRenderer& renderer); explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength); bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
private: private:
OpenGLRenderer& mRenderer; OpenGLRenderer& mRenderer;

View File

@@ -29,19 +29,21 @@ std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::Lay
} }
} }
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost) : OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRenderer(renderer), mRenderer(renderer),
mRuntimeHost(runtimeHost), mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mGlobalParamsBuffer(renderer), mGlobalParamsBuffer(renderer),
mCompiler(renderer, runtimeHost, mTextureBindings) mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
{ {
} }
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{ {
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost.GetLayerRenderStates(inputFrameWidth, inputFrameHeight); const RuntimeRenderStateSnapshot renderSnapshot =
mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
std::string temporalError; std::string temporalError;
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError)) if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
{ {
CopyErrorMessage(temporalError, errorMessageSize, errorMessage); CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
@@ -52,6 +54,12 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
CopyErrorMessage(temporalError, errorMessageSize, errorMessage); CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false; return false;
} }
if (mRenderer.ResourcesInitialized() &&
!mRenderer.FeedbackBuffers().EnsureResources(layerStates, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
// Initial startup still compiles synchronously; auto-reload uses the build // Initial startup still compiles synchronously; auto-reload uses the build
// queue so Slang/file work stays off the playback path. // queue so Slang/file work stays off the playback path.
@@ -81,10 +89,7 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
DestroyLayerPrograms(); DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms); mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = layerStates; mCommittedLayerStates = renderSnapshot.states;
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
mRuntimeHost.ClearReloadRequest();
return true; return true;
} }
@@ -98,13 +103,19 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
} }
std::string temporalError; std::string temporalError;
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.layerStates, historyCap, temporalError)) if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
{ {
CopyErrorMessage(temporalError, errorMessageSize, errorMessage); CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false; return false;
} }
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError)) if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (mRenderer.ResourcesInitialized() &&
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
{ {
CopyErrorMessage(temporalError, errorMessageSize, errorMessage); CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false; return false;
@@ -138,10 +149,7 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
DestroyLayerPrograms(); DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms); mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = preparedBuild.layerStates; mCommittedLayerStates = preparedBuild.renderSnapshot.states;
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
mRuntimeHost.ClearReloadRequest();
return true; return true;
} }
@@ -176,12 +184,17 @@ void OpenGLShaderPrograms::ResetTemporalHistoryState()
mRenderer.TemporalHistory().ResetState(); mRenderer.TemporalHistory().ResetState();
} }
void OpenGLShaderPrograms::ResetShaderFeedbackState()
{
mRenderer.FeedbackBuffers().ResetState();
}
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
{ {
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error); return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
} }
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength) bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{ {
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength); return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
} }

View File

@@ -2,7 +2,7 @@
#include "GlobalParamsBuffer.h" #include "GlobalParamsBuffer.h"
#include "OpenGLRenderer.h" #include "OpenGLRenderer.h"
#include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h"
#include "ShaderBuildQueue.h" #include "ShaderBuildQueue.h"
#include "ShaderTypes.h" #include "ShaderTypes.h"
#include "ShaderProgramCompiler.h" #include "ShaderProgramCompiler.h"
@@ -15,7 +15,7 @@ class OpenGLShaderPrograms
public: public:
using LayerProgram = OpenGLRenderer::LayerProgram; using LayerProgram = OpenGLRenderer::LayerProgram;
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost); OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider);
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
@@ -25,13 +25,14 @@ public:
void DestroySingleLayerProgram(LayerProgram& layerProgram); void DestroySingleLayerProgram(LayerProgram& layerProgram);
void DestroyDecodeShaderProgram(); void DestroyDecodeShaderProgram();
void ResetTemporalHistoryState(); void ResetTemporalHistoryState();
void ResetShaderFeedbackState();
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; } const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error); bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength); bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
private: private:
OpenGLRenderer& mRenderer; OpenGLRenderer& mRenderer;
RuntimeHost& mRuntimeHost; RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
ShaderTextureBindings mTextureBindings; ShaderTextureBindings mTextureBindings;
GlobalParamsBuffer mGlobalParamsBuffer; GlobalParamsBuffer mGlobalParamsBuffer;
ShaderProgramCompiler mCompiler; ShaderProgramCompiler mCompiler;

View File

@@ -1,6 +1,6 @@
#include "ShaderBuildQueue.h" #include "ShaderBuildQueue.h"
#include "RuntimeHost.h" #include "RuntimeEventDispatcher.h"
#include <chrono> #include <chrono>
#include <utility> #include <utility>
@@ -10,8 +10,9 @@ namespace
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400); constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
} }
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) : ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) :
mRuntimeHost(runtimeHost), mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mWorkerThread([this]() { WorkerLoop(); }) mWorkerThread([this]() { WorkerLoop(); })
{ {
} }
@@ -46,6 +47,18 @@ bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
return true; return true;
} }
bool ShaderBuildQueue::TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasReadyBuild || mReadyBuild.generation != expectedGeneration)
return false;
build = std::move(mReadyBuild);
mReadyBuild = PreparedShaderBuild();
mHasReadyBuild = false;
return true;
}
void ShaderBuildQueue::Stop() void ShaderBuildQueue::Stop()
{ {
{ {
@@ -99,13 +112,20 @@ void ShaderBuildQueue::WorkerLoop()
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight); PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
bool shouldPublish = false;
{
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
if (mStopping) if (mStopping)
return; return;
if (generation != mRequestedGeneration) if (generation != mRequestedGeneration)
continue; continue;
mReadyBuild = std::move(build); mReadyBuild = build;
mHasReadyBuild = true; mHasReadyBuild = true;
shouldPublish = true;
}
if (shouldPublish)
PublishBuildLifecycleEvent(build, outputWidth, outputHeight);
} }
} }
@@ -113,14 +133,14 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
{ {
PreparedShaderBuild build; PreparedShaderBuild build;
build.generation = generation; build.generation = generation;
build.layerStates = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight); build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight);
build.layers.reserve(build.layerStates.size()); build.layers.reserve(build.renderSnapshot.states.size());
for (const RuntimeRenderState& state : build.layerStates) for (const RuntimeRenderState& state : build.renderSnapshot.states)
{ {
PreparedLayerShader layer; PreparedLayerShader layer;
layer.state = state; layer.state = state;
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message)) if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
{ {
build.succeeded = false; build.succeeded = false;
return build; return build;
@@ -132,3 +152,15 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
build.message = "Shader layers prepared successfully."; build.message = "Shader layers prepared successfully.";
return build; return build;
} }
void ShaderBuildQueue::PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const
{
ShaderBuildEvent event;
event.phase = build.succeeded ? RuntimeEventShaderBuildPhase::Prepared : RuntimeEventShaderBuildPhase::Failed;
event.generation = build.generation;
event.inputWidth = outputWidth;
event.inputHeight = outputHeight;
event.succeeded = build.succeeded;
event.message = build.message;
mRuntimeEventDispatcher.PublishPayload(event, "ShaderBuildQueue");
}

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "RuntimeSnapshotProvider.h"
#include "ShaderTypes.h" #include "ShaderTypes.h"
#include <condition_variable> #include <condition_variable>
@@ -9,7 +10,7 @@
#include <thread> #include <thread>
#include <vector> #include <vector>
class RuntimeHost; class RuntimeEventDispatcher;
struct PreparedLayerShader struct PreparedLayerShader
{ {
@@ -22,14 +23,14 @@ struct PreparedShaderBuild
uint64_t generation = 0; uint64_t generation = 0;
bool succeeded = false; bool succeeded = false;
std::string message; std::string message;
std::vector<RuntimeRenderState> layerStates; RuntimeRenderStateSnapshot renderSnapshot;
std::vector<PreparedLayerShader> layers; std::vector<PreparedLayerShader> layers;
}; };
class ShaderBuildQueue class ShaderBuildQueue
{ {
public: public:
explicit ShaderBuildQueue(RuntimeHost& runtimeHost); ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher);
~ShaderBuildQueue(); ~ShaderBuildQueue();
ShaderBuildQueue(const ShaderBuildQueue&) = delete; ShaderBuildQueue(const ShaderBuildQueue&) = delete;
@@ -37,13 +38,16 @@ public:
void RequestBuild(unsigned outputWidth, unsigned outputHeight); void RequestBuild(unsigned outputWidth, unsigned outputHeight);
bool TryConsumeReadyBuild(PreparedShaderBuild& build); bool TryConsumeReadyBuild(PreparedShaderBuild& build);
bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build);
void Stop(); void Stop();
private: private:
void WorkerLoop(); void WorkerLoop();
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight); PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
void PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const;
RuntimeHost& mRuntimeHost; RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
std::thread mWorkerThread; std::thread mWorkerThread;
std::mutex mMutex; std::mutex mMutex;
std::condition_variable mCondition; std::condition_variable mCondition;

View File

@@ -19,9 +19,9 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er
} }
} }
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings) : ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) :
mRenderer(renderer), mRenderer(renderer),
mRuntimeHost(runtimeHost), mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mTextureBindings(textureBindings) mTextureBindings(textureBindings)
{ {
} }
@@ -31,7 +31,7 @@ bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state,
std::vector<ShaderPassBuildSource> passSources; std::vector<ShaderPassBuildSource> passSources;
std::string loadError; std::string loadError;
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError)) if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
{ {
CopyErrorMessage(loadError, errorMessageSize, errorMessage); CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false; return false;
@@ -117,7 +117,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
passProgram.passId = passSource.passId; passProgram.passId = passSource.passId;
passProgram.inputNames = passSource.inputNames; passProgram.inputNames = passSource.inputNames;
passProgram.outputName = passSource.outputName; passProgram.outputName = passSource.outputName;
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeHost.GetMaxTemporalHistoryFrames()); passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
passProgram.textureBindings.swap(textureBindings); passProgram.textureBindings.swap(textureBindings);
passProgram.textBindings.swap(textBindings); passProgram.textBindings.swap(textBindings);
@@ -125,7 +125,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState
if (globalParamsIndex != GL_INVALID_INDEX) if (globalParamsIndex != GL_INVALID_INDEX)
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint); glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
glUseProgram(newProgram.get()); glUseProgram(newProgram.get());
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap); mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
glUseProgram(0); glUseProgram(0);

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "OpenGLRenderer.h" #include "OpenGLRenderer.h"
#include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h"
#include "ShaderTextureBindings.h" #include "ShaderTextureBindings.h"
#include <string> #include <string>
@@ -13,7 +13,7 @@ public:
using LayerProgram = OpenGLRenderer::LayerProgram; using LayerProgram = OpenGLRenderer::LayerProgram;
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram; using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings); ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings);
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage); bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage); bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
@@ -22,6 +22,6 @@ public:
private: private:
OpenGLRenderer& mRenderer; OpenGLRenderer& mRenderer;
RuntimeHost& mRuntimeHost; RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
ShaderTextureBindings& mTextureBindings; ShaderTextureBindings& mTextureBindings;
}; };

View File

@@ -103,16 +103,25 @@ GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const st
return glGetUniformLocation(program, (samplerName + "_0").c_str()); return glGetUniformLocation(program, (samplerName + "_0").c_str());
} }
GLuint ShaderTextureBindings::ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const GLuint ShaderTextureBindings::ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const
{ {
return state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase; return state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
} }
GLuint ShaderTextureBindings::ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const
{
return ResolveFeedbackTextureUnit(state, historyCap) + (state.feedback.enabled ? 1u : 0u);
}
void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const
{ {
const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap); const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap);
const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput"); const GLint layerInputLocation = FindSamplerUniformLocation(program, "gLayerInput");
if (layerInputLocation >= 0)
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
const GLint videoInputLocation = FindSamplerUniformLocation(program, "gVideoInput");
if (videoInputLocation >= 0) if (videoInputLocation >= 0)
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit)); glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
@@ -129,6 +138,13 @@ void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const Run
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index)); glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
} }
if (state.feedback.enabled)
{
const GLint feedbackSamplerLocation = FindSamplerUniformLocation(program, "gFeedbackState");
if (feedbackSamplerLocation >= 0)
glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap)));
}
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index) for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
{ {
const GLint textureSamplerLocation = FindSamplerUniformLocation(program, passProgram.textureBindings[index].samplerName); const GLint textureSamplerLocation = FindSamplerUniformLocation(program, passProgram.textureBindings[index].samplerName);
@@ -148,10 +164,14 @@ void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const Run
ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan( ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram, const PassProgram& passProgram,
GLuint layerInputTexture, GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state,
GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures, const std::vector<GLuint>& sourceHistoryTextures,
const std::vector<GLuint>& temporalHistoryTextures) const const std::vector<GLuint>& temporalHistoryTextures) const
{ {
RuntimeTextureBindingPlan plan; RuntimeTextureBindingPlan plan;
plan.bindings.push_back({ "originalLayerInput", "gLayerInput", originalLayerInputTexture, kLayerInputTextureUnit });
plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit }); plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit });
for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index) for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index)
@@ -175,7 +195,20 @@ ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLay
}); });
} }
const GLuint shaderTextureBase = passProgram.shaderTextureBase != 0 ? passProgram.shaderTextureBase : kSourceHistoryTextureUnitBase; const GLuint feedbackTextureUnit = ResolveFeedbackTextureUnit(state, static_cast<unsigned>(sourceHistoryTextures.size()));
if (state.feedback.enabled)
{
plan.bindings.push_back({
"feedbackState",
"gFeedbackState",
feedbackTexture,
feedbackTextureUnit
});
}
const GLuint shaderTextureBase = passProgram.shaderTextureBase != 0
? passProgram.shaderTextureBase
: feedbackTextureUnit + (state.feedback.enabled ? 1u : 0u);
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index) for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
{ {
const LayerProgram::TextureBinding& textureBinding = passProgram.textureBindings[index]; const LayerProgram::TextureBinding& textureBinding = passProgram.textureBindings[index];

View File

@@ -29,11 +29,15 @@ public:
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings); void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error); bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const; GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
GLuint ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const;
GLuint ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const; GLuint ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const;
void AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const; void AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const;
RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan( RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram, const PassProgram& passProgram,
GLuint layerInputTexture, GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state,
GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures, const std::vector<GLuint>& sourceHistoryTextures,
const std::vector<GLuint>& temporalHistoryTextures) const; const std::vector<GLuint>& temporalHistoryTextures) const;
void BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const; void BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;

View File

@@ -0,0 +1,160 @@
#include "RenderCommandQueue.h"
void RenderCommandQueue::RequestPreviewPresent(const RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasPreviewPresentRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mPreviewPresentRequest = request;
mHasPreviewPresentRequest = true;
}
bool RenderCommandQueue::TryTakePreviewPresent(RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasPreviewPresentRequest)
return false;
request = mPreviewPresentRequest;
mPreviewPresentRequest = {};
mHasPreviewPresentRequest = false;
return true;
}
void RenderCommandQueue::RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasScreenshotCaptureRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mScreenshotCaptureRequest = request;
mHasScreenshotCaptureRequest = true;
}
bool RenderCommandQueue::TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasScreenshotCaptureRequest)
return false;
request = mScreenshotCaptureRequest;
mScreenshotCaptureRequest = {};
mHasScreenshotCaptureRequest = false;
return true;
}
void RenderCommandQueue::RequestInputUpload(const RenderInputUploadRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasInputUploadRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mInputUploadRequest = request;
mHasInputUploadRequest = true;
}
bool RenderCommandQueue::TryTakeInputUpload(RenderInputUploadRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasInputUploadRequest)
return false;
request = mInputUploadRequest;
mInputUploadRequest = {};
mHasInputUploadRequest = false;
return true;
}
void RenderCommandQueue::RequestOutputFrame(const RenderOutputFrameRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
mOutputFrameRequests.push_back(request);
++mEnqueuedCount;
}
bool RenderCommandQueue::TryTakeOutputFrame(RenderOutputFrameRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mOutputFrameRequests.empty())
return false;
request = mOutputFrameRequests.front();
mOutputFrameRequests.pop_front();
return true;
}
void RenderCommandQueue::RequestRenderReset(RenderCommandResetScope scope)
{
if (scope == RenderCommandResetScope::None)
return;
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope != RenderCommandResetScope::None)
++mCoalescedCount;
else
++mEnqueuedCount;
mRenderResetScope = MergeResetScopes(mRenderResetScope, scope);
}
bool RenderCommandQueue::TryTakeRenderReset(RenderCommandResetScope& scope)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope == RenderCommandResetScope::None)
return false;
scope = mRenderResetScope;
mRenderResetScope = RenderCommandResetScope::None;
return true;
}
RenderCommandQueueMetrics RenderCommandQueue::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderCommandQueueMetrics metrics;
metrics.depth =
(mHasPreviewPresentRequest ? 1u : 0u) +
(mHasScreenshotCaptureRequest ? 1u : 0u) +
(mHasInputUploadRequest ? 1u : 0u) +
mOutputFrameRequests.size() +
(mRenderResetScope != RenderCommandResetScope::None ? 1u : 0u);
metrics.enqueuedCount = mEnqueuedCount;
metrics.coalescedCount = mCoalescedCount;
return metrics;
}
RenderCommandResetScope RenderCommandQueue::MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested)
{
if (current == RenderCommandResetScope::TemporalHistoryAndFeedback ||
requested == RenderCommandResetScope::TemporalHistoryAndFeedback)
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if ((current == RenderCommandResetScope::TemporalHistoryOnly && requested == RenderCommandResetScope::ShaderFeedbackOnly) ||
(current == RenderCommandResetScope::ShaderFeedbackOnly && requested == RenderCommandResetScope::TemporalHistoryOnly))
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if (current == RenderCommandResetScope::TemporalHistoryOnly ||
requested == RenderCommandResetScope::TemporalHistoryOnly)
{
return RenderCommandResetScope::TemporalHistoryOnly;
}
if (current == RenderCommandResetScope::ShaderFeedbackOnly ||
requested == RenderCommandResetScope::ShaderFeedbackOnly)
{
return RenderCommandResetScope::ShaderFeedbackOnly;
}
return RenderCommandResetScope::None;
}

View File

@@ -0,0 +1,85 @@
#pragma once
#include "VideoIOTypes.h"
#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <vector>
enum class RenderCommandResetScope
{
None,
ShaderFeedbackOnly,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
struct RenderPreviewPresentRequest
{
unsigned outputFrameWidth = 0;
unsigned outputFrameHeight = 0;
};
struct RenderScreenshotCaptureRequest
{
unsigned width = 0;
unsigned height = 0;
};
struct RenderInputUploadRequest
{
VideoIOFrame inputFrame;
VideoIOState videoState;
std::vector<unsigned char> ownedBytes;
};
struct RenderOutputFrameRequest
{
VideoIOState videoState;
VideoIOCompletion completion;
};
struct RenderCommandQueueMetrics
{
std::size_t depth = 0;
uint64_t enqueuedCount = 0;
uint64_t coalescedCount = 0;
};
class RenderCommandQueue
{
public:
void RequestPreviewPresent(const RenderPreviewPresentRequest& request);
bool TryTakePreviewPresent(RenderPreviewPresentRequest& request);
void RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request);
bool TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request);
void RequestInputUpload(const RenderInputUploadRequest& request);
bool TryTakeInputUpload(RenderInputUploadRequest& request);
void RequestOutputFrame(const RenderOutputFrameRequest& request);
bool TryTakeOutputFrame(RenderOutputFrameRequest& request);
void RequestRenderReset(RenderCommandResetScope scope);
bool TryTakeRenderReset(RenderCommandResetScope& scope);
RenderCommandQueueMetrics GetMetrics() const;
private:
static RenderCommandResetScope MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested);
mutable std::mutex mMutex;
bool mHasPreviewPresentRequest = false;
RenderPreviewPresentRequest mPreviewPresentRequest;
bool mHasScreenshotCaptureRequest = false;
RenderScreenshotCaptureRequest mScreenshotCaptureRequest;
bool mHasInputUploadRequest = false;
RenderInputUploadRequest mInputUploadRequest;
std::deque<RenderOutputFrameRequest> mOutputFrameRequests;
RenderCommandResetScope mRenderResetScope = RenderCommandResetScope::None;
uint64_t mEnqueuedCount = 0;
uint64_t mCoalescedCount = 0;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,181 +0,0 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderTypes.h"
#include "VideoIOTypes.h"
#include <atomic>
#include <chrono>
#include <filesystem>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
class RuntimeHost
{
public:
RuntimeHost();
bool Initialize(std::string& error);
bool PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
bool ManualReloadRequested();
void ClearReloadRequest();
bool AddLayer(const std::string& shaderId, std::string& error);
bool RemoveLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
bool UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
bool ResetLayerParameters(const std::string& layerId, std::string& error);
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
bool LoadStackPreset(const std::string& presetName, std::string& error);
void SetCompileStatus(bool succeeded, const std::string& message);
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void SetVideoIOStatus(const VideoIOState& state);
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
void AdvanceFrame();
bool TryAdvanceFrame();
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error);
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
std::string BuildStateJson() const;
uint64_t GetRenderStateVersion() const { return mRenderStateVersion.load(std::memory_order_relaxed); }
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
unsigned short GetServerPort() const { return mServerPort; }
unsigned short GetOscPort() const { return mConfig.oscPort; }
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
bool ExternalKeyingEnabled() const { return mConfig.videoIO.externalKeyingEnabled; }
VideoIOConfiguration GetVideoIOConfiguration() const { return mConfig.videoIO; }
void SetServerPort(unsigned short port);
bool AutoReloadEnabled() const { return mAutoReloadEnabled; }
private:
struct AppConfig
{
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
unsigned short oscPort = 9000;
bool autoReload = true;
unsigned maxTemporalHistoryFrames = 4;
VideoIOConfiguration videoIO;
};
struct VideoIOStatusSnapshot
{
VideoIOBackendId backendId = VideoIOBackendId::DeckLink;
std::string deviceName;
VideoIOCapabilities capabilities;
bool hasInputDevice = false;
bool hasInputSource = false;
std::string inputDisplayModeName = "1080p59.94";
std::string outputDisplayModeName = "1080p59.94";
bool externalKeyingRequested = false;
bool externalKeyingActive = false;
std::string statusMessage;
std::string formatStatusMessage;
};
struct LayerPersistentState
{
std::string id;
std::string shaderId;
bool bypass = false;
std::map<std::string, ShaderParameterValue> parameterValues;
};
struct PersistentState
{
std::vector<LayerPersistentState> layers;
};
bool LoadConfig(std::string& error);
bool LoadPersistentState(std::string& error);
bool SavePersistentState(std::string& error) const;
bool ScanShaderPackages(std::string& error);
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
bool ResolvePaths(std::string& error);
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
JsonValue BuildStateValue() const;
JsonValue SerializeLayerStackLocked() const;
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
void NormalizePersistentLayerIdsLocked();
std::vector<std::string> GetStackPresetNamesLocked() const;
std::string MakeSafePresetFileStem(const std::string& presetName) const;
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
LayerPersistentState* FindLayerById(const std::string& layerId);
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
std::string GenerateLayerId();
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void MarkRenderStateDirtyLocked();
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
private:
mutable std::mutex mMutex;
AppConfig mConfig;
PersistentState mPersistentState;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
std::filesystem::path mGeneratedGlslPath;
std::filesystem::path mPatchedGlslPath;
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::vector<ShaderPackageStatus> mPackageStatuses;
bool mReloadRequested;
bool mCompileSucceeded;
std::string mCompileMessage;
bool mHasSignal;
unsigned mSignalWidth;
unsigned mSignalHeight;
std::string mSignalModeName;
double mFrameBudgetMilliseconds;
double mRenderMilliseconds;
double mSmoothedRenderMilliseconds;
double mCompletionIntervalMilliseconds;
double mSmoothedCompletionIntervalMilliseconds;
double mMaxCompletionIntervalMilliseconds;
double mStartupRandom;
uint64_t mLateFrameCount;
uint64_t mDroppedFrameCount;
uint64_t mFlushedFrameCount;
VideoIOStatusSnapshot mVideoIOStatus;
unsigned short mServerPort;
bool mAutoReloadEnabled;
std::chrono::steady_clock::time_point mStartTime;
std::chrono::steady_clock::time_point mLastScanTime;
std::atomic<uint64_t> mFrameCounter{ 0 };
std::atomic<uint64_t> mRenderStateVersion{ 0 };
uint64_t mNextLayerId;
};

View File

@@ -0,0 +1,613 @@
#include "RuntimeCoordinator.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeEventPayloads.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStore.h"
namespace
{
RuntimeEventRenderResetScope ToRuntimeEventRenderResetScope(RuntimeCoordinatorRenderResetScope scope)
{
switch (scope)
{
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
return RuntimeEventRenderResetScope::TemporalHistoryOnly;
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
return RuntimeEventRenderResetScope::TemporalHistoryAndFeedback;
case RuntimeCoordinatorRenderResetScope::None:
default:
return RuntimeEventRenderResetScope::None;
}
}
}
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher) :
mRuntimeStore(runtimeStore),
mRuntimeEventDispatcher(runtimeEventDispatcher)
{
}
RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateShaderExists(shaderId, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("AddLayer", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true, true);
PublishCoordinatorResult("AddLayer", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("RemoveLayer", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
if (result.accepted)
{
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
result.transientOscLayerKey = layerId;
}
PublishCoordinatorResult("RemoveLayer", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
bool shouldMove = false;
if (!ResolveLayerMove(layerId, direction, shouldMove, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("MoveLayer", result);
return result;
}
if (!shouldMove)
{
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true, true);
PublishCoordinatorResult("MoveLayer", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
bool shouldMove = false;
if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("MoveLayerToIndex", result);
return result;
}
if (!shouldMove)
{
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true, true);
PublishCoordinatorResult("MoveLayerToIndex", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("SetLayerBypass", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false, true);
PublishCoordinatorResult("SetLayerBypass", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("SetLayerShader", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false, true);
PublishCoordinatorResult("SetLayerShader", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
ResolvedParameterMutation mutation;
if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("UpdateLayerParameter", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
PublishCoordinatorResult("UpdateLayerParameter", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
ResolvedParameterMutation mutation;
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
PublishCoordinatorResult("UpdateLayerParameterByControlKey", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
constexpr RuntimeCoordinatorOscCommitPersistence kDefaultOscCommitPersistence =
RuntimeCoordinatorOscCommitPersistence::SessionOnly;
constexpr bool kPersistSettledOscCommits =
kDefaultOscCommitPersistence == RuntimeCoordinatorOscCommitPersistence::Persistent;
std::string error;
ResolvedParameterMutation mutation;
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, kPersistSettledOscCommits, mutation, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false, mutation.persistState);
PublishCoordinatorResult("CommitOscParameterByControlKey", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("ResetLayerParameters", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false, true);
if (!result.accepted)
{
PublishCoordinatorResult("ResetLayerParameters", result);
return result;
}
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
result.transientOscLayerKey = layerId;
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
PublishCoordinatorResult("ResetLayerParameters", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidatePresetName(presetName, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("SaveStackPreset", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false, true);
PublishCoordinatorResult("SaveStackPreset", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidatePresetName(presetName, error))
{
RuntimeCoordinatorResult result = ApplyStoreMutation(false, error, false, false, false);
PublishCoordinatorResult("LoadStackPreset", result);
return result;
}
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false, true);
PublishCoordinatorResult("LoadStackPreset", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
{
std::lock_guard<std::mutex> lock(mMutex);
PublishManualReloadRequested(preserveFeedbackState, "RequestShaderReload");
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
PublishCoordinatorFollowUpEvents("RequestShaderReload", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged)
{
std::lock_guard<std::mutex> lock(mMutex);
registryChanged = false;
bool reloadRequested = false;
std::string error;
if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error))
{
RuntimeCoordinatorResult result = HandleRuntimePollFailure(error);
return result;
}
if (reloadRequested)
{
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
return result;
}
if (registryChanged)
{
PublishFileChangeDetected("PollRuntimeStoreChanges", registryChanged, reloadRequested);
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
PublishCoordinatorFollowUpEvents("PollRuntimeStoreChanges", result);
return result;
}
RuntimeCoordinatorResult result;
result.accepted = true;
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
{
RuntimeCoordinatorResult result;
result.accepted = true;
result.runtimeStateBroadcastRequired = true;
result.compileStatusChanged = true;
result.compileStatusSucceeded = false;
result.compileStatusMessage = error;
PublishCoordinatorFollowUpEvents("HandleRuntimePollFailure", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
mPreserveFeedbackOnNextShaderBuild = false;
mUseCommittedLayerStates = true;
RuntimeCoordinatorResult result;
result.accepted = true;
result.runtimeStateBroadcastRequired = true;
result.compileStatusChanged = true;
result.compileStatusSucceeded = false;
result.compileStatusMessage = error;
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildFailure", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
{
std::lock_guard<std::mutex> lock(mMutex);
mUseCommittedLayerStates = false;
RuntimeCoordinatorResult result;
result.accepted = true;
result.runtimeStateBroadcastRequired = true;
result.compileStatusChanged = true;
result.compileStatusSucceeded = true;
result.compileStatusMessage = "Shader layers compiled successfully.";
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots;
mPreserveFeedbackOnNextShaderBuild = false;
PublishCoordinatorFollowUpEvents("HandlePreparedShaderBuildSuccess", result);
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
{
std::lock_guard<std::mutex> lock(mMutex);
PublishManualReloadRequested(false, "HandleRuntimeReloadRequest");
RuntimeCoordinatorResult result = BuildQueuedReloadResult(false);
PublishCoordinatorFollowUpEvents("HandleRuntimeReloadRequest", result);
return result;
}
void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode)
{
std::lock_guard<std::mutex> lock(mMutex);
switch (mode)
{
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
mUseCommittedLayerStates = true;
break;
case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
mUseCommittedLayerStates = false;
break;
case RuntimeCoordinatorCommittedStateMode::Unchanged:
default:
break;
}
}
bool RuntimeCoordinator::UseCommittedLayerStates() const
{
return mUseCommittedLayerStates.load();
}
bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mPreserveFeedbackOnNextShaderBuild;
}
bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
RuntimeStore::StoredParameterSnapshot snapshot;
if (!mRuntimeStore.TryGetStoredParameterById(layerId, parameterId, snapshot, error))
return false;
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
newValue, persistState, mutation, error);
}
bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
RuntimeStore::StoredParameterSnapshot snapshot;
if (!mRuntimeStore.TryGetStoredParameterByControlKey(layerKey, parameterKey, snapshot, error))
return false;
return BuildParameterMutationFromSnapshot(snapshot.layerId, snapshot.definition, snapshot.currentValue, snapshot.hasCurrentValue,
newValue, persistState, mutation, error);
}
bool RuntimeCoordinator::BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
mutation.layerId = layerId;
mutation.parameterId = definition.id;
mutation.persistState = persistState;
if (definition.type == ShaderParameterType::Trigger)
{
const double previousCount = !hasCurrentValue || currentValue.numberValues.empty()
? 0.0
: currentValue.numberValues[0];
const double triggerTime = mRuntimeStore.GetRuntimeElapsedSeconds();
mutation.value.numberValues = { previousCount + 1.0, triggerTime };
mutation.persistState = false;
return true;
}
return NormalizeAndValidateParameterValue(definition, newValue, mutation.value, error);
}
bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const
{
if (mRuntimeStore.HasStoredLayer(layerId))
return true;
error = "Unknown layer id: " + layerId;
return false;
}
bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const
{
if (mRuntimeStore.HasStoredShader(shaderId))
return true;
error = "Unknown shader id: " + shaderId;
return false;
}
bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
return mRuntimeStore.ResolveStoredLayerMove(layerId, direction, shouldMove, error);
}
bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
return mRuntimeStore.ResolveStoredLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
}
bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const
{
if (mRuntimeStore.IsValidStackPresetName(presetName))
return true;
error = "Preset name must include at least one letter or number.";
return false;
}
RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested)
{
if (!succeeded)
{
RuntimeCoordinatorResult result;
result.accepted = false;
result.errorMessage = errorMessage;
return result;
}
if (reloadRequired)
{
RuntimeCoordinatorResult result = BuildQueuedReloadResult(preserveFeedbackState);
result.persistenceRequested = persistenceRequested;
return result;
}
RuntimeCoordinatorResult result = BuildAcceptedNoReloadResult();
result.persistenceRequested = persistenceRequested;
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
{
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
mUseCommittedLayerStates = true;
RuntimeCoordinatorResult result;
result.accepted = true;
result.runtimeStateBroadcastRequired = true;
result.shaderBuildRequested = true;
result.compileStatusChanged = true;
result.compileStatusSucceeded = true;
result.compileStatusMessage = "Shader rebuild queued.";
result.clearReloadRequest = true;
result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates;
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const
{
RuntimeCoordinatorResult result;
result.accepted = true;
result.runtimeStateBroadcastRequired = true;
return result;
}
void RuntimeCoordinator::PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const
{
try
{
FileChangeDetectedEvent event;
event.path = reason;
event.shaderPackageCandidate = registryChanged || reloadRequested;
event.runtimeConfigCandidate = false;
event.presetCandidate = false;
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
}
catch (...)
{
}
}
void RuntimeCoordinator::PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const
{
try
{
ManualReloadRequestedEvent event;
event.preserveFeedbackState = preserveFeedbackState;
event.reason = reason;
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeCoordinator");
}
catch (...)
{
}
}
void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const
{
try
{
RuntimeMutationEvent mutation;
mutation.action = action;
mutation.accepted = result.accepted;
mutation.runtimeStateChanged = result.accepted && result.runtimeStateBroadcastRequired;
mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
mutation.shaderBuildRequested = result.shaderBuildRequested;
mutation.persistenceRequested = result.persistenceRequested;
mutation.clearTransientOscState = result.transientOscInvalidation != RuntimeCoordinatorTransientOscInvalidation::None;
mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
mutation.errorMessage = result.errorMessage;
mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");
PublishCoordinatorFollowUpEvents(action, result);
}
catch (...)
{
}
}
void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const
{
try
{
if (!result.accepted)
return;
if (result.runtimeStateBroadcastRequired)
{
RuntimeStateChangedEvent stateChanged;
stateChanged.reason = action;
stateChanged.renderVisible = result.renderResetScope != RuntimeCoordinatorRenderResetScope::None;
stateChanged.persistenceRequested = result.persistenceRequested;
mRuntimeEventDispatcher.PublishPayload(stateChanged, "RuntimeCoordinator");
}
if (result.persistenceRequested)
{
RuntimePersistenceRequestedEvent persistenceRequested;
persistenceRequested.request = PersistenceRequest::RuntimeStateRequest(action);
mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator");
}
if (result.shaderBuildRequested)
{
RuntimeReloadRequestedEvent reloadRequested;
reloadRequested.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
reloadRequested.reason = action;
mRuntimeEventDispatcher.PublishPayload(reloadRequested, "RuntimeCoordinator");
ShaderBuildEvent shaderBuild;
shaderBuild.phase = RuntimeEventShaderBuildPhase::Requested;
shaderBuild.preserveFeedbackState = mPreserveFeedbackOnNextShaderBuild;
shaderBuild.succeeded = true;
shaderBuild.message = result.compileStatusMessage;
mRuntimeEventDispatcher.PublishPayload(shaderBuild, "RuntimeCoordinator");
}
if (result.compileStatusChanged)
{
CompileStatusChangedEvent compileStatus;
compileStatus.succeeded = result.compileStatusSucceeded;
compileStatus.message = result.compileStatusMessage;
mRuntimeEventDispatcher.PublishPayload(compileStatus, "RuntimeCoordinator");
}
}
catch (...)
{
}
}

View File

@@ -0,0 +1,120 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderTypes.h"
#include <atomic>
#include <cstddef>
#include <mutex>
#include <string>
class RuntimeStore;
class RuntimeEventDispatcher;
enum class RuntimeCoordinatorCommittedStateMode
{
Unchanged,
UseCommittedStates,
UseLiveSnapshots
};
enum class RuntimeCoordinatorRenderResetScope
{
None,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
enum class RuntimeCoordinatorTransientOscInvalidation
{
None,
Layer,
All
};
enum class RuntimeCoordinatorOscCommitPersistence
{
SessionOnly,
Persistent
};
struct RuntimeCoordinatorResult
{
bool accepted = false;
bool runtimeStateBroadcastRequired = false;
bool shaderBuildRequested = false;
bool persistenceRequested = false;
bool compileStatusChanged = false;
bool compileStatusSucceeded = false;
bool clearReloadRequest = false;
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
RuntimeCoordinatorTransientOscInvalidation transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::None;
std::string transientOscLayerKey;
std::string compileStatusMessage;
std::string errorMessage;
};
class RuntimeCoordinator
{
public:
RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher);
RuntimeCoordinatorResult AddLayer(const std::string& shaderId);
RuntimeCoordinatorResult RemoveLayer(const std::string& layerId);
RuntimeCoordinatorResult MoveLayer(const std::string& layerId, int direction);
RuntimeCoordinatorResult MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex);
RuntimeCoordinatorResult SetLayerBypass(const std::string& layerId, bool bypassed);
RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId);
RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue);
RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
RuntimeCoordinatorResult CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId);
RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);
RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
RuntimeCoordinatorResult PollRuntimeStoreChanges(bool& registryChanged);
RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
RuntimeCoordinatorResult HandleRuntimeReloadRequest();
void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode);
bool UseCommittedLayerStates() const;
bool PreserveFeedbackOnNextShaderBuild() const;
private:
struct ResolvedParameterMutation
{
std::string layerId;
std::string parameterId;
ShaderParameterValue value;
bool persistState = true;
};
bool BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool ValidateLayerExists(const std::string& layerId, std::string& error) const;
bool ValidateShaderExists(const std::string& shaderId, std::string& error) const;
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
bool ValidatePresetName(const std::string& presetName, std::string& error) const;
RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested);
RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
void PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const;
void PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const;
void PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const;
void PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const;
RuntimeStore& mRuntimeStore;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
mutable std::mutex mMutex;
bool mPreserveFeedbackOnNextShaderBuild = false;
std::atomic<bool> mUseCommittedLayerStates{ false };
};

View File

@@ -0,0 +1,87 @@
#pragma once
#include "RuntimeEventPayloads.h"
#include <chrono>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
using RuntimeEventPayload = std::variant<
std::monostate,
OscValueReceivedEvent,
OscValueCoalescedEvent,
OscCommitRequestedEvent,
HttpControlMutationRequestedEvent,
WebSocketClientConnectedEvent,
RuntimeStateBroadcastRequestedEvent,
FileChangeDetectedEvent,
ManualReloadRequestedEvent,
RuntimeMutationEvent,
RuntimeStateChangedEvent,
RuntimePersistenceRequestedEvent,
RuntimeReloadRequestedEvent,
ShaderPackagesChangedEvent,
RenderSnapshotPublishRequestedEvent,
RuntimeStatePresentationChangedEvent,
ShaderBuildEvent,
CompileStatusChangedEvent,
RenderSnapshotPublishedEvent,
RenderResetEvent,
OscOverlayEvent,
FrameRenderedEvent,
PreviewFrameAvailableEvent,
InputSignalChangedEvent,
InputFrameArrivedEvent,
OutputFrameScheduledEvent,
OutputFrameCompletedEvent,
BackendStateChangedEvent,
SubsystemWarningEvent,
SubsystemRecoveredEvent,
TimingSampleRecordedEvent,
QueueDepthChangedEvent>;
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeEventPayload& payload)
{
return std::visit([](const auto& value) -> RuntimeEventType {
using PayloadType = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<PayloadType, std::monostate>)
return RuntimeEventType::Unknown;
else
return RuntimeEventPayloadType(value);
}, payload);
}
struct RuntimeEvent
{
RuntimeEventType type = RuntimeEventType::Unknown;
uint64_t sequence = 0;
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now();
std::string source;
RuntimeEventPayload payload;
bool HasPayload() const
{
return !std::holds_alternative<std::monostate>(payload);
}
bool PayloadMatchesType() const
{
return RuntimeEventPayloadType(payload) == type;
}
};
template <typename Payload>
RuntimeEvent MakeRuntimeEvent(Payload payload, std::string source = {}, uint64_t sequence = 0,
std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now())
{
RuntimeEvent event;
event.type = RuntimeEventPayloadType(payload);
event.sequence = sequence;
event.createdAt = createdAt;
event.source = std::move(source);
event.payload = std::move(payload);
return event;
}

View File

@@ -0,0 +1,158 @@
#pragma once
#include "RuntimeEvent.h"
#include <chrono>
#include <cstddef>
#include <deque>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
struct RuntimeEventCoalescingQueueMetrics
{
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
std::size_t coalescedCount = 0;
double oldestEventAgeMilliseconds = 0.0;
};
inline std::string RuntimeEventDefaultCoalescingKey(const RuntimeEvent& event)
{
if (const auto* payload = std::get_if<OscValueReceivedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
if (const auto* payload = std::get_if<OscCommitRequestedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->routeKey;
if (const auto* payload = std::get_if<FileChangeDetectedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->path;
if (const auto* payload = std::get_if<ShaderBuildEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" +
std::to_string(payload->inputWidth) + "x" +
std::to_string(payload->inputHeight) + ":" +
(payload->preserveFeedbackState ? "preserve" : "reset");
if (const auto* payload = std::get_if<RenderSnapshotPublishRequestedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" +
std::to_string(payload->outputWidth) + "x" +
std::to_string(payload->outputHeight);
if (const auto* payload = std::get_if<TimingSampleRecordedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->subsystem + ":" + payload->metric;
if (const auto* payload = std::get_if<QueueDepthChangedEvent>(&event.payload))
return std::string(RuntimeEventTypeName(event.type)) + ":" + payload->queueName;
return std::string(RuntimeEventTypeName(event.type));
}
class RuntimeEventCoalescingQueue
{
public:
using KeySelector = std::function<std::string(const RuntimeEvent&)>;
explicit RuntimeEventCoalescingQueue(std::size_t capacity = 256, KeySelector keySelector = RuntimeEventDefaultCoalescingKey) :
mCapacity(capacity),
mKeySelector(std::move(keySelector))
{
}
bool Push(RuntimeEvent event)
{
const std::string key = mKeySelector(event);
if (key.empty())
return false;
std::lock_guard<std::mutex> lock(mMutex);
auto found = mEntries.find(key);
if (found != mEntries.end())
{
const auto firstCreatedAt = found->second.event.createdAt;
found->second.event = std::move(event);
found->second.event.createdAt = firstCreatedAt;
++found->second.coalescedCount;
++mCoalescedCount;
return true;
}
if (mEntries.size() >= mCapacity)
{
++mDroppedCount;
return false;
}
mOrder.push_back(key);
Entry entry;
entry.event = std::move(event);
mEntries.emplace(key, std::move(entry));
return true;
}
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
{
std::vector<RuntimeEvent> events;
std::lock_guard<std::mutex> lock(mMutex);
const std::size_t count = maxEvents == 0 || maxEvents > mOrder.size() ? mOrder.size() : maxEvents;
events.reserve(count);
for (std::size_t index = 0; index < count; ++index)
{
const std::string key = std::move(mOrder.front());
mOrder.pop_front();
auto found = mEntries.find(key);
if (found == mEntries.end())
continue;
events.push_back(std::move(found->second.event));
mEntries.erase(found);
}
return events;
}
RuntimeEventCoalescingQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
std::lock_guard<std::mutex> lock(mMutex);
RuntimeEventCoalescingQueueMetrics metrics;
metrics.depth = mEntries.size();
metrics.capacity = mCapacity;
metrics.droppedCount = mDroppedCount;
metrics.coalescedCount = mCoalescedCount;
if (!mOrder.empty())
{
const auto found = mEntries.find(mOrder.front());
if (found != mEntries.end())
{
const auto age = now - found->second.event.createdAt;
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
}
}
return metrics;
}
std::size_t Depth() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mEntries.size();
}
private:
struct Entry
{
RuntimeEvent event;
std::size_t coalescedCount = 0;
};
mutable std::mutex mMutex;
std::size_t mCapacity = 0;
KeySelector mKeySelector;
std::deque<std::string> mOrder;
std::map<std::string, Entry> mEntries;
std::size_t mDroppedCount = 0;
std::size_t mCoalescedCount = 0;
};

View File

@@ -0,0 +1,170 @@
#pragma once
#include "RuntimeEventCoalescingQueue.h"
#include "RuntimeEventQueue.h"
#include <algorithm>
#include <atomic>
#include <functional>
#include <map>
#include <mutex>
#include <vector>
struct RuntimeEventDispatchResult
{
std::size_t dispatchedEvents = 0;
std::size_t handlerInvocations = 0;
std::size_t handlerFailures = 0;
double dispatchDurationMilliseconds = 0.0;
};
class RuntimeEventDispatcher
{
public:
using Handler = std::function<void(const RuntimeEvent&)>;
explicit RuntimeEventDispatcher(std::size_t queueCapacity = 1024) :
mQueue(queueCapacity),
mCoalescingQueue(queueCapacity)
{
}
bool Publish(RuntimeEvent event)
{
if (!event.PayloadMatchesType())
return false;
if (event.sequence == 0)
event.sequence = mNextSequence.fetch_add(1);
if (ShouldCoalesce(event))
return mCoalescingQueue.Push(std::move(event));
return mQueue.Push(std::move(event));
}
template <typename Payload>
bool PublishPayload(Payload payload, std::string source = {})
{
return Publish(MakeRuntimeEvent(std::move(payload), std::move(source)));
}
void Subscribe(RuntimeEventType type, Handler handler)
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
mHandlers[type].push_back(std::move(handler));
}
void SubscribeAll(Handler handler)
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
mAllHandlers.push_back(std::move(handler));
}
RuntimeEventDispatchResult DispatchPending(std::size_t maxEvents = 0)
{
const auto startedAt = std::chrono::steady_clock::now();
RuntimeEventDispatchResult result;
FlushCoalescedToFifo(maxEvents);
std::vector<RuntimeEvent> events = mQueue.Drain(maxEvents);
result.dispatchedEvents = events.size();
for (const RuntimeEvent& event : events)
{
std::vector<Handler> handlers = HandlersFor(event.type);
result.handlerInvocations += handlers.size();
for (const Handler& handler : handlers)
{
try
{
handler(event);
}
catch (...)
{
++result.handlerFailures;
}
}
}
result.dispatchDurationMilliseconds =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - startedAt).count();
return result;
}
bool TryPop(RuntimeEvent& event)
{
return mQueue.TryPop(event);
}
RuntimeEventQueueMetrics GetQueueMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
RuntimeEventQueueMetrics metrics = mQueue.GetMetrics(now);
const RuntimeEventCoalescingQueueMetrics coalescingMetrics = mCoalescingQueue.GetMetrics(now);
if (metrics.depth == 0)
metrics.oldestEventAgeMilliseconds = coalescingMetrics.oldestEventAgeMilliseconds;
else if (coalescingMetrics.depth > 0)
metrics.oldestEventAgeMilliseconds = (std::max)(metrics.oldestEventAgeMilliseconds, coalescingMetrics.oldestEventAgeMilliseconds);
metrics.depth += coalescingMetrics.depth;
metrics.capacity += coalescingMetrics.capacity;
metrics.droppedCount += coalescingMetrics.droppedCount;
metrics.coalescedCount = coalescingMetrics.coalescedCount;
return metrics;
}
std::size_t QueueDepth() const
{
return mQueue.Depth() + mCoalescingQueue.Depth();
}
private:
static bool ShouldCoalesce(const RuntimeEvent& event)
{
switch (event.type)
{
case RuntimeEventType::OscValueReceived:
case RuntimeEventType::OscCommitRequested:
case RuntimeEventType::RuntimeStateBroadcastRequested:
case RuntimeEventType::FileChangeDetected:
case RuntimeEventType::RuntimeReloadRequested:
case RuntimeEventType::ShaderBuildRequested:
case RuntimeEventType::RenderSnapshotPublishRequested:
case RuntimeEventType::TimingSampleRecorded:
case RuntimeEventType::QueueDepthChanged:
return true;
default:
return false;
}
}
void FlushCoalescedToFifo(std::size_t maxEvents)
{
const std::size_t fifoDepth = mQueue.Depth();
if (maxEvents != 0 && fifoDepth >= maxEvents)
return;
const std::size_t flushLimit = maxEvents == 0 ? 0 : maxEvents - fifoDepth;
std::vector<RuntimeEvent> events = mCoalescingQueue.Drain(flushLimit);
for (RuntimeEvent& event : events)
mQueue.Push(std::move(event));
}
std::vector<Handler> HandlersFor(RuntimeEventType type) const
{
std::lock_guard<std::mutex> lock(mHandlerMutex);
std::vector<Handler> handlers = mAllHandlers;
const auto found = mHandlers.find(type);
if (found != mHandlers.end())
handlers.insert(handlers.end(), found->second.begin(), found->second.end());
return handlers;
}
RuntimeEventQueue mQueue;
RuntimeEventCoalescingQueue mCoalescingQueue;
std::atomic<uint64_t> mNextSequence{ 1 };
mutable std::mutex mHandlerMutex;
std::map<RuntimeEventType, std::vector<Handler>> mHandlers;
std::vector<Handler> mAllHandlers;
};

View File

@@ -0,0 +1,442 @@
#pragma once
#include "RuntimeEventType.h"
#include <cstddef>
#include <cstdint>
#include "PersistenceRequest.h"
#include <string>
enum class RuntimeEventSeverity
{
Debug,
Info,
Warning,
Error
};
enum class RuntimeEventRenderResetScope
{
None,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
enum class RuntimeEventShaderBuildPhase
{
Requested,
Prepared,
Applied,
Failed
};
struct OscValueReceivedEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
std::string valueJson;
uint64_t generation = 0;
};
struct OscValueCoalescedEvent
{
std::string routeKey;
std::size_t coalescedCount = 0;
uint64_t latestGeneration = 0;
};
struct OscCommitRequestedEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
std::string valueJson;
uint64_t generation = 0;
};
struct HttpControlMutationRequestedEvent
{
std::string method;
std::string path;
std::string bodyJson;
};
struct WebSocketClientConnectedEvent
{
std::string clientId;
std::size_t connectedClientCount = 0;
};
struct RuntimeStateBroadcastRequestedEvent
{
std::string reason;
bool coalescable = true;
};
struct FileChangeDetectedEvent
{
std::string path;
bool shaderPackageCandidate = false;
bool runtimeConfigCandidate = false;
bool presetCandidate = false;
};
struct ManualReloadRequestedEvent
{
bool preserveFeedbackState = false;
std::string reason;
};
struct RuntimeMutationEvent
{
std::string action;
bool accepted = false;
bool runtimeStateChanged = false;
bool runtimeStateBroadcastRequired = false;
bool shaderBuildRequested = false;
bool persistenceRequested = false;
bool clearTransientOscState = false;
RuntimeEventRenderResetScope renderResetScope = RuntimeEventRenderResetScope::None;
std::string errorMessage;
};
struct RuntimeStateChangedEvent
{
std::string reason;
bool renderVisible = false;
bool persistenceRequested = false;
};
struct RuntimePersistenceRequestedEvent
{
PersistenceRequest request;
};
struct RuntimeReloadRequestedEvent
{
bool preserveFeedbackState = false;
std::string reason;
};
struct ShaderPackagesChangedEvent
{
bool registryChanged = false;
std::size_t packageCount = 0;
std::string reason;
};
struct RenderSnapshotPublishRequestedEvent
{
unsigned inputWidth = 0;
unsigned inputHeight = 0;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::string reason;
};
struct RuntimeStatePresentationChangedEvent
{
std::string reason;
};
struct ShaderBuildEvent
{
RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested;
uint64_t generation = 0;
unsigned inputWidth = 0;
unsigned inputHeight = 0;
bool preserveFeedbackState = false;
bool succeeded = false;
std::string message;
};
struct CompileStatusChangedEvent
{
bool succeeded = false;
std::string message;
};
struct RenderSnapshotPublishedEvent
{
uint64_t snapshotVersion = 0;
uint64_t structureVersion = 0;
uint64_t parameterVersion = 0;
uint64_t packageVersion = 0;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::size_t layerCount = 0;
};
struct RenderResetEvent
{
RuntimeEventRenderResetScope scope = RuntimeEventRenderResetScope::None;
bool applied = false;
std::string reason;
};
struct OscOverlayEvent
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
uint64_t generation = 0;
bool settled = false;
};
struct FrameRenderedEvent
{
uint64_t frameIndex = 0;
double renderMilliseconds = 0.0;
};
struct PreviewFrameAvailableEvent
{
uint64_t frameIndex = 0;
unsigned width = 0;
unsigned height = 0;
};
struct InputSignalChangedEvent
{
bool hasSignal = false;
unsigned width = 0;
unsigned height = 0;
std::string modeName;
};
struct InputFrameArrivedEvent
{
uint64_t frameIndex = 0;
unsigned width = 0;
unsigned height = 0;
long rowBytes = 0;
std::string pixelFormat;
bool hasNoInputSource = false;
};
struct OutputFrameScheduledEvent
{
uint64_t frameIndex = 0;
int64_t streamTime = 0;
int64_t duration = 0;
int64_t timeScale = 0;
};
struct OutputFrameCompletedEvent
{
uint64_t frameIndex = 0;
std::string result;
};
struct BackendStateChangedEvent
{
std::string backendName;
std::string state;
std::string message;
};
struct SubsystemWarningEvent
{
std::string subsystem;
std::string warningKey;
RuntimeEventSeverity severity = RuntimeEventSeverity::Warning;
std::string message;
bool cleared = false;
};
struct SubsystemRecoveredEvent
{
std::string subsystem;
std::string recoveryKey;
std::string message;
};
struct TimingSampleRecordedEvent
{
std::string subsystem;
std::string metric;
double value = 0.0;
std::string unit;
};
struct QueueDepthChangedEvent
{
std::string queueName;
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
std::size_t coalescedCount = 0;
};
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueReceivedEvent&)
{
return RuntimeEventType::OscValueReceived;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueCoalescedEvent&)
{
return RuntimeEventType::OscValueCoalesced;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OscCommitRequestedEvent&)
{
return RuntimeEventType::OscCommitRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const HttpControlMutationRequestedEvent&)
{
return RuntimeEventType::HttpControlMutationRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const WebSocketClientConnectedEvent&)
{
return RuntimeEventType::WebSocketClientConnected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateBroadcastRequestedEvent&)
{
return RuntimeEventType::RuntimeStateBroadcastRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const FileChangeDetectedEvent&)
{
return RuntimeEventType::FileChangeDetected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const ManualReloadRequestedEvent&)
{
return RuntimeEventType::ManualReloadRequested;
}
inline RuntimeEventType RuntimeEventPayloadType(const RuntimeMutationEvent& event)
{
return event.accepted ? RuntimeEventType::RuntimeMutationAccepted : RuntimeEventType::RuntimeMutationRejected;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateChangedEvent&)
{
return RuntimeEventType::RuntimeStateChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimePersistenceRequestedEvent&)
{
return RuntimeEventType::RuntimePersistenceRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeReloadRequestedEvent&)
{
return RuntimeEventType::RuntimeReloadRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const ShaderPackagesChangedEvent&)
{
return RuntimeEventType::ShaderPackagesChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishRequestedEvent&)
{
return RuntimeEventType::RenderSnapshotPublishRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStatePresentationChangedEvent&)
{
return RuntimeEventType::RuntimeStatePresentationChanged;
}
inline RuntimeEventType RuntimeEventPayloadType(const ShaderBuildEvent& event)
{
switch (event.phase)
{
case RuntimeEventShaderBuildPhase::Requested:
return RuntimeEventType::ShaderBuildRequested;
case RuntimeEventShaderBuildPhase::Prepared:
return RuntimeEventType::ShaderBuildPrepared;
case RuntimeEventShaderBuildPhase::Applied:
return RuntimeEventType::ShaderBuildApplied;
case RuntimeEventShaderBuildPhase::Failed:
return RuntimeEventType::ShaderBuildFailed;
}
return RuntimeEventType::ShaderBuildRequested;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const CompileStatusChangedEvent&)
{
return RuntimeEventType::CompileStatusChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishedEvent&)
{
return RuntimeEventType::RenderSnapshotPublished;
}
inline RuntimeEventType RuntimeEventPayloadType(const RenderResetEvent& event)
{
return event.applied ? RuntimeEventType::RenderResetApplied : RuntimeEventType::RenderResetRequested;
}
inline RuntimeEventType RuntimeEventPayloadType(const OscOverlayEvent& event)
{
return event.settled ? RuntimeEventType::OscOverlaySettled : RuntimeEventType::OscOverlayApplied;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const FrameRenderedEvent&)
{
return RuntimeEventType::FrameRendered;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const PreviewFrameAvailableEvent&)
{
return RuntimeEventType::PreviewFrameAvailable;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const InputSignalChangedEvent&)
{
return RuntimeEventType::InputSignalChanged;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const InputFrameArrivedEvent&)
{
return RuntimeEventType::InputFrameArrived;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameScheduledEvent&)
{
return RuntimeEventType::OutputFrameScheduled;
}
inline RuntimeEventType RuntimeEventPayloadType(const OutputFrameCompletedEvent& event)
{
if (event.result == "DisplayedLate")
return RuntimeEventType::OutputLateFrameDetected;
if (event.result == "Dropped")
return RuntimeEventType::OutputDroppedFrameDetected;
return RuntimeEventType::OutputFrameCompleted;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const BackendStateChangedEvent&)
{
return RuntimeEventType::BackendStateChanged;
}
inline RuntimeEventType RuntimeEventPayloadType(const SubsystemWarningEvent& event)
{
return event.cleared ? RuntimeEventType::SubsystemWarningCleared : RuntimeEventType::SubsystemWarningRaised;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const SubsystemRecoveredEvent&)
{
return RuntimeEventType::SubsystemRecovered;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const TimingSampleRecordedEvent&)
{
return RuntimeEventType::TimingSampleRecorded;
}
constexpr RuntimeEventType RuntimeEventPayloadType(const QueueDepthChangedEvent&)
{
return RuntimeEventType::QueueDepthChanged;
}

View File

@@ -0,0 +1,100 @@
#pragma once
#include "RuntimeEvent.h"
#include <chrono>
#include <cstddef>
#include <deque>
#include <mutex>
#include <vector>
struct RuntimeEventQueueMetrics
{
std::size_t depth = 0;
std::size_t capacity = 0;
std::size_t droppedCount = 0;
std::size_t coalescedCount = 0;
double oldestEventAgeMilliseconds = 0.0;
};
class RuntimeEventQueue
{
public:
explicit RuntimeEventQueue(std::size_t capacity = 1024) :
mCapacity(capacity)
{
}
bool Push(RuntimeEvent event)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mEvents.size() >= mCapacity)
{
++mDroppedCount;
return false;
}
mEvents.push_back(std::move(event));
return true;
}
bool TryPop(RuntimeEvent& event)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mEvents.empty())
return false;
event = std::move(mEvents.front());
mEvents.pop_front();
return true;
}
std::vector<RuntimeEvent> Drain(std::size_t maxEvents = 0)
{
std::vector<RuntimeEvent> events;
std::lock_guard<std::mutex> lock(mMutex);
const std::size_t count = maxEvents == 0 || maxEvents > mEvents.size() ? mEvents.size() : maxEvents;
events.reserve(count);
for (std::size_t index = 0; index < count; ++index)
{
events.push_back(std::move(mEvents.front()));
mEvents.pop_front();
}
return events;
}
RuntimeEventQueueMetrics GetMetrics(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const
{
std::lock_guard<std::mutex> lock(mMutex);
RuntimeEventQueueMetrics metrics;
metrics.depth = mEvents.size();
metrics.capacity = mCapacity;
metrics.droppedCount = mDroppedCount;
if (!mEvents.empty())
{
const auto age = now - mEvents.front().createdAt;
metrics.oldestEventAgeMilliseconds = std::chrono::duration<double, std::milli>(age).count();
}
return metrics;
}
std::size_t Depth() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mEvents.size();
}
std::size_t Capacity() const
{
return mCapacity;
}
private:
mutable std::mutex mMutex;
std::deque<RuntimeEvent> mEvents;
std::size_t mCapacity = 0;
std::size_t mDroppedCount = 0;
};

View File

@@ -0,0 +1,151 @@
#pragma once
#include <string_view>
enum class RuntimeEventType
{
Unknown = 0,
// Control ingress.
OscValueReceived,
OscValueCoalesced,
OscCommitRequested,
HttpControlMutationRequested,
WebSocketClientConnected,
RuntimeStateBroadcastRequested,
FileChangeDetected,
ManualReloadRequested,
// Runtime policy and state.
RuntimeMutationAccepted,
RuntimeMutationRejected,
RuntimeStateChanged,
RuntimePersistenceRequested,
RuntimeReloadRequested,
ShaderPackagesChanged,
RenderSnapshotPublishRequested,
RuntimeStatePresentationChanged,
// Shader build lifecycle.
ShaderBuildRequested,
ShaderBuildPrepared,
ShaderBuildApplied,
ShaderBuildFailed,
CompileStatusChanged,
// Render lifecycle.
RenderSnapshotPublished,
RenderResetRequested,
RenderResetApplied,
OscOverlayApplied,
OscOverlaySettled,
FrameRendered,
PreviewFrameAvailable,
// Video backend lifecycle.
InputSignalChanged,
InputFrameArrived,
OutputFrameScheduled,
OutputFrameCompleted,
OutputLateFrameDetected,
OutputDroppedFrameDetected,
BackendStateChanged,
// Health and telemetry.
SubsystemWarningRaised,
SubsystemWarningCleared,
SubsystemRecovered,
TimingSampleRecorded,
QueueDepthChanged
};
constexpr std::string_view RuntimeEventTypeName(RuntimeEventType type)
{
switch (type)
{
case RuntimeEventType::Unknown:
return "Unknown";
case RuntimeEventType::OscValueReceived:
return "OscValueReceived";
case RuntimeEventType::OscValueCoalesced:
return "OscValueCoalesced";
case RuntimeEventType::OscCommitRequested:
return "OscCommitRequested";
case RuntimeEventType::HttpControlMutationRequested:
return "HttpControlMutationRequested";
case RuntimeEventType::WebSocketClientConnected:
return "WebSocketClientConnected";
case RuntimeEventType::RuntimeStateBroadcastRequested:
return "RuntimeStateBroadcastRequested";
case RuntimeEventType::FileChangeDetected:
return "FileChangeDetected";
case RuntimeEventType::ManualReloadRequested:
return "ManualReloadRequested";
case RuntimeEventType::RuntimeMutationAccepted:
return "RuntimeMutationAccepted";
case RuntimeEventType::RuntimeMutationRejected:
return "RuntimeMutationRejected";
case RuntimeEventType::RuntimeStateChanged:
return "RuntimeStateChanged";
case RuntimeEventType::RuntimePersistenceRequested:
return "RuntimePersistenceRequested";
case RuntimeEventType::RuntimeReloadRequested:
return "RuntimeReloadRequested";
case RuntimeEventType::ShaderPackagesChanged:
return "ShaderPackagesChanged";
case RuntimeEventType::RenderSnapshotPublishRequested:
return "RenderSnapshotPublishRequested";
case RuntimeEventType::RuntimeStatePresentationChanged:
return "RuntimeStatePresentationChanged";
case RuntimeEventType::ShaderBuildRequested:
return "ShaderBuildRequested";
case RuntimeEventType::ShaderBuildPrepared:
return "ShaderBuildPrepared";
case RuntimeEventType::ShaderBuildApplied:
return "ShaderBuildApplied";
case RuntimeEventType::ShaderBuildFailed:
return "ShaderBuildFailed";
case RuntimeEventType::CompileStatusChanged:
return "CompileStatusChanged";
case RuntimeEventType::RenderSnapshotPublished:
return "RenderSnapshotPublished";
case RuntimeEventType::RenderResetRequested:
return "RenderResetRequested";
case RuntimeEventType::RenderResetApplied:
return "RenderResetApplied";
case RuntimeEventType::OscOverlayApplied:
return "OscOverlayApplied";
case RuntimeEventType::OscOverlaySettled:
return "OscOverlaySettled";
case RuntimeEventType::FrameRendered:
return "FrameRendered";
case RuntimeEventType::PreviewFrameAvailable:
return "PreviewFrameAvailable";
case RuntimeEventType::InputSignalChanged:
return "InputSignalChanged";
case RuntimeEventType::InputFrameArrived:
return "InputFrameArrived";
case RuntimeEventType::OutputFrameScheduled:
return "OutputFrameScheduled";
case RuntimeEventType::OutputFrameCompleted:
return "OutputFrameCompleted";
case RuntimeEventType::OutputLateFrameDetected:
return "OutputLateFrameDetected";
case RuntimeEventType::OutputDroppedFrameDetected:
return "OutputDroppedFrameDetected";
case RuntimeEventType::BackendStateChanged:
return "BackendStateChanged";
case RuntimeEventType::SubsystemWarningRaised:
return "SubsystemWarningRaised";
case RuntimeEventType::SubsystemWarningCleared:
return "SubsystemWarningCleared";
case RuntimeEventType::SubsystemRecovered:
return "SubsystemRecovered";
case RuntimeEventType::TimingSampleRecorded:
return "TimingSampleRecorded";
case RuntimeEventType::QueueDepthChanged:
return "QueueDepthChanged";
}
return "Unknown";
}

View File

@@ -0,0 +1,144 @@
#include "CommittedLiveState.h"
bool CommittedLiveState::LoadPersistentStateValue(const JsonValue& root)
{
return mLayerStack.LoadPersistentStateValue(root);
}
JsonValue CommittedLiveState::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
{
return mLayerStack.BuildPersistentStateValue(shaderCatalog);
}
void CommittedLiveState::NormalizeLayerIds()
{
mLayerStack.NormalizeLayerIds();
}
void CommittedLiveState::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
{
mLayerStack.EnsureDefaultsForAllLayers(shaderCatalog);
}
void CommittedLiveState::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
{
mLayerStack.EnsureDefaultLayer(shaderCatalog);
}
void CommittedLiveState::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
{
mLayerStack.RemoveLayersWithMissingPackages(shaderCatalog);
}
bool CommittedLiveState::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{
return mLayerStack.CreateLayer(shaderCatalog, shaderId, error);
}
bool CommittedLiveState::DeleteLayer(const std::string& layerId, std::string& error)
{
return mLayerStack.DeleteLayer(layerId, error);
}
bool CommittedLiveState::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
return mLayerStack.MoveLayer(layerId, direction, error);
}
bool CommittedLiveState::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
return mLayerStack.MoveLayerToIndex(layerId, targetIndex, error);
}
bool CommittedLiveState::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
return mLayerStack.SetLayerBypassState(layerId, bypassed, error);
}
bool CommittedLiveState::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
{
return mLayerStack.SetLayerShaderSelection(shaderCatalog, layerId, shaderId, error);
}
bool CommittedLiveState::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
{
return mLayerStack.SetParameterValue(layerId, parameterId, value, error);
}
bool CommittedLiveState::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
{
return mLayerStack.ResetLayerParameterValues(shaderCatalog, layerId, error);
}
bool CommittedLiveState::HasLayer(const std::string& layerId) const
{
return mLayerStack.HasLayer(layerId);
}
bool CommittedLiveState::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
return mLayerStack.TryGetParameterById(shaderCatalog, layerId, parameterId, snapshot, error);
}
bool CommittedLiveState::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
return mLayerStack.TryGetParameterByControlKey(shaderCatalog, layerKey, parameterKey, snapshot, error);
}
bool CommittedLiveState::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
return mLayerStack.ResolveLayerMove(layerId, direction, shouldMove, error);
}
bool CommittedLiveState::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
return mLayerStack.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
}
JsonValue CommittedLiveState::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
{
return mLayerStack.BuildStackPresetValue(shaderCatalog, presetName);
}
bool CommittedLiveState::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
{
return mLayerStack.LoadStackPresetValue(shaderCatalog, root, error);
}
CommittedLiveStateReadModel CommittedLiveState::BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const
{
CommittedLiveStateReadModel model;
model.layers = mLayerStack.Layers();
model.packagesById = shaderCatalog.CaptureSnapshot().packagesById;
return model;
}
std::vector<CommittedLiveState::LayerPersistentState> CommittedLiveState::CopyLayerStates() const
{
return mLayerStack.Layers();
}
const std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers() const
{
return mLayerStack.Layers();
}
std::vector<CommittedLiveState::LayerPersistentState>& CommittedLiveState::Layers()
{
return mLayerStack.Layers();
}
const CommittedLiveState::LayerPersistentState* CommittedLiveState::FindLayerById(const std::string& layerId) const
{
return mLayerStack.FindLayerById(layerId);
}
const LayerStackStore& CommittedLiveState::LayerStack() const
{
return mLayerStack;
}
LayerStackStore& CommittedLiveState::LayerStack()
{
return mLayerStack;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include "LayerStackStore.h"
#include "RuntimeStoreReadModels.h"
#include "ShaderPackageCatalog.h"
#include <cstddef>
#include <string>
#include <vector>
class CommittedLiveState
{
public:
using LayerPersistentState = LayerStackStore::LayerPersistentState;
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
bool LoadPersistentStateValue(const JsonValue& root);
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
void NormalizeLayerIds();
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
bool DeleteLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
bool HasLayer(const std::string& layerId) const;
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
CommittedLiveStateReadModel BuildReadModel(const ShaderPackageCatalog& shaderCatalog) const;
std::vector<LayerPersistentState> CopyLayerStates() const;
const std::vector<LayerPersistentState>& Layers() const;
std::vector<LayerPersistentState>& Layers();
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
const LayerStackStore& LayerStack() const;
LayerStackStore& LayerStack();
private:
LayerStackStore mLayerStack;
};

View File

@@ -0,0 +1,26 @@
#include "RenderStateComposer.h"
RenderStateCompositionResult RenderStateComposer::BuildFrameState(const LayeredRenderStateInput& input) const
{
RenderStateCompositionResult result;
const std::vector<RuntimeRenderState>* layerStates =
input.committedLiveLayerStates ? input.committedLiveLayerStates : input.basePersistedLayerStates;
if (!layerStates)
return result;
result.layerStates = *layerStates;
result.hasLayerStates = !result.layerStates.empty();
if (input.transientAutomationOverlay)
{
RuntimeLiveStateApplyOptions options;
options.allowCommit = input.allowTransientAutomationCommits;
options.smoothing = input.transientAutomationSmoothing;
options.commitDelay = input.transientAutomationCommitDelay;
options.now = input.now;
input.transientAutomationOverlay->ApplyToLayerStates(
result.layerStates,
options,
input.collectTransientAutomationCommitRequests ? &result.commitRequests : nullptr);
}
return result;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "RuntimeLiveState.h"
#include <chrono>
#include <vector>
struct LayeredRenderStateInput
{
const std::vector<RuntimeRenderState>* basePersistedLayerStates = nullptr;
const std::vector<RuntimeRenderState>* committedLiveLayerStates = nullptr;
RuntimeLiveState* transientAutomationOverlay = nullptr;
bool allowTransientAutomationCommits = false;
bool collectTransientAutomationCommitRequests = true;
double transientAutomationSmoothing = 0.0;
std::chrono::milliseconds transientAutomationCommitDelay = std::chrono::milliseconds(150);
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
};
struct RenderStateCompositionResult
{
std::vector<RuntimeRenderState> layerStates;
std::vector<RuntimeLiveOscCommitRequest> commitRequests;
bool hasLayerStates = false;
};
class RenderStateComposer
{
public:
RenderStateCompositionResult BuildFrameState(const LayeredRenderStateInput& input) const;
};

View File

@@ -0,0 +1,329 @@
#include "RuntimeLiveState.h"
#include "RuntimeParameterUtils.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstddef>
namespace
{
constexpr double kOscSmoothingReferenceFps = 60.0;
constexpr double kOscSmoothingMaxStepSeconds = 0.25;
std::string SimplifyOscControlKey(const std::string& text)
{
std::string simplified;
for (unsigned char ch : text)
{
if (std::isalnum(ch))
simplified.push_back(static_cast<char>(std::tolower(ch)));
}
return simplified;
}
bool MatchesOscControlKey(const std::string& candidate, const std::string& key)
{
return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key);
}
double ClampOscAlpha(double value)
{
return (std::max)(0.0, (std::min)(1.0, value));
}
double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds)
{
const double clampedSmoothing = ClampOscAlpha(smoothing);
if (clampedSmoothing <= 0.0)
return 0.0;
if (clampedSmoothing >= 1.0)
return 1.0;
const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds));
if (clampedDeltaSeconds <= 0.0)
return 0.0;
const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps;
return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale));
}
JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
switch (definition.type)
{
case ShaderParameterType::Boolean:
return JsonValue(value.booleanValue);
case ShaderParameterType::Enum:
return JsonValue(value.enumValue);
case ShaderParameterType::Text:
return JsonValue(value.textValue);
case ShaderParameterType::Trigger:
case ShaderParameterType::Float:
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
{
JsonValue array = JsonValue::MakeArray();
for (double number : value.numberValues)
array.pushBack(JsonValue(number));
return array;
}
}
return JsonValue();
}
}
void RuntimeLiveState::Clear()
{
mOscOverlayStates.clear();
}
void RuntimeLiveState::ClearForLayerKey(const std::string& layerKey)
{
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
{
if (OverlayMatchesLayerKey(it->second, layerKey))
it = mOscOverlayStates.erase(it);
else
++it;
}
}
bool RuntimeLiveState::OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey)
{
return MatchesOscControlKey(overlay.layerKey, layerKey);
}
bool RuntimeLiveState::TryResolveOverlayTarget(
const OscOverlayState& overlay,
const std::vector<RuntimeRenderState>& states,
std::vector<RuntimeRenderState>::const_iterator& stateIt,
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt)
{
stateIt = std::find_if(states.begin(), states.end(),
[&overlay](const RuntimeRenderState& state)
{
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
});
if (stateIt == states.end())
return false;
definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
[&overlay](const ShaderParameterDefinition& definition)
{
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
MatchesOscControlKey(definition.label, overlay.parameterKey);
});
return definitionIt != stateIt->parameterDefinitions.end();
}
std::size_t RuntimeLiveState::OverlayCount() const
{
return mOscOverlayStates.size();
}
void RuntimeLiveState::ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates)
{
const auto now = std::chrono::steady_clock::now();
for (const RuntimeLiveOscUpdate& update : updates)
{
auto overlayIt = mOscOverlayStates.find(update.routeKey);
if (overlayIt == mOscOverlayStates.end())
{
OscOverlayState overlay;
overlay.layerKey = update.layerKey;
overlay.parameterKey = update.parameterKey;
overlay.targetValue = update.targetValue;
overlay.lastUpdatedTime = now;
overlay.lastAppliedTime = now;
overlay.generation = 1;
mOscOverlayStates[update.routeKey] = std::move(overlay);
}
else
{
overlayIt->second.targetValue = update.targetValue;
overlayIt->second.lastUpdatedTime = now;
overlayIt->second.generation += 1;
overlayIt->second.commitQueued = false;
}
}
}
void RuntimeLiveState::ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits)
{
for (const RuntimeLiveOscCommitCompletion& completedCommit : completedCommits)
{
auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey);
if (overlayIt == mOscOverlayStates.end())
continue;
OscOverlayState& overlay = overlayIt->second;
if (overlay.commitQueued &&
overlay.pendingCommitGeneration == completedCommit.generation &&
overlay.generation == completedCommit.generation)
{
mOscOverlayStates.erase(overlayIt);
}
}
}
void RuntimeLiveState::PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states)
{
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
{
std::vector<RuntimeRenderState>::const_iterator stateIt;
std::vector<ShaderParameterDefinition>::const_iterator definitionIt;
if (TryResolveOverlayTarget(it->second, states, stateIt, definitionIt))
{
ShaderParameterValue targetValue;
std::string normalizeError;
if (NormalizeAndValidateParameterValue(*definitionIt, it->second.targetValue, targetValue, normalizeError))
{
++it;
continue;
}
}
it = mOscOverlayStates.erase(it);
}
}
void RuntimeLiveState::ApplyToLayerStates(
std::vector<RuntimeRenderState>& states,
const RuntimeLiveStateApplyOptions& options,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests)
{
if (states.empty() || mOscOverlayStates.empty())
return;
PruneIncompatibleOverlays(states);
if (mOscOverlayStates.empty())
return;
const auto now = options.now;
const double clampedSmoothing = ClampOscAlpha(options.smoothing);
std::vector<std::string> overlayKeysToRemove;
for (auto& item : mOscOverlayStates)
{
const std::string& routeKey = item.first;
OscOverlayState& overlay = item.second;
auto stateIt = std::find_if(states.begin(), states.end(),
[&overlay](const RuntimeRenderState& state)
{
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
MatchesOscControlKey(state.shaderName, overlay.layerKey);
});
if (stateIt == states.end())
continue;
auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
[&overlay](const ShaderParameterDefinition& definition)
{
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
MatchesOscControlKey(definition.label, overlay.parameterKey);
});
if (definitionIt == stateIt->parameterDefinitions.end())
continue;
ShaderParameterValue targetValue;
std::string normalizeError;
if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError))
continue;
if (definitionIt->type == ShaderParameterType::Trigger)
{
ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id];
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
const double triggerTime = stateIt->timeSeconds;
value.numberValues = { previousCount + 1.0, triggerTime };
overlayKeysToRemove.push_back(routeKey);
continue;
}
const bool smoothable =
clampedSmoothing > 0.0 &&
(definitionIt->type == ShaderParameterType::Float ||
definitionIt->type == ShaderParameterType::Vec2 ||
definitionIt->type == ShaderParameterType::Color);
if (!smoothable)
{
overlay.currentValue = targetValue;
overlay.hasCurrentValue = true;
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
if (options.allowCommit &&
!overlay.commitQueued &&
now - overlay.lastUpdatedTime >= options.commitDelay &&
commitRequests)
{
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation });
overlay.pendingCommitGeneration = overlay.generation;
overlay.commitQueued = true;
}
continue;
}
if (!overlay.hasCurrentValue)
{
overlay.currentValue = DefaultValueForDefinition(*definitionIt);
auto currentIt = stateIt->parameterValues.find(definitionIt->id);
if (currentIt != stateIt->parameterValues.end())
overlay.currentValue = currentIt->second;
overlay.hasCurrentValue = true;
}
if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
overlay.currentValue.numberValues = targetValue.numberValues;
double smoothingAlpha = clampedSmoothing;
if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point())
{
const double deltaSeconds =
std::chrono::duration_cast<std::chrono::duration<double>>(now - overlay.lastAppliedTime).count();
smoothingAlpha = ComputeTimeBasedOscAlpha(clampedSmoothing, deltaSeconds);
}
overlay.lastAppliedTime = now;
ShaderParameterValue nextValue = targetValue;
bool converged = true;
for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
{
const double currentNumber = overlay.currentValue.numberValues[index];
const double targetNumber = targetValue.numberValues[index];
const double delta = targetNumber - currentNumber;
double nextNumber = currentNumber + delta * smoothingAlpha;
if (std::fabs(delta) <= 0.0005)
nextNumber = targetNumber;
else
converged = false;
nextValue.numberValues[index] = nextNumber;
}
if (converged)
nextValue.numberValues = targetValue.numberValues;
overlay.currentValue = nextValue;
overlay.hasCurrentValue = true;
stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
if (options.allowCommit &&
converged &&
!overlay.commitQueued &&
now - overlay.lastUpdatedTime >= options.commitDelay &&
commitRequests)
{
commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, BuildOscCommitValue(*definitionIt, overlay.currentValue), overlay.generation });
overlay.pendingCommitGeneration = overlay.generation;
overlay.commitQueued = true;
}
}
for (const std::string& overlayKey : overlayKeysToRemove)
mOscOverlayStates.erase(overlayKey);
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderTypes.h"
#include <chrono>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
struct RuntimeLiveOscUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct RuntimeLiveOscCommitCompletion
{
std::string routeKey;
uint64_t generation = 0;
};
struct RuntimeLiveOscCommitRequest
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
struct RuntimeLiveStateApplyOptions
{
bool allowCommit = false;
double smoothing = 0.0;
std::chrono::milliseconds commitDelay = std::chrono::milliseconds(150);
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
};
class RuntimeLiveState
{
public:
void Clear();
void ClearForLayerKey(const std::string& layerKey);
std::size_t OverlayCount() const;
void ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates);
void ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits);
void PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states);
void ApplyToLayerStates(
std::vector<RuntimeRenderState>& states,
const RuntimeLiveStateApplyOptions& options,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests);
private:
struct OscOverlayState
{
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
ShaderParameterValue currentValue;
bool hasCurrentValue = false;
std::chrono::steady_clock::time_point lastUpdatedTime;
std::chrono::steady_clock::time_point lastAppliedTime;
uint64_t generation = 0;
uint64_t pendingCommitGeneration = 0;
bool commitQueued = false;
};
static bool OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey);
static bool TryResolveOverlayTarget(
const OscOverlayState& overlay,
const std::vector<RuntimeRenderState>& states,
std::vector<RuntimeRenderState>::const_iterator& stateIt,
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt);
std::map<std::string, OscOverlayState> mOscOverlayStates;
};

View File

@@ -0,0 +1,229 @@
#include "RuntimeStateLayerModel.h"
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind)
{
switch (kind)
{
case RuntimeStateLayerKind::BasePersisted:
return "base persisted";
case RuntimeStateLayerKind::CommittedLive:
return "committed live";
case RuntimeStateLayerKind::TransientAutomation:
return "transient automation";
case RuntimeStateLayerKind::RenderLocal:
return "render local";
case RuntimeStateLayerKind::HealthConfig:
return "health/config";
default:
return "unknown";
}
}
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind)
{
switch (kind)
{
case RuntimeStateLayerKind::BasePersisted:
return 0;
case RuntimeStateLayerKind::CommittedLive:
return 1;
case RuntimeStateLayerKind::TransientAutomation:
return 2;
case RuntimeStateLayerKind::RenderLocal:
case RuntimeStateLayerKind::HealthConfig:
default:
return -1;
}
}
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind)
{
return kind == RuntimeStateLayerKind::BasePersisted;
}
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind)
{
return RuntimeStateLayerCompositionPrecedence(kind) >= 0;
}
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind)
{
return kind == RuntimeStateLayerKind::RenderLocal;
}
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field)
{
switch (field)
{
case RuntimeStateField::PersistedLayerStack:
case RuntimeStateField::PersistedParameterValues:
case RuntimeStateField::StackPresets:
return RuntimeStateLayerKind::BasePersisted;
case RuntimeStateField::CommittedSessionParameterValues:
case RuntimeStateField::CommittedLayerBypass:
case RuntimeStateField::RuntimeCompileReloadFlags:
return RuntimeStateLayerKind::CommittedLive;
case RuntimeStateField::TransientOscOverlay:
case RuntimeStateField::TransientAutomationCommitState:
return RuntimeStateLayerKind::TransientAutomation;
case RuntimeStateField::RenderLocalTemporalHistory:
case RuntimeStateField::RenderLocalFeedbackState:
case RuntimeStateField::RenderLocalInputFrames:
case RuntimeStateField::RenderLocalOutputFrames:
return RuntimeStateLayerKind::RenderLocal;
case RuntimeStateField::RuntimeConfiguration:
case RuntimeStateField::HealthTelemetry:
default:
return RuntimeStateLayerKind::HealthConfig;
}
}
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory()
{
return {
{
RuntimeStateLayerKind::BasePersisted,
"Base persisted state",
"RuntimeStore / LayerStackStore",
"Survives restart",
"Written to disk",
"Default layer stack, shader selections, saved parameter values"
},
{
RuntimeStateLayerKind::CommittedLive,
"Committed live state",
"RuntimeCoordinator / CommittedLiveState",
"Current running session",
"May request persistence depending on mutation policy",
"Operator/session truth until changed again"
},
{
RuntimeStateLayerKind::TransientAutomation,
"Transient automation overlay",
"RuntimeLiveState / RuntimeServiceLiveBridge",
"High-rate and short-lived",
"Not persisted directly",
"Temporary OSC/automation target applied over committed truth"
},
{
RuntimeStateLayerKind::RenderLocal,
"Render-local state",
"RenderEngine",
"Render-thread/resource lifetime",
"Not persisted",
"Temporal history, feedback, input/output queues, and GL-local caches"
},
{
RuntimeStateLayerKind::HealthConfig,
"Health/config state",
"RuntimeConfigStore / HealthTelemetry",
"Config survives restart; health is observational",
"Config is file-backed; health is reported, not composed",
"Does not participate in parameter composition"
}
};
}
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory()
{
return {
{
RuntimeStateField::PersistedLayerStack,
ClassifyRuntimeStateField(RuntimeStateField::PersistedLayerStack),
"persisted layer stack",
"LayerStackStore",
"Durable layer order, ids, shader selections, and bypass flags"
},
{
RuntimeStateField::PersistedParameterValues,
ClassifyRuntimeStateField(RuntimeStateField::PersistedParameterValues),
"persisted parameter values",
"LayerStackStore",
"Saved parameter values used as the baseline for snapshots and presets"
},
{
RuntimeStateField::StackPresets,
ClassifyRuntimeStateField(RuntimeStateField::StackPresets),
"stack presets",
"RuntimeStore / LayerStackStore",
"Durable preset files and preset serialization shape"
},
{
RuntimeStateField::CommittedSessionParameterValues,
ClassifyRuntimeStateField(RuntimeStateField::CommittedSessionParameterValues),
"committed session parameter values",
"RuntimeCoordinator policy, CommittedLiveState backing",
"Operator/API truth after accepted mutations"
},
{
RuntimeStateField::CommittedLayerBypass,
ClassifyRuntimeStateField(RuntimeStateField::CommittedLayerBypass),
"committed layer bypass",
"RuntimeCoordinator policy, CommittedLiveState backing",
"Current operator/API bypass state"
},
{
RuntimeStateField::RuntimeCompileReloadFlags,
ClassifyRuntimeStateField(RuntimeStateField::RuntimeCompileReloadFlags),
"runtime compile/reload flags",
"RuntimeCoordinator / RuntimeUpdateController",
"Session coordination state used to request snapshot or render rebuild work"
},
{
RuntimeStateField::TransientOscOverlay,
ClassifyRuntimeStateField(RuntimeStateField::TransientOscOverlay),
"transient OSC overlays",
"RuntimeLiveState",
"High-rate automation values applied above committed state"
},
{
RuntimeStateField::TransientAutomationCommitState,
ClassifyRuntimeStateField(RuntimeStateField::TransientAutomationCommitState),
"transient automation commit state",
"RuntimeLiveState / RuntimeServiceLiveBridge",
"Generation and completion bookkeeping for settled overlay commits"
},
{
RuntimeStateField::RenderLocalTemporalHistory,
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalTemporalHistory),
"render-local temporal history",
"RenderEngine",
"GL/resource history that must stay out of parameter layering"
},
{
RuntimeStateField::RenderLocalFeedbackState,
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalFeedbackState),
"render-local feedback state",
"RenderEngine",
"Feedback buffers and ping-pong resources"
},
{
RuntimeStateField::RenderLocalInputFrames,
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalInputFrames),
"render-local input frames",
"RenderEngine",
"Latest accepted input frame payloads and upload staging"
},
{
RuntimeStateField::RenderLocalOutputFrames,
ClassifyRuntimeStateField(RuntimeStateField::RenderLocalOutputFrames),
"render-local output frames",
"RenderEngine",
"Readback, packed output, screenshot, and preview staging"
},
{
RuntimeStateField::RuntimeConfiguration,
ClassifyRuntimeStateField(RuntimeStateField::RuntimeConfiguration),
"runtime configuration",
"RuntimeConfigStore",
"File-backed config, not a live parameter layer"
},
{
RuntimeStateField::HealthTelemetry,
ClassifyRuntimeStateField(RuntimeStateField::HealthTelemetry),
"health telemetry",
"HealthTelemetry",
"Operational observations, not source state for render values"
}
};
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <vector>
enum class RuntimeStateLayerKind
{
BasePersisted,
CommittedLive,
TransientAutomation,
RenderLocal,
HealthConfig
};
enum class RuntimeStateField
{
PersistedLayerStack,
PersistedParameterValues,
StackPresets,
CommittedSessionParameterValues,
CommittedLayerBypass,
RuntimeCompileReloadFlags,
TransientOscOverlay,
TransientAutomationCommitState,
RenderLocalTemporalHistory,
RenderLocalFeedbackState,
RenderLocalInputFrames,
RenderLocalOutputFrames,
RuntimeConfiguration,
HealthTelemetry
};
struct RuntimeStateLayerDescriptor
{
RuntimeStateLayerKind kind = RuntimeStateLayerKind::BasePersisted;
const char* name = "";
const char* owner = "";
const char* lifetime = "";
const char* persistence = "";
const char* renderRole = "";
};
struct RuntimeStateFieldDescriptor
{
RuntimeStateField field = RuntimeStateField::PersistedLayerStack;
RuntimeStateLayerKind layerKind = RuntimeStateLayerKind::BasePersisted;
const char* name = "";
const char* currentOwner = "";
const char* notes = "";
};
const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind);
int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind);
bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind);
bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind);
bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind);
RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field);
std::vector<RuntimeStateLayerDescriptor> GetRuntimeStateLayerInventory();
std::vector<RuntimeStateFieldDescriptor> GetRuntimeStateFieldInventory();

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
enum class PersistenceTargetKind
{
RuntimeState,
StackPreset,
RuntimeConfig
};
struct PersistenceRequest
{
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
std::string reason;
std::string debounceKey = "runtime-state";
bool debounceAllowed = true;
bool flushRequested = false;
uint64_t sequence = 0;
static PersistenceRequest RuntimeStateRequest(const std::string& reason)
{
PersistenceRequest request;
request.targetKind = PersistenceTargetKind::RuntimeState;
request.reason = reason;
request.debounceKey = "runtime-state";
request.debounceAllowed = true;
return request;
}
};
struct PersistenceSnapshot
{
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
std::filesystem::path targetPath;
std::string contents;
std::string reason;
std::string debounceKey;
bool debounceAllowed = false;
bool flushRequested = false;
uint64_t generation = 0;
};

View File

@@ -0,0 +1,271 @@
#include "PersistenceWriter.h"
#include <windows.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <utility>
PersistenceWriter::PersistenceWriter(std::chrono::milliseconds debounceDelay, SnapshotSink sink) :
mDebounceDelay(debounceDelay),
mSink(std::move(sink))
{
}
PersistenceWriter::~PersistenceWriter()
{
std::string error;
StopAndFlush((std::chrono::milliseconds::max)(), error);
}
void PersistenceWriter::SetResultCallback(ResultCallback callback)
{
std::lock_guard<std::mutex> lock(mMutex);
mResultCallback = std::move(callback);
}
bool PersistenceWriter::WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
{
if (!ValidateSnapshot(snapshot, error))
return false;
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
PublishWriteResult(snapshot, succeeded, error, false);
return succeeded;
}
bool PersistenceWriter::EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error)
{
if (!ValidateSnapshot(snapshot, error))
return false;
std::lock_guard<std::mutex> lock(mMutex);
if (!mAcceptingRequests)
{
error = "Persistence writer is stopping.";
return false;
}
StartWorkerLocked();
const auto now = std::chrono::steady_clock::now();
if (snapshot.debounceAllowed)
{
const std::string debounceKey = snapshot.debounceKey.empty() ? snapshot.targetPath.string() : snapshot.debounceKey;
PendingSnapshot& pending = mDebouncedSnapshots[debounceKey];
if (!pending.snapshot.targetPath.empty())
++mCoalescedCount;
else
++mEnqueuedCount;
pending.snapshot = snapshot;
pending.readyAt = snapshot.flushRequested ? now : now + mDebounceDelay;
}
else
{
mImmediateSnapshots.push_back(snapshot);
++mEnqueuedCount;
}
mCondition.notify_one();
return true;
}
bool PersistenceWriter::StopAndFlush(std::chrono::milliseconds timeout, std::string& error)
{
{
std::lock_guard<std::mutex> lock(mMutex);
mAcceptingRequests = false;
mStopping = true;
const auto now = std::chrono::steady_clock::now();
for (auto& entry : mDebouncedSnapshots)
entry.second.readyAt = now;
}
mCondition.notify_all();
std::unique_lock<std::mutex> lock(mMutex);
if (mWorkerRunning)
{
if (timeout == (std::chrono::milliseconds::max)())
{
mCondition.wait(lock, [this]() { return !mWorkerRunning; });
}
else
{
const auto deadline = std::chrono::steady_clock::now() + timeout;
if (!mCondition.wait_until(lock, deadline, [this]() { return !mWorkerRunning; }))
{
error = "Timed out while flushing persistence writer.";
return false;
}
}
}
lock.unlock();
if (mWorker.joinable())
mWorker.join();
return true;
}
PersistenceWriterMetrics PersistenceWriter::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
PersistenceWriterMetrics metrics;
metrics.pendingCount = PendingCountLocked();
metrics.enqueuedCount = mEnqueuedCount;
metrics.coalescedCount = mCoalescedCount;
metrics.writtenCount = mWrittenCount;
metrics.failedCount = mFailedCount;
return metrics;
}
bool PersistenceWriter::ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const
{
if (snapshot.targetPath.empty())
{
error = "Persistence snapshot target path is empty.";
return false;
}
return true;
}
bool PersistenceWriter::WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const
{
if (mSink)
return mSink(snapshot, error);
std::error_code fsError;
std::filesystem::create_directories(snapshot.targetPath.parent_path(), fsError);
const std::filesystem::path temporaryPath = snapshot.targetPath.string() + ".tmp";
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
if (!output)
{
error = "Could not write file: " + temporaryPath.string();
return false;
}
output << snapshot.contents;
output.close();
if (!output.good())
{
error = "Could not finish writing file: " + temporaryPath.string();
return false;
}
if (!MoveFileExA(temporaryPath.string().c_str(), snapshot.targetPath.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
{
const DWORD lastError = GetLastError();
std::filesystem::remove(temporaryPath, fsError);
error = "Could not replace file: " + snapshot.targetPath.string() + " (Win32 error " + std::to_string(lastError) + ")";
return false;
}
return true;
}
void PersistenceWriter::PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending)
{
ResultCallback callback;
{
std::lock_guard<std::mutex> lock(mMutex);
callback = mResultCallback;
}
if (!callback)
return;
PersistenceWriteResult result;
result.targetKind = snapshot.targetKind;
result.targetPath = snapshot.targetPath.string();
result.reason = snapshot.reason;
result.succeeded = succeeded;
result.errorMessage = errorMessage;
result.newerRequestPending = newerRequestPending;
callback(result);
}
void PersistenceWriter::StartWorkerLocked()
{
if (mWorkerRunning)
return;
mWorkerRunning = true;
mWorker = std::thread([this]() { WorkerMain(); });
}
void PersistenceWriter::WorkerMain()
{
for (;;)
{
PersistenceSnapshot snapshot;
{
std::unique_lock<std::mutex> lock(mMutex);
for (;;)
{
if (!mImmediateSnapshots.empty())
{
snapshot = std::move(mImmediateSnapshots.front());
mImmediateSnapshots.pop_front();
break;
}
if (!mDebouncedSnapshots.empty())
{
const auto now = std::chrono::steady_clock::now();
auto readyIt = mDebouncedSnapshots.end();
auto nextReadyAt = (std::chrono::steady_clock::time_point::max)();
for (auto it = mDebouncedSnapshots.begin(); it != mDebouncedSnapshots.end(); ++it)
{
if (it->second.readyAt <= now)
{
readyIt = it;
break;
}
if (it->second.readyAt < nextReadyAt)
nextReadyAt = it->second.readyAt;
}
if (readyIt != mDebouncedSnapshots.end())
{
snapshot = std::move(readyIt->second.snapshot);
mDebouncedSnapshots.erase(readyIt);
break;
}
mCondition.wait_until(lock, nextReadyAt);
continue;
}
if (mStopping)
{
mWorkerRunning = false;
mCondition.notify_all();
return;
}
mCondition.wait(lock);
}
}
std::string error;
const bool succeeded = WriteSnapshotThroughSink(snapshot, error);
bool newerRequestPending = false;
{
std::lock_guard<std::mutex> lock(mMutex);
if (succeeded)
++mWrittenCount;
else
++mFailedCount;
newerRequestPending = PendingCountLocked() > 0;
}
PublishWriteResult(snapshot, succeeded, error, newerRequestPending);
}
}
std::size_t PersistenceWriter::PendingCountLocked() const
{
return mImmediateSnapshots.size() + mDebouncedSnapshots.size();
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include "PersistenceRequest.h"
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
struct PersistenceWriterMetrics
{
std::size_t pendingCount = 0;
uint64_t enqueuedCount = 0;
uint64_t coalescedCount = 0;
uint64_t writtenCount = 0;
uint64_t failedCount = 0;
};
struct PersistenceWriteResult
{
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
std::string targetPath;
std::string reason;
bool succeeded = false;
std::string errorMessage;
bool newerRequestPending = false;
};
class PersistenceWriter
{
public:
using SnapshotSink = std::function<bool(const PersistenceSnapshot&, std::string&)>;
using ResultCallback = std::function<void(const PersistenceWriteResult&)>;
explicit PersistenceWriter(
std::chrono::milliseconds debounceDelay = std::chrono::milliseconds(50),
SnapshotSink sink = SnapshotSink());
~PersistenceWriter();
void SetResultCallback(ResultCallback callback);
bool WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
bool EnqueueSnapshot(const PersistenceSnapshot& snapshot, std::string& error);
bool StopAndFlush(std::chrono::milliseconds timeout, std::string& error);
PersistenceWriterMetrics GetMetrics() const;
private:
struct PendingSnapshot
{
PersistenceSnapshot snapshot;
std::chrono::steady_clock::time_point readyAt;
};
bool ValidateSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const;
bool WriteSnapshotThroughSink(const PersistenceSnapshot& snapshot, std::string& error) const;
void PublishWriteResult(const PersistenceSnapshot& snapshot, bool succeeded, const std::string& errorMessage, bool newerRequestPending);
void StartWorkerLocked();
void WorkerMain();
std::size_t PendingCountLocked() const;
std::chrono::milliseconds mDebounceDelay;
SnapshotSink mSink;
ResultCallback mResultCallback;
mutable std::mutex mMutex;
std::condition_variable mCondition;
std::thread mWorker;
bool mWorkerRunning = false;
bool mStopping = false;
bool mAcceptingRequests = true;
std::unordered_map<std::string, PendingSnapshot> mDebouncedSnapshots;
std::deque<PersistenceSnapshot> mImmediateSnapshots;
uint64_t mEnqueuedCount = 0;
uint64_t mCoalescedCount = 0;
uint64_t mWrittenCount = 0;
uint64_t mFailedCount = 0;
};

View File

@@ -0,0 +1,170 @@
#include "RuntimeStateJson.h"
#include "RuntimeParameterUtils.h"
namespace
{
std::string ShaderParameterTypeToString(ShaderParameterType type)
{
switch (type)
{
case ShaderParameterType::Float: return "float";
case ShaderParameterType::Vec2: return "vec2";
case ShaderParameterType::Color: return "color";
case ShaderParameterType::Boolean: return "bool";
case ShaderParameterType::Enum: return "enum";
case ShaderParameterType::Text: return "text";
case ShaderParameterType::Trigger: return "trigger";
}
return "unknown";
}
}
JsonValue RuntimeStateJson::SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog)
{
std::map<std::string, ShaderPackage> packagesById;
for (const std::string& packageId : shaderCatalog.PackageOrder())
{
ShaderPackage shaderPackage;
if (shaderCatalog.CopyPackage(packageId, shaderPackage))
packagesById[packageId] = shaderPackage;
}
return SerializeLayerStack(layerStack.Layers(), packagesById);
}
JsonValue RuntimeStateJson::SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layerStates, const std::map<std::string, ShaderPackage>& packagesById)
{
JsonValue layersValue = JsonValue::MakeArray();
for (const LayerStackStore::LayerPersistentState& layer : layerStates)
{
auto shaderIt = packagesById.find(layer.shaderId);
if (shaderIt == packagesById.end())
continue;
const ShaderPackage& shaderPackage = shaderIt->second;
JsonValue layerValue = JsonValue::MakeObject();
layerValue.set("id", JsonValue(layer.id));
layerValue.set("shaderId", JsonValue(layer.shaderId));
layerValue.set("shaderName", JsonValue(shaderPackage.displayName));
layerValue.set("bypass", JsonValue(layer.bypass));
if (shaderPackage.temporal.enabled)
{
JsonValue temporal = JsonValue::MakeObject();
temporal.set("enabled", JsonValue(true));
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
layerValue.set("temporal", temporal);
}
if (shaderPackage.feedback.enabled)
{
JsonValue feedback = JsonValue::MakeObject();
feedback.set("enabled", JsonValue(true));
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
layerValue.set("feedback", feedback);
}
JsonValue parameters = JsonValue::MakeArray();
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{
JsonValue parameter = JsonValue::MakeObject();
parameter.set("id", JsonValue(definition.id));
parameter.set("label", JsonValue(definition.label));
if (!definition.description.empty())
parameter.set("description", JsonValue(definition.description));
parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type)));
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
if (!definition.minNumbers.empty())
{
JsonValue minValue = JsonValue::MakeArray();
for (double number : definition.minNumbers)
minValue.pushBack(JsonValue(number));
parameter.set("min", minValue);
}
if (!definition.maxNumbers.empty())
{
JsonValue maxValue = JsonValue::MakeArray();
for (double number : definition.maxNumbers)
maxValue.pushBack(JsonValue(number));
parameter.set("max", maxValue);
}
if (!definition.stepNumbers.empty())
{
JsonValue stepValue = JsonValue::MakeArray();
for (double number : definition.stepNumbers)
stepValue.pushBack(JsonValue(number));
parameter.set("step", stepValue);
}
if (definition.type == ShaderParameterType::Enum)
{
JsonValue options = JsonValue::MakeArray();
for (const ShaderParameterOption& option : definition.enumOptions)
{
JsonValue optionValue = JsonValue::MakeObject();
optionValue.set("value", JsonValue(option.value));
optionValue.set("label", JsonValue(option.label));
options.pushBack(optionValue);
}
parameter.set("options", options);
}
if (definition.type == ShaderParameterType::Text)
{
parameter.set("maxLength", JsonValue(static_cast<double>(definition.maxLength)));
if (!definition.fontId.empty())
parameter.set("font", JsonValue(definition.fontId));
}
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
parameter.set("value", SerializeParameterValue(definition, value));
parameters.pushBack(parameter);
}
layerValue.set("parameters", parameters);
layersValue.pushBack(layerValue);
}
return layersValue;
}
JsonValue RuntimeStateJson::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
switch (definition.type)
{
case ShaderParameterType::Boolean:
return JsonValue(value.booleanValue);
case ShaderParameterType::Enum:
return JsonValue(value.enumValue);
case ShaderParameterType::Text:
return JsonValue(value.textValue);
case ShaderParameterType::Trigger:
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
case ShaderParameterType::Float:
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
{
JsonValue array = JsonValue::MakeArray();
for (double number : value.numberValues)
array.pushBack(JsonValue(number));
return array;
}
}
return JsonValue();
}
std::string RuntimeStateJson::TemporalHistorySourceToString(TemporalHistorySource source)
{
switch (source)
{
case TemporalHistorySource::Source:
return "source";
case TemporalHistorySource::PreLayerInput:
return "preLayerInput";
case TemporalHistorySource::None:
default:
return "none";
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "LayerStackStore.h"
#include "RuntimeJson.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <map>
#include <string>
#include <vector>
class RuntimeStateJson
{
public:
static JsonValue SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog);
static JsonValue SerializeLayerStack(const std::vector<LayerStackStore::LayerPersistentState>& layers, const std::map<std::string, ShaderPackage>& packagesById);
static JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value);
static std::string TemporalHistorySourceToString(TemporalHistorySource source);
};

View File

@@ -0,0 +1,230 @@
#include "RuntimeStatePresenter.h"
#include "RuntimeStateJson.h"
#include "RuntimeStore.h"
std::string RuntimeStatePresenter::BuildRuntimeStateJson(const RuntimeStore& runtimeStore)
{
return SerializeJson(BuildRuntimeStateValue(runtimeStore), true);
}
JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runtimeStore)
{
const RuntimeStatePresentationReadModel model = runtimeStore.BuildRuntimeStatePresentationReadModel();
const HealthTelemetry::Snapshot& telemetrySnapshot = model.telemetry;
JsonValue root = JsonValue::MakeObject();
JsonValue app = JsonValue::MakeObject();
app.set("serverPort", JsonValue(static_cast<double>(model.serverPort)));
app.set("oscPort", JsonValue(static_cast<double>(model.config.oscPort)));
app.set("oscBindAddress", JsonValue(model.config.oscBindAddress));
app.set("oscSmoothing", JsonValue(model.config.oscSmoothing));
app.set("autoReload", JsonValue(model.autoReloadEnabled));
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(model.config.maxTemporalHistoryFrames)));
app.set("previewFps", JsonValue(static_cast<double>(model.config.previewFps)));
app.set("enableExternalKeying", JsonValue(model.config.enableExternalKeying));
app.set("inputVideoFormat", JsonValue(model.config.inputVideoFormat));
app.set("inputFrameRate", JsonValue(model.config.inputFrameRate));
app.set("outputVideoFormat", JsonValue(model.config.outputVideoFormat));
app.set("outputFrameRate", JsonValue(model.config.outputFrameRate));
root.set("app", app);
JsonValue runtime = JsonValue::MakeObject();
runtime.set("layerCount", JsonValue(static_cast<double>(model.layerStack.LayerCount())));
runtime.set("compileSucceeded", JsonValue(model.compileSucceeded));
runtime.set("compileMessage", JsonValue(model.compileMessage));
root.set("runtime", runtime);
JsonValue video = JsonValue::MakeObject();
video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
root.set("video", video);
JsonValue deckLink = JsonValue::MakeObject();
deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
deckLink.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
deckLink.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
deckLink.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
deckLink.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
deckLink.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
root.set("decklink", deckLink);
JsonValue videoIO = JsonValue::MakeObject();
videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
root.set("videoIO", videoIO);
JsonValue performance = JsonValue::MakeObject();
performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
performance.set("budgetUsedPercent", JsonValue(
telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
: 0.0));
performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
root.set("performance", performance);
JsonValue readyQueue = JsonValue::MakeObject();
readyQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDepth)));
readyQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueCapacity)));
readyQueue.set("minDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.minReadyQueueDepth)));
readyQueue.set("maxDepth", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.maxReadyQueueDepth)));
readyQueue.set("zeroDepthCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueZeroDepthCount)));
readyQueue.set("pushedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePushedCount)));
readyQueue.set("poppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueuePoppedCount)));
readyQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueDroppedCount)));
readyQueue.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.readyQueueUnderrunCount)));
JsonValue systemMemory = JsonValue::MakeObject();
systemMemory.set("freeFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolFree)));
systemMemory.set("readyFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolReady)));
systemMemory.set("scheduledFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFramePoolScheduled)));
systemMemory.set("underrunCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameUnderrunCount)));
systemMemory.set("repeatCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameRepeatCount)));
systemMemory.set("dropCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.systemFrameDropCount)));
systemMemory.set("ageAtScheduleMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtScheduleMilliseconds));
systemMemory.set("ageAtCompletionMs", JsonValue(telemetrySnapshot.backendPlayout.systemFrameAgeAtCompletionMilliseconds));
JsonValue outputRender = JsonValue::MakeObject();
outputRender.set("renderMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderMilliseconds));
outputRender.set("smoothedRenderMs", JsonValue(telemetrySnapshot.backendPlayout.smoothedOutputRenderMilliseconds));
outputRender.set("maxRenderMs", JsonValue(telemetrySnapshot.backendPlayout.maxOutputRenderMilliseconds));
outputRender.set("acquireFrameMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameAcquireMilliseconds));
outputRender.set("renderRequestMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameRenderRequestMilliseconds));
outputRender.set("endAccessMs", JsonValue(telemetrySnapshot.backendPlayout.outputFrameEndAccessMilliseconds));
outputRender.set("queueWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderQueueWaitMilliseconds));
outputRender.set("drawMs", JsonValue(telemetrySnapshot.backendPlayout.outputRenderDrawMilliseconds));
outputRender.set("fenceWaitMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackFenceWaitMilliseconds));
outputRender.set("mapMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackMapMilliseconds));
outputRender.set("readbackCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputReadbackCopyMilliseconds));
outputRender.set("cachedCopyMs", JsonValue(telemetrySnapshot.backendPlayout.outputCachedCopyMilliseconds));
outputRender.set("asyncQueueMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueMilliseconds));
outputRender.set("asyncQueueBufferMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueBufferMilliseconds));
outputRender.set("asyncQueueSetupMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueSetupMilliseconds));
outputRender.set("asyncQueueReadPixelsMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueReadPixelsMilliseconds));
outputRender.set("asyncQueueFenceMs", JsonValue(telemetrySnapshot.backendPlayout.outputAsyncQueueFenceMilliseconds));
outputRender.set("syncReadMs", JsonValue(telemetrySnapshot.backendPlayout.outputSyncReadMilliseconds));
outputRender.set("asyncReadbackMissCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputAsyncReadbackMissCount)));
outputRender.set("cachedFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputCachedFallbackCount)));
outputRender.set("syncFallbackCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.outputSyncFallbackCount)));
JsonValue recovery = JsonValue::MakeObject();
recovery.set("completionResult", JsonValue(telemetrySnapshot.backendPlayout.completionResult));
recovery.set("completedFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.completedFrameIndex)));
recovery.set("scheduledFrameIndex", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledFrameIndex)));
recovery.set("scheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
recovery.set("syntheticScheduledLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
recovery.set("measuredLagFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.measuredLagFrames)));
recovery.set("catchUpFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.catchUpFrames)));
recovery.set("lateStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateStreak)));
recovery.set("dropStreak", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.dropStreak)));
JsonValue deckLinkPlayout = JsonValue::MakeObject();
deckLinkPlayout.set("actualBufferedFramesAvailable", JsonValue(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFramesAvailable));
deckLinkPlayout.set("actualBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.actualDeckLinkBufferedFrames)));
deckLinkPlayout.set("targetBufferedFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.targetDeckLinkBufferedFrames)));
deckLinkPlayout.set("scheduleCallMs", JsonValue(telemetrySnapshot.backendPlayout.deckLinkScheduleCallMilliseconds));
deckLinkPlayout.set("scheduleFailures", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.deckLinkScheduleFailureCount)));
JsonValue scheduler = JsonValue::MakeObject();
scheduler.set("syntheticLeadFrames", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.scheduledLeadFrames)));
JsonValue backendPlayout = JsonValue::MakeObject();
backendPlayout.set("lifecycleState", JsonValue(telemetrySnapshot.backendPlayout.lifecycleState));
backendPlayout.set("degraded", JsonValue(telemetrySnapshot.backendPlayout.degraded));
backendPlayout.set("statusMessage", JsonValue(telemetrySnapshot.backendPlayout.statusMessage));
backendPlayout.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.lateFrameCount)));
backendPlayout.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.droppedFrameCount)));
backendPlayout.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.backendPlayout.flushedFrameCount)));
backendPlayout.set("readyQueue", readyQueue);
backendPlayout.set("systemMemory", systemMemory);
backendPlayout.set("outputRender", outputRender);
backendPlayout.set("decklink", deckLinkPlayout);
backendPlayout.set("scheduler", scheduler);
backendPlayout.set("recovery", recovery);
root.set("backendPlayout", backendPlayout);
JsonValue eventQueue = JsonValue::MakeObject();
eventQueue.set("name", JsonValue(telemetrySnapshot.runtimeEvents.queue.queueName));
eventQueue.set("depth", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.depth)));
eventQueue.set("capacity", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.capacity)));
eventQueue.set("droppedCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.queue.droppedCount)));
eventQueue.set("oldestEventAgeMs", JsonValue(telemetrySnapshot.runtimeEvents.queue.oldestEventAgeMilliseconds));
JsonValue eventDispatch = JsonValue::MakeObject();
eventDispatch.set("dispatchCallCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchCallCount)));
eventDispatch.set("dispatchedEventCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.dispatchedEventCount)));
eventDispatch.set("handlerInvocationCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerInvocationCount)));
eventDispatch.set("handlerFailureCount", JsonValue(static_cast<double>(telemetrySnapshot.runtimeEvents.dispatch.handlerFailureCount)));
eventDispatch.set("lastDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.lastDispatchDurationMilliseconds));
eventDispatch.set("maxDispatchDurationMs", JsonValue(telemetrySnapshot.runtimeEvents.dispatch.maxDispatchDurationMilliseconds));
JsonValue runtimeEvents = JsonValue::MakeObject();
runtimeEvents.set("queue", eventQueue);
runtimeEvents.set("dispatch", eventDispatch);
root.set("runtimeEvents", runtimeEvents);
JsonValue shaderLibrary = JsonValue::MakeArray();
for (const ShaderPackageStatus& status : model.packageStatuses)
{
JsonValue shader = JsonValue::MakeObject();
shader.set("id", JsonValue(status.id));
shader.set("name", JsonValue(status.displayName));
shader.set("description", JsonValue(status.description));
shader.set("category", JsonValue(status.category));
shader.set("available", JsonValue(status.available));
if (!status.available)
shader.set("error", JsonValue(status.error));
auto shaderIt = model.shaderCatalog.packagesById.find(status.id);
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.temporal.enabled)
{
const ShaderPackage& shaderPackage = shaderIt->second;
JsonValue temporal = JsonValue::MakeObject();
temporal.set("enabled", JsonValue(true));
temporal.set("historySource", JsonValue(RuntimeStateJson::TemporalHistorySourceToString(shaderPackage.temporal.historySource)));
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.requestedHistoryLength)));
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage.temporal.effectiveHistoryLength)));
shader.set("temporal", temporal);
}
if (status.available && shaderIt != model.shaderCatalog.packagesById.end() && shaderIt->second.feedback.enabled)
{
const ShaderPackage& shaderPackage = shaderIt->second;
JsonValue feedback = JsonValue::MakeObject();
feedback.set("enabled", JsonValue(true));
feedback.set("writePass", JsonValue(shaderPackage.feedback.writePassId));
shader.set("feedback", feedback);
}
shaderLibrary.pushBack(shader);
}
root.set("shaders", shaderLibrary);
JsonValue stackPresets = JsonValue::MakeArray();
for (const std::string& presetName : model.stackPresetNames)
stackPresets.pushBack(JsonValue(presetName));
root.set("stackPresets", stackPresets);
root.set("layers", RuntimeStateJson::SerializeLayerStack(model.layerStack.Layers(), model.shaderCatalog.packagesById));
return root;
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "RuntimeJson.h"
#include <string>
class RuntimeStore;
class RuntimeStatePresenter
{
public:
static std::string BuildRuntimeStateJson(const RuntimeStore& runtimeStore);
static JsonValue BuildRuntimeStateValue(const RuntimeStore& runtimeStore);
};

View File

@@ -0,0 +1,193 @@
#include "RenderSnapshotBuilder.h"
#include "RuntimeClock.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStore.h"
#include "ShaderCompiler.h"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <mutex>
#include <utility>
RenderSnapshotBuilder::RenderSnapshotBuilder(RuntimeStore& runtimeStore) :
mRuntimeStore(runtimeStore)
{
}
bool RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{
try
{
ShaderPackage shaderPackage;
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
return false;
const ShaderCompilerInputs inputs = mRuntimeStore.GetShaderCompilerInputs();
ShaderCompiler compiler(
inputs.repoRoot,
inputs.wrapperPath,
inputs.generatedGlslPath,
inputs.patchedGlslPath,
inputs.maxTemporalHistoryFrames);
passSources.clear();
passSources.reserve(shaderPackage.passes.size());
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
ShaderPassBuildSource passSource;
passSource.passId = pass.id;
passSource.inputNames = pass.inputNames;
passSource.outputName = pass.outputName;
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
return false;
passSources.push_back(std::move(passSource));
}
return true;
}
catch (const std::exception& exception)
{
error = std::string("RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
return false;
}
}
unsigned RenderSnapshotBuilder::GetMaxTemporalHistoryFrames() const
{
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
}
RuntimeSnapshotVersions RenderSnapshotBuilder::GetVersions() const
{
RuntimeSnapshotVersions versions;
versions.renderStateVersion = mRenderStateVersion.load(std::memory_order_relaxed);
versions.parameterStateVersion = mParameterStateVersion.load(std::memory_order_relaxed);
return versions;
}
void RenderSnapshotBuilder::AdvanceFrame()
{
++mFrameCounter;
}
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
}
bool RenderSnapshotBuilder::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
BuildLayerRenderStates(outputWidth, outputHeight, mRuntimeStore.BuildRenderSnapshotReadModel(), states);
return true;
}
bool RenderSnapshotBuilder::TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const
{
RefreshLayerParameters(mRuntimeStore.CopyCommittedLiveLayerStates(), states);
return true;
}
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
RefreshDynamicRenderStateFields(mRuntimeStore.GetRenderTimingSnapshot(), states);
}
void RenderSnapshotBuilder::MarkRenderStateDirty()
{
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
}
void RenderSnapshotBuilder::MarkParameterStateDirty()
{
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
}
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const
{
states.clear();
for (const LayerStackStore::LayerPersistentState& layer : readModel.committedLiveState.layers)
{
auto shaderIt = readModel.committedLiveState.packagesById.find(layer.shaderId);
if (shaderIt == readModel.committedLiveState.packagesById.end())
continue;
const ShaderPackage& shaderPackage = shaderIt->second;
RuntimeRenderState state;
state.layerId = layer.id;
state.shaderId = layer.shaderId;
state.shaderName = shaderPackage.displayName;
state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = readModel.signalStatus.width;
state.inputHeight = readModel.signalStatus.height;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderPackage.parameters;
state.textureAssets = shaderPackage.textureAssets;
state.fontAssets = shaderPackage.fontAssets;
state.isTemporal = shaderPackage.temporal.enabled;
state.temporalHistorySource = shaderPackage.temporal.historySource;
state.requestedTemporalHistoryLength = shaderPackage.temporal.requestedHistoryLength;
state.effectiveTemporalHistoryLength = shaderPackage.temporal.effectiveHistoryLength;
state.feedback = shaderPackage.feedback;
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
states.push_back(state);
}
RefreshDynamicRenderStateFields(readModel.timing, states);
}
void RenderSnapshotBuilder::RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const
{
for (RuntimeRenderState& state : states)
{
const auto layerIt = std::find_if(layers.begin(), layers.end(),
[&state](const LayerStackStore::LayerPersistentState& layer) { return layer.id == state.layerId; });
if (layerIt == layers.end())
continue;
state.bypass = layerIt->bypass ? 1.0 : 0.0;
state.parameterValues.clear();
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layerIt->parameterValues.find(definition.id);
if (valueIt != layerIt->parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
}
}
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const
{
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - timing.startTime).count();
const double frameCount = static_cast<double>(mFrameCounter.load(std::memory_order_relaxed));
for (RuntimeRenderState& state : states)
{
state.timeSeconds = timeSeconds;
state.utcTimeSeconds = clock.utcTimeSeconds;
state.utcOffsetSeconds = clock.utcOffsetSeconds;
state.startupRandom = timing.startupRandom;
state.frameCount = frameCount;
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include "RuntimeStoreReadModels.h"
#include "ShaderTypes.h"
#include <atomic>
#include <cstdint>
#include <string>
#include <vector>
class RuntimeStore;
struct RuntimeSnapshotVersions
{
uint64_t renderStateVersion = 0;
uint64_t parameterStateVersion = 0;
};
class RenderSnapshotBuilder
{
public:
explicit RenderSnapshotBuilder(RuntimeStore& runtimeStore);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
unsigned GetMaxTemporalHistoryFrames() const;
RuntimeSnapshotVersions GetVersions() const;
void AdvanceFrame();
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
void MarkRenderStateDirty();
void MarkParameterStateDirty();
private:
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, const RenderSnapshotReadModel& readModel, std::vector<RuntimeRenderState>& states) const;
void RefreshLayerParameters(const std::vector<LayerStackStore::LayerPersistentState>& layers, std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(const RenderTimingSnapshot& timing, std::vector<RuntimeRenderState>& states) const;
RuntimeStore& mRuntimeStore;
std::atomic<uint64_t> mFrameCounter{ 0 };
std::atomic<uint64_t> mRenderStateVersion{ 0 };
std::atomic<uint64_t> mParameterStateVersion{ 0 };
};

View File

@@ -0,0 +1,196 @@
#include "RuntimeSnapshotProvider.h"
#include "RuntimeEventDispatcher.h"
#include <windows.h>
#include <utility>
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher) :
mRenderSnapshotBuilder(renderSnapshotBuilder),
mRuntimeEventDispatcher(runtimeEventDispatcher)
{
}
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{
try
{
return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
}
catch (const std::exception& exception)
{
error = std::string("RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
return false;
}
}
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
{
return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
}
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{
return mRenderSnapshotBuilder.GetVersions();
}
void RuntimeSnapshotProvider::AdvanceFrame()
{
mRenderSnapshotBuilder.AdvanceFrame();
}
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
{
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "publish-render-state-snapshot");
for (;;)
{
const RuntimeSnapshotVersions versionsBefore = GetVersions();
RuntimeRenderStateSnapshot publishedSnapshot;
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, publishedSnapshot))
{
PublishRenderSnapshotPublished(publishedSnapshot);
return publishedSnapshot;
}
RuntimeRenderStateSnapshot snapshot;
snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight;
mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
{
snapshot.versions = versionsAfter;
StorePublishedRenderStateSnapshot(snapshot);
PublishRenderSnapshotPublished(snapshot);
return snapshot;
}
}
}
bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
{
PublishRenderSnapshotPublishRequested(outputWidth, outputHeight, "try-publish-render-state-snapshot");
const RuntimeSnapshotVersions versionsBefore = GetVersions();
if (TryGetPublishedRenderStateSnapshot(outputWidth, outputHeight, versionsBefore, snapshot))
{
PublishRenderSnapshotPublished(snapshot);
return true;
}
std::vector<RuntimeRenderState> states;
if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
return false;
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
{
return false;
}
snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight;
snapshot.versions = versionsAfter;
snapshot.states = std::move(states);
StorePublishedRenderStateSnapshot(snapshot);
PublishRenderSnapshotPublished(snapshot);
return true;
}
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
{
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
return false;
const RuntimeSnapshotVersions versions = GetVersions();
if (versions.renderStateVersion != expectedRenderStateVersion)
return false;
snapshot.versions = versions;
StorePublishedRenderStateSnapshot(snapshot);
PublishRenderSnapshotPublished(snapshot);
return true;
}
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
}
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const
{
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
if (!mHasPublishedRenderStateSnapshot ||
!SnapshotMatches(mPublishedRenderStateSnapshot, outputWidth, outputHeight, versions))
{
return false;
}
snapshot = mPublishedRenderStateSnapshot;
return true;
}
void RuntimeSnapshotProvider::StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const
{
std::lock_guard<std::mutex> lock(mPublishedSnapshotMutex);
mPublishedRenderStateSnapshot = snapshot;
mHasPublishedRenderStateSnapshot = true;
}
bool RuntimeSnapshotProvider::SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
const RuntimeSnapshotVersions& versions)
{
return snapshot.outputWidth == outputWidth &&
snapshot.outputHeight == outputHeight &&
snapshot.versions.renderStateVersion == versions.renderStateVersion &&
snapshot.versions.parameterStateVersion == versions.parameterStateVersion;
}
void RuntimeSnapshotProvider::PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const
{
try
{
RenderSnapshotPublishRequestedEvent event;
event.outputWidth = outputWidth;
event.outputHeight = outputHeight;
event.reason = reason;
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
OutputDebugStringA("RenderSnapshotPublishRequested event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("RenderSnapshotPublishRequested event publish threw.\n");
}
}
void RuntimeSnapshotProvider::PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const
{
try
{
RenderSnapshotPublishedEvent event;
event.snapshotVersion = snapshot.versions.renderStateVersion;
event.structureVersion = snapshot.versions.renderStateVersion;
event.parameterVersion = snapshot.versions.parameterStateVersion;
event.outputWidth = snapshot.outputWidth;
event.outputHeight = snapshot.outputHeight;
event.layerCount = snapshot.states.size();
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeSnapshotProvider"))
OutputDebugStringA("RenderSnapshotPublished event publish failed.\n");
}
catch (...)
{
OutputDebugStringA("RenderSnapshotPublished event publish threw.\n");
}
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "RenderSnapshotBuilder.h"
#include <mutex>
#include <string>
#include <vector>
class RuntimeEventDispatcher;
struct RuntimeRenderStateSnapshot
{
RuntimeSnapshotVersions versions;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::vector<RuntimeRenderState> states;
};
class RuntimeSnapshotProvider
{
public:
RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder, RuntimeEventDispatcher& runtimeEventDispatcher);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
unsigned GetMaxTemporalHistoryFrames() const;
RuntimeSnapshotVersions GetVersions() const;
void AdvanceFrame();
RuntimeRenderStateSnapshot PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const;
bool TryPublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const;
bool TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
private:
bool TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
const RuntimeSnapshotVersions& versions, RuntimeRenderStateSnapshot& snapshot) const;
void StorePublishedRenderStateSnapshot(const RuntimeRenderStateSnapshot& snapshot) const;
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
const RuntimeSnapshotVersions& versions);
void PublishRenderSnapshotPublishRequested(unsigned outputWidth, unsigned outputHeight, const std::string& reason) const;
void PublishRenderSnapshotPublished(const RuntimeRenderStateSnapshot& snapshot) const;
RenderSnapshotBuilder& mRenderSnapshotBuilder;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
mutable std::mutex mPublishedSnapshotMutex;
mutable bool mHasPublishedRenderStateSnapshot = false;
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
};

View File

@@ -0,0 +1,738 @@
#include "LayerStackStore.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStateJson.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <set>
#include <sstream>
#include <utility>
namespace
{
std::string TrimCopy(const std::string& text)
{
std::size_t start = 0;
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
++start;
std::size_t end = text.size();
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
--end;
return text.substr(start, end - start);
}
std::string SimplifyControlKey(const std::string& text)
{
std::string simplified;
for (unsigned char ch : text)
{
if (std::isalnum(ch))
simplified.push_back(static_cast<char>(std::tolower(ch)));
}
return simplified;
}
bool MatchesControlKey(const std::string& candidate, const std::string& key)
{
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
}
bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
{
const std::string prefix = "layer-";
if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size())
return false;
uint64_t parsed = 0;
for (std::size_t index = prefix.size(); index < layerId.size(); ++index)
{
const unsigned char ch = static_cast<unsigned char>(layerId[index]);
if (!std::isdigit(ch))
return false;
parsed = parsed * 10 + static_cast<uint64_t>(ch - '0');
}
number = parsed;
return true;
}
}
bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root)
{
if (const JsonValue* layersValue = root.find("layers"))
{
for (const JsonValue& layerValue : layersValue->asArray())
{
if (!layerValue.isObject())
continue;
LayerPersistentState layer;
if (const JsonValue* idValue = layerValue.find("id"))
layer.id = idValue->asString();
if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
layer.shaderId = shaderIdValue->asString();
if (const JsonValue* bypassValue = layerValue.find("bypass"))
layer.bypass = bypassValue->asBoolean(false);
else if (const JsonValue* enabledValue = layerValue.find("enabled"))
layer.bypass = !enabledValue->asBoolean(true);
if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
{
for (const auto& parameterItem : parameterValues->asObject())
{
ShaderParameterValue value;
const JsonValue& jsonValue = parameterItem.second;
if (jsonValue.isBoolean())
value.booleanValue = jsonValue.asBoolean();
else if (jsonValue.isString())
value.enumValue = jsonValue.asString();
else if (jsonValue.isNumber())
value.numberValues.push_back(jsonValue.asNumber());
else if (jsonValue.isArray())
value.numberValues = JsonArrayToNumbers(jsonValue);
layer.parameterValues[parameterItem.first] = value;
}
}
if (!layer.shaderId.empty())
mLayers.push_back(layer);
}
}
else
{
std::string activeShaderId;
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
activeShaderId = activeShaderValue->asString();
if (!activeShaderId.empty())
{
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
layer.shaderId = activeShaderId;
layer.bypass = false;
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
{
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
if (shaderValues)
{
for (const auto& parameterItem : shaderValues->asObject())
{
ShaderParameterValue value;
const JsonValue& jsonValue = parameterItem.second;
if (jsonValue.isBoolean())
value.booleanValue = jsonValue.asBoolean();
else if (jsonValue.isString())
value.enumValue = jsonValue.asString();
else if (jsonValue.isNumber())
value.numberValues.push_back(jsonValue.asNumber());
else if (jsonValue.isArray())
value.numberValues = JsonArrayToNumbers(jsonValue);
layer.parameterValues[parameterItem.first] = value;
}
}
}
mLayers.push_back(layer);
}
}
return true;
}
JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
{
JsonValue root = JsonValue::MakeObject();
JsonValue layers = JsonValue::MakeArray();
for (const LayerPersistentState& layer : mLayers)
{
JsonValue layerValue = JsonValue::MakeObject();
layerValue.set("id", JsonValue(layer.id));
layerValue.set("shaderId", JsonValue(layer.shaderId));
layerValue.set("bypass", JsonValue(layer.bypass));
JsonValue parameterValues = JsonValue::MakeObject();
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
for (const auto& parameterItem : layer.parameterValues)
{
const ShaderParameterDefinition* definition = nullptr;
if (shaderPackage)
{
for (const ShaderParameterDefinition& candidate : shaderPackage->parameters)
{
if (candidate.id == parameterItem.first)
{
definition = &candidate;
break;
}
}
}
if (definition)
parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second));
}
layerValue.set("parameterValues", parameterValues);
layers.pushBack(layerValue);
}
root.set("layers", layers);
return root;
}
void LayerStackStore::NormalizeLayerIds()
{
std::set<std::string> usedIds;
uint64_t maxLayerNumber = mNextLayerId;
for (LayerPersistentState& layer : mLayers)
{
uint64_t layerNumber = 0;
const bool hasReusableId = !layer.id.empty() &&
usedIds.find(layer.id) == usedIds.end() &&
TryParseLayerIdNumber(layer.id, layerNumber);
if (hasReusableId)
{
usedIds.insert(layer.id);
maxLayerNumber = (std::max)(maxLayerNumber, layerNumber);
continue;
}
do
{
++maxLayerNumber;
layer.id = "layer-" + std::to_string(maxLayerNumber);
}
while (usedIds.find(layer.id) != usedIds.end());
usedIds.insert(layer.id);
}
mNextLayerId = maxLayerNumber;
}
void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
{
for (LayerPersistentState& layer : mLayers)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
if (shaderPackage)
EnsureLayerDefaults(layer, *shaderPackage);
}
}
void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
{
if (!mLayers.empty() || shaderCatalog.PackageOrder().empty())
return;
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
layer.shaderId = shaderCatalog.PackageOrder().front();
layer.bypass = false;
if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId))
EnsureLayerDefaults(layer, *shaderPackage);
mLayers.push_back(layer);
}
void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
{
for (auto it = mLayers.begin(); it != mLayers.end();)
{
if (!shaderCatalog.HasPackage(it->shaderId))
it = mLayers.erase(it);
else
++it;
}
}
bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + shaderId;
return false;
}
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
layer.shaderId = shaderId;
layer.bypass = false;
EnsureLayerDefaults(layer, *shaderPackage);
mLayers.push_back(layer);
return true;
}
bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
mLayers.erase(it);
return true;
}
bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mLayers.size()))
return true;
std::swap(mLayers[index], mLayers[newIndex]);
return true;
}
bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (mLayers.empty())
return true;
if (targetIndex >= mLayers.size())
targetIndex = mLayers.size() - 1;
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
if (sourceIndex == targetIndex)
return true;
LayerPersistentState movedLayer = *it;
mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
return true;
}
bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
layer->bypass = bypassed;
return true;
}
bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + shaderId;
return false;
}
layer->shaderId = shaderId;
layer->parameterValues.clear();
EnsureLayerDefaults(*layer, *shaderPackage);
return true;
}
bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
layer->parameterValues[parameterId] = value;
return true;
}
bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
layer->parameterValues.clear();
EnsureLayerDefaults(*layer, *shaderPackage);
return true;
}
bool LayerStackStore::HasLayer(const std::string& layerId) const
{
return FindLayerById(layerId) != nullptr;
}
bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
const LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (parameterIt == shaderPackage->parameters.end())
{
error = "Unknown parameter id: " + parameterId;
return false;
}
snapshot = StoredParameterSnapshot();
snapshot.layerId = layer->id;
snapshot.definition = *parameterIt;
auto valueIt = layer->parameterValues.find(parameterIt->id);
if (valueIt != layer->parameterValues.end())
{
snapshot.currentValue = valueIt->second;
snapshot.hasCurrentValue = true;
}
return true;
}
bool LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
const LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
for (const LayerPersistentState& layer : mLayers)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
if (!shaderPackage)
continue;
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) ||
MatchesControlKey(shaderPackage->displayName, layerKey))
{
matchedLayer = &layer;
matchedPackage = shaderPackage;
break;
}
}
if (!matchedLayer || !matchedPackage)
{
error = "Unknown OSC layer key: " + layerKey;
return false;
}
auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
[&parameterKey](const ShaderParameterDefinition& definition)
{
return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
});
if (parameterIt == matchedPackage->parameters.end())
{
error = "Unknown OSC parameter key: " + parameterKey;
return false;
}
snapshot = StoredParameterSnapshot();
snapshot.layerId = matchedLayer->id;
snapshot.definition = *parameterIt;
auto valueIt = matchedLayer->parameterValues.find(parameterIt->id);
if (valueIt != matchedLayer->parameterValues.end())
{
snapshot.currentValue = valueIt->second;
snapshot.hasCurrentValue = true;
}
return true;
}
bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(mLayers.size()) && newIndex != index;
return true;
}
bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (mLayers.empty())
{
shouldMove = false;
return true;
}
const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1);
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
shouldMove = sourceIndex != clampedTargetIndex;
return true;
}
JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
{
JsonValue root = JsonValue::MakeObject();
root.set("version", JsonValue(1.0));
root.set("name", JsonValue(TrimCopy(presetName)));
root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog));
return root;
}
bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
{
const JsonValue* layersValue = root.find("layers");
if (!layersValue || !layersValue->isArray())
{
error = "Preset file is missing a valid 'layers' array.";
return false;
}
std::vector<LayerPersistentState> nextLayers;
uint64_t nextLayerId = mNextLayerId;
if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error))
return false;
if (nextLayers.empty())
{
error = "Preset does not contain any valid layers.";
return false;
}
mLayers = std::move(nextLayers);
mNextLayerId = nextLayerId;
return true;
}
std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName)
{
return ::MakeSafePresetFileStem(presetName);
}
const std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers() const
{
return mLayers;
}
std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers()
{
return mLayers;
}
std::size_t LayerStackStore::LayerCount() const
{
return mLayers.size();
}
const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mLayers.end() ? nullptr : &*it;
}
LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mLayers.end() ? nullptr : &*it;
}
ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition)
{
return ::DefaultValueForDefinition(definition);
}
void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage)
{
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{
auto valueIt = layerState.parameterValues.find(definition.id);
if (valueIt == layerState.parameterValues.end())
{
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
continue;
}
JsonValue valueJson;
bool shouldNormalize = true;
switch (definition.type)
{
case ShaderParameterType::Float:
if (valueIt->second.numberValues.empty())
shouldNormalize = false;
else
valueJson = JsonValue(valueIt->second.numberValues.front());
break;
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
valueJson = JsonValue::MakeArray();
for (double number : valueIt->second.numberValues)
valueJson.pushBack(JsonValue(number));
break;
case ShaderParameterType::Boolean:
valueJson = JsonValue(valueIt->second.booleanValue);
break;
case ShaderParameterType::Enum:
valueJson = JsonValue(valueIt->second.enumValue);
break;
case ShaderParameterType::Text:
{
const std::string textValue = !valueIt->second.textValue.empty()
? valueIt->second.textValue
: valueIt->second.enumValue;
if (textValue.empty())
{
valueIt->second = DefaultValueForDefinition(definition);
shouldNormalize = false;
}
else
{
valueJson = JsonValue(textValue);
}
break;
}
case ShaderParameterType::Trigger:
if (valueIt->second.numberValues.empty())
valueJson = JsonValue(0.0);
else
valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front())));
break;
}
if (!shouldNormalize)
continue;
ShaderParameterValue normalizedValue;
std::string normalizeError;
if (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError))
valueIt->second = normalizedValue;
else
valueIt->second = DefaultValueForDefinition(definition);
}
}
bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error)
{
for (const JsonValue& layerValue : layersValue.asArray())
{
if (!layerValue.isObject())
continue;
const JsonValue* shaderIdValue = layerValue.find("shaderId");
if (!shaderIdValue)
continue;
const std::string shaderId = shaderIdValue->asString();
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Preset references unknown shader id: " + shaderId;
return false;
}
LayerPersistentState layer;
layer.id = GenerateLayerId(layers, nextLayerId);
layer.shaderId = shaderId;
if (const JsonValue* bypassValue = layerValue.find("bypass"))
layer.bypass = bypassValue->asBoolean(false);
if (const JsonValue* parametersValue = layerValue.find("parameters"))
{
for (const JsonValue& parameterValue : parametersValue->asArray())
{
if (!parameterValue.isObject())
continue;
const JsonValue* parameterIdValue = parameterValue.find("id");
const JsonValue* valueValue = parameterValue.find("value");
if (!parameterIdValue || !valueValue)
continue;
const std::string parameterId = parameterIdValue->asString();
auto definitionIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (definitionIt == shaderPackage->parameters.end())
continue;
ShaderParameterValue normalizedValue;
if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error))
return false;
layer.parameterValues[parameterId] = normalizedValue;
}
}
EnsureLayerDefaults(layer, *shaderPackage);
layers.push_back(layer);
}
return true;
}
std::string LayerStackStore::GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId)
{
while (true)
{
++nextLayerId;
const std::string candidate = "layer-" + std::to_string(nextLayerId);
auto it = std::find_if(layers.begin(), layers.end(),
[&candidate](const LayerPersistentState& layer) { return layer.id == candidate; });
if (it == layers.end())
return candidate;
}
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <cstddef>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
class LayerStackStore
{
public:
struct LayerPersistentState
{
std::string id;
std::string shaderId;
bool bypass = false;
std::map<std::string, ShaderParameterValue> parameterValues;
};
struct StoredParameterSnapshot
{
std::string layerId;
ShaderParameterDefinition definition;
ShaderParameterValue currentValue;
bool hasCurrentValue = false;
};
bool LoadPersistentStateValue(const JsonValue& root);
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
void NormalizeLayerIds();
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
bool DeleteLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
bool HasLayer(const std::string& layerId) const;
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
static std::string MakeSafePresetFileStem(const std::string& presetName);
const std::vector<LayerPersistentState>& Layers() const;
std::vector<LayerPersistentState>& Layers();
std::size_t LayerCount() const;
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
LayerPersistentState* FindLayerById(const std::string& layerId);
private:
static ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
static void EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage);
static bool DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error);
static std::string GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId);
std::vector<LayerPersistentState> mLayers;
uint64_t mNextLayerId = 0;
};

View File

@@ -0,0 +1,270 @@
#include "RuntimeConfigStore.h"
#include "RuntimeJson.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <vector>
#include <windows.h>
namespace
{
double Clamp01(double value)
{
return (std::max)(0.0, (std::min)(1.0, value));
}
bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate)
{
return std::filesystem::exists(candidate / "config" / "runtime-host.json") &&
std::filesystem::exists(candidate / "runtime" / "templates" / "shader_wrapper.slang.in") &&
std::filesystem::exists(candidate / "shaders");
}
bool LooksLikeRepoRoot(const std::filesystem::path& candidate)
{
return std::filesystem::exists(candidate / "CMakeLists.txt") &&
std::filesystem::exists(candidate / "apps" / "LoopThroughWithOpenGLCompositing");
}
std::filesystem::path FindRepoRootCandidate()
{
std::vector<std::filesystem::path> rootsToTry;
char currentDirectory[MAX_PATH] = {};
if (GetCurrentDirectoryA(MAX_PATH, currentDirectory) > 0)
rootsToTry.push_back(std::filesystem::path(currentDirectory));
char modulePath[MAX_PATH] = {};
DWORD moduleLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
if (moduleLength > 0 && moduleLength < MAX_PATH)
rootsToTry.push_back(std::filesystem::path(modulePath).parent_path());
for (const std::filesystem::path& startPath : rootsToTry)
{
std::filesystem::path candidate = startPath;
for (int depth = 0; depth < 10 && !candidate.empty(); ++depth)
{
if (LooksLikePackagedRuntimeRoot(candidate) || LooksLikeRepoRoot(candidate))
return candidate;
candidate = candidate.parent_path();
}
}
return std::filesystem::path();
}
}
bool RuntimeConfigStore::Initialize(std::string& error)
{
if (!ResolvePaths(error))
return false;
if (!LoadConfig(error))
return false;
RefreshConfigDependentPaths();
return true;
}
const RuntimeConfigStore::AppConfig& RuntimeConfigStore::GetConfig() const
{
return mConfig;
}
const std::filesystem::path& RuntimeConfigStore::GetRepoRoot() const
{
return mRepoRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetUiRoot() const
{
return mUiRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetDocsRoot() const
{
return mDocsRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetShaderRoot() const
{
return mShaderRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetRuntimeRoot() const
{
return mRuntimeRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetPresetRoot() const
{
return mPresetRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetRuntimeStatePath() const
{
return mRuntimeStatePath;
}
const std::filesystem::path& RuntimeConfigStore::GetWrapperPath() const
{
return mWrapperPath;
}
const std::filesystem::path& RuntimeConfigStore::GetGeneratedGlslPath() const
{
return mGeneratedGlslPath;
}
const std::filesystem::path& RuntimeConfigStore::GetPatchedGlslPath() const
{
return mPatchedGlslPath;
}
void RuntimeConfigStore::SetBoundControlServerPort(unsigned short port)
{
mConfig.serverPort = port;
}
bool RuntimeConfigStore::ResolvePaths(std::string& error)
{
mRepoRoot = FindRepoRootCandidate();
if (mRepoRoot.empty())
{
error = "Could not locate the repository root from the current runtime path.";
return false;
}
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
mDocsRoot = mRepoRoot / "docs";
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
mRuntimeRoot = mRepoRoot / "runtime";
mPresetRoot = mRuntimeRoot / "stack_presets";
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
RefreshConfigDependentPaths();
std::error_code fsError;
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
std::filesystem::create_directories(mPresetRoot, fsError);
return true;
}
bool RuntimeConfigStore::LoadConfig(std::string& error)
{
if (!std::filesystem::exists(mConfigPath))
return true;
std::string configText = ReadTextFile(mConfigPath, error);
if (configText.empty())
return false;
JsonValue configJson;
if (!ParseJson(configText, configJson, error))
return false;
if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary"))
mConfig.shaderLibrary = shaderLibraryValue->asString();
if (const JsonValue* serverPortValue = configJson.find("serverPort"))
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.oscPort));
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
mConfig.oscBindAddress = oscBindAddressValue->asString();
if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing"))
mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing));
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
{
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
}
if (const JsonValue* previewFpsValue = configJson.find("previewFps"))
{
const double configuredValue = previewFpsValue->asNumber(static_cast<double>(mConfig.previewFps));
mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
}
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
{
if (videoFormatValue->isString() && !videoFormatValue->asString().empty())
{
mConfig.inputVideoFormat = videoFormatValue->asString();
mConfig.outputVideoFormat = videoFormatValue->asString();
}
}
if (const JsonValue* frameRateValue = configJson.find("frameRate"))
{
if (frameRateValue->isString() && !frameRateValue->asString().empty())
{
mConfig.inputFrameRate = frameRateValue->asString();
mConfig.outputFrameRate = frameRateValue->asString();
}
else if (frameRateValue->isNumber())
{
std::ostringstream stream;
stream << frameRateValue->asNumber();
mConfig.inputFrameRate = stream.str();
mConfig.outputFrameRate = stream.str();
}
}
if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat"))
{
if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty())
mConfig.inputVideoFormat = inputVideoFormatValue->asString();
}
if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate"))
{
if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty())
mConfig.inputFrameRate = inputFrameRateValue->asString();
else if (inputFrameRateValue->isNumber())
{
std::ostringstream stream;
stream << inputFrameRateValue->asNumber();
mConfig.inputFrameRate = stream.str();
}
}
if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat"))
{
if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty())
mConfig.outputVideoFormat = outputVideoFormatValue->asString();
}
if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate"))
{
if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty())
mConfig.outputFrameRate = outputFrameRateValue->asString();
else if (outputFrameRateValue->isNumber())
{
std::ostringstream stream;
stream << outputFrameRateValue->asNumber();
mConfig.outputFrameRate = stream.str();
}
}
return true;
}
std::string RuntimeConfigStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
{
std::ifstream input(path, std::ios::binary);
if (!input)
{
error = "Could not open file: " + path.string();
return std::string();
}
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
void RuntimeConfigStore::RefreshConfigDependentPaths()
{
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag";
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include <filesystem>
#include <string>
class RuntimeConfigStore
{
public:
struct AppConfig
{
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
unsigned short oscPort = 9000;
std::string oscBindAddress = "127.0.0.1";
double oscSmoothing = 0.18;
bool autoReload = true;
unsigned maxTemporalHistoryFrames = 4;
unsigned previewFps = 30;
bool enableExternalKeying = false;
std::string inputVideoFormat = "1080p";
std::string inputFrameRate = "59.94";
std::string outputVideoFormat = "1080p";
std::string outputFrameRate = "59.94";
};
bool Initialize(std::string& error);
const AppConfig& GetConfig() const;
const std::filesystem::path& GetRepoRoot() const;
const std::filesystem::path& GetUiRoot() const;
const std::filesystem::path& GetDocsRoot() const;
const std::filesystem::path& GetShaderRoot() const;
const std::filesystem::path& GetRuntimeRoot() const;
const std::filesystem::path& GetPresetRoot() const;
const std::filesystem::path& GetRuntimeStatePath() const;
const std::filesystem::path& GetWrapperPath() const;
const std::filesystem::path& GetGeneratedGlslPath() const;
const std::filesystem::path& GetPatchedGlslPath() const;
void SetBoundControlServerPort(unsigned short port);
private:
bool ResolvePaths(std::string& error);
bool LoadConfig(std::string& error);
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
void RefreshConfigDependentPaths();
AppConfig mConfig;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
std::filesystem::path mGeneratedGlslPath;
std::filesystem::path mPatchedGlslPath;
};

View File

@@ -0,0 +1,704 @@
#include "RuntimeStore.h"
#include "RuntimeStatePresenter.h"
#include <cctype>
#include <fstream>
#include <mutex>
#include <random>
#include <sstream>
namespace
{
std::string ToLowerCopy(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
return text;
}
double GenerateStartupRandom()
{
std::random_device randomDevice;
std::uniform_real_distribution<double> distribution(0.0, 1.0);
return distribution(randomDevice);
}
std::string PersistenceTargetKindName(PersistenceTargetKind targetKind)
{
switch (targetKind)
{
case PersistenceTargetKind::RuntimeState:
return "runtime-state";
case PersistenceTargetKind::StackPreset:
return "stack-preset";
case PersistenceTargetKind::RuntimeConfig:
return "runtime-config";
default:
return "unknown";
}
}
}
RuntimeStore::RuntimeStore() :
mRenderSnapshotBuilder(*this),
mReloadRequested(false),
mCompileSucceeded(false),
mStartupRandom(GenerateStartupRandom()),
mServerPort(8080),
mAutoReloadEnabled(true),
mStartTime(std::chrono::steady_clock::now()),
mLastScanTime((std::chrono::steady_clock::time_point::min)())
{
mPersistenceWriter.SetResultCallback([this](const PersistenceWriteResult& result) {
mHealthTelemetry.RecordPersistenceWriteResult(
result.succeeded,
PersistenceTargetKindName(result.targetKind),
result.targetPath,
result.reason,
result.errorMessage,
result.newerRequestPending);
});
}
HealthTelemetry& RuntimeStore::GetHealthTelemetry()
{
return mHealthTelemetry;
}
const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const
{
return mHealthTelemetry;
}
RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder()
{
return mRenderSnapshotBuilder;
}
const RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder() const
{
return mRenderSnapshotBuilder;
}
bool RuntimeStore::InitializeStore(std::string& error)
{
try
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mConfigStore.Initialize(error))
return false;
if (!LoadPersistentState(error))
return false;
if (!ScanShaderPackages(error))
return false;
mCommittedLiveState.NormalizeLayerIds();
mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog);
mCommittedLiveState.EnsureDefaultLayer(mShaderCatalog);
mServerPort = mConfigStore.GetConfig().serverPort;
mAutoReloadEnabled = mConfigStore.GetConfig().autoReload;
mReloadRequested = true;
mCompileMessage = "Waiting for shader compile.";
return true;
}
catch (const std::exception& exception)
{
error = std::string("RuntimeStore::InitializeStore exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeStore::InitializeStore threw a non-standard exception.";
return false;
}
}
std::string RuntimeStore::BuildPersistentStateJson() const
{
return RuntimeStatePresenter::BuildRuntimeStateJson(*this);
}
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const
{
std::lock_guard<std::mutex> lock(mMutex);
return BuildRuntimeStatePersistenceSnapshotLocked(request);
}
bool RuntimeStore::RequestPersistence(const PersistenceRequest& request, std::string& error)
{
if (request.targetKind != PersistenceTargetKind::RuntimeState)
{
error = "Unsupported persistence request target: " + PersistenceTargetKindName(request.targetKind);
mHealthTelemetry.RecordPersistenceWriteResult(
false,
PersistenceTargetKindName(request.targetKind),
std::string(),
request.reason,
error,
false);
return false;
}
const PersistenceSnapshot snapshot = BuildRuntimeStatePersistenceSnapshot(request);
if (mPersistenceWriter.EnqueueSnapshot(snapshot, error))
return true;
mHealthTelemetry.RecordPersistenceWriteResult(
false,
PersistenceTargetKindName(request.targetKind),
snapshot.targetPath.string(),
request.reason,
error,
false);
return false;
}
bool RuntimeStore::FlushPersistenceForShutdown(std::chrono::milliseconds timeout, std::string& error)
{
if (mPersistenceWriter.StopAndFlush(timeout, error))
return true;
mHealthTelemetry.RecordPersistenceWriteResult(
false,
PersistenceTargetKindName(PersistenceTargetKind::RuntimeState),
std::string(),
"shutdown-flush",
error,
true);
return false;
}
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const
{
PersistenceSnapshot snapshot;
snapshot.targetKind = PersistenceTargetKind::RuntimeState;
snapshot.targetPath = mConfigStore.GetRuntimeStatePath();
snapshot.contents = SerializeJson(mCommittedLiveState.BuildPersistentStateValue(mShaderCatalog), true);
snapshot.reason = request.reason;
snapshot.debounceKey = request.debounceKey;
snapshot.debounceAllowed = request.debounceAllowed;
snapshot.flushRequested = request.flushRequested;
snapshot.generation = request.sequence;
return snapshot;
}
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
{
try
{
std::lock_guard<std::mutex> lock(mMutex);
registryChanged = false;
reloadRequested = false;
if (!mAutoReloadEnabled)
{
reloadRequested = mReloadRequested;
return true;
}
const auto now = std::chrono::steady_clock::now();
if (mLastScanTime != (std::chrono::steady_clock::time_point::min)() &&
std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastScanTime).count() < 250)
{
reloadRequested = mReloadRequested;
return true;
}
mLastScanTime = now;
std::string scanError;
const ShaderPackageCatalog::Snapshot previousCatalog = mShaderCatalog.CaptureSnapshot();
if (!ScanShaderPackages(scanError))
{
error = scanError;
return false;
}
registryChanged = mShaderCatalog.HasCatalogChangedSince(previousCatalog);
mCommittedLiveState.EnsureDefaultsForAllLayers(mShaderCatalog);
for (RuntimeStore::LayerPersistentState& layer : mCommittedLiveState.Layers())
{
const ShaderPackage* active = mShaderCatalog.FindPackage(layer.shaderId);
if (!active)
continue;
if (mShaderCatalog.HasPackageChangedSince(previousCatalog, layer.shaderId))
mReloadRequested = true;
}
reloadRequested = mReloadRequested;
if (registryChanged || reloadRequested)
MarkRenderStateDirtyLocked();
return true;
}
catch (const std::exception& exception)
{
error = std::string("RuntimeStore::PollStoredFileChanges exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeStore::PollStoredFileChanges threw a non-standard exception.";
return false;
}
}
bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.CreateLayer(mShaderCatalog, shaderId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.DeleteLayer(layerId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
bool shouldMove = false;
if (!mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error))
return false;
if (!shouldMove)
return true;
if (!mCommittedLiveState.MoveLayer(layerId, direction, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
bool shouldMove = false;
if (!mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
return false;
if (!shouldMove)
return true;
if (!mCommittedLiveState.MoveLayerToIndex(layerId, targetIndex, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.SetLayerBypassState(layerId, bypassed, error))
return false;
mReloadRequested = true;
MarkParameterStateDirtyLocked();
return true;
}
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.SetLayerShaderSelection(mShaderCatalog, layerId, shaderId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error)
{
(void)persistState;
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.SetParameterValue(layerId, parameterId, value, error))
return false;
MarkParameterStateDirtyLocked();
return true;
}
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.ResetLayerParameterValues(mShaderCatalog, layerId, error))
return false;
MarkParameterStateDirtyLocked();
return true;
}
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Preset name must include at least one letter or number.";
return false;
}
return mPersistenceWriter.WriteSnapshot(BuildStackPresetPersistenceSnapshot(presetName), error);
}
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Preset name must include at least one letter or number.";
return false;
}
const std::filesystem::path presetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
std::string presetText = ReadTextFile(presetPath, error);
if (presetText.empty())
return false;
JsonValue root;
if (!ParseJson(presetText, root, error))
return false;
if (!mCommittedLiveState.LoadStackPresetValue(mShaderCatalog, root, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return true;
}
bool RuntimeStore::HasStoredLayer(const std::string& layerId) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.HasLayer(layerId);
}
bool RuntimeStore::HasStoredShader(const std::string& shaderId) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mShaderCatalog.HasPackage(shaderId);
}
bool RuntimeStore::TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.TryGetParameterById(mShaderCatalog, layerId, parameterId, snapshot, error);
}
bool RuntimeStore::TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.TryGetParameterByControlKey(mShaderCatalog, layerKey, parameterKey, snapshot, error);
}
bool RuntimeStore::ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.ResolveLayerMove(layerId, direction, shouldMove, error);
}
bool RuntimeStore::ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
}
bool RuntimeStore::IsValidStackPresetName(const std::string& presetName) const
{
return !LayerStackStore::MakeSafePresetFileStem(presetName).empty();
}
double RuntimeStore::GetRuntimeElapsedSeconds() const
{
return std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::steady_clock::now() - mStartTime).count();
}
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
{
return mConfigStore.GetRepoRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
{
return mConfigStore.GetUiRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
{
return mConfigStore.GetDocsRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
{
return mConfigStore.GetRuntimeRoot();
}
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
{
return mServerPort;
}
unsigned short RuntimeStore::GetConfiguredOscPort() const
{
return mConfigStore.GetConfig().oscPort;
}
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
{
return mConfigStore.GetConfig().oscBindAddress;
}
double RuntimeStore::GetConfiguredOscSmoothing() const
{
return mConfigStore.GetConfig().oscSmoothing;
}
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
{
return mConfigStore.GetConfig().maxTemporalHistoryFrames;
}
unsigned RuntimeStore::GetConfiguredPreviewFps() const
{
return mConfigStore.GetConfig().previewFps;
}
bool RuntimeStore::IsExternalKeyingConfigured() const
{
return mConfigStore.GetConfig().enableExternalKeying;
}
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
{
return mConfigStore.GetConfig().inputVideoFormat;
}
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
{
return mConfigStore.GetConfig().inputFrameRate;
}
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
{
return mConfigStore.GetConfig().outputVideoFormat;
}
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
{
return mConfigStore.GetConfig().outputFrameRate;
}
void RuntimeStore::SetBoundControlServerPort(unsigned short port)
{
std::lock_guard<std::mutex> lock(mMutex);
mServerPort = port;
mConfigStore.SetBoundControlServerPort(port);
}
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
{
std::lock_guard<std::mutex> lock(mMutex);
mCompileSucceeded = succeeded;
mCompileMessage = message;
}
void RuntimeStore::ClearReloadRequest()
{
std::lock_guard<std::mutex> lock(mMutex);
mReloadRequested = false;
}
bool RuntimeStore::LoadPersistentState(std::string& error)
{
if (!std::filesystem::exists(mConfigStore.GetRuntimeStatePath()))
return true;
std::string stateText = ReadTextFile(mConfigStore.GetRuntimeStatePath(), error);
if (stateText.empty())
return false;
JsonValue root;
if (!ParseJson(stateText, root, error))
return false;
return mCommittedLiveState.LoadPersistentStateValue(root);
}
PersistenceSnapshot RuntimeStore::BuildStackPresetPersistenceSnapshot(const std::string& presetName) const
{
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
PersistenceSnapshot snapshot;
snapshot.targetKind = PersistenceTargetKind::StackPreset;
snapshot.targetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
snapshot.contents = SerializeJson(mCommittedLiveState.BuildStackPresetValue(mShaderCatalog, presetName), true);
snapshot.reason = "SaveStackPreset";
snapshot.debounceKey = "stack-preset:" + safeStem;
snapshot.debounceAllowed = false;
snapshot.flushRequested = true;
snapshot.generation = 0;
return snapshot;
}
bool RuntimeStore::ScanShaderPackages(std::string& error)
{
if (!mShaderCatalog.Scan(mConfigStore.GetShaderRoot(), mConfigStore.GetConfig().maxTemporalHistoryFrames, error))
return false;
mCommittedLiveState.RemoveLayersWithMissingPackages(mShaderCatalog);
MarkRenderStateDirtyLocked();
return true;
}
std::string RuntimeStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
{
std::ifstream input(path, std::ios::binary);
if (!input)
{
error = "Could not open file: " + path.string();
return std::string();
}
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
std::vector<std::string> RuntimeStore::GetStackPresetNamesLocked() const
{
std::vector<std::string> presetNames;
std::error_code fsError;
if (!std::filesystem::exists(mConfigStore.GetPresetRoot(), fsError))
return presetNames;
for (const auto& entry : std::filesystem::directory_iterator(mConfigStore.GetPresetRoot(), fsError))
{
if (!entry.is_regular_file())
continue;
if (ToLowerCopy(entry.path().extension().string()) != ".json")
continue;
presetNames.push_back(entry.path().stem().string());
}
std::sort(presetNames.begin(), presetNames.end());
return presetNames;
}
bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
const RuntimeStore::LayerPersistentState* layer = mCommittedLiveState.FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
if (!mShaderCatalog.CopyPackage(layer->shaderId, shaderPackage))
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
return true;
}
ShaderCompilerInputs RuntimeStore::GetShaderCompilerInputs() const
{
std::lock_guard<std::mutex> lock(mMutex);
ShaderCompilerInputs inputs;
inputs.repoRoot = mConfigStore.GetRepoRoot();
inputs.wrapperPath = mConfigStore.GetWrapperPath();
inputs.generatedGlslPath = mConfigStore.GetGeneratedGlslPath();
inputs.patchedGlslPath = mConfigStore.GetPatchedGlslPath();
inputs.maxTemporalHistoryFrames = mConfigStore.GetConfig().maxTemporalHistoryFrames;
return inputs;
}
CommittedLiveStateReadModel RuntimeStore::BuildCommittedLiveStateReadModel() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.BuildReadModel(mShaderCatalog);
}
RenderSnapshotReadModel RuntimeStore::BuildRenderSnapshotReadModel() const
{
RenderSnapshotReadModel model;
model.signalStatus = mHealthTelemetry.GetSignalStatusSnapshot();
model.committedLiveState = BuildCommittedLiveStateReadModel();
std::lock_guard<std::mutex> lock(mMutex);
model.timing.startTime = mStartTime;
model.timing.startupRandom = mStartupRandom;
return model;
}
std::vector<RuntimeStore::LayerPersistentState> RuntimeStore::CopyCommittedLiveLayerStates() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mCommittedLiveState.CopyLayerStates();
}
std::vector<RuntimeStore::LayerPersistentState> RuntimeStore::CopyLayerStates() const
{
return CopyCommittedLiveLayerStates();
}
RenderTimingSnapshot RuntimeStore::GetRenderTimingSnapshot() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderTimingSnapshot snapshot;
snapshot.startTime = mStartTime;
snapshot.startupRandom = mStartupRandom;
return snapshot;
}
RuntimeStatePresentationReadModel RuntimeStore::BuildRuntimeStatePresentationReadModel() const
{
RuntimeStatePresentationReadModel model;
model.telemetry = mHealthTelemetry.GetSnapshot();
std::lock_guard<std::mutex> lock(mMutex);
model.config = mConfigStore.GetConfig();
model.layerStack = mCommittedLiveState.LayerStack();
model.shaderCatalog = mShaderCatalog.CaptureSnapshot();
model.packageStatuses = mShaderCatalog.PackageStatuses();
model.stackPresetNames = GetStackPresetNamesLocked();
model.serverPort = mServerPort;
model.autoReloadEnabled = mAutoReloadEnabled;
model.compileSucceeded = mCompileSucceeded;
model.compileMessage = mCompileMessage;
return model;
}
void RuntimeStore::MarkRenderStateDirtyLocked()
{
mRenderSnapshotBuilder.MarkRenderStateDirty();
}
void RuntimeStore::MarkParameterStateDirtyLocked()
{
mRenderSnapshotBuilder.MarkParameterStateDirty();
}

View File

@@ -0,0 +1,111 @@
#pragma once
#include "HealthTelemetry.h"
#include "CommittedLiveState.h"
#include "LayerStackStore.h"
#include "PersistenceWriter.h"
#include "RenderSnapshotBuilder.h"
#include "RuntimeConfigStore.h"
#include "RuntimeJson.h"
#include "RuntimeStoreReadModels.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <chrono>
#include <filesystem>
#include <mutex>
#include <string>
#include <vector>
class RuntimeStore
{
public:
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
using LayerPersistentState = LayerStackStore::LayerPersistentState;
RuntimeStore();
HealthTelemetry& GetHealthTelemetry();
const HealthTelemetry& GetHealthTelemetry() const;
RenderSnapshotBuilder& GetRenderSnapshotBuilder();
const RenderSnapshotBuilder& GetRenderSnapshotBuilder() const;
bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const;
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const;
bool RequestPersistence(const PersistenceRequest& request, std::string& error);
bool FlushPersistenceForShutdown(std::chrono::milliseconds timeout, std::string& error);
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
bool DeleteStoredLayer(const std::string& layerId, std::string& error);
bool MoveStoredLayer(const std::string& layerId, int direction, std::string& error);
bool MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
bool SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error);
bool SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error);
bool ResetStoredLayerParameterValues(const std::string& layerId, std::string& error);
bool SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const;
bool LoadStackPresetSnapshot(const std::string& presetName, std::string& error);
bool HasStoredLayer(const std::string& layerId) const;
bool HasStoredShader(const std::string& shaderId) const;
bool TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
bool TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
bool ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
bool IsValidStackPresetName(const std::string& presetName) const;
double GetRuntimeElapsedSeconds() const;
const std::filesystem::path& GetRuntimeRepositoryRoot() const;
const std::filesystem::path& GetRuntimeUiRoot() const;
const std::filesystem::path& GetRuntimeDocsRoot() const;
const std::filesystem::path& GetRuntimeDataRoot() const;
unsigned short GetConfiguredControlServerPort() const;
unsigned short GetConfiguredOscPort() const;
const std::string& GetConfiguredOscBindAddress() const;
double GetConfiguredOscSmoothing() const;
unsigned GetConfiguredMaxTemporalHistoryFrames() const;
unsigned GetConfiguredPreviewFps() const;
bool IsExternalKeyingConfigured() const;
const std::string& GetConfiguredInputVideoFormat() const;
const std::string& GetConfiguredInputFrameRate() const;
const std::string& GetConfiguredOutputVideoFormat() const;
const std::string& GetConfiguredOutputFrameRate() const;
void SetBoundControlServerPort(unsigned short port);
void SetCompileStatus(bool succeeded, const std::string& message);
void ClearReloadRequest();
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
::ShaderCompilerInputs GetShaderCompilerInputs() const;
::CommittedLiveStateReadModel BuildCommittedLiveStateReadModel() const;
::RenderSnapshotReadModel BuildRenderSnapshotReadModel() const;
std::vector<LayerPersistentState> CopyCommittedLiveLayerStates() const;
std::vector<LayerPersistentState> CopyLayerStates() const;
::RenderTimingSnapshot GetRenderTimingSnapshot() const;
::RuntimeStatePresentationReadModel BuildRuntimeStatePresentationReadModel() const;
private:
bool LoadPersistentState(std::string& error);
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const;
PersistenceSnapshot BuildStackPresetPersistenceSnapshot(const std::string& presetName) const;
bool ScanShaderPackages(std::string& error);
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
std::vector<std::string> GetStackPresetNamesLocked() const;
void MarkRenderStateDirtyLocked();
void MarkParameterStateDirtyLocked();
RenderSnapshotBuilder mRenderSnapshotBuilder;
RuntimeConfigStore mConfigStore;
ShaderPackageCatalog mShaderCatalog;
CommittedLiveState mCommittedLiveState;
HealthTelemetry mHealthTelemetry;
mutable PersistenceWriter mPersistenceWriter;
mutable std::mutex mMutex;
bool mReloadRequested;
bool mCompileSucceeded;
std::string mCompileMessage;
double mStartupRandom;
unsigned short mServerPort;
bool mAutoReloadEnabled;
std::chrono::steady_clock::time_point mStartTime;
std::chrono::steady_clock::time_point mLastScanTime;
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include "HealthTelemetry.h"
#include "LayerStackStore.h"
#include "RuntimeConfigStore.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <chrono>
#include <filesystem>
#include <map>
#include <string>
#include <vector>
struct ShaderCompilerInputs
{
std::filesystem::path repoRoot;
std::filesystem::path wrapperPath;
std::filesystem::path generatedGlslPath;
std::filesystem::path patchedGlslPath;
unsigned maxTemporalHistoryFrames = 0;
};
struct RenderTimingSnapshot
{
std::chrono::steady_clock::time_point startTime;
double startupRandom = 0.0;
};
struct CommittedLiveStateReadModel
{
std::vector<LayerStackStore::LayerPersistentState> layers;
std::map<std::string, ShaderPackage> packagesById;
};
struct RenderSnapshotReadModel
{
CommittedLiveStateReadModel committedLiveState;
HealthTelemetry::SignalStatusSnapshot signalStatus;
RenderTimingSnapshot timing;
};
struct RuntimeStatePresentationReadModel
{
RuntimeConfigStore::AppConfig config;
HealthTelemetry::Snapshot telemetry;
LayerStackStore layerStack;
ShaderPackageCatalog::Snapshot shaderCatalog;
std::vector<ShaderPackageStatus> packageStatuses;
std::vector<std::string> stackPresetNames;
unsigned short serverPort = 0;
bool autoReloadEnabled = false;
bool compileSucceeded = false;
std::string compileMessage;
};

View File

@@ -0,0 +1,127 @@
#include "ShaderPackageCatalog.h"
#include "ShaderPackageRegistry.h"
bool ShaderPackageCatalog::Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
{
std::map<std::string, ShaderPackage> packagesById;
std::vector<std::string> packageOrder;
std::vector<ShaderPackageStatus> packageStatuses;
ShaderPackageRegistry registry(maxTemporalHistoryFrames);
if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
return false;
mPackagesById.swap(packagesById);
mPackageOrder.swap(packageOrder);
mPackageStatuses.swap(packageStatuses);
return true;
}
ShaderPackageCatalog::Snapshot ShaderPackageCatalog::CaptureSnapshot() const
{
Snapshot snapshot;
snapshot.packagesById = mPackagesById;
snapshot.packageOrder = mPackageOrder;
return snapshot;
}
bool ShaderPackageCatalog::HasCatalogChangedSince(const Snapshot& snapshot) const
{
if (snapshot.packageOrder != mPackageOrder || snapshot.packagesById.size() != mPackagesById.size())
return true;
for (const auto& item : mPackagesById)
{
auto previous = snapshot.packagesById.find(item.first);
if (previous == snapshot.packagesById.end() || !PackagesEquivalent(previous->second, item.second))
return true;
}
return false;
}
bool ShaderPackageCatalog::HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const
{
auto previous = snapshot.packagesById.find(shaderId);
auto current = mPackagesById.find(shaderId);
if (previous == snapshot.packagesById.end() || current == mPackagesById.end())
return previous != snapshot.packagesById.end() || current != mPackagesById.end();
return !PackagesEquivalent(previous->second, current->second);
}
bool ShaderPackageCatalog::HasPackage(const std::string& shaderId) const
{
return mPackagesById.find(shaderId) != mPackagesById.end();
}
const ShaderPackage* ShaderPackageCatalog::FindPackage(const std::string& shaderId) const
{
auto it = mPackagesById.find(shaderId);
return it == mPackagesById.end() ? nullptr : &it->second;
}
bool ShaderPackageCatalog::CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const
{
const ShaderPackage* package = FindPackage(shaderId);
if (!package)
return false;
shaderPackage = *package;
return true;
}
const std::vector<std::string>& ShaderPackageCatalog::PackageOrder() const
{
return mPackageOrder;
}
const std::vector<ShaderPackageStatus>& ShaderPackageCatalog::PackageStatuses() const
{
return mPackageStatuses;
}
bool ShaderPackageCatalog::PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right)
{
return left.shaderWriteTime == right.shaderWriteTime &&
left.manifestWriteTime == right.manifestWriteTime &&
TextureAssetsEqual(left.textureAssets, right.textureAssets) &&
FontAssetsEqual(left.fontAssets, right.fontAssets);
}
bool ShaderPackageCatalog::TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right)
{
if (left.size() != right.size())
return false;
for (std::size_t index = 0; index < left.size(); ++index)
{
if (left[index].id != right[index].id ||
left[index].path != right[index].path ||
left[index].writeTime != right[index].writeTime)
{
return false;
}
}
return true;
}
bool ShaderPackageCatalog::FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right)
{
if (left.size() != right.size())
return false;
for (std::size_t index = 0; index < left.size(); ++index)
{
if (left[index].id != right[index].id ||
left[index].path != right[index].path ||
left[index].writeTime != right[index].writeTime)
{
return false;
}
}
return true;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "ShaderTypes.h"
#include <filesystem>
#include <map>
#include <string>
#include <vector>
class ShaderPackageCatalog
{
public:
struct Snapshot
{
std::map<std::string, ShaderPackage> packagesById;
std::vector<std::string> packageOrder;
};
bool Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error);
Snapshot CaptureSnapshot() const;
bool HasCatalogChangedSince(const Snapshot& snapshot) const;
bool HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const;
bool HasPackage(const std::string& shaderId) const;
const ShaderPackage* FindPackage(const std::string& shaderId) const;
bool CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const;
const std::vector<std::string>& PackageOrder() const;
const std::vector<ShaderPackageStatus>& PackageStatuses() const;
private:
static bool PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right);
static bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right);
static bool FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right);
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::vector<ShaderPackageStatus> mPackageStatuses;
};

Some files were not shown because too many files have changed in this diff Show More