Compare commits

...

686 Commits
0.4.2 ... 0.6.3

Author SHA1 Message Date
Bruno Herbelin
109e6f590a Disabling vtenc streaming for OSX (could not make it work) 2022-01-04 14:37:56 +01:00
Bruno Herbelin
8f0491ea57 Video Streamer with H264 hardware acceleration
Simplified option in user menu; lower bandwidth with H264, otherwise use JPEG. Always use RAW for localhost.
2022-01-04 12:33:46 +01:00
Bruno Herbelin
e0522608a4 UI improvement; hovering icons indicate possible action
Source filters icons without button. Unified lock icon with view. Updated help.
2022-01-04 00:54:12 +01:00
Bruno Herbelin
4b9a230803 oops 2022-01-03 22:16:55 +01:00
Bruno Herbelin
fc5246efaa New & improved align and distribute in MixingView 2022-01-03 18:20:02 +01:00
Bruno Herbelin
4eebfbb89f Improved Glyph layout
Support for shape and placement of glyph
2022-01-03 17:16:55 +01:00
Bruno Herbelin
353d2c4744 New Glyph decoration to show initials of source 2022-01-03 15:41:08 +01:00
Bruno Herbelin
2718e83132 Happy new year 2022 2022-01-02 23:17:22 +01:00
Bruno Herbelin
7547d1179d Cleanup UI
Ensure icons are dedicated to one single thing.
2022-01-02 19:54:48 +01:00
Bruno Herbelin
61e89286bc Fixed Device manager: restore gmainloop
The gmainloop is used by gst to detect devices. Fixed bugs on detection of invalid devices.
2022-01-02 14:17:10 +01:00
Bruno Herbelin
53ae715816 Restart the source after insertion from New Source panel 2022-01-02 11:49:02 +01:00
Bruno Herbelin
8cb37dba36 ORDER change: add sources at the end when create
Inserting sources at front was changing their index every time, which broke OSC addressing
2022-01-02 11:41:47 +01:00
Bruno Herbelin
4426f70de7 Code editor for Custom pattern gstreamer
Bugfix in Streamsource and UI
2022-01-01 23:59:30 +01:00
Bruno Herbelin
f0ca13150f New Custom pattern entry for New Source 2022-01-01 10:14:51 +01:00
Bruno Herbelin
780a20689c Improved user message for stream discovery failure 2022-01-01 10:13:46 +01:00
Bruno Herbelin
28f9ed1d8d Cleanup & new unwrapped function 2022-01-01 10:12:50 +01:00
Bruno Herbelin
2b5b8ad02c Bugfix Stream timeout initialization test 2021-12-31 14:24:51 +01:00
Bruno Herbelin
d5092b1765 Save & load GenericStrreamSource 2021-12-31 13:30:51 +01:00
Bruno Herbelin
fda62314f9 UI integration of GenericStreamSource 2021-12-31 13:16:39 +01:00
Bruno Herbelin
17018c137f MultiLine text display 2021-12-31 13:16:16 +01:00
Bruno Herbelin
8838c19c39 String functions to wrap test or join lists 2021-12-31 13:16:00 +01:00
Bruno Herbelin
f02a99a4e2 Improved GenericStreamSource, with stream discoverer
Also timeout to fail if open does not works + new GST icon.
2021-12-31 13:15:23 +01:00
Bruno Herbelin
7b26b0f23e Unified IMGUI_SAME_LINE width 2021-12-31 13:13:29 +01:00
Bruno Herbelin
0e9984827a Cleanup gst_element_get_state 2021-12-31 13:12:12 +01:00
Bruno Herbelin
033d41863a Added Stream Discoverer to detect frame size from a gstreamer pipeline
Previous use of Stream are not affected (the discoverer is passively returning the given width and height). But if the Stream is created without dimensions, it will run a discoverer to try to get preroll frames and detect width and height from there.
2021-12-30 00:15:43 +01:00
Bruno Herbelin
bc540044ac Accept launch of vimix if OSC connection failed 2021-12-29 14:41:27 +01:00
Bruno Herbelin
76a2535da3 Fixed issue of low quality stream in JPEG: new default to RGB RAW RTP stream
Backward compatibility through menu un stream output view (allow selecting JPEG)
2021-12-29 14:37:56 +01:00
Bruno Herbelin
ff48877d16 Fixed OSC feedback after source change. Added OSC command to lock source. 2021-12-27 23:36:28 +01:00
Bruno Herbelin
4b8efabc5f Improve and cleanup OSC control and translation
Changed default send Port to 7001. Updated documentation.
2021-12-27 17:28:11 +01:00
Bruno Herbelin
c79be090df Implementation of OSC settings and translator
Translations are in a config xml file in settings directory, and can be directly edited in text by the user. Settings UI allows changing Ports for incoming and outgoing UDP.
2021-12-27 01:04:49 +01:00
Bruno Herbelin
626eab7e8f Update and advertising of TouchOSC layout 2021-12-27 01:02:43 +01:00
Bruno Herbelin
c103b7d883 Finalization of OSC API, Tutorial for TouchOSC 2021-12-26 14:44:07 +01:00
Bruno Herbelin
cde055e29b Implementation of Session control
With Session recall from OSC
2021-12-26 01:20:44 +01:00
Bruno Herbelin
1cb448c42e Output session fading fixed for OSC and animation.
Linear interpolation (instead of dichotomy converge) for fading at Session update. Mixing View update reads value of session fading to animate the cursor (which was preventing other manipulation of fading). Cleanup fading in OSC controller, with animation options and fade-in and fade-out controls.
2021-12-26 00:41:02 +01:00
Bruno Herbelin
3d05444f30 Improved OSC control with TouchOSC
Added Looming source callback, and cleanup sync of sources. New horizontal version of OSCTouch UI.
2021-12-25 16:05:43 +01:00
Bruno Herbelin
7a551189d9 Improved log of OSC message. 2021-12-25 00:41:51 +01:00
Bruno Herbelin
b885e70fed Remove spaces from Source name
Replace space by underscore
2021-12-25 00:41:24 +01:00
Bruno Herbelin
0a27c14041 Control manager and TouchOSC sync 2021-12-23 22:17:05 +01:00
Bruno Herbelin
eb8e33e311 Correct call to Source Activation (inheritance) 2021-12-23 22:16:16 +01:00
Bruno Herbelin
2d44a60b90 Bi-directional OSC communication for Control manager
Unified OSC message declaration with Communicator
2021-12-21 23:48:20 +01:00
Bruno Herbelin
135b6a5702 cleanup SourceCallbacks on source destructor 2021-12-21 00:19:55 +01:00
Bruno Herbelin
706c72fda8 More OSC control
Grab and resize dynamically, select source by index, etc.
2021-12-21 00:19:39 +01:00
Bruno Herbelin
fb7bdba388 Code cleanup 2021-12-20 00:30:59 +01:00
Bruno Herbelin
733d08638d Control manager thread save with SourceCallbacks 2021-12-20 00:30:50 +01:00
Bruno Herbelin
cb3cca8a64 catchup previous commits 2021-12-20 00:29:57 +01:00
Bruno Herbelin
a3a581794e Node update callbacks do not need to be disabled 2021-12-20 00:26:08 +01:00
Bruno Herbelin
f921e7610c New mechanism for source update with callbacks
Similarly to Node update callbacks, sources now have SourceCallbacks called at the start of each update. Several SourceCallback are implemented to ensure thread safe update of more complex properties (mixing alpha, depth, etc.).
2021-12-20 00:25:42 +01:00
Bruno Herbelin
8deb364025 Cleanup of main update calbacks
Clarify update and draw of rendering manager by using callbacks (instead of hidden calls in draw method).
2021-12-19 01:12:25 +01:00
Bruno Herbelin
3a9c6f56bf Work in progress OSC Control manager
Support for log, output and source targets. Now needs to be developed for all attributes.
2021-12-19 01:11:29 +01:00
Bruno Herbelin
a612154123 Initial implementation of Control manager
Control manager will handle control actions, recorded or from OSC. Here skeleton for receiving OSC messages is in place. Cleanup of includes for NetworkToolkit. Touched a bit the BaseToolkit.
2021-12-18 16:02:37 +01:00
Bruno Herbelin
bbc5e50491 bugfix show Player when clic source 2021-12-18 10:18:36 +01:00
Bruno Herbelin
a7689a8f54 Help window, setting to show/hide Tooltips
Menu and keyboard shortcut declaration centralized. List of all keyboard shortcuts. ImGuiToolkit unified tooltips.
2021-12-12 23:12:56 +01:00
Bruno Herbelin
731a1af1a6 Defines for ImGuiToolkit icons for source. 2021-12-12 23:10:07 +01:00
Bruno Herbelin
f53ebd4389 BugFix crash on close Player window 2021-12-11 21:35:23 +01:00
Bruno Herbelin
baa6ddb401 Implementation of user defined mixing deactivation limit
Mixing view handles to grab and scale limbo area. Saving of user defined limit in Session (and snapshot). Testing for source activation outside of update during session update loop.
2021-12-08 23:55:27 +01:00
Bruno Herbelin
315a8534d5 Store output PNG capture in list of recent recordings 2021-12-06 12:35:09 +01:00
Bruno Herbelin
d77bd4034d Improved UI tooltips 2021-12-06 12:29:22 +01:00
Bruno Herbelin
fa71797ed2 Unified implementation saving and loading settings history files 2021-12-06 11:41:03 +01:00
Bruno Herbelin
8c63552573 Global settings for Save and continue auto-preload
Added configuration for recent recording list. Added tooltip for filename in list.
2021-12-06 11:16:47 +01:00
Bruno Herbelin
a18d53c637 Improved Save and continue recording
When triggered from menu, prepare the UI for next openning of the new source pannel
2021-12-06 00:16:53 +01:00
Bruno Herbelin
ffe05368e8 Update New Source panel for Media
Added list of recent files, recent recordings, and folders list of media files. All saved in settings. Connect list of recent recordings with recorder.
2021-12-05 18:41:58 +01:00
Bruno Herbelin
923d84f378 Unified SystemToolkit list directory with dialog file patterns 2021-12-05 18:39:58 +01:00
Bruno Herbelin
e5334aae0a Cleaner file patterns for dialogs 2021-12-05 18:33:53 +01:00
Bruno Herbelin
4675be7e2a Unified notification of source creation with Source info
New virtual function source::info used for notification after adding source
2021-12-05 18:32:23 +01:00
Bruno Herbelin
bf3fc61ef7 Bad idea: do not use nvidia hardware decoder for jpeg
just crashes without reason and not very useful
2021-12-04 23:05:12 +01:00
Bruno Herbelin
ebd9fab312 Improved ordering of hardware decoding and log info 2021-12-04 00:23:27 +01:00
Bruno Herbelin
d359cf33d1 Improved runtime check of hardware encoder
gstreamer toolkit has_feature now tests possible instanciation
2021-12-03 23:36:07 +01:00
Bruno Herbelin
14bab1e299 Runtime selection of hardware GPU encoder
temporary implementation
2021-12-03 01:05:32 +01:00
Bruno Herbelin
4c4ad144b9 Finish Frame grabber after return on failed initialization 2021-12-03 01:05:04 +01:00
Bruno Herbelin
1157c0b1c5 cleanup frame grabber gst timer 2021-12-03 01:03:21 +01:00
Bruno Herbelin
68b2c5e0c1 Frame grabber threaded initialization
Start gstreamer init of frame grabber in a thread and wait future return from initializer before switching to active recording mode.
2021-12-02 11:45:22 +01:00
Bruno Herbelin
b97fd06f2a Bugfix Resolution frame buffer
Set width to power of 2, needed for encoding (even if OpenGL accepts to display it)
2021-12-02 11:40:31 +01:00
Bruno Herbelin
51f0f5bd66 Fixed ending of recording on exit 2021-12-02 09:35:02 +01:00
Bruno Herbelin
66f445997d Preliminary implementation of recording 'save & continue' 2021-12-01 23:05:41 +01:00
Bruno Herbelin
73d4f7c1ea Ensure swap interval 2021-11-28 23:58:01 +01:00
Bruno Herbelin
25fc5562db Unified layout of HelpMarkers 2021-11-28 23:57:33 +01:00
Bruno Herbelin
4d52bcb5b3 Fix glfw set window pos 2021-11-28 20:50:56 +01:00
Bruno Herbelin
3d2de560b0 Timelines of metro-synched media player 2021-11-28 11:36:56 +01:00
Bruno Herbelin
809e30d906 Timeline display in beat unit for synched to metronome 2021-11-27 19:26:33 +01:00
Bruno Herbelin
ef9e41f20d Fixed display of time in minimal string mode 2021-11-27 19:23:51 +01:00
Bruno Herbelin
1b4849f214 Media player synchronicity to beat or phase
Metronome synched play, rewind and step. saving in xml.
2021-11-26 12:22:39 +01:00
Bruno Herbelin
e123d139e4 Introducing modes of Metronome synchronicity 2021-11-24 21:48:51 +01:00
Bruno Herbelin
a8abd52afb Merge remote-tracking branch 'origin/add-code-of-conduct-1' 2021-11-24 20:32:49 +01:00
Bruno Herbelin
091e99f21b New export function for Version of a session 2021-11-24 20:32:29 +01:00
Bruno Herbelin
b6593c2a83 Added date to snapshot
Allows showing date of version
2021-11-24 00:15:40 +01:00
Bruno Herbelin
5ac7887360 Convert Snapshots into Versions of session
Added auto-snapshot on save to have an Iterative Saving mode, and change terminology of 'snapshots' to 'versions' management.
2021-11-23 22:47:44 +01:00
Bruno Herbelin
ed7627af6f Fixed UI spacing proportional to screen DPI 2021-11-23 22:46:22 +01:00
BHBN
1ea9fc54b2 Create CODE_OF_CONDUCT.md 2021-11-22 00:22:18 +01:00
Bruno Herbelin
3819571ec0 Fixed UI display time in readable form 2021-11-21 22:39:11 +01:00
Bruno Herbelin
94f131fc57 Fixed panel window show/hide 2021-11-21 22:10:17 +01:00
Bruno Herbelin
3c20314aab Metronome and Stopwatch User Interface
New Timer window in UI for Metronome (Ableton Link management) and replaces Timers. Former Timers in Metrics are replaced with Runtime (of session, of program and of total vimix runtime in settings). Temporarily disconnected Metronome from MediaPlayer actions.
2021-11-21 16:54:56 +01:00
Bruno Herbelin
1506d36407 Readable GST time is at 1/10th second precision 2021-11-21 16:52:09 +01:00
Bruno Herbelin
aa4b2967c7 Adding a timestamp to Session instanciation
Used to compute runtime of a session
2021-11-21 16:51:19 +01:00
Bruno Herbelin
a42881d31f Texture view slider UI fix 2021-11-21 16:46:18 +01:00
Bruno Herbelin
6a3ff2f235 More freedom of grab translation for all views 2021-11-17 23:11:15 +01:00
Bruno Herbelin
fc4e3dc362 Metronome settings and UI improvements 2021-11-14 00:18:32 +01:00
Bruno Herbelin
d6c689c5bb Cleanup include ImGuiToolkit 2021-11-14 00:18:10 +01:00
Bruno Herbelin
8676e9b900 Integration of Ableton link in vimix application
No useful functionality yet. Only connecting, set parameters, show metrics and save settings.
2021-11-13 15:01:02 +01:00
Bruno Herbelin
c271cad9aa Cleanup headers and dependencies 2021-11-13 00:14:05 +01:00
Bruno Herbelin
8e3bf786c0 Initial implementation of Metronome from Ableton LINK
Added submodule for github ableton link, and compiled draft of Metronome class.
2021-11-13 00:13:50 +01:00
Bruno Herbelin
a6ba694fbd Code warning cleanup and add GPL license header to all CPP files 2021-11-10 23:19:38 +01:00
Bruno
790ccc320e OSX bundle install of Frei0r plugins 2021-11-10 21:16:16 +01:00
Bruno Herbelin
26e951e59b Update website screenshot 2021-11-10 11:02:05 +01:00
Bruno Herbelin
a97581f5d7 semi-transparent icon of lock to inform its not interactive
need to CTRL+clic to unlock, as opposed to other handles: this is not perfect but shows the difference
2021-11-10 00:13:57 +01:00
Bruno Herbelin
5bf280ca4d warning on not found pattern 2021-11-10 00:13:08 +01:00
Bruno Herbelin
fe00baa701 Fixed snap to find frei0r plugins
added the package to install and the PATH to find in $SNAP
2021-11-10 00:12:55 +01:00
Bruno Herbelin
d3cb1d7f42 oops 2021-11-08 00:08:19 +01:00
Bruno Herbelin
593363732a Pattern generator improvement
Testing gstreamer feature to provide only available patterns, and added many more patterns to choose from.
2021-11-08 00:05:16 +01:00
Bruno Herbelin
d00f4cf715 Cosmetics
More adapted icons and link to user manual
2021-11-07 12:25:51 +01:00
Bruno Herbelin
cac31dbb21 Help info for mask file open 2021-11-07 12:25:06 +01:00
Bruno Herbelin
eb1fa6ca04 clean file dialog after cancel 2021-11-07 12:24:46 +01:00
Bruno Herbelin
0ac515ea5a Yet more update graphical manual 2021-11-07 01:42:39 +01:00
Bruno Herbelin
190e2a4952 Continue update graphical manual 2021-11-06 22:50:59 +01:00
Bruno Herbelin
0857b1bab6 Update graphical user manual
New screenshots from vimix 6.1
2021-11-06 18:08:52 +01:00
Bruno Herbelin
f3e42fdc95 Bugfix window resize log window 2021-10-31 00:06:08 +02:00
Bruno Herbelin
d617f3308a Added Cancel button to Transition view
And minor code improvements.
2021-10-30 19:57:43 +02:00
Bruno Herbelin
27cec85443 Do not re-open previous session if new session last created 2021-10-26 23:50:26 +02:00
Bruno Herbelin
63f7cab508 Improved gstreamer support for GPU decoding in Linux 2021-10-26 23:38:41 +02:00
Bruno Herbelin
ce0ac1bee1 Minor improvement precision media player gap timing 2021-10-12 23:20:16 +02:00
Bruno Herbelin
2c2584c8df Keyboard shortcut END for Disable output 2021-10-11 23:17:29 +02:00
Bruno Herbelin
14fd4d96c3 Shortcut for output window fullscreen and raise 2021-10-11 22:46:38 +02:00
Bruno Herbelin
dd7a63413c Fixed keyboard arrows control 2021-10-09 23:40:18 +02:00
Bruno Herbelin
6d0c2301c1 Keyboard input for Source Controller
SPACE toggle play/pause, B for begin restart
2021-10-09 00:16:55 +02:00
Bruno Herbelin
8bf8f05add compilation warnings 2021-09-24 00:46:20 +02:00
Bruno Herbelin
bd773d54c6 Improve stability FPS measure in timecounter 2021-09-24 00:45:47 +02:00
Bruno Herbelin
f4c52b7ed3 Fixed output monitor disablling 2021-09-19 11:07:13 +02:00
Bruno Herbelin
5b1504c8f6 Added general DISABLE output action menu
Makes sure the output is black, unrelated to session openning or opacity
2021-09-17 11:31:52 +02:00
Bruno
06187b9a1a work-in progress Helper and keyboard shortcuts 2021-08-26 15:51:07 +02:00
Bruno
7fb6e57829 Added 1200px height selection (e.g. for WUXGA displays) 2021-08-20 21:32:12 +02:00
Bruno
5ec954dbb5 UI fix 2021-08-16 22:55:21 +02:00
Bruno
a6bc30cf62 Fixed Frame grabber 2021-08-16 22:26:59 +02:00
Bruno
df165252fa Fixed OSX vtenc_h264_hw support for Recording 2021-08-16 15:52:58 +02:00
Bruno
da9c94f675 Temporarily disable v4l loopback: not working anymore 2021-08-15 00:32:20 +02:00
Bruno
031cef6357 optimize jpegenc 2021-08-15 00:30:28 +02:00
Bruno
ef5f3efd2e BugFix changing resolution of session 2021-08-14 23:15:18 +02:00
Bruno
bc8c4e3c7b Cleanup UI for centralized Recording settings 2021-08-14 21:57:59 +02:00
Bruno
f5da4c8bc2 Recording: support for NVIDIA nvenc and improved stability
Let gstreamer appsrc generate PTS automatically (seems to fix crash of encoding after long duration). Added test for GPU encoders and switch if enabled and available.
2021-08-14 13:41:53 +02:00
Bruno
644741a1ab Attempt at improving recoding when buffer if full 2021-08-12 00:05:41 +02:00
Bruno
09f46e7a27 Minor GUI layout improvement 2021-08-12 00:05:22 +02:00
Bruno
db4e1d214f BugFix drop Session file 2021-08-11 22:18:08 +02:00
Bruno
79433dd45c Improved Log message Video recording 2021-08-11 22:17:43 +02:00
Bruno
fe72c9b829 Cleanup and improve stability of FrameGrabber 2021-08-11 20:48:18 +02:00
Bruno
b37d22ba47 Improved FrameGrabber with clock duration and priority strategies
Keep track of actual FrameGrabber duration (different from timestamp). Two strategies for frame PTS: clock and framerate priorities. Implemented variable Framerate selection for VideoRecorder.  Integration of all this in UserInterface and Settings.
2021-08-11 00:20:28 +02:00
Bruno
7fb08e618f Added a READABLE time string conversion 2021-08-11 00:17:07 +02:00
Bruno
63c6f1169b Add icons on info and warning Logs 2021-08-11 00:16:19 +02:00
Bruno
0eff8fd24d Minor compilation warning fixed 2021-08-09 10:08:32 +02:00
Bruno
818e554d35 removing debuging info 2021-08-08 23:58:54 +02:00
Bruno
5a18dbaf37 Video Recoding Buffer management
Implemented methods to supervise encoding in FrameGrabber, avoid running out of buffer, and give user a selection of buffer sizes for recording.
2021-08-08 23:58:35 +02:00
Bruno
ddd9bb4e99 minor compilation fix 2021-08-07 20:26:59 +02:00
Bruno
38a2aa90e0 Improved FrameBufferSuface API 2021-08-07 20:26:30 +02:00
Bruno
139137770c BugFix: prevent missing symbol when attach 2021-08-07 20:25:52 +02:00
Bruno
789bf1bd00 BugFix prevent failed FrameBuffer fill 2021-08-07 20:23:20 +02:00
Bruno
563e762dde Store mask after fill 2021-08-07 20:22:07 +02:00
Bruno
28da5f8f39 BugFix Restore fading when cross fading 2021-08-07 20:21:37 +02:00
Bruno
5c42061fd9 Work in progress - towards display initials on sources 2021-08-07 20:21:37 +02:00
Bruno
843224ca35 oops.. correct Mesh file parse 2021-08-07 14:14:16 +02:00
Bruno
e47e76962b Fixed Recording (timing and UI)
Improved frame grabber timing and fixed UserInterface to show the "saving file" info.
2021-08-07 12:34:05 +02:00
Bruno
2f0e4e3212 Improved recording time acuracy 2021-08-07 01:02:39 +02:00
Bruno
fb3e1d0d25 Detecting EOF recording and unexpected termination 2021-08-06 21:23:01 +02:00
Bruno
e9b7e55570 work in progress recording probe 2021-08-06 17:56:48 +02:00
Bruno
8c206898f0 Dialog media include more formats
Integrate exotic file extensions and uppercase equivalent of all possible files to select with dialogs. 
Code cleanup
2021-08-06 16:43:25 +02:00
Bruno
a9c9683b8b longer recording timeout 2021-08-06 13:26:19 +02:00
Bruno
4f43ddf088 Draw a glyph in IMGUI
Proof of concept to show how to access Font texture in opengl to draw one glygh
2021-08-06 13:25:19 +02:00
Bruno
a9c8b67975 Implementation of custom Masks
FrameBuffer accepts to fill any size of FrameBufferImage as input, and a Dialog in TextureView allows to select a JPG or PNG.
2021-08-06 13:23:59 +02:00
Bruno
d1b7073ff9 Reimplementation of Dialogs
Cleanup code to integrate multithreading process for dialogs into the DialogToolkit (avoid poluting UserInterfaceManager and improves reliability)
2021-08-06 13:21:16 +02:00
Bruno
58afcacab9 BugFix thumbnailing
1. avoid crash by cathing the correct exception and 2. ensure we capture a frame by waiting a little
2021-08-04 12:55:51 +02:00
Bruno
5eddfcf196 Add tag icon in thumbnail to identify as user defined 2021-08-04 00:29:31 +02:00
Bruno
9a87764949 Improved UI for inactive videos
Display Player for videos even in disabled state, but with disabled controls
2021-08-03 20:02:04 +02:00
Bruno
fc4e40fba3 Display mixing source original texture when inactive
Re-using activesurface_ for manipulation and display of the source's input texture in the Mixer icon when inactive.
2021-08-02 22:27:06 +02:00
Bruno
e8acfc1c26 New Media Player option to Rewind on Disabled 2021-08-01 19:10:46 +02:00
Bruno
eaadc210ae Performance improvement for transliteration
Tracing CPU usage identified the cost of ICU transliteration: using a static dictionnary to improve performance
2021-08-01 16:44:46 +02:00
Bruno
8002f3164c Confirmed performance improvement without glBufferSubData 2021-08-01 16:43:25 +02:00
Bruno
48f92bc52b Cleanup session properties panel 2021-08-01 12:13:38 +02:00
Bruno
d1e833e0a1 Properties pannel of Session
Also added custom thumbnail of session.
2021-08-01 00:29:44 +02:00
Bruno
c5f0be2b32 Compilation Linux 2021-07-30 21:36:47 +02:00
Bruno
dbcf3bb0ea backward compatibility title window 2021-07-30 16:48:26 +02:00
Bruno
e7a79f6cdc Cleanup path_relative_to_path and path_absolute_from_path 2021-07-30 16:08:24 +02:00
Bruno
63b043dc4b Improved windows titles management
Display filename (no path) before APP_NAME, clean APP_TITLE when no file, bugs fixed.
2021-07-30 16:08:00 +02:00
Bruno
d2a576c99c Support for relative path for files in mix
File path in mix session file add a relative reference to the location of the session mix file. If SessionCreator cannot find the absolute path, it tries to load the file at the relative location. Done for MediaSource, SessionFileSource and SequenceSource.
2021-07-30 00:22:44 +02:00
Bruno
fc91e7cbdd Draft Function path_relative_to_path 2021-07-28 19:03:38 +02:00
Bruno
0555361a57 BugFix glfw set Window Title
Function is not thread safe, causing crash when saving.
2021-07-27 20:05:39 +02:00
Bruno
c923815a01 Added Apple Code signing script in cmake 2021-07-27 20:05:01 +02:00
Bruno
7a4d2ac027 Merge remote-tracking branch 'origin/master' 2021-07-27 09:15:47 +02:00
Bruno
442e1096be Compilation and packaging OSX 10.14 2021-07-27 09:12:53 +02:00
Bruno
6eaf8852ae OSX compilation 2021-07-27 09:06:53 +02:00
Bruno
3612fca707 Add keyboard shortcut play/pause
Space bar to toggle play/pause current source, B  for 'beginning' and N for 'next frame'
2021-07-26 12:51:07 +02:00
Bruno
4736d403a1 bugfix save as 2021-07-26 12:21:06 +02:00
Bruno
a18fd3177c Follow clang-tidy and clazy suggestions
variables non-POD should not be 'static' outside a class. Always use and init variables. Delete useless classes.
2021-07-17 16:45:01 +02:00
Bruno
5930b0f8fe UI bugfix locked source in texture view 2021-07-17 11:55:23 +02:00
Bruno
1c7c64db59 Merge remote-tracking branch 'origin/master' 2021-07-17 10:33:53 +02:00
Bruno
9bc780bcda Update CMAKE for OSX
minor text message changes
2021-07-17 10:33:43 +02:00
Bruno
b3f89e0464 for OSX 2021-07-17 10:32:13 +02:00
Bruno
1de4822c67 OSX compile 2021-07-03 11:27:08 +02:00
Bruno
c846e4072a postponing the dev of snapshot interpolation 2021-07-03 10:19:00 +02:00
Bruno
c4f26bd500 added -v and -t command line options 2021-07-02 22:16:55 +02:00
Bruno
041c01135a Small improvement timing fade in and out
Adding a buffer of 0 opacity before or after fading to avoid jumps to previous or next frame of a segment
2021-06-28 23:43:44 +02:00
Bruno
aa904f26ad Recording timeout with timing slider
Changed timout recording in uint milisecond.
2021-06-28 21:33:17 +02:00
Bruno
ff99d37eb6 Player Video Fading dialog
New dialog to apply fade in & out with parameters. Fixed Timeline fading functions. New ImGuiToolkit items to draw icons in Combo boxes.
2021-06-28 00:21:29 +02:00
Bruno
e8a500dc99 BugFix negative play speed Selection 2021-06-24 23:15:50 +02:00
Bruno
9f4f247cd2 Bugfix jump gaps MediaPlayer 2
Timeline copy should not overwrite pts of first frame.
2021-06-23 20:34:00 +02:00
Bruno
e1ac930dd6 Pedantic tooltip 2021-06-21 23:15:17 +02:00
Bruno
c20ed94f46 Bugfix jump gaps MediaPlayer
now that we use first frame time, testing jumps should be done with beginning time
2021-06-21 23:14:43 +02:00
Bruno
4efe754a8d MediaPlayer decoder information improved 2021-06-20 23:54:19 +02:00
Bruno
bb83f7fcb7 Timeline fade in and fade out 2021-06-20 23:53:52 +02:00
Bruno
79fa6082b0 Player: shoft slider on first frame of MediaPlayer 2021-06-20 18:50:12 +02:00
Bruno
f2ecc88955 Clean string from infor visitor on media player 2021-06-20 18:49:39 +02:00
Bruno
7253c1ec1a Merge remote-tracking branch 'origin/master' 2021-06-19 09:49:53 +02:00
BHBN
cf32c9fc12 Merge pull request #39 from brunoherbelin/dev
Accept new Player (dev)
2021-06-19 09:49:11 +02:00
Bruno
3086735be1 Merge branch 'HEAD' 2021-06-19 01:04:11 +02:00
Bruno
5a54e84dd8 Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 01:03:21 +02:00
Bruno
1ef26c0c95 Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-19 01:03:21 +02:00
Bruno
887142079b Fixed Timeline display 2021-06-19 01:03:21 +02:00
Bruno
b75ea00c0d Unique play/pause button for multiple sources 2021-06-19 01:03:21 +02:00
Bruno
319fbfa84d Bugfix display STRING time 2021-06-19 01:03:21 +02:00
Bruno
3874252797 Bugfix computation time with gaps 2021-06-19 01:03:21 +02:00
Bruno
5dfc45af5f Fixed Timeline ticks display 2021-06-19 01:03:21 +02:00
Bruno
1f203801db Player: reset selection on session change 2021-06-19 01:03:21 +02:00
Bruno
291410a2b3 Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-19 01:03:21 +02:00
Bruno
dfc4937688 Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-19 01:03:21 +02:00
Bruno
a0b763ab71 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-19 01:03:21 +02:00
Bruno
ad36ac5cd9 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-19 01:03:21 +02:00
Bruno
cd40d6d7e8 Improved management of selection in Player 2021-06-19 01:03:21 +02:00
Bruno
ec4214ebf8 improved quality realtime recorder h264 2021-06-19 01:03:21 +02:00
Bruno
a403d40b6c Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-19 01:03:21 +02:00
Bruno
7dcfc97f33 UI details 2021-06-19 01:03:21 +02:00
Bruno
5ea056a483 Bugfix timeline display array 2021-06-19 01:03:21 +02:00
Bruno
cd1702bb53 Define UNICODE symbols 2021-06-19 01:03:21 +02:00
Bruno
6ff266581a work in progress selection timeline 2021-06-19 01:03:21 +02:00
Bruno
6b7d108407 Minor improvements timeline display 2021-06-19 01:03:21 +02:00
Bruno
473e24bcd7 Fixed and improved TimeCounter 2021-06-19 01:03:21 +02:00
Bruno
1f5056bf15 BugFix IconButton (pop id) 2021-06-19 01:03:21 +02:00
Bruno
61fa062794 Correct implementation of DeviceSource resize
previous commit was bad...
2021-06-19 01:03:21 +02:00
Bruno
0e48cf4505 Bugfix and cleanup Info on source in UI 2021-06-19 01:03:21 +02:00
Bruno
b606f479e9 Bugfix change device source resolution 2021-06-19 01:03:21 +02:00
Bruno
2ccbf1ec12 Bugfix closing single frame stream 2021-06-19 01:03:21 +02:00
Bruno
ac6e84bb1c New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-06-19 01:03:21 +02:00
Bruno
11d12c1f29 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-06-19 01:03:21 +02:00
Bruno
c9707e7335 Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-06-19 01:03:21 +02:00
Bruno
2c0be68a3c Cleaup UI Selection source Player 2021-06-19 01:03:21 +02:00
Bruno
2add317106 Improved UI media player (info media) 2021-06-19 01:03:21 +02:00
Bruno
60ec11982a OSX compilation 2021-06-19 01:03:21 +02:00
Bruno
b2284cf1b4 Improved cursor EditPlotHistoLines 2021-06-19 01:02:12 +02:00
Bruno
e87ef2774b New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-06-19 01:02:12 +02:00
Bruno
7a9fcaefd6 Minor code improvements 2021-06-19 01:02:12 +02:00
Bruno
2333a7a11a Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-06-19 01:02:12 +02:00
Bruno
2a7857c499 Bugfix; verify frame grabbers before use 2021-06-19 01:02:12 +02:00
Bruno
e892dc1eb5 Implemented delayed start of recording 2021-06-19 01:02:12 +02:00
Bruno
9c8d1f31f6 bugfix linux shared webcam ui 2021-06-19 01:02:12 +02:00
Bruno
048db7a44b Merge remote-tracking branch 'origin/master' 2021-06-19 01:01:59 +02:00
Bruno
1d94d494b6 Merge remote-tracking branch 'origin/dev' into dev 2021-06-19 00:50:31 +02:00
Bruno
c6ac35addb Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 00:49:20 +02:00
Bruno
9ec279754b Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-19 00:49:20 +02:00
Bruno
bdcf28c5da Fixed Timeline display 2021-06-19 00:49:20 +02:00
Bruno
edf0f8074a Unique play/pause button for multiple sources 2021-06-19 00:49:20 +02:00
Bruno
c83a946cbd Bugfix display STRING time 2021-06-19 00:49:20 +02:00
Bruno
8604babeb6 Bugfix computation time with gaps 2021-06-19 00:49:20 +02:00
Bruno
7252b74539 Fixed Timeline ticks display 2021-06-19 00:49:20 +02:00
Bruno
08fbaa039f Player: reset selection on session change 2021-06-19 00:49:20 +02:00
Bruno
30f9fb50eb Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-19 00:49:20 +02:00
Bruno
e3578df8a0 Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-19 00:49:20 +02:00
Bruno
37445b8857 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-19 00:49:20 +02:00
Bruno
99ea14fab0 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-19 00:49:20 +02:00
Bruno
1717c143b2 Improved management of selection in Player 2021-06-19 00:49:20 +02:00
Bruno
53223d0876 improved quality realtime recorder h264 2021-06-19 00:49:20 +02:00
Bruno
096bcb4132 Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-19 00:49:20 +02:00
Bruno
05cc70bdbd UI details 2021-06-19 00:49:20 +02:00
Bruno
45653b52b5 Bugfix timeline display array 2021-06-19 00:49:20 +02:00
Bruno
d1841f2863 Define UNICODE symbols 2021-06-19 00:49:20 +02:00
Bruno
f85de11711 work in progress selection timeline 2021-06-19 00:49:20 +02:00
Bruno
9d81a105ee Minor improvements timeline display 2021-06-19 00:49:20 +02:00
Bruno
deb6af9dea Fixed and improved TimeCounter 2021-06-19 00:49:20 +02:00
Bruno
ab512b76aa BugFix IconButton (pop id) 2021-06-19 00:49:20 +02:00
Bruno
bcbeee7247 Correct implementation of DeviceSource resize
previous commit was bad...
2021-06-19 00:49:20 +02:00
Bruno
bcdc94c3b9 Bugfix and cleanup Info on source in UI 2021-06-19 00:49:20 +02:00
Bruno
6ebcf49758 Bugfix change device source resolution 2021-06-19 00:49:20 +02:00
Bruno
a936ab6851 Bugfix closing single frame stream 2021-06-19 00:49:20 +02:00
Bruno
95378660dd New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-06-19 00:49:20 +02:00
Bruno
e49bdac3e8 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-06-19 00:49:20 +02:00
Bruno
48380fab7e Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-06-19 00:49:20 +02:00
Bruno
ce92529a84 Cleaup UI Selection source Player 2021-06-19 00:49:20 +02:00
Bruno
8e29a555c8 Improved UI media player (info media) 2021-06-19 00:49:20 +02:00
Bruno
a1b6ec066b OSX compilation 2021-06-19 00:49:20 +02:00
Bruno
fb59bf491f Improved cursor EditPlotHistoLines 2021-06-19 00:48:11 +02:00
Bruno
86aec7d2ba New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-06-19 00:48:11 +02:00
Bruno
579f7d5609 Minor code improvements 2021-06-19 00:48:11 +02:00
Bruno
c87b1ac363 Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-06-19 00:48:11 +02:00
Bruno
543648112b Bugfix; verify frame grabbers before use 2021-06-19 00:48:11 +02:00
Bruno
daa3b9e978 Implemented delayed start of recording 2021-06-19 00:48:11 +02:00
Bruno
fb8da181da bugfix linux shared webcam ui 2021-06-19 00:48:11 +02:00
Bruno
6cc5a8af9e Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 00:47:47 +02:00
Bruno
1a1956962a Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-18 23:58:23 +02:00
Bruno
e422a1b403 Fixed Timeline display 2021-06-18 23:57:42 +02:00
Bruno
295ece79ae Unique play/pause button for multiple sources 2021-06-18 00:22:07 +02:00
Bruno
08f8ee159a Bugfix display STRING time 2021-06-18 00:21:36 +02:00
Bruno
e2d416b3fb Bugfix computation time with gaps 2021-06-18 00:21:09 +02:00
Bruno
16ed97b4cb Fixed Timeline ticks display 2021-06-17 21:42:15 +02:00
Bruno
82739702bd Player: reset selection on session change 2021-06-16 23:47:42 +02:00
Bruno
0010c9e3d5 Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-16 23:12:31 +02:00
Bruno
f59d4af92b Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-15 23:51:59 +02:00
Bruno
a7df619a05 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-14 23:42:20 +02:00
Bruno
f3759c2ef5 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-13 00:24:45 +02:00
Bruno
d547f3a8a8 Improved management of selection in Player 2021-06-07 22:53:29 +02:00
Bruno
583e53d8a8 improved quality realtime recorder h264 2021-06-07 00:04:23 +02:00
Bruno
a27bf08ce0 Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-07 00:04:06 +02:00
Bruno
060fb5ad2d UI details 2021-06-07 00:03:06 +02:00
Bruno
ffc00d9035 Bugfix timeline display array 2021-06-06 14:54:55 +02:00
Bruno
83a9d281c2 Define UNICODE symbols 2021-06-06 14:54:21 +02:00
Bruno
b6c853c308 work in progress selection timeline 2021-06-06 00:17:19 +02:00
Bruno
552c09d377 Minor improvements timeline display 2021-05-30 22:56:13 +02:00
Bruno
61164e627b Fixed and improved TimeCounter 2021-05-26 23:31:34 +02:00
Bruno
3c71ee1ff2 BugFix IconButton (pop id) 2021-05-25 22:17:57 +02:00
Bruno
07f610e84a Correct implementation of DeviceSource resize
previous commit was bad...
2021-05-25 20:20:43 +02:00
Bruno
faf344bc03 Bugfix and cleanup Info on source in UI 2021-05-25 09:09:23 +02:00
Bruno
e9482a3dfc Bugfix change device source resolution 2021-05-25 09:08:51 +02:00
Bruno
d4a7ce3487 Bugfix closing single frame stream 2021-05-25 09:08:24 +02:00
Bruno
3ef2737d82 New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-05-24 20:39:56 +02:00
BHBN
582b67f4e1 Add ref to GLP 3 or later 2021-05-24 12:10:51 +02:00
Bruno
5dd6c0af78 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-05-24 00:58:21 +02:00
Bruno
893e4f4723 Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-05-23 12:32:32 +02:00
Bruno
d137e87f0e Cleaup UI Selection source Player 2021-05-23 00:55:24 +02:00
Bruno
7f152077e5 Improved UI media player (info media) 2021-05-22 01:34:19 +02:00
Bruno
74a9b229d0 OSX compilation 2021-05-19 23:30:07 +02:00
Bruno
80cf57a979 Improved cursor EditPlotHistoLines 2021-05-19 23:29:57 +02:00
Bruno
19ba943075 New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-05-19 00:31:37 +02:00
Bruno
ba0e25a272 Minor code improvements 2021-05-17 16:13:21 +02:00
Bruno
575c487fa6 Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-05-17 16:10:46 +02:00
Bruno
1e91b2aa29 Bugfix; verify frame grabbers before use 2021-05-13 23:15:47 +02:00
Bruno
e10cf40f38 Implemented delayed start of recording 2021-05-13 22:15:50 +02:00
Bruno
23defd2117 bugfix linux shared webcam ui 2021-05-12 21:01:19 +02:00
Bruno
1227b87565 Merge remote-tracking branch 'origin/master' 2021-05-08 23:19:23 +02:00
Bruno
424ef16831 ignore local OSX files 2021-05-08 12:34:47 +02:00
Bruno
87059df28a Merge remote-tracking branch 'origin/master' 2021-05-08 12:33:41 +02:00
BHBN
520ff24ac2 Merge pull request #33 from brunoherbelin/dev
Dev
2021-05-08 12:32:48 +02:00
Bruno
920304773b Merge remote-tracking branch 'origin/dev' 2021-05-08 12:27:07 +02:00
Bruno
e55059c82c OSX update 2021-05-08 12:24:37 +02:00
Bruno
6a96c91fe1 Merge branch 'dev' 2021-05-08 12:19:13 +02:00
Bruno
f995e1cf72 Reordering source type in UI 2021-05-08 12:17:35 +02:00
Bruno
3be09553cf Multiple file selection for OSX
and finetuning creation UI for MultiFile source
2021-05-07 18:56:54 +02:00
Bruno
ee28a03a6c clean MultiFileSource 2021-05-06 23:56:31 +02:00
Bruno
d56700a9d5 Saving, undo and dynamic change of MultiFileSource 2021-05-06 23:44:15 +02:00
Bruno
250df8b651 Bugfix: allow changing resolution of stream 2021-05-06 22:18:46 +02:00
Bruno
e071ffe590 Create new Source type MultiFile
MultiFileSource plays a sequence of numbered images.
2021-05-06 00:24:01 +02:00
brunoherbelin
737b45a18c bugfix test initialization before render source 2021-05-04 21:40:24 +02:00
Bruno
1d2b7b17e8 CTRL + clic to lock/unlock 2021-05-01 20:03:42 +02:00
Bruno
bd71a4b581 restoring copy of transform_ matrix
would be possible to optimize and avoid this copy, but should be verified everywhere that this change of paradigm is taken into account (i.e. run update after copy transform if needed)
2021-05-01 19:12:09 +02:00
Bruno
451ff64b6f Cosmetics
Moved string truncate to BaseToolkit, fixed SystemToolkit max memory, clean left panel UI
2021-05-01 16:39:01 +02:00
Bruno
5e0dd60adb re-open should not change gl texture
rename 'ready' to 'opened' to avoid confusion
2021-05-01 15:35:36 +02:00
Bruno
a05cd380fb Using source mode UNINITIALIZED to replace initialized_ bool
info about source initialization was duplicated; using the mode state machine only avoids to keep a flag
2021-05-01 10:29:34 +02:00
Bruno
fd3102c0d3 memory monitor on latest value (not average) 2021-05-01 09:38:21 +02:00
Bruno
1bdd52232c compile fix 2021-05-01 00:36:02 +02:00
Bruno
381f68aaae Change READY state of source
a source is ready after rendering one frame (which is after being initialized)
2021-05-01 00:34:58 +02:00
Bruno
058fde19ce Fixed merging and action manager bug 2021-04-30 12:33:23 +02:00
Bruno
63544c603d Fixed Session thumbnail
wait for all sources to be ready before creating the thumbnail.
2021-04-30 10:17:10 +02:00
Bruno
92cd8f945e align behavior with stream 2021-04-29 23:13:30 +02:00
Bruno
5244941d9b remove commented code 2021-04-29 23:13:03 +02:00
Bruno
877fb09fa3 more elegant session source init 2021-04-29 23:12:46 +02:00
Bruno
34827ab068 ensure execute open immediately
and various code cleanup
2021-04-29 23:12:05 +02:00
Bruno
9bbe875690 Cleanup and bigsfix UI for thumbnail and preview source 2021-04-29 23:11:04 +02:00
Bruno
c7f09fb12d Prevent crash on exit
Global objects are deleted before  glfw terminates, thus crashing opengl access. Omitting glfw terminate on program exit is harmless, so...
2021-04-29 23:08:23 +02:00
Bruno
3f6d2bb6e3 Unifying list selection UI in session panel 2021-04-28 13:52:38 +02:00
Bruno
bd2500314f decide for byte aligned opengl textures all over 2021-04-28 13:18:01 +02:00
Bruno
a28f46a9eb perform render thumbnail only if framebuffer available 2021-04-28 11:23:11 +02:00
Bruno
7b7bd763c9 clear code copy vec4 2021-04-28 11:22:27 +02:00
Bruno
1118e4bfe0 add source propose a name, does not change it yet
and use filename only if provided
2021-04-28 11:21:47 +02:00
Bruno
b0bc88e9c2 unified behavior stream and media player 2021-04-28 11:19:59 +02:00
Bruno
038fea3539 opengl good practice: unbind texture after set 2021-04-28 11:18:45 +02:00
Bruno
ee20718c51 remove debug code 2021-04-27 23:21:33 +02:00
Bruno
f46ffc004a Validate list of filenames 2021-04-27 23:20:18 +02:00
Bruno
d2f0f42c2d imgui code cleanup 2021-04-27 23:19:58 +02:00
Bruno
be36662efc Fixed unique source name algorithm 2021-04-26 23:51:47 +02:00
Bruno
c7d6d37a8e oops 2021-04-26 22:06:06 +02:00
Bruno
7ba87fcee8 Fixed ButtonOpenUrl 2021-04-26 18:49:01 +02:00
Bruno
c3713c9ce7 work in progress tooltips with thumbnails of sessions 2021-04-26 13:48:20 +02:00
Bruno
98861cea6c Fixed Mixer delete and rename 2021-04-26 13:47:48 +02:00
brunoherbelin
d89290f9b2 alphabetically sort list of files in a dir 2021-04-26 09:22:22 +02:00
Bruno
1b6b2ecd4d Bugfix undo of multiple sources delete 2021-04-26 00:29:26 +02:00
Bruno
0e3575c1ca Using new basetoolkit function for unique naming
applied to source and snapshot names
2021-04-25 23:59:18 +02:00
Bruno
055f5c4c4e Creating a base toolkit for functions independent from other toolkits 2021-04-25 20:09:22 +02:00
Bruno
129d5445c3 Implementation of UI for snapshot manipulation
with thumbnails appearing on mouse over and in edit context menu
2021-04-25 14:02:06 +02:00
Bruno
6e202def1e Bugfix Action Snapshot replace 2021-04-25 11:39:45 +02:00
Bruno
0c51b16353 use framebuffer blit for thumbnail of RenderView 2021-04-25 01:27:51 +02:00
Bruno
c5ee77e969 Fixed blit of framebuffer 2021-04-25 01:27:32 +02:00
Bruno
c7962abfbb Save thumbnail of session when storing history or snapshot 2021-04-25 00:27:59 +02:00
Bruno
d371f6ae8e Cleanup FrameBufferImage API 2021-04-25 00:27:32 +02:00
Bruno
8336f6a595 Fixed RenderView thumbnailer 2021-04-25 00:26:38 +02:00
Bruno
c3a24a6d7f Cleanup XML read & write utility functions 2021-04-25 00:26:05 +02:00
Bruno
f643e80de7 Using std future promises to get thumbnail of render view 2021-04-24 13:14:57 +02:00
Bruno
5473c0b38b Store image size in XML
and discard loaded image if size does not match
2021-04-24 13:14:03 +02:00
Bruno
eb54b67caa New function to get thumbnail of render view 2021-04-22 23:38:00 +02:00
Bruno
97e7e5f4a1 Cleanup image saving and loading in xml session 2021-04-22 23:37:39 +02:00
brunoherbelin
da64172848 Work-in-progress: Interpolation of snapshot in Action manager 2021-04-21 23:35:34 +02:00
Bruno
e2d2e6ddd8 prefix ++View::need_deep_update_ 2021-04-19 19:24:50 +02:00
Bruno
896cae2d07 Fixed window resize 2021-04-19 18:29:10 +02:00
Bruno
409870dddb OSX cmake fix 2021-04-19 18:28:17 +02:00
Bruno
c19acda62d Added Snapshot Replace 2021-04-18 21:02:23 +02:00
Bruno
a50a6e129c Better implementation of SessionSnapshots saving 2021-04-18 18:22:21 +02:00
Bruno
d68987be0f C++ improved declaration of singleton managers 2021-04-18 13:27:19 +02:00
Bruno
bb64579fa5 Cleanup FileDialog (unused) 2021-04-18 13:15:18 +02:00
Bruno
2392d844d9 Making classes non-assignable
Following CppCheck recomendation, all classes that should not be manipulated by value are made non-assignable to ensure no mistake is made.
2021-04-18 13:04:16 +02:00
Bruno
c6d01c1420 Optimizing iteration
prefix ++i is faster than post i++
2021-04-18 11:38:03 +02:00
Bruno
8389010002 CoreSource complement
The CORE of a source is all what can be interpolated
2021-04-18 10:59:39 +02:00
Bruno
31eb13e16d Unused =operator: copy function instead 2021-04-18 10:58:03 +02:00
Bruno
7cafbc032b Working on source interpolation 2021-04-18 10:56:37 +02:00
brunoherbelin
cc752050f8 SourceCore to isolate core properties of a source 2021-04-17 14:40:00 +02:00
Bruno
c90bec36c5 clean useless ptr 2021-04-17 14:10:42 +02:00
Bruno
c1415b021a convert linklist to list of sources 2021-04-17 14:10:14 +02:00
brunoherbelin
1081b4a54d allow undo of trigger snapshot 2021-04-17 14:09:09 +02:00
brunoherbelin
ebb5fd16bb Draft implementation of Snapshots, with saving and UI 2021-04-17 10:28:12 +02:00
brunoherbelin
2d4a6d1fe6 Snapshots in Action manager 2021-04-15 22:50:28 +02:00
brunoherbelin
11df7c28b4 More robust clone XML mechanism: use ID 2021-04-13 22:41:57 +02:00
brunoherbelin
17d2a63132 Important change: sources keep their id all lifelong.
This simplifies a lot history and testing in session.
2021-04-13 22:26:26 +02:00
brunoherbelin
268486b652 std::string compatible imgui text input 2021-04-13 21:28:36 +02:00
brunoherbelin
f5af24b384 Update settings 2021-04-13 21:27:51 +02:00
brunoherbelin
6d522876ad Loading session with menu do not use smooth transition 2021-04-13 21:24:56 +02:00
brunoherbelin
2814763b97 std::string compatible imgui text input 2021-04-13 21:23:42 +02:00
brunoherbelin
765133a3bd BugFix 2021-04-11 15:24:24 +02:00
brunoherbelin
ab41a0c5d8 First implementation of Sticky Notes 2021-04-11 15:13:46 +02:00
brunoherbelin
eee9f49c05 Refurbishing the left panel
Toggle settings to show whole panel of settings
More space for main session panel (added notes)
2021-04-11 01:27:21 +02:00
Bruno
d7102809fc Auto stash before checking out "origin/dev" 2021-04-10 00:47:05 +02:00
Bruno
e792c119ea Merge branch 'master' into origin/dev 2021-04-10 00:45:00 +02:00
brunoherbelin
6e4ced8dcb Unified undo history messages 2021-04-10 00:22:16 +02:00
brunoherbelin
e69be79aed Compilation defines to cleanup old code 2021-04-09 22:50:16 +02:00
brunoherbelin
87dc282fb7 Improved MediaPlayer memory consumption:
Avoid duplicating Timeline object and limit number of URI discoverers to
two parallel threads.
2021-04-09 11:23:05 +02:00
brunoherbelin
38f7fb7c16 Bugfix timeline (prevent zero div) 2021-04-09 11:22:06 +02:00
brunoherbelin
6173e8279f Improved dev toolbox (saving statistics of memory) 2021-04-09 11:21:43 +02:00
brunoherbelin
4f661c5c05 Merge branch 'master' of github.com:brunoherbelin/vimix 2021-04-08 00:27:20 +02:00
brunoherbelin
28172430dc Added 'Source' to metrics (moved to UserInterfaceManager) 2021-04-08 00:26:52 +02:00
brunoherbelin
5a9d4dd55e Temporary disabling feature 'follow image processing' 2021-04-07 23:00:09 +02:00
brunoherbelin
b3880ad380 Limiting memory for media player 2021-04-07 22:57:29 +02:00
BHBN
b45c846a85 Update README.md 2021-04-07 13:23:22 +02:00
brunoherbelin
788fa693fd Draft implementation of Following mechanism for Image processing 2021-04-06 23:20:13 +02:00
brunoherbelin
1d45ab1d20 Cleanup DummySource (bad bad bad) 2021-04-05 14:55:21 +02:00
brunoherbelin
ae1c3d85ab bugfix: store mask after applying effect 2021-04-05 13:31:31 +02:00
brunoherbelin
c22df2eb2a (continue) Migrating clipboard manipulation to Session XML management 2021-04-05 13:06:31 +02:00
brunoherbelin
d3a130d9ba (continue) Migrating clipboard manipulation to Session XML management 2021-04-05 13:05:38 +02:00
brunoherbelin
8a57b52fcc Migrating clipboard manipulation to Session XML management 2021-04-05 13:04:44 +02:00
brunoherbelin
dbc9803f9e center on source only if source is not visible 2021-04-04 22:21:42 +02:00
brunoherbelin
3e376eb166 ensure output and media player window are visible 2021-04-04 20:55:55 +02:00
brunoherbelin
12aca05aef prevent potential memoryleak 2021-04-04 14:38:04 +02:00
brunoherbelin
ce1de27618 Improved README 2021-04-04 14:19:36 +02:00
brunoherbelin
66d5148e3a Prevent potential memory leak 2021-04-04 13:40:17 +02:00
brunoherbelin
d2b4a825eb avoid pedantic compilation warning 2021-04-04 13:27:56 +02:00
brunoherbelin
f443720319 Programming style improvement: following Cppcheck suggestions. 2021-04-04 13:13:06 +02:00
brunoherbelin
b4627a1613 Various potential memory leak fixed 2021-04-04 01:25:35 +02:00
brunoherbelin
ceea9c10d5 gstreamer memory cleanup in mediaplayer and stream 2021-04-04 01:24:13 +02:00
brunoherbelin
a143360497 Memory leak fix 2021-04-02 22:38:34 +02:00
brunoherbelin
163757cb69 Improved layout and menu Media Player UI 2021-04-02 13:57:11 +02:00
brunoherbelin
4537b986ca Support to forced software decoding option
With reload of media player when option is changed
2021-04-02 12:14:20 +02:00
Bruno
aafac2a7a8 merge 2021-04-01 20:46:46 +02:00
brunoherbelin
7344258b2f fixed hardware decoding detection OSX 2021-04-01 09:12:41 +02:00
brunoherbelin
c59994b7e5 Implemented a detection of hardware decoding used in pipeline
Simple check for names of decoder inside uridecodebin and cross check
with the list of known hardware Decoder Names
2021-04-01 00:14:02 +02:00
brunoherbelin
b089f59e2a Implemented a detection of hardware decoding used in pipeline
Simple check for names of decoder inside uridecodebin and cross check
with the list of known hardware Decoder Names
2021-04-01 00:11:05 +02:00
Bruno
649d2b7ef7 Merge remote-tracking branch 'origin/master' 2021-03-31 19:24:48 +02:00
brunoherbelin
8bef575e8b minor changes to media player pipeline (improved performance?) 2021-03-31 15:40:02 +02:00
brunoherbelin
559a036e6d BugFix: reload list of recent sessions after any change to history 2021-03-30 23:51:06 +02:00
brunoherbelin
0b845591f9 Improved transition view
Responsive buttons placement and clarified actions.
2021-03-30 23:02:24 +02:00
brunoherbelin
a8ef68ed59 Thought of the day. 2021-03-30 19:14:34 +02:00
brunoherbelin
7293b8b9dd BugFix: clean interrupt stream when ending abruptly 2021-03-30 19:03:33 +02:00
brunoherbelin
8eef5465c9 eyecandy: better icons for file menu 2021-03-29 23:18:14 +02:00
brunoherbelin
c08cb95dc1 Bugfix: restating correct order session loading properties 2021-03-29 23:17:54 +02:00
brunoherbelin
010166e7b0 renaming mediaplayer attribute force software decoding 2021-03-29 23:17:16 +02:00
brunoherbelin
4c7fb94616 Small update of webpage (link to installation guide) 2021-03-29 22:23:00 +02:00
Bruno
46f486a367 Documenting installation for wiki 2021-03-29 22:05:41 +02:00
brunoherbelin
ea195dcf11 add link to vimeo 2021-03-28 21:03:43 +02:00
brunoherbelin
e6979acded Revert "Try to integrate embedded video in Jekyll webpage"
This reverts commit 43b4fc81b9.
2021-03-28 20:59:46 +02:00
brunoherbelin
43b4fc81b9 Try to integrate embedded video in Jekyll webpage 2021-03-28 20:56:30 +02:00
brunoherbelin
d4ce6ebee6 trying snap eextensions: [gnome-3-28] 2021-03-28 18:37:29 +02:00
brunoherbelin
1c9a5ece83 setLocale in C (not std C++) 2021-03-27 23:36:51 +01:00
brunoherbelin
8a75664264 Preventing display glitch from invalid scaling of view 2021-03-27 23:31:18 +01:00
brunoherbelin
6687bdd258 BugFix: mixed-up Locale for XML I/O caused by GTK Dialogs 2021-03-27 23:17:19 +01:00
brunoherbelin
e525ecad36 Cleanup main 2021-03-27 23:15:49 +01:00
brunoherbelin
e8b5dc0649 BugFix: not using GST g_main_context to avoid GTK conflict 2021-03-27 19:21:18 +01:00
brunoherbelin
3a0f96c1ec fixed add extension on saved filename 2021-03-27 18:23:54 +01:00
brunoherbelin
e4c06ba1bb Fixed #ifdef compilation 2021-03-27 18:21:13 +01:00
brunoherbelin
bc4eadfd08 Bugfix view config loading 2021-03-27 18:13:09 +01:00
Bruno
ee2ce3802f Linux Dialogs in GTK for SNAP compatibility
Discarding use of ZENITY under linux (previously used with the tinyfiledialog) because snapcraft makes  it impossible to use :(. Reimplementation of GTK+ dialogs directly inside vimix code. Note: no changes for OSX. Complete cleanup of cmake file.
2021-03-27 13:03:22 +01:00
brunoherbelin
43d44634f7 Trying to fix the tinyfiledialog zenity integration 2021-03-22 16:25:51 +01:00
brunoherbelin
41bd7fc958 problem snap and zenity 2021-03-22 14:34:47 +01:00
brunoherbelin
1e458a642c Merge branch 'master' of github.com:brunoherbelin/vimix 2021-03-22 13:59:05 +01:00
brunoherbelin
b255e41091 Work in progress: force sofware decoder for a media player 2021-03-22 13:58:55 +01:00
brunoherbelin
bc22832ad6 information on tinyfiledialog in about. 2021-03-22 13:35:17 +01:00
brunoherbelin
f59ac505b7 shift grab source (even on rotation) 2021-03-21 14:23:30 +01:00
brunoherbelin
e15b962221 Cosmetics: improve reordering source in left panel 2021-03-21 14:09:11 +01:00
brunoherbelin
28d4d4acc4 Bugfix: prevent crash with current source when reordering 2021-03-21 13:24:54 +01:00
brunoherbelin
5f800f3723 Creating texture only on draw 2021-03-20 23:29:30 +01:00
brunoherbelin
2537ca03c8 fix 2021-03-20 22:05:30 +01:00
brunoherbelin
1860402452 Bugfix un-understandable crash on texture mixing quadratic. 2021-03-20 22:03:57 +01:00
brunoherbelin
bec9385c68 BugFix (apparently problematic memmove under OSX) 2021-03-20 18:38:46 +01:00
brunoherbelin
798139e097 Cosmetic: add label to button in source imgui visitor 2021-03-20 14:57:13 +01:00
brunoherbelin
6683d76222 OSX package name with patch version 2021-03-20 13:49:56 +01:00
brunoherbelin
6d2d16b644 updated OSX icon 2021-03-20 13:31:09 +01:00
brunoherbelin
4e83cdf30f hide history with ESC 2021-03-20 13:01:00 +01:00
brunoherbelin
71891292b4 Cosmetics: improved naming and actions on SessionSources (Groups and
File)
2021-03-20 11:51:46 +01:00
brunoherbelin
10ac384e7e Cosmetics: mouse over MixingCircle global opacity slider. 2021-03-20 10:46:06 +01:00
brunoherbelin
6e7df60f2c Minor bugfix and Actionmanager undo message improvement. 2021-03-20 10:03:54 +01:00
brunoherbelin
112b583379 Entire cleanup of ActionManager
History of previous id of sources is now obtained in SessionLoader; no
need to store the id of the source in action manager XML (so also
removed in View current id).
2021-03-19 23:09:49 +01:00
brunoherbelin
b07009f3ce BugFix: SessionGroupSource creation and update in ActionManager and
SessionCreator.
2021-03-19 22:53:08 +01:00
brunoherbelin
74e9553d56 bugfix textureview lock source 2021-03-19 17:52:02 +01:00
brunoherbelin
2c3d6ff02e Implement lock source mechanism in TextureView 2021-03-19 13:30:58 +01:00
brunoherbelin
8dd47b3a41 Flatten selection to lower depth 2021-03-19 00:21:47 +01:00
brunoherbelin
09f052a5d6 Added undo-redo to locking of sources. 2021-03-18 21:56:06 +01:00
brunoherbelin
ac5e885fb3 Brute force implementation of undo-redo of mixing group 2021-03-18 21:44:38 +01:00
brunoherbelin
dd9c8ac0b8 Added option to choose mirror or repeat texture on source 2021-03-18 19:43:51 +01:00
brunoherbelin
0de1b87028 reorder ESC sequence 2021-03-18 14:42:51 +01:00
brunoherbelin
e830a6eefe Mixing Group improved UI feedback 2021-03-18 14:42:33 +01:00
brunoherbelin
3c875a064e Active usual function keys without focus on workspace 2021-03-17 23:08:17 +01:00
brunoherbelin
2227c97a57 New action when clic on source symbol in Mixing or Layer views: open
editor in UI
2021-03-17 22:50:21 +01:00
brunoherbelin
ea211cb8ab Prevent action on source after locking it from side panel 2021-03-17 22:20:49 +01:00
brunoherbelin
6d2112fcd9 missing initialization layer view default appearance 2021-03-17 22:19:54 +01:00
brunoherbelin
05cb1db020 Minor UX improvement when mixing group rotation does not have the
expected effect
2021-03-17 22:03:19 +01:00
brunoherbelin
cd4d8f02cb Fixed source picking problems
Allow unlock of source in geometry, do not allow selection of locked
source with CTRL
2021-03-17 21:56:35 +01:00
brunoherbelin
b8fe0d7c69 Improved selection action in mixing and layer views 2021-03-17 21:13:22 +01:00
brunoherbelin
81c173e9c3 prevent null scale texture UV 2021-03-17 20:03:40 +01:00
brunoherbelin
63c954dedc Improved overlay grid 2021-03-17 18:49:20 +01:00
brunoherbelin
91d1ff1eb1 Mixing center action takes barycenter 2021-03-17 05:18:27 +01:00
brunoherbelin
41efc572e0 Improved keyboard manipulation of selection of sources in Views. 2021-03-17 05:12:00 +01:00
brunoherbelin
77764248b5 Added ALT modifier to selection rotation 2021-03-16 22:09:15 +01:00
brunoherbelin
ca0058c741 Action manager for undo action of keyboard arrow keys 2021-03-16 21:29:38 +01:00
brunoherbelin
8bd74ec725 Geometry selection Mirror action 2021-03-15 23:39:39 +01:00
brunoherbelin
9a5983d6de Selection pick bug fix 2021-03-15 23:25:54 +01:00
brunoherbelin
ce38bf72b8 Action manager for undo of context menu actions 2021-03-15 22:55:22 +01:00
brunoherbelin
ecba54196f Mixing and Geometry Selection menu actions 2021-03-15 21:26:30 +01:00
brunoherbelin
3b09bc877c Introducing Oriented bounding box for GeometryView selection
First implementation of MixingView selection manipulation (scale and
rotate)
2021-03-15 11:56:16 +01:00
brunoherbelin
92663aa171 Select group sources with ctrl+clic 2021-03-13 09:23:39 +01:00
brunoherbelin
c41d7ee067 BugFix select current 2021-03-12 23:56:49 +01:00
brunoherbelin
5ab5f1b60f bruteforce and efficient implementation of mixing groups management in
session.
2021-03-12 20:25:36 +01:00
brunoherbelin
10f9c1b329 Work in progress Implementation of mixing group
link and unlink methods, integration in MixingView, update groups on
source change, undo-redo improved.
2021-03-10 23:38:09 +01:00
brunoherbelin
2d62ab969c Work in progress: undo & redo of mixing group creation and delete
actions.
2021-03-10 00:16:49 +01:00
Bruno
7656113dcc Large commit for implementation of load&save of MixingGroups 2021-03-07 19:27:00 +01:00
Bruno
56f0165d75 Implementation of mixing group actions 2021-03-06 11:40:00 +01:00
Bruno
d79c4cbfe1 Fixed rendering LineStrip 2021-03-06 11:39:01 +01:00
Bruno
a55765c100 Add Symbol rotation 2021-03-06 11:38:17 +01:00
Bruno
134617bbd1 Created new Object MixingGoup 2021-03-03 22:39:36 +01:00
Bruno
2ccedd42e4 Cleanup code and includes 2021-03-03 22:39:17 +01:00
Bruno
d6d1ab5099 Clean code and includes 2021-03-03 22:37:56 +01:00
Bruno
b8d323ad59 Longer notification time 2021-03-03 22:37:18 +01:00
Bruno
737269bf5a New Primitive LineLoop (and cleanup associated visitors) 2021-03-03 22:36:59 +01:00
brunoherbelin
e54389b79c Improve context menu (icons and labels) 2021-02-28 17:38:18 +01:00
brunoherbelin
2906c50642 Change terminology sub-session 2021-02-28 14:10:55 +01:00
brunoherbelin
8123e61e34 Cleanup depth management 2021-02-28 14:10:32 +01:00
brunoherbelin
70cc66a7f4 Added edit menu in New source panel 2021-02-28 10:19:30 +01:00
brunoherbelin
13672a9d01 Use dichotomic algorithm to converge to new Alpha 2021-02-28 10:18:42 +01:00
brunoherbelin
f2cd18f754 Cleanup and unify views interface (combo) 2021-02-28 10:18:10 +01:00
brunoherbelin
7e723f4142 Use SourceTrail to analyse structure 2021-02-28 10:15:50 +01:00
brunoherbelin
adcd735127 Clean include tree for view cpp 2021-02-26 23:33:50 +01:00
brunoherbelin
70c28d4226 Renamed Appearance view to Texture view. 2021-02-26 23:15:14 +01:00
Bruno
004e1aaead Compile Views in separate source files 2021-02-26 23:09:51 +01:00
Bruno
e7a5d341e4 Dispatch code of Views in separate source files 2021-02-26 23:09:22 +01:00
brunoherbelin
f7b93478ed Reimplementation of LineStrip primitive using DYNAMIC vertext array and
triangle strips (basic mesh).
2021-02-26 17:01:24 +01:00
brunoherbelin
afc0c7af0e Fixed FPS stable computation 2021-02-23 23:44:04 +01:00
brunoherbelin
0ee5eebf91 Linux compilation fix 2021-02-23 23:43:41 +01:00
brunoherbelin
d0fdbeb14f Changed dt and fps computation in mixer 2021-02-23 23:11:16 +01:00
brunoherbelin
38f1288571 Reading version from git 2021-02-23 20:04:37 +01:00
brunoherbelin
4093170599 New blending: hard light 2021-02-22 18:26:14 +01:00
brunoherbelin
27112a2b57 AlphaShader for mapping alpha in pre-render 2021-02-22 18:26:00 +01:00
Bruno
ef7722bb5c Better terminology and icons for SessionGroup 2021-02-22 14:06:10 +01:00
brunoherbelin
8019f4ea25 Cleanup blending update 2021-02-21 22:02:04 +01:00
Bruno
a612395ca3 Added Lighten only blending mode 2021-02-20 00:26:21 +01:00
brunoherbelin
4718bf166f Shading pre-multiplied alpha for simple shapes too 2021-02-19 16:53:38 +01:00
Bruno
f51bc1f1f4 New Blending with pre-multiplied alpha
Finally found how to improve blending modes by pre-multiplying color by alpha in the shader, so that the blending equations can be applied on top of the apha manipulation.
2021-02-18 23:36:01 +01:00
Bruno
64071a4a55 Merge remote-tracking branch 'origin/master' 2021-02-16 23:01:21 +01:00
Bruno
678bdf066e Temporarily acceptable SessionGroup with opaque background 2021-02-16 23:01:02 +01:00
Bruno
cb5562eca2 Blending with separate alpha and color functions 2021-02-16 23:01:02 +01:00
Bruno
23386fccc2 Minor improvement XML
Do not save timeline for single frame media
2021-02-16 23:01:02 +01:00
Bruno
4b1d6a8ac0 Temporarily acceptable SessionGroup with opaque background 2021-02-16 22:58:45 +01:00
Bruno
146408607a Blending with separate alpha and color functions 2021-02-16 22:58:19 +01:00
Bruno
ffee2f067a Minor improvement XML
Do not same timeline for single frame media
2021-02-16 22:57:40 +01:00
Bruno
935762506d Bugfix: frae grabber should be called in Mixer
Grab frames in session update fails with SessionSource and SessionGroups.
2021-02-16 22:50:15 +01:00
brunoherbelin
885ce67174 OSX compile fix 2021-02-15 09:03:30 +01:00
Bruno
e37b21760e BugFix: interrupting recursive session loading
Prevent crash on  recursive (infinite) loading of session file (containing itself).
2021-02-14 18:56:48 +01:00
Bruno
25c2bb59f5 Draft searchFileVisitor 2021-02-13 13:45:00 +01:00
brunoherbelin
a1e4709910 OGL optimization (no mipmap, antialias lines) 2021-02-13 12:39:21 +01:00
brunoherbelin
0593e46e62 Changed colors a bit
Selection area, group and pain tools matching the highlight color. White
mask tool.
2021-02-11 21:14:21 +01:00
brunoherbelin
dca3033c06 Bugfix; correcting introduced bug with RenderSource and new Session
resolution.
2021-02-11 20:28:57 +01:00
brunoherbelin
d45554e162 Eyecandy: better color and transparency for icons in Mixing and Layer
views
2021-02-09 18:52:26 +01:00
brunoherbelin
8c4d3f3a18 Unified use of SessionSource in Mixer (import) for SessionFile and
SessionGroup sources.
2021-02-09 18:47:54 +01:00
Bruno
6bb5c0d208 Merge remote-tracking branch 'origin/master' 2021-02-09 18:35:55 +01:00
brunoherbelin
209caadd44 Bugfix in realtime vtenc OSX recording (allow-frame-reordering=0) 2021-02-08 12:09:53 +01:00
Bruno
89fa11447a New decoration frame for group in Layers view 2021-02-07 23:22:15 +01:00
Bruno
84416f566b Early implementation of groups: SessionGroupSource.
Rename SessionSource to SessionFileSource.
2021-02-07 22:01:07 +01:00
Bruno
65564065d9 New Symbol cube 2021-02-07 20:48:32 +01:00
Bruno
79540c0232 cleanup header 2021-02-07 20:48:10 +01:00
Bruno
5d23a285b4 get color of highlight 2021-02-07 20:47:55 +01:00
Bruno
1964a26fc3 add source find by depth 2021-02-07 20:46:30 +01:00
Bruno
e37a189bae add pop of source in selection 2021-02-07 20:45:13 +01:00
Bruno
5328995a79 added force non visible to BBox visitor 2021-02-07 20:43:56 +01:00
brunoherbelin
6929eb3307 Minor improvement log window 2021-02-05 22:27:47 +01:00
brunoherbelin
33f00f9da4 Bugfix (crash when reordering source) 2021-02-05 19:04:13 +01:00
brunoherbelin
34380e8592 BugFix: copy-paste a selection containing a source and its clone: fixed
that the clone is created (after the source).
2021-02-05 18:16:13 +01:00
brunoherbelin
8185c93457 Fixed replacement of failed RenderView after sessionSource import (if a
sessionSource contains a RenderView, the later should be re-created).
2021-02-05 18:11:16 +01:00
brunoherbelin
93b6bc9ca4 Bugfix in Recursive loopback of RenderView inside a SessionSource: fixed
loading and import into main session.
2021-02-04 23:25:49 +01:00
brunoherbelin
d76dfa4a9d Revert "Cleanup tooltips."
This reverts commit d23267d333.
2021-02-04 22:45:13 +01:00
brunoherbelin
d23267d333 Cleanup tooltips. 2021-01-31 20:09:11 +01:00
brunoherbelin
e8a258094f Eye candy on help markers and icon; added display of shortcut on the
side (grey text).
2021-01-31 14:18:43 +01:00
brunoherbelin
ffb30bc292 Slight modification of button play behavior in transition view (allows to
stop animation)
2021-01-30 23:51:39 +01:00
brunoherbelin
fa798c8809 Fixed position of UI in views to match ImGui size 2021-01-30 23:40:31 +01:00
brunoherbelin
ac15bbc840 Display preview of source centered in fixed-size preview area. 2021-01-30 23:23:30 +01:00
brunoherbelin
e26052013c Matching creation of new session source with recent changes on
SessionSource.
2021-01-30 22:45:27 +01:00
brunoherbelin
9215be6bfc ..and also reset fading after new empty session is created. 2021-01-30 22:43:22 +01:00
brunoherbelin
691c6d174b Bugfix in rare cases of smooth transition and combined session fading. 2021-01-30 22:34:16 +01:00
brunoherbelin
4bc9bf581e Cleanup pattern and session sources 2021-01-30 16:03:26 +01:00
brunoherbelin
3686106dab Bugfix restoring aspect ratio action. 2021-01-30 16:02:38 +01:00
brunoherbelin
62bc779dee Import of SessionSource: the merging of sources in session now applies
transformations of the sessionsource; so visually nothing (almost)
should change on the output.
2021-01-30 12:26:49 +01:00
brunoherbelin
843fa86c00 Depth management: layer actions operate on depth only (Z), and update of
source places the icon in LayerView (X,Y)
2021-01-30 12:24:18 +01:00
brunoherbelin
9bfc5b269a Fixed session source import; merge sources from the inside session,
adjust their alpha and depth, and delete former session source
immediately.
2021-01-29 22:32:02 +01:00
brunoherbelin
a7b6a67a92 reimplementation of LineSquare using rectangular polygons for horizontal
and vertical lines.
2021-01-28 13:50:31 +01:00
brunoherbelin
9b795a0df7 Improved time management for software framerate limiter. 2021-01-27 09:35:46 +01:00
brunoherbelin
29c40036b2 have to use ALT+TAB for view switcher in OSX 2021-01-26 22:35:59 +01:00
brunoherbelin
49e845137a Make CTRL+TAB compatible for OSX 2021-01-26 22:23:28 +01:00
brunoherbelin
394bfe2da4 Size dependent spaces for combo box in views. 2021-01-26 22:17:28 +01:00
brunoherbelin
6607bd319c New view navigation with [CTRL+TAB] 2021-01-26 22:16:57 +01:00
brunoherbelin
54c5eb6155 Reordering of Sources in list. 2021-01-25 22:24:08 +01:00
brunoherbelin
49ec387cfa New icon. 2021-01-25 19:17:43 +01:00
brunoherbelin
0ef6164b24 Added close icon to widgets. 2021-01-24 23:02:27 +01:00
brunoherbelin
87a25ca19f Improved transition view interface. 2021-01-24 22:31:09 +01:00
brunoherbelin
e564b63f77 Added icon to toggle lock in pannel (and fixed icon) 2021-01-24 22:18:31 +01:00
brunoherbelin
0e6ad3e25c BugFix changing workspace current selected source. 2021-01-24 20:41:06 +01:00
brunoherbelin
c3442a1090 Using brush settings in AppearanceView 2021-01-24 20:21:26 +01:00
brunoherbelin
83e5c37b60 Use settings for global brush parmeters. 2021-01-24 19:23:51 +01:00
Bruno
2dda3da8b1 thematic color of View UI 2021-01-24 18:21:14 +01:00
brunoherbelin
7e6ee0806d oops; terminate properly painting action. 2021-01-24 17:44:09 +01:00
Bruno
9c0adb4ce6 Important feature: source locking and workspace.
Source locking property, views ability to test if a source is selectable, change of selection when switch view, picking testing locking and workspace.
2021-01-24 17:19:41 +01:00
Bruno
b17136d23a Improved DrawVisitor to accept list of nodes 2021-01-24 17:16:28 +01:00
Bruno
edeec9568e Mixer Utility to deselect quickly a source 2021-01-24 10:55:19 +01:00
Bruno
e5ed27180f New buttons and icon modes 2021-01-24 10:54:25 +01:00
Bruno
207ac11ded Improved and new icons 2021-01-24 10:53:18 +01:00
brunoherbelin
2bc8420c24 New decoration handle for locked/unlocked. Bugfix picking mirrored
handles.
2021-01-23 10:08:26 +01:00
brunoherbelin
f4048fca04 Increased size of source icon 2021-01-21 23:02:50 +01:00
brunoherbelin
9942d8e628 Do not warn about canceled session set. Information message when user
quits transtition.
2021-01-21 22:47:58 +01:00
brunoherbelin
2fe282ef6a new Gui icon button 2021-01-21 22:31:43 +01:00
brunoherbelin
12dcd34b3d New eye icon 2021-01-21 21:14:13 +01:00
brunoherbelin
0cf4732347 (no change - keep code for later) 2021-01-19 19:08:19 +01:00
brunoherbelin
4e6a402142 Improved message and log. 2021-01-19 19:07:24 +01:00
brunoherbelin
9449936df0 Making sure disabling accelerated hardware acceleration on codec
decoding discards the corresponding gstreamer decoders.
2021-01-18 18:30:16 +01:00
brunoherbelin
7c555465b8 Bugfix copy imageprocessing shader should not copy shader properties 2021-01-18 18:24:36 +01:00
brunoherbelin
5262b8ae29 System configuration in main pannel: toggled with config button. Cleanup
the About vimix dialog, giving access to other about dialogs.
2021-01-17 23:59:25 +01:00
Bruno
1028aeea9f VERSION 0.5 2021-01-17 12:55:02 +01:00
Bruno
e02071047a Fine tuning appearance of layers view 2021-01-17 12:54:50 +01:00
brunoherbelin
227658d2fc Improved error message on different xml version. 2021-01-17 00:29:12 +01:00
brunoherbelin
56dc299fc9 Changed Mixing alpha transition function (less abrupt on the sides). 2021-01-17 00:28:45 +01:00
brunoherbelin
f7e1ff14d9 Fixed alpha blending and stippling 2021-01-16 23:37:43 +01:00
Bruno
9e865b3677 Preliminary implementation of source locking and layer stage levels 2021-01-16 22:32:02 +01:00
Bruno
e4da7de06f eye candy - changing icon 2021-01-16 15:17:11 +01:00
brunoherbelin
67f45793da Bugfix: forced DrawVisitor was not forcing actual draw() 2021-01-15 23:22:51 +01:00
brunoherbelin
5a2949609e reload view after clear new session 2021-01-13 23:46:35 +01:00
brunoherbelin
f20597656e Eye candy in texturing view 2021-01-13 23:37:02 +01:00
brunoherbelin
aaf700baba Implementation of arrow keys to move objects in views layer and
transition
2021-01-13 21:19:48 +01:00
brunoherbelin
a3e121d6a0 Fixed softwar FPS limiter when not v-sync 2021-01-13 18:38:44 +01:00
brunoherbelin
d3269e8aaa Merge commit 2021-01-13 18:38:08 +01:00
Bruno
b9104df26e Merge remote-tracking branch 'origin/master' 2021-01-13 18:23:29 +01:00
brunoherbelin
717f560326 software framerate limiter 60FPS if not v-sync 2021-01-13 18:20:55 +01:00
brunoherbelin
6fdb93a020 Support for Shift-Tab to loop backward in list of sources. 2021-01-13 14:06:54 +01:00
brunoherbelin
767b0d8084 Link zoom and grab in unified way for all views. 2021-01-12 21:49:05 +01:00
brunoherbelin
52eb9284f7 UI bugfix 2021-01-11 23:34:06 +01:00
brunoherbelin
c355486955 Improved interface mask, eye candies and new icons. 2021-01-11 23:09:52 +01:00
brunoherbelin
0e8f87d6c6 OSX compilation 2021-01-11 18:31:38 +01:00
Bruno
dbd3c071e8 Improved GUI for mask editing, added effects. 2021-01-10 23:56:50 +01:00
Bruno
398995648a Mask Paint!! New MaskShader for mouse paiting of masks and associated changes.
UI for pain mask, load & save of FrameBuffer Image.
2021-01-10 14:52:57 +01:00
brunoherbelin
3fc9401d97 Bugfix: do not forget settings for AppearanceView 2021-01-10 09:54:43 +01:00
brunoherbelin
d31320ae4b Code cleaning in screenshot 2021-01-10 09:53:55 +01:00
brunoherbelin
fe54afbe1c Cleanup and securing XMLElementEncode and DecodeArray 2021-01-10 09:52:58 +01:00
brunoherbelin
6b5ccb4450 Bugfix draw visitor (ensure clean start when visiting scene) 2021-01-10 09:51:22 +01:00
brunoherbelin
805baa75f4 Bugfix handle rotation 2021-01-02 13:59:21 +01:00
brunoherbelin
b1dd2f0bc9 Avoid unavailable pattern generators with versions of gstreamer < 18 2021-01-01 23:02:35 +01:00
brunoherbelin
f8e926040a Display transliterated filename in GUI 2021-01-01 20:41:39 +01:00
brunoherbelin
11690dfb8c ICU compilation error 2021-01-01 17:13:18 +01:00
brunoherbelin
63369223ca Compilation and snap with ICU libs 2021-01-01 15:41:31 +01:00
brunoherbelin
a1e81b58b1 OSX compilation fix 2021-01-01 14:36:44 +01:00
brunoherbelin
cf2b6b4b39 Improved information icon for source preview 2021-01-01 12:17:49 +01:00
brunoherbelin
c4e584a1da cleanup framebuffer info string 2021-01-01 12:00:30 +01:00
brunoherbelin
25b58b76f3 Enable resize of session frame buffer resolution 2021-01-01 11:54:40 +01:00
Bruno
b346403887 Try to use GPU video decoding plugins when possible 2021-01-01 10:09:17 +01:00
Bruno
e0cd560dfb Implementation of 2 corner masks 2020-12-31 18:27:33 +01:00
brunoherbelin
4313e51530 Minor GUI bugfix 2020-12-31 10:51:41 +01:00
brunoherbelin
e2bb90208e Bugs fixed and eye candies 2020-12-31 00:50:50 +01:00
Bruno
85d72a1c0e Transliteration of source name 2020-12-30 17:23:31 +01:00
Bruno
a073ab41dd Improved procedural masks 2020-12-27 21:43:33 +01:00
Bruno
34c24d99df Integration procedural GLSL masks 2020-12-27 14:05:03 +01:00
Bruno
69d9d9473b Implemented procedural GLSL masks
Create MaskShader and added manipulation of masks in Appearance view
2020-12-27 14:04:23 +01:00
brunoherbelin
a58c06c617 Fixed display cropped source in AppearanceView and code cleanup. 2020-12-08 23:43:50 +01:00
brunoherbelin
b7a54d0512 BugFix deep update views (depth and layout) and crop. 2020-12-08 23:04:12 +01:00
brunoherbelin
1677582034 Bug fix on deep update of views and scenes: incrementing a counter of
updates needed (instead of only a flag).
2020-12-07 23:39:41 +01:00
brunoherbelin
44b888fd04 Work in progress - implementation of cropping in Geometry view instead
of AppearanceView. Display of scaled mixing surface in Mixing and Layers
view. Changed stippling shading.
2020-12-07 00:17:10 +01:00
brunoherbelin
78f9216d32 Hiding grips in geometry manipulation when operating. 2020-12-05 00:22:46 +01:00
brunoherbelin
1ea0ec53af Minor fixed GUI and mouse cursor 2020-12-03 23:21:30 +01:00
brunoherbelin
688823e63f New doc images for Appearance view 2020-12-01 23:37:52 +01:00
260 changed files with 36904 additions and 9290 deletions

10
.gitignore vendored
View File

@@ -16,3 +16,13 @@ rules.ninja
/vmix
/vimix_*.snap
/CMakeLists.txt.user.*
rsc/shaders/paint.fs
osx/.DS_Store
.DS_Store
osx/runvimix
*.autosave

3
.gitmodules vendored
View File

@@ -19,3 +19,6 @@
[submodule "ext/glm"]
path = ext/glm
url = https://github.com/g-truc/glm.git
[submodule "ext/link"]
path = ext/link
url = https://github.com/Ableton/link.git

View File

@@ -1,13 +1,38 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <string>
#include <algorithm>
#include <thread>
#include "Log.h"
#include "View.h"
#include "Mixer.h"
#include "MixingGroup.h"
#include "tinyxml2Toolkit.h"
#include "ImageProcessingShader.h"
#include "SessionVisitor.h"
#include "SessionCreator.h"
#include "Settings.h"
#include "BaseToolkit.h"
#include "Interpolator.h"
#include "SystemToolkit.h"
#include "ActionManager.h"
@@ -15,241 +40,476 @@
#define ACTION_DEBUG
#endif
#define HISTORY_NODE(i) std::to_string(i).insert(0,1,'H')
#define SNAPSHOT_NODE(i) std::to_string(i).insert(0,1,'S')
using namespace tinyxml2;
Action::Action(): step_(0), max_step_(0)
void captureMixerSession(tinyxml2::XMLDocument *doc, std::string node, std::string label)
{
}
void Action::clear()
{
// clean the history
xmlDoc_.Clear();
step_ = 0;
max_step_ = 0;
// start fresh
store("Session start");
}
void Action::store(const std::string &label, uint64_t id)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
step_++;
std::string nodename = "H" + std::to_string(step_);
// erase future
for (uint e = step_; e <= max_step_; e++) {
std::string name = "H" + std::to_string(e);
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
if ( node )
xmlDoc_.DeleteChild(node);
}
max_step_ = step_;
// create history node
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
xmlDoc_.InsertEndChild(sessionNode);
// create node
XMLElement *sessionNode = doc->NewElement( node.c_str() );
doc->InsertEndChild(sessionNode);
// label describes the action
sessionNode->SetAttribute("label", label.c_str());
// id indicates which object was modified
sessionNode->SetAttribute("id", id);
sessionNode->SetAttribute("label", label.c_str() );
// label describes the action
sessionNode->SetAttribute("date", SystemToolkit::date_time_string().c_str() );
// view indicates the view when this action occured
sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode());
// get session to operate on
Session *se = Mixer::manager().session();
// get the thumbnail (requires one opengl update to render)
FrameBufferImage *thumbnail = se->renderThumbnail();
if (thumbnail) {
XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc);
if (imageelement)
sessionNode->InsertEndChild(imageelement);
delete thumbnail;
}
// save session attributes
sessionNode->SetAttribute("activationThreshold", se->activationThreshold());
// save all sources using source visitor
SessionVisitor sv(&xmlDoc_, sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
SessionVisitor sv(doc, sessionNode);
for (auto iter = se->begin(); iter != se->end(); ++iter, sv.setRoot(sessionNode) )
(*iter)->accept(sv);
// debug
}
Action::Action(): history_step_(0), history_max_step_(0), locked_(false),
snapshot_id_(0), snapshot_node_(nullptr), interpolator_(nullptr), interpolator_node_(nullptr)
{
}
void Action::init()
{
// clean the history
history_doc_.Clear();
history_step_ = 0;
history_max_step_ = 0;
// reset snapshot
snapshot_id_ = 0;
snapshot_node_ = nullptr;
store("Session start");
}
void Action::store(const std::string &label)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
history_step_++;
// erase future
for (uint e = history_step_; e <= history_max_step_; e++) {
XMLElement *node = history_doc_.FirstChildElement( HISTORY_NODE(e).c_str() );
if ( node )
history_doc_.DeleteChild(node);
}
history_max_step_ = history_step_;
// threaded capturing state of current session
std::thread(captureMixerSession, &history_doc_, HISTORY_NODE(history_step_), label).detach();
#ifdef ACTION_DEBUG
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
Log::Info("Action stored %d '%s'", history_step_, label.c_str());
// XMLSaveDoc(&history_doc_, "/home/bhbn/history.xml");
#endif
}
void Action::undo()
{
// not possible to go to 1 -1 = 0
if (step_ <= 1)
if (history_step_ <= 1)
return;
// what id was modified to get to this step ?
// get history node of current step
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ - 1
restore( step_ - 1, id);
restore( history_step_ - 1);
}
void Action::redo()
{
// not possible to go to max_step_ + 1
if (step_ >= max_step_)
if (history_step_ >= history_max_step_)
return;
// what id to modify to go to next step ?
std::string nodename = "H" + std::to_string(step_ + 1);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ + 1
restore( step_ + 1, id);
restore( history_step_ + 1);
}
void Action::stepTo(uint target)
{
// get reasonable target
uint t = CLAMP(target, 1, max_step_);
uint t = CLAMP(target, 1, history_max_step_);
// going backward
if ( t < step_ ) {
// go back one step at a time
while (t < step_)
undo();
}
// step forward
else if ( t > step_ ) {
// go forward one step at a time
while (t > step_)
redo();
}
// ignore t == step_
if (t != history_step_)
restore(t);
}
std::string Action::label(uint s) const
{
std::string l = "";
if (s > 0 && s <= max_step_) {
std::string nodename = "H" + std::to_string(s);
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
l = sessionNode->Attribute("label");
if (s > 0 && s <= history_max_step_) {
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
if (sessionNode)
l = sessionNode->Attribute("label");
}
return l;
}
void Action::restore(uint target, uint64_t id)
FrameBufferImage *Action::thumbnail(uint s) const
{
FrameBufferImage *img = nullptr;
if (s > 0 && s <= history_max_step_) {
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
if (sessionNode)
img = SessionLoader::XMLToImage(sessionNode);
}
return img;
}
void Action::restore(uint target)
{
// lock
locked_ = true;
// get history node of target step
step_ = CLAMP(target, 1, max_step_);
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
history_step_ = CLAMP(target, 1, history_max_step_);
XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(history_step_).c_str() );
// ask view to refresh, and switch to action view if user prefers
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
if (sessionNode) {
#ifdef ACTION_DEBUG
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
#endif
// ask view to refresh, and switch to action view if user prefers
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
// we operate on the current session
Session *se = Mixer::manager().session();
if (se == nullptr)
return;
// sessionsources contains list of ids of all sources currently in the session
std::list<uint64_t> sessionsources = se->getIdList();
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
// load history status:
// - if a source exists, its attributes are updated, and that's all
// - if a source does not exists (in session), it is created in the session
SessionLoader loader( se );
loader.load( sessionNode );
// loadersources contains list of ids of all sources generated by loader
std::list<uint64_t> loadersources = loader.getIdList();
// for( auto it = loadersources.begin(); it != loadersources.end(); it++)
// Log::Info("loadersources id %s", std::to_string(*it).c_str());
// remove intersect of both lists (sources were updated by SessionLoader)
for( auto lsit = loadersources.begin(); lsit != loadersources.end(); ){
auto ssit = std::find(sessionsources.begin(), sessionsources.end(), (*lsit));
if ( ssit != sessionsources.end() ) {
lsit = loadersources.erase(lsit);
sessionsources.erase(ssit);
}
else
lsit++;
}
// remaining ids in list sessionsources : to remove
while ( !sessionsources.empty() ){
Source *s = Mixer::manager().findSource( sessionsources.front() );
if (s!=nullptr) {
#ifdef ACTION_DEBUG
Log::Info("Delete id %s", std::to_string(sessionsources.front() ).c_str());
#endif
// remove the source from the mixer
Mixer::manager().detach( s );
// delete source from session
se->deleteSource( s );
}
sessionsources.pop_front();
}
// remaining ids in list loadersources : to add
while ( !loadersources.empty() ){
#ifdef ACTION_DEBUG
Log::Info("Recreate id %s to %s", std::to_string(id).c_str(), std::to_string(loadersources.front()).c_str());
#endif
// change the history to match the new id
replaceSourceId(id, loadersources.front());
// add the source to the mixer
Mixer::manager().attach( Mixer::manager().findSource( loadersources.front() ) );
loadersources.pop_front();
// actually restore
Mixer::manager().restore(sessionNode);
}
// free
locked_ = false;
}
void Action::replaceSourceId(uint64_t previousid, uint64_t newid)
{
// loop over every session history step
XMLElement* historyNode = xmlDoc_.FirstChildElement("H1");
for( ; historyNode ; historyNode = historyNode->NextSiblingElement())
{
// check if this history node references this id
uint64_t id_history_ = 0;
historyNode->QueryUnsigned64Attribute("id", &id_history_);
if ( id_history_ == previousid )
// change to new id
historyNode->SetAttribute("id", newid);
// loop over every source in session history
XMLElement* sourceNode = historyNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
// check if this source node has this id
uint64_t id_source_ = 0;
sourceNode->QueryUnsigned64Attribute("id", &id_source_);
if ( id_source_ == previousid )
// change to new id
sourceNode->SetAttribute("id", newid);
void Action::snapshot(const std::string &label, bool threaded)
{
// ignore if locked
if (locked_)
return;
std::string snap_label = BaseToolkit::uniqueName(label, labels());
// create snapshot id
u_int64_t id = BaseToolkit::uniqueId();
// get session to operate on
Session *se = Mixer::manager().session();
se->snapshots()->keys_.push_back(id);
if (threaded)
// threaded capture state of current session
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label).detach();
else
captureMixerSession(se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label);
#ifdef ACTION_DEBUG
Log::Info("Snapshot stored %d '%s'", id, snap_label.c_str());
#endif
}
void Action::open(uint64_t snapshotid)
{
if ( snapshot_id_ != snapshotid )
{
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
snapshot_node_ = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snapshot_node_)
snapshot_id_ = snapshotid;
else
snapshot_id_ = 0;
interpolator_node_ = nullptr;
}
}
void Action::replace(uint64_t snapshotid)
{
// ignore if locked or if no label is given
if (locked_)
return;
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
// remember label
std::string label = snapshot_node_->Attribute("label");
// remove previous node
Session *se = Mixer::manager().session();
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
// threaded capture state of current session
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(snapshot_id_), label).detach();
#ifdef ACTION_DEBUG
Log::Info("Snapshot replaced %d '%s'", snapshot_id_, label.c_str());
#endif
}
}
std::list<uint64_t> Action::snapshots() const
{
return Mixer::manager().session()->snapshots()->keys_;
}
std::list<std::string> Action::labels() const
{
std::list<std::string> names;
tinyxml2::XMLDocument *doc = Mixer::manager().session()->snapshots()->xmlDoc_;
for ( XMLElement *snap = doc->FirstChildElement(); snap ; snap = snap->NextSiblingElement() )
names.push_back( snap->Attribute("label"));
return names;
}
std::string Action::label(uint64_t snapshotid) const
{
std::string label = "";
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snap)
label = snap->Attribute("label");
return label;
}
std::string Action::date(uint64_t snapshotid) const
{
std::string date = "";
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snap){
const char *d = snap->Attribute("date");
if (d)
date = std::string(d);
}
return date;
}
void Action::setLabel (uint64_t snapshotid, const std::string &label)
{
open(snapshotid);
if (snapshot_node_)
snapshot_node_->SetAttribute("label", label.c_str());
}
FrameBufferImage *Action::thumbnail(uint64_t snapshotid) const
{
FrameBufferImage *img = nullptr;
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snap){
img = SessionLoader::XMLToImage(snap);
}
return img;
}
void Action::clearSnapshots()
{
Session *se = Mixer::manager().session();
while (!se->snapshots()->keys_.empty())
remove(se->snapshots()->keys_.front());
}
void Action::remove(uint64_t snapshotid)
{
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
// remove
Session *se = Mixer::manager().session();
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
se->snapshots()->keys_.remove( snapshot_id_ );
}
snapshot_node_ = nullptr;
snapshot_id_ = 0;
}
void Action::restore(uint64_t snapshotid)
{
// lock
locked_ = true;
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_)
// actually restore
Mixer::manager().restore(snapshot_node_);
// free
locked_ = false;
store("Snapshot " + label(snapshot_id_));
}
float Action::interpolation()
{
float ret = 0.f;
if ( interpolator_node_ == snapshot_node_ && interpolator_)
ret = interpolator_->current();
return ret;
}
void Action::interpolate(float val, uint64_t snapshotid)
{
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
if ( interpolator_node_ != snapshot_node_ ) {
// change interpolator
if (interpolator_)
delete interpolator_;
// create new interpolator
interpolator_ = new Interpolator;
// current session
Session *se = Mixer::manager().session();
XMLElement* N = snapshot_node_->FirstChildElement("Source");
for( ; N ; N = N->NextSiblingElement()) {
// check if a source with the given id exists in the session
uint64_t id_xml_ = 0;
N->QueryUnsigned64Attribute("id", &id_xml_);
SourceList::iterator sit = se->find(id_xml_);
// a source with this id exists
if ( sit != se->end() ) {
// read target in the snapshot xml
SourceCore target;
SessionLoader::XMLToSourcecore(N, target);
// add an interpolator for this source
interpolator_->add(*sit, target);
}
}
// operate interpolation on opened snapshot
interpolator_node_ = snapshot_node_;
}
if (interpolator_) {
// Log::Info("Action::interpolate %f", val);
interpolator_->apply( val );
}
}
}
// static multithreaded version saving
static void saveSnapshot(const std::string& filename, tinyxml2::XMLElement *snapshot_node)
{
if (!snapshot_node){
Log::Warning("Invalid version.", filename.c_str());
return;
}
const char *l = snapshot_node->Attribute("label");
if (!l) {
Log::Warning("Invalid version.", filename.c_str());
return;
}
// load the file: is it a session?
tinyxml2::XMLDocument xmlDoc;
XMLError eResult = xmlDoc.LoadFile(filename.c_str());
if ( XMLResultError(eResult)){
Log::Warning("%s could not be openned for re-export.", filename.c_str());
return;
}
XMLElement *header = xmlDoc.FirstChildElement(APP_NAME);
if (header == nullptr) {
Log::Warning("%s is not a %s session file.", filename.c_str(), APP_NAME);
return;
}
// remove all snapshots
XMLElement* snapshotNode = xmlDoc.FirstChildElement("Snapshots");
xmlDoc.DeleteChild(snapshotNode);
// swap "Session" node with version_node
XMLElement *sessionNode = xmlDoc.FirstChildElement("Session");
xmlDoc.DeleteChild(sessionNode);
sessionNode = snapshot_node->DeepClone(&xmlDoc)->ToElement();
sessionNode->SetName("Session");
xmlDoc.InsertEndChild(sessionNode);
// we got a session, set a new export filename
std::string newfilename = filename;
newfilename.insert(filename.size()-4, "_" + std::string(l));
// save new file to disk
if ( XMLSaveDoc(&xmlDoc, newfilename) )
Log::Notify("Version exported to %s.", newfilename.c_str());
else
// error
Log::Warning("Failed to export Session file %s.", newfilename.c_str());
}
void Action::saveas(const std::string& filename, uint64_t snapshotid)
{
// ignore if locked or if no label is given
if (locked_)
return;
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
// launch a thread to save the session
std::thread (saveSnapshot, filename, snapshot_node_).detach();
}
}

View File

@@ -1,48 +1,79 @@
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include <list>
#include <string>
#include <atomic>
#include <tinyxml2.h>
class Interpolator;
class FrameBufferImage;
class Action
{
// Private Constructor
Action();
Action(Action const& copy); // Not Implemented
Action& operator=(Action const& copy); // Not Implemented
Action(Action const& copy) = delete;
Action& operator=(Action const& copy) = delete;
public:
static Action& manager()
static Action& manager ()
{
// The only instance
static Action _instance;
return _instance;
}
void init ();
void store(const std::string &label, uint64_t id = 0);
// Undo History
void store (const std::string &label);
void undo ();
void redo ();
void stepTo (uint target);
void clear();
void undo();
void redo();
void stepTo(uint target);
inline uint current () const { return history_step_; }
inline uint max () const { return history_max_step_; }
std::string label (uint s) const;
FrameBufferImage *thumbnail (uint s) const;
inline uint current() const { return step_; }
inline uint max() const { return max_step_; }
// Snapshots
void snapshot (const std::string &label = "", bool threaded = false);
void clearSnapshots ();
std::list<uint64_t> snapshots () const;
uint64_t currentSnapshot () const { return snapshot_id_; }
std::string label(uint s) const;
void open (uint64_t snapshotid);
void replace (uint64_t snapshotid = 0);
void restore (uint64_t snapshotid = 0);
void remove (uint64_t snapshotid = 0);
void saveas (const std::string& filename, uint64_t snapshotid = 0);
std::string label (uint64_t snapshotid) const;
std::string date (uint64_t snapshotid) const;
std::list<std::string> labels () const;
void setLabel (uint64_t snapshotid, const std::string &label);
FrameBufferImage *thumbnail (uint64_t snapshotid) const;
float interpolation ();
void interpolate (float val, uint64_t snapshotid = 0);
private:
void restore(uint target, uint64_t id);
void replaceSourceId(uint64_t previousid, uint64_t newid);
tinyxml2::XMLDocument xmlDoc_;
uint step_;
uint max_step_;
tinyxml2::XMLDocument history_doc_;
uint history_step_;
uint history_max_step_;
std::atomic<bool> locked_;
void restore(uint target);
uint64_t snapshot_id_;
tinyxml2::XMLElement *snapshot_node_;
Interpolator *interpolator_;
tinyxml2::XMLElement *interpolator_node_;
};
#endif // ACTIONMANAGER_H

322
BaseToolkit.cpp Normal file
View File

@@ -0,0 +1,322 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "BaseToolkit.h"
#include <chrono>
#include <ctime>
#include <sstream>
#include <list>
#include <iomanip>
#include <algorithm>
#include <climits>
#include <map>
#include <locale>
#include <unicode/ustream.h>
#include <unicode/translit.h>
uint64_t BaseToolkit::uniqueId()
{
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
// 64-bit int 18446744073709551615UL
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
}
std::string BaseToolkit::uniqueName(const std::string &basename, std::list<std::string> existingnames)
{
std::string tentativename = basename;
int count = 1;
int max = 100;
// while tentativename can be found in the list of existingnames
while ( std::find( existingnames.begin(), existingnames.end(), tentativename ) != existingnames.end() )
{
for( auto it = existingnames.cbegin(); it != existingnames.cend(); ++it) {
if ( it->find(tentativename) != std::string::npos)
++count;
}
if (count > 1)
tentativename = basename + "_" + std::to_string( count );
else
tentativename += "_";
if ( --max < 0 ) // for safety only, should never be needed
break;
}
return tentativename;
}
// Using ICU transliteration :
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
std::string BaseToolkit::transliterate(const std::string &input)
{
// because icu::Transliterator is slow, we keep a dictionnary of already
// transliterated texts to be faster during repeated calls (update of user interface)
static std::map<std::string, std::string> dictionnary_;
std::map<std::string, std::string>::const_iterator existingentry = dictionnary_.find(input);
if (existingentry == dictionnary_.cend()) {
auto ucs = icu::UnicodeString::fromUTF8(input);
UErrorCode status = U_ZERO_ERROR;
icu::Transliterator *firstTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status);
firstTrans->transliterate(ucs);
delete firstTrans;
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
secondTrans->transliterate(ucs);
delete secondTrans;
std::ostringstream output;
output << ucs;
// remember for future
dictionnary_[input] = output.str();
}
// return remembered transliterated text
return dictionnary_[input];
}
std::string BaseToolkit::unspace(const std::string &input)
{
std::string output = input;
std::replace( output.begin(), output.end(), ' ', '_');
return output;
}
std::string BaseToolkit::byte_to_string(long b)
{
double numbytes = static_cast<double>(b);
std::ostringstream oss;
std::list<std::string> list = {" Bytes", " KB", " MB", " GB", " TB"};
std::list<std::string>::iterator i = list.begin();
while(numbytes >= 1024.0 && i != list.end())
{
++i;
numbytes /= 1024.0;
}
oss << std::fixed << std::setprecision(2) << numbytes;
if (i != list.end()) oss << *i;
return oss.str();
}
std::string BaseToolkit::bits_to_string(long b)
{
double numbytes = static_cast<double>(b);
std::ostringstream oss;
std::list<std::string> list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"};
std::list<std::string>::iterator i = list.begin();
while(numbytes >= 1000.0 && i != list.end())
{
++i;
numbytes /= 1000.0;
}
oss << std::fixed << std::setprecision(2) << numbytes;
if (i != list.end()) oss << *i;
return oss.str();
}
std::string BaseToolkit::truncated(const std::string& str, int N)
{
std::string trunc = str;
int l = str.size();
if ( l > N ) {
trunc = std::string("...") + str.substr( l - N + 3 );
}
return trunc;
}
std::list<std::string> BaseToolkit::splitted(const std::string& str, char delim)
{
std::list<std::string> strings;
size_t start = 0;
size_t end = 0;
while ((start = str.find_first_not_of(delim, end)) != std::string::npos) {
end = str.find(delim, start);
size_t delta = start > 0 ? 1 : 0;
strings.push_back(str.substr( start -delta, end - start + delta));
}
return strings;
}
std::string BaseToolkit::joinned(std::list<std::string> strlist, char separator)
{
std::string str;
for (auto it = strlist.cbegin(); it != strlist.cend(); ++it)
str += (*it) + separator;
return str;
}
bool BaseToolkit::is_a_number(const std::string& str, int *val)
{
bool isanumber = false;
try {
*val = std::stoi(str);
isanumber = true;
}
catch (const std::invalid_argument&) {
// avoids crash
}
return isanumber;
}
std::string BaseToolkit::common_prefix( const std::list<std::string> & allStrings )
{
if (allStrings.empty())
return std::string();
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
}
return std::string(s0.cbegin(), _end);
}
std::string BaseToolkit::common_suffix(const std::list<std::string> & allStrings)
{
if (allStrings.empty())
return std::string();
const std::string &s0 = allStrings.front();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
return suffix;
}
std::string BaseToolkit::common_pattern(const std::list<std::string> &allStrings)
{
if (allStrings.empty())
return std::string();
// find common prefix and suffix
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
return std::string(s0.cbegin(), _end) + "*" + suffix;
}
std::string BaseToolkit::common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max)
{
if (allStrings.empty())
return std::string();
// find common prefix and suffix
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
// range of middle string, after prefix and before suffix
size_t pos_prefix = std::distance(s0.cbegin(), _end);
size_t pos_suffix = s0.size() - pos_prefix - std::distance(s0.crbegin(), r_end);
int n = -1;
*max = 0;
*min = INT_MAX;
// loop over all strings to verify there are numbers between prefix and suffix
for (auto it = allStrings.cbegin(); it != allStrings.cend(); ++it)
{
// get middle string, after prefix and before suffix
std::string s = it->substr(pos_prefix, pos_suffix);
// is this central string ONLY made of digits?
if (s.end() == std::find_if(s.begin(), s.end(), [](unsigned char c)->bool { return !isdigit(c); })) {
// yes, validate
*max = std::max(*max, std::atoi(s.c_str()) );
*min = std::min(*min, std::atoi(s.c_str()) );
if (n < 0)
n = s.size();
else if ( n != (int) s.size() ) {
n = 0;
break;
}
}
else {
n = 0;
break;
}
}
if ( n < 1 )
return std::string();
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
std::string pattern = std::string(s0.cbegin(), _end);
pattern += "%0" + std::to_string(n) + "d";
pattern += suffix;
return pattern;
}

53
BaseToolkit.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef BASETOOLKIT_H
#define BASETOOLKIT_H
#include <list>
#include <string>
namespace BaseToolkit
{
// get integer with unique id
uint64_t uniqueId();
// proposes a name that is not already in the list
std::string uniqueName(const std::string &basename, std::list<std::string> existingnames);
// get a transliteration to Latin of any string
std::string transliterate(const std::string &input);
// replaces spaces by underscores in a string
std::string unspace(const std::string &input);
// get a string to display memory size with unit KB, MB, GB, TB
std::string byte_to_string(long b);
// get a string to display bit size with unit Kbit, MBit, Gbit, Tbit
std::string bits_to_string(long b);
// cut a string to display the right most N characters (e.g. /home/me/toto.mpg -> ...ome/me/toto.mpg)
std::string truncated(const std::string& str, int N);
// split a string into list of strings separated by delimitor (e.g. /home/me/toto.mpg -> {home, me, toto.mpg} )
std::list<std::string> splitted(const std::string& str, char delim);
// rebuilds a splitted string
std::string joinned(std::list<std::string> strlist, char separator = ' ');
// returns true if the string
bool is_a_number(const std::string& str, int *val = nullptr);
// find common parts in a list of strings
std::string common_prefix(const std::list<std::string> &allStrings);
std::string common_suffix(const std::list<std::string> &allStrings);
// form a pattern "prefix*suffix" (e.g. file list)
std::string common_pattern(const std::list<std::string> &allStrings);
// form a pattern "prefix%03dsuffix" (e.g. numbered file list)
std::string common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max);
}
#endif // BASETOOLKIT_H

View File

@@ -1,14 +1,18 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#include <map>
#include "Log.h"
#include "View.h"
#include "Primitives.h"
#include "Source.h"
#include "Decorations.h"
#include "BoundingBoxVisitor.h"
#include "Log.h"
#include "Primitives.h"
#include "Decorations.h"
BoundingBoxVisitor::BoundingBoxVisitor(): Visitor()
BoundingBoxVisitor::BoundingBoxVisitor(bool force): force_(force), modelview_(glm::identity<glm::mat4>())
{
modelview_ = glm::identity<glm::mat4>();
}
@@ -34,8 +38,8 @@ void BoundingBoxVisitor::visit(Group &n)
if (!n.visible_)
return;
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
if ( (*node)->visible_ )
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
}
@@ -46,14 +50,13 @@ void BoundingBoxVisitor::visit(Switch &n)
if (!n.visible_ || n.numChildren() < 1)
return;
glm::mat4 mv = modelview_;
n.activeChild()->accept(*this);
if ( n.activeChild()->visible_ || force_)
n.activeChild()->accept(*this);
modelview_ = mv;
}
void BoundingBoxVisitor::visit(Primitive &n)
{
if (!n.visible_)
return;
bbox_.extend(n.bbox().transformed(modelview_));
@@ -64,3 +67,48 @@ void BoundingBoxVisitor::visit(Scene &n)
{
n.ws()->accept(*this);
}
GlmToolkit::AxisAlignedBoundingBox BoundingBoxVisitor::AABB(SourceList l, View *view)
{
// calculate bbox on selection
BoundingBoxVisitor selection_visitor_bbox;
for (auto it = l.begin(); it != l.end(); ++it) {
// calculate bounding box of area covered by selection
selection_visitor_bbox.setModelview( view->scene.ws()->transform_ );
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
}
return selection_visitor_bbox.bbox();
}
GlmToolkit::OrientedBoundingBox BoundingBoxVisitor::OBB(SourceList l, View *view)
{
GlmToolkit::OrientedBoundingBox obb_;
// try the orientation of each source in the list
for (auto source_it = l.begin(); source_it != l.end(); ++source_it) {
float angle = (*source_it)->group( view->mode() )->rotation_.z;
glm::mat4 transform = view->scene.ws()->transform_;
transform = glm::rotate(transform, -angle, glm::vec3(0.f, 0.f, 1.f) );
// calculate bbox of the list in this orientation
BoundingBoxVisitor selection_visitor_bbox;
for (auto it = l.begin(); it != l.end(); ++it) {
// calculate bounding box of area covered by sources' nodes
selection_visitor_bbox.setModelview( transform );
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
}
// if not initialized or if new bbox is smaller than previous
if ( obb_.aabb.isNull() || selection_visitor_bbox.bbox() < obb_.aabb) {
// keep this bbox as candidate
obb_.aabb = selection_visitor_bbox.bbox();
obb_.orientation = glm::vec3(0.f, 0.f, angle);
}
}
return obb_;
}

View File

@@ -3,16 +3,18 @@
#include "GlmToolkit.h"
#include "Visitor.h"
#include "SourceList.h"
class View;
class BoundingBoxVisitor: public Visitor
{
glm::mat4 modelview_;
bool force_;
GlmToolkit::AxisAlignedBoundingBox bbox_;
public:
BoundingBoxVisitor();
public:
BoundingBoxVisitor(bool force = false);
void setModelview(glm::mat4 modelview);
GlmToolkit::AxisAlignedBoundingBox bbox();
@@ -23,6 +25,9 @@ public:
void visit(Group& n) override;
void visit(Switch& n) override;
void visit(Primitive& n) override;
static GlmToolkit::AxisAlignedBoundingBox AABB(SourceList l, View *view);
static GlmToolkit::OrientedBoundingBox OBB(SourceList l, View *view);
};
#endif // BOUNDINGBOXVISITOR_H

View File

@@ -1,11 +1,37 @@
cmake_minimum_required(VERSION 3.8.0)
cmake_minimum_required(VERSION 3.8.2)
project(vimix VERSION 0.0.1 LANGUAGES CXX C)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# use git
find_package (Git)
if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT GIT_DESCRIBE_ERROR_CODE)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 0 1 VIMIX_VERSION_MAJOR)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 2 1 VIMIX_VERSION_MINOR)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 4 1 VIMIX_VERSION_PATCH)
add_definitions(-DVIMIX_VERSION_MAJOR=${VIMIX_VERSION_MAJOR})
add_definitions(-DVIMIX_VERSION_MINOR=${VIMIX_VERSION_MINOR})
add_definitions(-DVIMIX_VERSION_PATCH=${VIMIX_VERSION_PATCH})
message(STATUS "Compiling vimix version ${VIMIX_VERSION_MAJOR}.${VIMIX_VERSION_MINOR}.${VIMIX_VERSION_PATCH}")
else()
message(STATUS "Compiling vimix (unknown version)")
endif()
endif()
set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON")
set(CMAKE_INCLUDE_CURRENTDIR ON)
# Find the cmake modules
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules )
include(MacroLogFeature)
include(MacroFindGStreamerLibrary)
if(UNIX)
if (APPLE)
@@ -14,15 +40,22 @@ if(UNIX)
# the RPATH to be used when installing
set(CMAKE_SKIP_RPATH TRUE)
set(OpenGL_DIR /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/)
set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
# set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment")
set(CMAKE_OSX_ARCHITECTURES "x86_64")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
# find icu4c in OSX (pretty well hidden...)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/icu4c/lib/pkgconfig")
else()
add_definitions(-DLINUX)
# linux opengl
set(OpenGL_GL_PREFERENCE "GLVND")
# linux dialogs use GTK
find_package(GTK 3.0 REQUIRED)
macro_log_feature(GTK_FOUND "GTK" "GTK cross-platform widget toolkit" "http://www.gtk.org" TRUE)
endif()
add_definitions(-DUNIX)
elseif(WIN32)
@@ -31,8 +64,18 @@ elseif(WIN32)
endif()
# Include the CMake RC module
include(CMakeRC)
# Basics
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
find_package(GLIB2)
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org" TRUE)
find_package(GObject)
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org" TRUE)
find_package(PNG REQUIRED)
macro_log_feature(PNG_FOUND "PNG" "Portable Network Graphics" "http://www.libpng.org" TRUE)
#
# GSTREAMER
@@ -42,64 +85,48 @@ find_package(GStreamer 1.0.0 COMPONENTS base)
macro_log_feature(GSTREAMER_FOUND "GStreamer"
"Open Source Multiplatform Multimedia Framework"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_BASE_LIBRARY_FOUND "GStreamer base library"
"${GSTREAMER_BASE_LIBRARY}"
"http://gstreamer.freedesktop.org/" FALSE "1.0.0")
find_package(GStreamerPluginsBase 1.0.0 COMPONENTS app audio video pbutils gl)
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamer app library"
"${GSTREAMER_APP_LIBRARY}"
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer app library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamer audio library"
"${GSTREAMER_AUDIO_LIBRARY}"
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer audio library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamer video library"
"${GSTREAMER_VIDEO_LIBRARY}"
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer video library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamer pbutils library"
"${GSTREAMER_PBUTILS_LIBRARY}"
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer pbutils library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamer opengl library"
"${GSTREAMER_GL_LIBRARY}"
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer opengl library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
#find_package(GStreamerPluginsBad 1.0.0 COMPONENTS player)
#macro_log_feature(GSTREAMER_PLAYER_LIBRARY_FOUND "GStreamer player library"
#"${GSTREAMER_PLAYER_LIBRARY}"
#"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
# Various preprocessor definitions for GST
add_definitions(-DGST_DISABLE_XML -DGST_DISABLE_LOADSAVE)
# Basics
find_package(GLIB2)
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org/" TRUE)
find_package(GObject)
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org/" TRUE)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
set(THREAD_LIBRARY Threads::Threads)
find_package(PNG REQUIRED)
set(PNG_LIBRARY PNG::PNG)
#
# ICU4C
#
if (PKG_CONFIG_FOUND)
pkg_check_modules(ICU REQUIRED icu-i18n icu-uc icu-io)
else ()
find_package(ICU REQUIRED COMPONENTS i18n io uc)
endif ()
macro_log_feature(ICU_FOUND "ICU" "International Components for Unicode" "http://site.icu-project.org" TRUE)
#
# GLFW3
# NB: set glfw3_PATH to /usr/local/Cellar/glfw/3.3.2/lib/cmake/glfw3
#
find_package(glfw3 3.2 REQUIRED)
macro_log_feature(glfw3_FOUND "GLFW3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org/" TRUE)
set(GLFW_LIBRARY glfw)
if (PKG_CONFIG_FOUND)
pkg_check_modules(GLFW3 REQUIRED glfw3>=3.2)
else ()
find_package(glfw3 3.2 REQUIRED)
endif()
macro_log_feature(GLFW3_FOUND "glfw3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org" TRUE)
#find_package(OpenGL REQUIRED)
macro_display_feature_log()
# static sub packages in ext
set(BUILD_STATIC_LIBS ON)
@@ -108,14 +135,21 @@ set(BUILD_STATIC_LIBS ON)
# GLM
#
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/glm)
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net.")
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net -- ${CMAKE_CURRENT_SOURCE_DIR}/ext/glm")
#
# Ableton LINK
#
#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/link)
include(${CMAKE_CURRENT_SOURCE_DIR}/ext/link/AbletonLinkConfig.cmake)
message(STATUS "Compiling Ableton 'Link' https://github.com/Ableton/link -- ${CMAKE_CURRENT_SOURCE_DIR}/ext/link")
#
# GLAD
#
set(GLAD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/include)
add_library(GLAD "${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/src/glad.c")
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de/ -- ${GLAD_INCLUDE_DIR}.")
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de -- ${GLAD_INCLUDE_DIR}")
#
# DEAR IMGUI
@@ -131,23 +165,14 @@ set(IMGUI_SRCS
set(IMGUI_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/imgui)
add_library(IMGUI "${IMGUI_SRCS}")
target_compile_definitions(IMGUI PRIVATE "IMGUI_IMPL_OPENGL_LOADER_GLAD")
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}.")
#
# ImGui Color Text Editor
#
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
set(IMGUITEXTEDIT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
)
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}.")
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}")
#
# TINY XML 2
#
set(TINYXML2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2)
add_library(TINYXML2 "${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2/tinyxml2.cpp")
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}.")
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}")
#
# OSCPack
@@ -168,40 +193,47 @@ set(OSCPACK_SRCS
)
set(OSCPACK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack)
add_library(OSCPACK "${OSCPACK_SRCS}")
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}.")
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}")
#
# FILE DIALOG: use tinyfiledialog for all except Linux
#
if(UNIX)
if (APPLE)
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
set(TINYFD_LIBRARY TINYFD)
endif()
else()
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
set(TINYFD_LIBRARY TINYFD)
endif()
#
# ImGui Color Text Editor
#
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
set(IMGUITEXTEDIT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
)
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}")
#
# STB
#
set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/stb)
add_definitions(-DIMGUI_USE_STB_SPRINTF)
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}.")
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}")
#
# DIRENT
#
# DIRENT (windows only)
if(WIN32)
set(DIRENT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/Dirent/include)
message(STATUS "Including 'Dirent' from https://github.com/tronkko/dirent -- ${DIRENT_INCLUDE_DIR}.")
endif( WIN32 )
#
# TINY FILE DIALOG
#
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
#
# OBJ LOADER
#
#set(OBJLOADER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/obj)
#add_library(OBJLOADER "${CMAKE_CURRENT_SOURCE_DIR}/ext/obj/ObjLoader.cpp")
#message(STATUS "Compiling 'ObjLoader' from https://github.com/mortennobel/OpenGL_3_2_Utils -- ${OBJLOADER_INCLUDE_DIR}.")
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
endif()
#
# Application
@@ -216,9 +248,11 @@ include_directories(
${GSTREAMER_APP_INCLUDE_DIR}
${GSTREAMER_PBUTILS_INCLUDE_DIR}
${GSTREAMER_GL_INCLUDE_DIR}
${GLFW3_INCLUDE_DIRS}
${ICU_INCLUDE_DIRS}
${GLM_INCLUDE_DIRS}
${GLIB2_INCLUDE_DIR}
${GLAD_INCLUDE_DIR}
${GLM_INCLUDE_DIRS}
${IMGUI_INCLUDE_DIR}
${IMGUI_INCLUDE_DIR}/examples
${IMGUITEXTEDIT_INCLUDE_DIR}
@@ -227,6 +261,12 @@ include_directories(
${STB_INCLUDE_DIR}
${DIRENT_INCLUDE_DIR}
${OSCPACK_INCLUDE_DIR}
${link_HEADERS}
)
link_directories(
${GLFW3_LIBRARY_DIRS}
${ICU_LIBRARY_DIRS}
)
@@ -234,6 +274,7 @@ set(VMIX_BINARY "vimix")
set(VMIX_SRCS
main.cpp
Log.cpp
BaseToolkit.cpp
Shader.cpp
ImageShader.cpp
ImageProcessingShader.cpp
@@ -243,13 +284,23 @@ set(VMIX_SRCS
Mesh.cpp
Decorations.cpp
View.cpp
RenderView.cpp
GeometryView.cpp
MixingView.cpp
MixingGroup.cpp
LayerView.cpp
TextureView.cpp
TransitionView.cpp
Source.cpp
SourceCallback.cpp
SourceList.cpp
Session.cpp
Selection.cpp
SessionSource.cpp
SessionVisitor.cpp
GarbageVisitor.cpp
Interpolator.cpp
SessionCreator.cpp
SessionParser.cpp
Mixer.cpp
FrameGrabber.cpp
Recorder.cpp
@@ -258,7 +309,6 @@ set(VMIX_SRCS
Settings.cpp
Screenshot.cpp
Resource.cpp
FileDialog.cpp
Timeline.cpp
Stream.cpp
MediaPlayer.cpp
@@ -267,6 +317,7 @@ set(VMIX_SRCS
PatternSource.cpp
DeviceSource.cpp
NetworkSource.cpp
MultiFileSource.cpp
FrameBuffer.cpp
RenderingManager.cpp
UserInterfaceManager.cpp
@@ -276,13 +327,18 @@ set(VMIX_SRCS
SearchVisitor.cpp
ImGuiToolkit.cpp
ImGuiVisitor.cpp
InfoVisitor.cpp
GstToolkit.cpp
GlmToolkit.cpp
SystemToolkit.cpp
DialogToolkit.cpp
tinyxml2Toolkit.cpp
NetworkToolkit.cpp
Connection.cpp
ActionManager.cpp
Overlay.cpp
Metronome.cpp
ControlManager.cpp
)
@@ -290,8 +346,15 @@ set(VMIX_RSC_FILES
./rsc/shaders/simple.fs
./rsc/shaders/simple.vs
./rsc/shaders/image.fs
./rsc/shaders/mask_elipse.fs
./rsc/shaders/mask_box.fs
./rsc/shaders/mask_round.fs
./rsc/shaders/mask_horizontal.fs
./rsc/shaders/mask_vertical.fs
./rsc/shaders/mask_draw.fs
./rsc/shaders/image.vs
./rsc/shaders/imageprocessing.fs
./rsc/shaders/imageblending.fs
./rsc/fonts/Hack-Regular.ttf
./rsc/fonts/Roboto-Regular.ttf
./rsc/fonts/Roboto-Bold.ttf
@@ -321,11 +384,17 @@ set(VMIX_RSC_FILES
./rsc/images/soft_shadow.dds
./rsc/mesh/disk.ply
./rsc/mesh/circle.ply
./rsc/mesh/corner.ply
./rsc/mesh/shadow.ply
./rsc/mesh/glow.ply
./rsc/mesh/border_round_left.ply
./rsc/mesh/border_round.ply
./rsc/mesh/border_top.ply
./rsc/mesh/border_perspective_round_left.ply
./rsc/mesh/border_perspective_round.ply
./rsc/mesh/border_perspective_top.ply
./rsc/mesh/border_sharp.ply
./rsc/mesh/border_large_round_left.ply
./rsc/mesh/border_large_round.ply
./rsc/mesh/border_large_top.ply
./rsc/mesh/border_handles_rotation.ply
@@ -334,13 +403,19 @@ set(VMIX_RSC_FILES
./rsc/mesh/border_handles_overlay_filled.ply
./rsc/mesh/border_handles_sharp.ply
./rsc/mesh/border_handles_menu.ply
./rsc/mesh/border_handles_crop.ply
./rsc/mesh/border_handles_lock.ply
./rsc/mesh/border_handles_lock_open.ply
./rsc/mesh/border_handles_shadow.ply
./rsc/mesh/border_large_sharp.ply
./rsc/mesh/border_vertical_overlay.ply
./rsc/mesh/perspective_layer.ply
./rsc/mesh/perspective_axis_left.ply
./rsc/mesh/perspective_axis_right.ply
./rsc/mesh/shadow_perspective.ply
./rsc/mesh/point.ply
./rsc/mesh/square_point.ply
./rsc/mesh/triangle_point.ply
./rsc/mesh/icon_video.ply
./rsc/mesh/icon_image.ply
./rsc/mesh/icon_render.ply
@@ -349,6 +424,7 @@ set(VMIX_RSC_FILES
./rsc/mesh/icon_share.ply
./rsc/mesh/icon_clone.ply
./rsc/mesh/icon_vimix.ply
./rsc/mesh/icon_group_vimix.ply
./rsc/mesh/icon_circles.ply
./rsc/mesh/icon_dots.ply
./rsc/mesh/icon_empty.ply
@@ -361,10 +437,21 @@ set(VMIX_RSC_FILES
./rsc/mesh/icon_clock_hand.ply
./rsc/mesh/icon_grid.ply
./rsc/mesh/icon_rightarrow.ply
./rsc/mesh/icon_crop.ply
./rsc/mesh/icon_eye.ply
./rsc/mesh/icon_eye_slash.ply
./rsc/mesh/icon_vector_square_slash.ply
./rsc/mesh/icon_cube.ply
./rsc/mesh/icon_sequence.ply
./rsc/mesh/h_line.ply
./rsc/mesh/h_mark.ply
)
# Include the CMake RC module
include(CMakeRC)
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
### DEFINE THE TARGET (OS specific)
IF(APPLE)
@@ -378,7 +465,6 @@ IF(APPLE)
# create the application
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
${VMIX_SRCS}
./osx/CustomDelegate.m
${IMGUITEXTEDIT_SRC}
${MACOSX_BUNDLE_ICON_FILE}
)
@@ -394,17 +480,19 @@ IF(APPLE)
ELSE(APPLE)
link_directories (${GTK3_LIBRARY_DIRS})
add_executable(${VMIX_BINARY}
${VMIX_SRCS}
${IMGUITEXTEDIT_SRC}
)
set(PLATFORM_LIBS ""
set(PLATFORM_LIBS
GTK::GTK
)
ENDIF(APPLE)
### COMPILE THE TARGET (all OS)
set_property(TARGET ${VMIX_BINARY} PROPERTY CXX_STANDARD 17)
@@ -412,14 +500,16 @@ set_property(TARGET ${VMIX_BINARY} PROPERTY C_STANDARD 11)
target_compile_definitions(${VMIX_BINARY} PUBLIC "IMGUI_IMPL_OPENGL_LOADER_GLAD")
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
${GLFW_LIBRARY}
GLAD
vmix::rc
glm::glm
GLAD
TINYXML2
IMGUI
OSCPACK
${TINYFD_LIBRARY}
${GLFW3_LIBRARIES}
${ICU_LIBRARIES}
${CMAKE_DL_LIBS}
${GOBJECT_LIBRARIES}
${GSTREAMER_LIBRARY}
@@ -429,30 +519,24 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
${GSTREAMER_VIDEO_LIBRARY}
${GSTREAMER_PBUTILS_LIBRARY}
${GSTREAMER_GL_LIBRARY}
${GSTREAMER_PLAYER_LIBRARY}
${NFD_LIBRARY}
${PNG_LIBRARY}
${THREAD_LIBRARY}
TINYXML2
TINYFD
IMGUI
OSCPACK
vmix::rc
Threads::Threads
PNG::PNG
Ableton::Link
${PLATFORM_LIBS}
)
macro_display_feature_log()
### DEFINE THE PACKAGING (all OS)
SET(CPACK_PACKAGE_NAME "vimix")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\n Real-time video mixing for live performance.")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\nReal-time video mixing for live performance.")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt")
SET(CPACK_PACKAGE_VERSION_MAJOR "0")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
SET(CPACK_PACKAGE_HOMEPAGE_URL "https://brunoherbelin.github.io/vimix")
SET(CPACK_PACKAGE_VERSION_MAJOR "${VIMIX_VERSION_MAJOR}")
SET(CPACK_PACKAGE_VERSION_MINOR "${VIMIX_VERSION_MINOR}")
SET(CPACK_PACKAGE_VERSION_PATCH "${VIMIX_VERSION_PATCH}")
SET(CPACK_PACKAGE_VENDOR "Bruno Herbelin")
SET(CPACK_SOURCE_IGNORE_FILES
"/\\\\.git/"
@@ -461,23 +545,19 @@ SET(CPACK_SOURCE_IGNORE_FILES
)
# optimize size ?
SET(CPACK_STRIP_FILES TRUE)
### DEFINE THE PACKAGING (OS specific)
IF(APPLE)
# include( InstallRequiredSystemLibraries )
# Bundle target
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_GENERATOR DragNDrop)
set(CPACK_BINARY_DRAGNDROP ON)
# OSX cpack info
set(CPACK_SYSTEM_NAME "OSX_${CMAKE_OSX_DEPLOYMENT_TARGET}_${CMAKE_OSX_ARCHITECTURES}")
set(CPACK_BUNDLE_NAME ${CPACK_PACKAGE_NAME})
set(CPACK_BUNDLE_ICON ${MACOSX_BUNDLE_ICON_FILE})
set(CPACK_BUNDLE_PLIST ${MACOSX_BUNDLE_PLIST_FILE})
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
# set( APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/vimix${CMAKE_EXECUTABLE_SUFFIX}")
install(TARGETS ${VMIX_BINARY}
CONFIGURATIONS Release RelWithDebInfo
@@ -490,34 +570,56 @@ IF(APPLE)
### TODO configure auto to find installation dir of gst
message(STATUS "install gst-plugins ${PKG_GSTREAMER_PLUGIN_DIR}")
message(STATUS "install gst-plugins-base ${PKG_GSTREAMER_BASE_PLUGIN_DIR}")
if (PKG_CONFIG_FOUND)
pkg_check_modules(PKG_GSTREAMER_PLUGINS_BAD gstreamer-plugins-bad-${GSTREAMER_ABI_VERSION})
set(PKG_GSTREAMER_BAD_PLUGIN_DIR ${PKG_GSTREAMER_PLUGINS_BAD_LIBDIR}/gstreamer-${GSTREAMER_ABI_VERSION})
message(STATUS "install gst-plugins-bad ${PKG_GSTREAMER_BAD_PLUGIN_DIR}")
endif()
# intall the gst-plugin-scanner program (used by plugins at load time)
install(FILES "/usr/local/Cellar/gstreamer/1.18.1/libexec/gstreamer-1.0/gst-plugin-scanner"
set(PKG_GSTREAMER_SCANNER "${PKG_GSTREAMER_PREFIX}/libexec/gstreamer-1.0/gst-plugin-scanner")
message(STATUS "install gst-plugin-scanner ${PKG_GSTREAMER_SCANNER}")
install(FILES "${PKG_GSTREAMER_SCANNER}"
DESTINATION "${plugin_dest_dir}"
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
COMPONENT Runtime
)
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
install(FILES "/usr/local/Cellar/icu4c/67.1/lib/libicudata.67.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.67.dylib" COMPONENT Runtime)
# Install the gst-plugins (all those installed with brew )
install(DIRECTORY "${PKG_GSTREAMER_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-base/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.18.1_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "${PKG_GSTREAMER_BASE_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "${PKG_GSTREAMER_BAD_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled gst-plugins (because not included in brew package)
set(LOCAL_BUILD_BAD "/Users/herbelin/Development/gst/gst-plugins-bad-1.18.0/build")
install(FILES "${LOCAL_BUILD_BAD}/sys/applemedia/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/x265/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.4_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled & installed gst-plugins (because not included in brew package)
install(FILES "/usr/local/lib/gstreamer-1.0/libgstapplemedia.dylib"
"/usr/local/lib/gstreamer-1.0/libgstde265.dylib"
"/usr/local/lib/gstreamer-1.0/libgstx265.dylib"
DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
# install frei0r plugins (dependencies of gstreamer-1.0/libgstfrei0r.dylib plugin)
install(FILES "/usr/local/Cellar/frei0r/1.7.0/lib/frei0r-1/lissajous0r.so"
"/usr/local/Cellar/frei0r/1.7.0/lib/frei0r-1/rgbnoise.so"
DESTINATION "${plugin_dest_dir}/frei0r-1" COMPONENT Runtime)
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
# install(FILES "${ICU_LINK_LIBRARIES}" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/usr/local/Cellar/icu4c/69.1/lib/libicudata.69.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.69.dylib" COMPONENT Runtime)
message(STATUS "install ${ICU_LINK_LIBRARIES} from ${ICU_LIBRARY_DIRS}")
# package runtime fixup bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
install(CODE "
file(GLOB_RECURSE GSTPLUGINS \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/gstreamer-1.0/*.dylib\")
list(APPEND LIBS_PATH \"/usr/local/Cellar/icu4c/67.1/lib\")
list(APPEND LIBS_PATH \"\${ICU_LIBRARY_DIRS}\")
include(BundleUtilities)
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
fixup_bundle(\"${APPS}\" \"\${GSTPLUGINS}\" \"${LIBS_PATH}\")
@@ -525,22 +627,43 @@ IF(APPLE)
COMPONENT Runtime
)
set(CPACK_BINARY_DRAGNDROP ON)
set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
set(APPLE_CODESIGN_IDENTITY "" CACHE STRING "")
string(LENGTH "${APPLE_CODESIGN_IDENTITY}" APPLE_CODESIGN_IDENTITY_LENGHT)
if( ${APPLE_CODESIGN_IDENTITY_LENGHT} LESS 40 )
message(STATUS "Not signing bundle. Specify APPLE_CODESIGN_IDENTITY to cmake before running cpack to sign")
else()
install(CODE "
execute_process(COMMAND
codesign --verbose=4 --deep --force
--entitlements \"${APPLE_CODESIGN_ENTITLEMENTS}\"
--sign \"${APPLE_CODESIGN_IDENTITY}\"
\"${APPS}\" )
"
COMPONENT Runtime
)
endif()
# # package runtime fixup bundle and codesign
# set(BUNDLE_NAME "vimix.app")
# set(BUNDLE_LIBS_DIR "${plugin_dest_dir}/gstreamer-1.0")
# set(BUNDLE_DIRS "${ICU_LIBRARY_DIRS}")
# set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
# configure_file(cmake/modules/BundleInstall.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" @ONLY)
# install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" COMPONENT Runtime)
ELSE(APPLE)
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/vimix")
install(TARGETS ${VMIX_BINARY}
CONFIGURATIONS Release RelWithDebInfo
RUNTIME DESTINATION bin COMPONENT Runtime
)
ENDIF(APPLE)
# Package full name
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}_${CPACK_SYSTEM_NAME}")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_SYSTEM_NAME}")
# To Create a package, run "cpack"
include(CPack)

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,28 +1,44 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <thread>
#include <chrono>
#include <vector>
#include <algorithm>
#include "osc/OscOutboundPacketStream.h"
#include "defines.h"
#include "Connection.h"
#include "Settings.h"
#include "Streamer.h"
#include "Log.h"
#include "Connection.h"
#ifndef NDEBUG
#define CONNECTION_DEBUG
#endif
Connection::Connection()
Connection::Connection() : receiver_(nullptr)
{
receiver_ = nullptr;
}
Connection::~Connection()
{
if (receiver_!=nullptr) {
@@ -114,10 +130,10 @@ ConnectionInfo Connection::info(int index)
struct hasName: public std::unary_function<ConnectionInfo, bool>
{
inline bool operator()(const ConnectionInfo elem) const {
inline bool operator()(const ConnectionInfo &elem) const {
return (elem.name.compare(_a) == 0);
}
hasName(std::string a) : _a(a) { }
explicit hasName(const std::string &a) : _a(a) { }
private:
std::string _a;
};
@@ -147,7 +163,7 @@ int Connection::index(ConnectionInfo i) const
void Connection::print()
{
for(int i = 0; i<connections_.size(); i++) {
for(size_t i = 0; i<connections_.size(); i++) {
Log::Info(" - %s %s:%d", connections_[i].name.c_str(), connections_[i].address.c_str(), connections_[i].port_handshake);
}
}
@@ -157,7 +173,8 @@ void Connection::listen()
#ifdef CONNECTION_DEBUG
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
#endif
Connection::manager().receiver_->Run();
if (Connection::manager().receiver_)
Connection::manager().receiver_->Run();
}
void Connection::ask()
@@ -185,7 +202,7 @@ void Connection::ask()
// check the list of connections for non responding (disconnected)
std::vector< ConnectionInfo >::iterator it = Connection::manager().connections_.begin();
for(it++; it!=Connection::manager().connections_.end(); ) {
for(++it; it!=Connection::manager().connections_.end(); ) {
// decrease life score
(*it).alive--;
// erase connection if its life score is negative (not responding too many times)
@@ -201,13 +218,13 @@ void Connection::ask()
}
// loop
else
it++;
++it;
}
}
}
void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
void Connection::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];

View File

@@ -1,22 +1,17 @@
#ifndef CONNECTION_H
#define CONNECTION_H
#include <string>
#include <vector>
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "ip/UdpSocket.h"
#include "NetworkToolkit.h"
#define MAX_HANDSHAKE 20
#define HANDSHAKE_PORT 71310
#define STREAM_REQUEST_PORT 71510
#define OSC_DIALOG_PORT 71010
#define ALIVE 3
class ConnectionRequestListener : public osc::OscPacketListener {
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
};
struct ConnectionInfo {
@@ -58,12 +53,10 @@ struct ConnectionInfo {
class Connection
{
friend class ConnectionRequestListener;
// Private Constructor
Connection();
Connection(Connection const& copy); // Not Implemented
Connection& operator=(Connection const& copy); // Not Implemented
Connection(Connection const& copy) = delete;
Connection& operator=(Connection const& copy) = delete;
public:
static Connection& manager()
@@ -82,11 +75,19 @@ public:
int index(const std::string &name) const;
ConnectionInfo info(int index = 0); // index 0 for self
protected:
class RequestListener : public osc::OscPacketListener {
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
};
private:
static void ask();
static void listen();
ConnectionRequestListener listener_;
RequestListener listener_;
UdpListeningReceiveSocket *receiver_;
std::vector< ConnectionInfo > connections_;

756
ControlManager.cpp Normal file
View File

@@ -0,0 +1,756 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <thread>
#include <mutex>
#include <sstream>
#include <iomanip>
#include "osc/OscOutboundPacketStream.h"
#include "defines.h"
#include "Log.h"
#include "Settings.h"
#include "BaseToolkit.h"
#include "Mixer.h"
#include "Source.h"
#include "ActionManager.h"
#include "SystemToolkit.h"
#include "tinyxml2Toolkit.h"
#include "ControlManager.h"
#ifndef NDEBUG
#define CONTROL_DEBUG
#endif
#define CONTROL_OSC_MSG "OSC: "
void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
remoteEndpoint.AddressAndPortAsString(sender);
try{
#ifdef CONTROL_DEBUG
Log::Info(CONTROL_OSC_MSG "received '%s' from %s", FullMessage(m).c_str(), sender);
#endif
// Preprocessing with Translator
std::string address_pattern = Control::manager().translate(m.AddressPattern());
// structured OSC address
std::list<std::string> address = BaseToolkit::splitted(address_pattern, OSC_SEPARATOR);
//
// A wellformed OSC address is in the form '/vimix/target/attribute {arguments}'
// First test: should have 3 elements and start with APP_NAME ('vimix')
//
if (address.size() > 2 && address.front().compare(OSC_PREFIX) == 0 ){
// done with the first part of the OSC address
address.pop_front();
// next part of the OSC message is the target
std::string target = address.front();
// next part of the OSC message is the attribute
address.pop_front();
std::string attribute = address.front();
// Log target: just print text in log window
if ( target.compare(OSC_INFO) == 0 )
{
if ( attribute.compare(OSC_INFO_NOTIFY) == 0) {
Log::Notify(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
}
else if ( attribute.compare(OSC_INFO_LOG) == 0) {
Log::Info(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
}
}
// Output target: concerns attributes of the rendering output
else if ( target.compare(OSC_OUTPUT) == 0 )
{
if ( Control::manager().receiveOutputAttribute(attribute, m.ArgumentStream())) {
// send the global status
Control::manager().sendOutputStatus(remoteEndpoint);
}
}
// Session target: concerns attributes of the session
else if ( target.compare(OSC_SESSION) == 0 )
{
if ( Control::manager().receiveSessionAttribute(attribute, m.ArgumentStream()) ) {
// send the global status
Control::manager().sendOutputStatus(remoteEndpoint);
// send the status of all sources
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
}
}
// ALL sources target: apply attribute to all sources of the session
else if ( target.compare(OSC_ALL) == 0 )
{
// Loop over selected sources
for (SourceList::iterator it = Mixer::manager().session()->begin(); it != Mixer::manager().session()->end(); ++it) {
// apply attributes
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
// and send back feedback if needed
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
}
}
// Selected sources target: apply attribute to all sources of the selection
else if ( target.compare(OSC_SELECTED) == 0 )
{
// Loop over selected sources
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// apply attributes
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
// and send back feedback if needed
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
}
}
// Current source target: apply attribute to the current sources
else if ( target.compare(OSC_CURRENT) == 0 )
{
int sourceid = -1;
if ( attribute.compare(OSC_SYNC) == 0) {
// send the status of all sources
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
}
else if ( attribute.compare(OSC_NEXT) == 0) {
// set current to NEXT
Mixer::manager().setCurrentNext();
// send the status of all sources
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
}
else if ( attribute.compare(OSC_PREVIOUS) == 0) {
// set current to PREVIOUS
Mixer::manager().setCurrentPrevious();
// send the status of all sources
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
}
else if ( BaseToolkit::is_a_number( attribute.substr(1), &sourceid) ){
// set current to given INDEX
Mixer::manager().setCurrentIndex(sourceid);
// send the status of all sources
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
}
// all other attributes operate on current source
else {
// apply attributes to current source
if ( Control::manager().receiveSourceAttribute( Mixer::manager().currentSource(), attribute, m.ArgumentStream()) )
// and send back feedback if needed
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
}
}
// General case: try to identify the target
else {
// try to find source by index
Source *s = nullptr;
int sourceid = -1;
if ( BaseToolkit::is_a_number(target.substr(1), &sourceid) )
s = Mixer::manager().sourceAtIndex(sourceid);
// if failed, try to find source by name
if (s == nullptr)
s = Mixer::manager().findSource(target.substr(1));
// if a source with the given target name or index was found
if (s) {
// apply attributes to source
if ( Control::manager().receiveSourceAttribute(s, attribute, m.ArgumentStream()) )
// and send back feedback if needed
Control::manager().sendSourceAttibutes(remoteEndpoint, target, s);
}
else
Log::Info(CONTROL_OSC_MSG "Unknown target '%s' requested by %s.", target.c_str(), sender);
}
}
else {
Log::Info(CONTROL_OSC_MSG "Unknown osc message '%s' sent by %s.", m.AddressPattern(), sender);
}
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
}
}
std::string Control::RequestListener::FullMessage( const osc::ReceivedMessage& m )
{
// build a string with the address pattern of the message
std::ostringstream message;
message << m.AddressPattern() << " ";
// try to fill the string with the arguments
std::ostringstream arguments;
try{
// loop over all arguments
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
while (arg != m.ArgumentsEnd()) {
if( arg->IsBool() ){
bool a = (arg++)->AsBoolUnchecked();
message << (a ? "T" : "F");
}
else if( arg->IsInt32() ){
int a = (arg++)->AsInt32Unchecked();
message << "i";
arguments << " " << a;
}
else if( arg->IsFloat() ){
float a = (arg++)->AsFloatUnchecked();
message << "f";
arguments << " " << std::fixed << std::setprecision(2) << a;
}
else if( arg->IsString() ){
const char *a = (arg++)->AsStringUnchecked();
message << "s";
arguments << " " << a;
}
}
}
catch( osc::Exception& e ){
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s': %s", m.AddressPattern(), e.what());
}
// append list of arguments to the message string
message << arguments.str();
// returns the full message
return message.str();
}
Control::Control() : receiver_(nullptr)
{
}
Control::~Control()
{
terminate();
}
std::string Control::translate (std::string addresspattern)
{
auto it_translation = translation_.find(addresspattern);
if ( it_translation != translation_.end() )
return it_translation->second;
else
return addresspattern;
}
void Control::loadOscConfig()
{
// reset translations
translation_.clear();
// load osc config file
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLError eResult = xmlDoc.LoadFile(Settings::application.control.osc_filename.c_str());
// the only reason to return false is if the file does not exist or is empty
if (eResult == tinyxml2::XML_ERROR_FILE_NOT_FOUND
| eResult == tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED
| eResult == tinyxml2::XML_ERROR_FILE_READ_ERROR
| eResult == tinyxml2::XML_ERROR_EMPTY_DOCUMENT )
resetOscConfig();
// found the file, could open and read it
else if (eResult != tinyxml2::XML_SUCCESS)
Log::Warning(CONTROL_OSC_MSG "Error while parsing Translator: %s", xmlDoc.ErrorIDToName(eResult));
// no XML parsing error
else {
// parse all entries 'osc'
tinyxml2::XMLElement* osc = xmlDoc.FirstChildElement("osc");
for( ; osc ; osc=osc->NextSiblingElement()) {
// get the 'from' entry
tinyxml2::XMLElement* from = osc->FirstChildElement("from");
if (from) {
const char *str_from = from->GetText();
if (str_from) {
// get the 'to' entry
tinyxml2::XMLElement* to = osc->FirstChildElement("to");
if (to) {
const char *str_to = to->GetText();
// if could get both; add to translator
if (str_to)
translation_[str_from] = str_to;
}
}
}
}
}
Log::Info(CONTROL_OSC_MSG "Loaded %d translation%s.", translation_.size(), translation_.size()>1?"s":"");
}
void Control::resetOscConfig()
{
// generate a template xml translation dictionnary
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLDeclaration *pDec = xmlDoc.NewDeclaration();
xmlDoc.InsertFirstChild(pDec);
tinyxml2::XMLComment *pComment = xmlDoc.NewComment("The OSC translator converts OSC address patterns into other ones.\n"
"Complete the dictionnary by adding as many <osc> translations as you want.\n"
"Each <osc> should contain a <from> pattern to translate into a <to> pattern.\n"
"More at https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API.");
xmlDoc.InsertEndChild(pComment);
tinyxml2::XMLElement *from = xmlDoc.NewElement( "from" );
from->InsertFirstChild( xmlDoc.NewText("/example/osc/message") );
tinyxml2::XMLElement *to = xmlDoc.NewElement( "to" );
to->InsertFirstChild( xmlDoc.NewText("/vimix/info/log") );
tinyxml2::XMLElement *osc = xmlDoc.NewElement("osc");
osc->InsertEndChild(from);
osc->InsertEndChild(to);
xmlDoc.InsertEndChild(osc);
// save xml in osc config file
xmlDoc.SaveFile(Settings::application.control.osc_filename.c_str());
// reset and fill translation with default example
translation_.clear();
translation_["/example/osc/message"] = "/vimix/info/log";
}
bool Control::init()
{
//
// terminate before init (allows calling init() multiple times)
//
terminate();
//
// load OSC Translator
//
loadOscConfig();
//
// launch OSC listener
//
try {
// try to create listenning socket
// through exception runtime if fails
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
Settings::application.control.osc_port_receive ), &listener_ );
// listen for answers in a separate thread
std::thread(listen).detach();
// inform user
IpEndpointName ip = receiver_->LocalEndpointFor( IpEndpointName( NetworkToolkit::hostname().c_str(),
Settings::application.control.osc_port_receive ));
static char *addresseip = (char *)malloc(IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH);
ip.AddressAndPortAsString(addresseip);
Log::Info(CONTROL_OSC_MSG "Listening to UDP messages sent to %s", addresseip);
}
catch (const std::runtime_error &e) {
// arg, the receiver could not be initialized
// (often because the port was not available)
receiver_ = nullptr;
Log::Warning(CONTROL_OSC_MSG "Failed to init listener on port %d; %s", Settings::application.control.osc_port_receive, e.what());
}
return receiver_ != nullptr;
}
void Control::listen()
{
if (Control::manager().receiver_)
Control::manager().receiver_->Run();
Control::manager().receiver_end_.notify_all();
}
void Control::terminate()
{
if ( receiver_ != nullptr ) {
// request termination of receiver
receiver_->AsynchronousBreak();
// wait for the receiver_end_ notification
std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
// if waited more than 2 seconds, its dead :(
if ( receiver_end_.wait_for(lck,std::chrono::seconds(2)) == std::cv_status::timeout)
Log::Warning(CONTROL_OSC_MSG "Failed to terminate; try again.");
// delete receiver and ready to initialize
delete receiver_;
receiver_ = nullptr;
}
}
bool Control::receiveOutputAttribute(const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments)
{
bool need_feedback = false;
try {
if ( attribute.compare(OSC_SYNC) == 0) {
need_feedback = true;
}
/// e.g. '/vimix/output/enable' or '/vimix/output/enable 1.0' or '/vimix/output/enable 0.0'
else if ( attribute.compare(OSC_OUTPUT_ENABLE) == 0) {
float on = 1.f;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
Settings::application.render.disabled = on < 0.5f;
}
/// e.g. '/vimix/output/disable' or '/vimix/output/disable 1.0' or '/vimix/output/disable 0.0'
else if ( attribute.compare(OSC_OUTPUT_DISABLE) == 0) {
float on = 1.f;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
Settings::application.render.disabled = on > 0.5f;
}
/// e.g. '/vimix/output/fading f 0.2' or '/vimix/output/fading ff 1.0 300.f'
else if ( attribute.compare(OSC_OUTPUT_FADING) == 0) {
float f = 0.f, d = 0.f;
// first argument is fading value
arguments >> f;
if (arguments.Eos())
arguments >> osc::EndMessage;
// if a second argument is given, it is a duration
else
arguments >> d >> osc::EndMessage;
Mixer::manager().session()->setFadingTarget(f, d);
}
/// e.g. '/vimix/output/fadein' or '/vimix/output/fadein f 300.f'
else if ( attribute.compare(OSC_OUTPUT_FADE_IN) == 0) {
float f = 0.f;
// if argument is given, it is a duration
if (!arguments.Eos())
arguments >> f >> osc::EndMessage;
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() - f * 0.01);
need_feedback = true;
}
else if ( attribute.compare(OSC_OUTPUT_FADE_OUT) == 0) {
float f = 0.f;
// if argument is given, it is a duration
if (!arguments.Eos())
arguments >> f >> osc::EndMessage;
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() + f * 0.01);
need_feedback = true;
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'output'", attribute.c_str());
}
#endif
}
catch (osc::MissingArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'output'", attribute.c_str());
}
catch (osc::ExcessArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'output'", attribute.c_str());
}
catch (osc::WrongArgumentTypeException &e) {
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'output'", attribute.c_str());
}
return need_feedback;
}
bool Control::receiveSourceAttribute(Source *target, const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments)
{
bool send_feedback = false;
if (target == nullptr)
return send_feedback;
try {
/// e.g. '/vimix/current/play' or '/vimix/current/play T' or '/vimix/current/play F'
if ( attribute.compare(OSC_SOURCE_PLAY) == 0) {
float on = 1.f;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
target->call( new SetPlay(on > 0.5f) );
}
/// e.g. '/vimix/current/pause' or '/vimix/current/pause T' or '/vimix/current/pause F'
else if ( attribute.compare(OSC_SOURCE_PAUSE) == 0) {
float on = 1.f;
if ( !arguments.Eos()) {
arguments >> on >> osc::EndMessage;
}
target->call( new SetPlay(on < 0.5f) );
}
/// e.g. '/vimix/current/replay'
else if ( attribute.compare(OSC_SOURCE_REPLAY) == 0) {
target->call( new RePlay() );
}
/// e.g. '/vimix/current/alpha f 0.3'
else if ( attribute.compare(OSC_SOURCE_LOCK) == 0) {
float x = 1.f;
arguments >> x >> osc::EndMessage;
target->call( new SetLock(x > 0.5f ? true : false) );
}
/// e.g. '/vimix/current/alpha f 0.3'
else if ( attribute.compare(OSC_SOURCE_ALPHA) == 0) {
float x = 1.f;
arguments >> x >> osc::EndMessage;
target->call( new SetAlpha(x), true );
}
/// e.g. '/vimix/current/alpha f 0.3'
else if ( attribute.compare(OSC_SOURCE_LOOM) == 0) {
float x = 1.f;
arguments >> x >> osc::EndMessage;
target->call( new Loom(x), true );
// this will require to send feedback status about source
send_feedback = true;
}
/// e.g. '/vimix/current/transparency f 0.7'
else if ( attribute.compare(OSC_SOURCE_TRANSPARENCY) == 0) {
float x = 0.f;
arguments >> x >> osc::EndMessage;
target->call( new SetAlpha(1.f - x), true );
}
/// e.g. '/vimix/current/depth f 5.0'
else if ( attribute.compare(OSC_SOURCE_DEPTH) == 0) {
float x = 0.f;
arguments >> x >> osc::EndMessage;
target->call( new SetDepth(x), true );
}
/// e.g. '/vimix/current/translation ff 10.0 2.2'
else if ( attribute.compare(OSC_SOURCE_GRAB) == 0) {
float x = 0.f, y = 0.f;
arguments >> x >> y >> osc::EndMessage;
target->call( new Grab( x, y), true );
}
/// e.g. '/vimix/current/scale ff 10.0 2.2'
else if ( attribute.compare(OSC_SOURCE_RESIZE) == 0) {
float x = 0.f, y = 0.f;
arguments >> x >> y >> osc::EndMessage;
target->call( new Resize( x, y), true );
}
/// e.g. '/vimix/current/turn f 1.0'
else if ( attribute.compare(OSC_SOURCE_TURN) == 0) {
float x = 0.f, y = 0.f;
arguments >> x;
if (arguments.Eos())
arguments >> osc::EndMessage;
else // ignore second argument
arguments >> y >> osc::EndMessage;
target->call( new Turn( x ), true );
}
/// e.g. '/vimix/current/reset'
else if ( attribute.compare(OSC_SOURCE_RESET) == 0) {
target->call( new ResetGeometry(), true );
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
#endif
// overwrite value if source locked
if (target->locked())
send_feedback = true;
}
catch (osc::MissingArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
catch (osc::ExcessArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
catch (osc::WrongArgumentTypeException &e) {
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
}
return send_feedback;
}
bool Control::receiveSessionAttribute(const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments)
{
bool send_feedback = false;
try {
if ( attribute.compare(OSC_SYNC) == 0) {
send_feedback = true;
}
else if ( attribute.compare(OSC_SESSION_VERSION) == 0) {
float v = 0.f;
arguments >> v >> osc::EndMessage;
size_t id = (int) ceil(v);
std::list<uint64_t> snapshots = Action::manager().snapshots();
if ( id < snapshots.size() ) {
for (size_t i = 0; i < id; ++i)
snapshots.pop_back();
uint64_t snap = snapshots.back();
Action::manager().restore(snap);
}
send_feedback = true;
}
#ifdef CONTROL_DEBUG
else {
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'session'", attribute.c_str());
}
#endif
}
catch (osc::MissingArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'session'", attribute.c_str());
}
catch (osc::ExcessArgumentException &e) {
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'session'", attribute.c_str());
}
catch (osc::WrongArgumentTypeException &e) {
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'session'", attribute.c_str());
}
return send_feedback;
}
void Control::sendSourceAttibutes(const IpEndpointName &remoteEndpoint, std::string target, Source *s)
{
// default values
char name[21] = {"\0"};
float lock = 0.f;
float play = 0.f;
float depth = 0.f;
float alpha = 0.f;
// get source or current source
Source *_s = s;
if ( target.compare(OSC_CURRENT) == 0 )
_s = Mixer::manager().currentSource();
// fill values if the source is valid
if (_s!=nullptr) {
strncpy(name, _s->name().c_str(), 20);
lock = _s->locked() ? 1.f : 0.f;
play = _s->playing() ? 1.f : 0.f;
depth = _s->depth();
alpha = _s->alpha();
}
// build socket to send message to indicated endpoint
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
// build messages packet
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
// create bundle
p.Clear();
p << osc::BeginBundle();
/// name
std::string address = std::string(OSC_PREFIX) + target + OSC_SOURCE_NAME;
p << osc::BeginMessage( address.c_str() ) << name << osc::EndMessage;
/// Play status
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_LOCK;
p << osc::BeginMessage( address.c_str() ) << lock << osc::EndMessage;
/// Play status
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_PLAY;
p << osc::BeginMessage( address.c_str() ) << play << osc::EndMessage;
/// Depth
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_DEPTH;
p << osc::BeginMessage( address.c_str() ) << depth << osc::EndMessage;
/// Alpha
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_ALPHA;
p << osc::BeginMessage( address.c_str() ) << alpha << osc::EndMessage;
// send bundle
p << osc::EndBundle;
socket.Send( p.Data(), p.Size() );
}
void Control::sendSourcesStatus(const IpEndpointName &remoteEndpoint, osc::ReceivedMessageArgumentStream arguments)
{
// (if an argument is given, it indicates the number of sources to update)
float N = 0.f;
if ( !arguments.Eos())
arguments >> N >> osc::EndMessage;
// build socket to send message to indicated endpoint
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
// build messages packet
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginBundle();
int i = 0;
char oscaddr[128];
int index_current = Mixer::manager().indexCurrentSource();
for (; i < Mixer::manager().count(); ++i) {
// send status of currently selected
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
p << osc::BeginMessage( oscaddr ) << (index_current == i ? 1.f : 0.f) << osc::EndMessage;
// send status of alpha
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
p << osc::BeginMessage( oscaddr ) << Mixer::manager().sourceAtIndex(i)->alpha() << osc::EndMessage;
}
for (; i < (int) N ; ++i) {
// reset status of currently selected
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
// reset status of alpha
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
}
p << osc::EndBundle;
socket.Send( p.Data(), p.Size() );
// send status of current source
sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
}
void Control::sendOutputStatus(const IpEndpointName &remoteEndpoint)
{
// build socket to send message to indicated endpoint
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
// build messages packet
char buffer[IP_MTU_SIZE];
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
p.Clear();
p << osc::BeginBundle();
/// output attributes
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_ENABLE );
p << (Settings::application.render.disabled ? 0.f : 1.f);
p << osc::EndMessage;
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_FADING );
p << Mixer::manager().session()->fading();
p << osc::EndMessage;
p << osc::EndBundle;
socket.Send( p.Data(), p.Size() );
}

105
ControlManager.h Normal file
View File

@@ -0,0 +1,105 @@
#ifndef CONTROL_H
#define CONTROL_H
#include <map>
#include <string>
#include <condition_variable>
#include "NetworkToolkit.h"
#define OSC_SYNC "/sync"
#define OSC_INFO "/info"
#define OSC_INFO_LOG "/log"
#define OSC_INFO_NOTIFY "/notify"
#define OSC_OUTPUT "/output"
#define OSC_OUTPUT_ENABLE "/enable"
#define OSC_OUTPUT_DISABLE "/disable"
#define OSC_OUTPUT_FADING "/fading"
#define OSC_OUTPUT_FADE_IN "/fade-in"
#define OSC_OUTPUT_FADE_OUT "/fade-out"
#define OSC_ALL "/all"
#define OSC_SELECTED "/selected"
#define OSC_CURRENT "/current"
#define OSC_NEXT "/next"
#define OSC_PREVIOUS "/previous"
#define OSC_SOURCE_NAME "/name"
#define OSC_SOURCE_LOCK "/lock"
#define OSC_SOURCE_PLAY "/play"
#define OSC_SOURCE_PAUSE "/pause"
#define OSC_SOURCE_REPLAY "/replay"
#define OSC_SOURCE_ALPHA "/alpha"
#define OSC_SOURCE_LOOM "/loom"
#define OSC_SOURCE_TRANSPARENCY "/transparency"
#define OSC_SOURCE_DEPTH "/depth"
#define OSC_SOURCE_GRAB "/grab"
#define OSC_SOURCE_RESIZE "/resize"
#define OSC_SOURCE_TURN "/turn"
#define OSC_SOURCE_RESET "/reset"
#define OSC_SESSION "/session"
#define OSC_SESSION_VERSION "/version"
class Session;
class Source;
class Control
{
// Private Constructor
Control();
Control(Control const& copy) = delete;
Control& operator=(Control const& copy) = delete;
public:
static Control& manager ()
{
// The only instance
static Control _instance;
return _instance;
}
~Control();
bool init();
void terminate();
std::string translate (std::string addresspattern);
protected:
class RequestListener : public osc::OscPacketListener {
protected:
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
std::string FullMessage( const osc::ReceivedMessage& m );
};
bool receiveOutputAttribute(const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments);
bool receiveSourceAttribute(Source *target, const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments);
bool receiveSessionAttribute(const std::string &attribute,
osc::ReceivedMessageArgumentStream arguments);
void sendSourceAttibutes(const IpEndpointName& remoteEndpoint, std::string target, Source *s = nullptr);
void sendSourcesStatus(const IpEndpointName& remoteEndpoint, osc::ReceivedMessageArgumentStream arguments);
void sendOutputStatus(const IpEndpointName& remoteEndpoint);
private:
static void listen();
RequestListener listener_;
std::condition_variable receiver_end_;
UdpListeningReceiveSocket *receiver_;
std::map<std::string, std::string> translation_;
void loadOscConfig();
void resetOscConfig();
};
#endif // CONTROL_H

104
CopyVisitor.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* This file is part of vimix - Live video mixer
*
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "GlmToolkit.h"
#include "Scene.h"
#include "Source.h"
#include "CopyVisitor.h"
Node *CopyVisitor::deepCopy(Node *node)
{
CopyVisitor cv;
node->accept(cv);
return cv.current_;
}
CopyVisitor::CopyVisitor() : current_(nullptr)
{
}
void CopyVisitor::visit(Node &n)
{
}
void CopyVisitor::visit(Group &n)
{
Group *here = new Group;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
// loop
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
here->attach( current_ );
current_ = here;
}
}
void CopyVisitor::visit(Switch &n)
{
Switch *here = new Switch;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
// switch properties
here->setActive( n.active() );
// loop
for (uint i = 0; i < n.numChildren(); ++i) {
n.child(i)->accept(*this);
here->attach( current_ );
current_ = here;
}
}
void CopyVisitor::visit(Scene &n)
{
Scene *here = new Scene;
current_ = here->root();
n.root()->accept(*this);
}
void CopyVisitor::visit(Primitive &n)
{
Primitive *here = new Primitive;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
}

24
CopyVisitor.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef COPYVISITOR_H
#define COPYVISITOR_H
#include "Visitor.h"
class SourceCore;
class CopyVisitor : public Visitor
{
Node *current_;
CopyVisitor();
public:
static Node *deepCopy(Node *node);
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Primitive& n) override;
void visit(Group& n) override;
void visit(Switch& n) override;
};
#endif // COPYVISITOR_H

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/component_wise.hpp>
@@ -11,8 +30,11 @@
#include "Resource.h"
#include "Log.h"
#include "imgui.h"
#include <glad/glad.h>
Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(), side_(nullptr), top_(nullptr), shadow_(nullptr), square_(nullptr)
Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
right_(nullptr), left_(nullptr), top_(nullptr), shadow_(nullptr), square_(nullptr)
{
static Mesh *shadows[3] = {nullptr};
if (shadows[0] == nullptr) {
@@ -20,33 +42,54 @@ Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
shadows[1] = new Mesh("mesh/shadow.ply", "images/shadow.dds");
shadows[2] = new Mesh("mesh/shadow_perspective.ply", "images/shadow_perspective.dds");
}
static Mesh *frames[4] = {nullptr};
static Mesh *frames[9] = {nullptr};
if (frames[0] == nullptr) {
frames[0] = new Mesh("mesh/border_round.ply");
frames[1] = new Mesh("mesh/border_top.ply");
frames[2] = new Mesh("mesh/border_large_round.ply");
frames[3] = new Mesh("mesh/border_large_top.ply");
frames[1] = new Mesh("mesh/border_round_left.ply");
frames[2] = new Mesh("mesh/border_top.ply");
frames[3] = new Mesh("mesh/border_large_round.ply");
frames[4] = new Mesh("mesh/border_large_round_left.ply");
frames[5] = new Mesh("mesh/border_large_top.ply");
frames[6] = new Mesh("mesh/border_perspective_round.ply");
frames[7] = new Mesh("mesh/border_perspective_round_left.ply");
frames[8] = new Mesh("mesh/border_perspective_top.ply");
}
static LineSquare *sharpframethin = new LineSquare( 3 );
static LineSquare *sharpframelarge = new LineSquare( 5 );
static LineSquare *sharpframethin = new LineSquare( 4.f );
static LineSquare *sharpframelarge = new LineSquare( 6.f );
if (corner == SHARP) {
// Round corners
if (corner == ROUND){
if (border == THIN) {
right_ = frames[0];
left_ = frames[1];
top_ = frames[2];
}
else{
right_ = frames[3];
left_ = frames[4];
top_ = frames[5];
}
}
// Group corners
else if (corner == GROUP){
if (border == THIN) {
right_ = frames[6];
left_ = frames[7];
top_ = frames[8];
}
else{
right_ = frames[6];
left_ = frames[7];
top_ = frames[8];
}
}
// Sharp corner
else {
if (border == LARGE)
square_ = sharpframelarge;
else
square_ = sharpframethin;
}
else {
// Round corners
if (border == THIN) {
side_ = frames[0];
top_ = frames[1];
}
else{
side_ = frames[2];
top_ = frames[3];
}
}
switch (shadow) {
default:
@@ -76,8 +119,10 @@ void Frame::update( float dt )
Node::update(dt);
if(top_)
top_->update(dt);
if(side_)
side_->update(dt);
if(right_)
right_->update(dt);
if(left_)
left_->update(dt);
if(shadow_)
shadow_->update(dt);
if(square_)
@@ -87,9 +132,11 @@ void Frame::update( float dt )
void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() ) {
if(side_ && !side_->initialized())
side_->init();
if(top_ && !top_->initialized())
if(right_ && !right_->initialized())
right_->init();
if(left_ && !left_->initialized())
left_->init();
if(top_ && !top_->initialized())
top_->init();
if(shadow_ && !shadow_->initialized())
shadow_->init();
@@ -102,27 +149,28 @@ void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
glm::mat4 ctm = modelview * transform_;
// sharp border (scaled)
if(square_) {
square_->setColor(color);
square_->draw( ctm, projection);
}
// shadow (scaled)
if(shadow_){
shadow_->shader()->color.a = 0.98f;
shadow_->draw( ctm, projection);
}
// top (scaled)
// round top (scaled)
if(top_) {
top_->shader()->color = color;
top_->draw( ctm, projection);
}
// top (scaled)
if(square_) {
square_->shader()->color = color;
square_->draw( ctm, projection);
}
// round sides
if(right_) {
if(side_) {
side_->shader()->color = color;
right_->shader()->color = color;
// get scale
glm::vec4 scale = ctm * glm::vec4(1.f, 1.0f, 0.f, 0.f);
@@ -132,17 +180,26 @@ void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
glm::vec4 vec = ctm * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
if(side_) {
// right side
vec = ctm * glm::vec4(1.f, 0.f, 0.f, 1.f);
right_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
// left side
vec = ctm * glm::vec4(1.f, 0.f, 0.f, 1.f);
side_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
}
if(left_) {
// right side
vec = ctm * glm::vec4(-1.f, 0.f, 0.f, 1.f);
side_->draw( GlmToolkit::transform(vec, rot, glm::vec3(-scale.y, scale.y, 1.f)), projection );
left_->shader()->color = color;
}
// get scale
glm::vec4 scale = ctm * glm::vec4(1.f, 1.0f, 0.f, 0.f);
// get rotation
glm::vec3 rot(0.f);
glm::vec4 vec = ctm * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
// right side
vec = ctm * glm::vec4(-1.f, 0.f, 0.f, 1.f);
left_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
}
}
}
@@ -159,10 +216,12 @@ Handles::Handles(Type type) : Node(), type_(type)
static Mesh *handle_rotation = new Mesh("mesh/border_handles_rotation.ply");
static Mesh *handle_corner = new Mesh("mesh/border_handles_overlay.ply");
static Mesh *handle_scale = new Mesh("mesh/border_handles_scale.ply");
static Mesh *handle_restore = new Mesh("mesh/border_handles_menu.ply");
static Mesh *handle_crop = new Mesh("mesh/border_handles_crop.ply");
static Mesh *handle_menu = new Mesh("mesh/border_handles_menu.ply");
static Mesh *handle_lock = new Mesh("mesh/border_handles_lock.ply");
static Mesh *handle_unlock = new Mesh("mesh/border_handles_lock_open.ply");
static Mesh *handle_shadow = new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
color = glm::vec4( 1.f, 1.f, 0.f, 1.f);
if ( type_ == Handles::ROTATE ) {
handle_ = handle_rotation;
}
@@ -170,14 +229,23 @@ Handles::Handles(Type type) : Node(), type_(type)
handle_ = handle_scale;
}
else if ( type_ == Handles::MENU ) {
handle_ = handle_restore;
handle_ = handle_menu;
}
else if ( type_ == Handles::CROP ) {
handle_ = handle_crop;
}
else if ( type_ == Handles::LOCKED ) {
handle_ = handle_lock;
}
else if ( type_ == Handles::UNLOCKED ) {
handle_ = handle_unlock;
}
else {
handle_ = handle_corner;
}
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
corner_ = glm::vec2(0.f, 0.f);
shadow_ = handle_shadow;
}
@@ -193,7 +261,6 @@ void Handles::update( float dt )
void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
{
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
if ( !initialized() ) {
if(handle_ && !handle_->initialized())
@@ -203,21 +270,20 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
init();
}
if ( visible_ ) {
if ( visible_ && handle_) {
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
// set color
handle_->shader()->color = color;
handle_active->shader()->color = color;
// extract rotation from modelview
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
glm::vec4 vec;
glm::vec3 tra, rot, sca;
// extract scaling and mirroring
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
glm::vec4 mirror = glm::sign(vec);
// get rotation and mirroring from the modelview
GlmToolkit::inverse_transform(modelview, tra, rot, sca);
glm::vec3 mirror = glm::sign(sca);
if ( type_ == Handles::RESIZE ) {
@@ -295,7 +361,19 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
glm::vec4 pos = ctm * glm::vec4(mirror.x * 0.12f, mirror.x * -0.12f, 0.f, 1.f);
// 2. ..from the bottom right corner (1,1)
vec = ( modelview * glm::vec4(1.f, -1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(mirror.x, mirror.y, 1.f));
ctm = GlmToolkit::transform(vec, rot, mirror);
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
else if ( type_ == Handles::CROP ){
// one icon in bottom right corner
// 1. Fixed displacement by (0.12,0.12) along the rotation..
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
glm::vec4 pos = ctm * glm::vec4(mirror.x * 0.12f, mirror.x * 0.12f, 0.f, 1.f);
// 2. ..from the bottom right corner (1,-1)
vec = ( modelview * glm::vec4(-1.f, -1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, mirror);
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
@@ -307,7 +385,19 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
glm::vec4 pos = ctm * glm::vec4( -0.12f, 0.12f, 0.f, 1.f);
// 2. ..from the top right corner (1,1)
vec = ( modelview * glm::vec4(-1.f, 1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
ctm = GlmToolkit::transform(vec, rot, mirror);
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
}
else if ( type_ == Handles::LOCKED || type_ == Handles::UNLOCKED ){
// one icon in top left corner
// 1. Fixed displacement by (-0.12,0.12) along the rotation..
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
glm::vec4 pos = ctm * glm::vec4( -0.12f, 0.12f, 0.f, 1.f);
// 2. ..from the bottom right corner (1,-1)
vec = ( modelview * glm::vec4(1.f, -1.f, 0.f, 1.f) ) + pos;
ctm = GlmToolkit::transform(vec, rot, mirror);
// 3. draw
shadow_->draw( ctm, projection );
handle_->draw( ctm, projection );
@@ -325,36 +415,73 @@ void Handles::accept(Visitor& v)
Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
{
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
static Mesh *shadows[(int)EMPTY+1] = {nullptr};
static Mesh *icons[(int)EMPTY+1] = {nullptr};
if (icons[0] == nullptr) {
icons[CIRCLE_POINT] = new Mesh("mesh/point.ply");
shadows[CIRCLE_POINT] = nullptr;
icons[SQUARE_POINT] = new Mesh("mesh/square_point.ply");
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
icons[RENDER] = new Mesh("mesh/icon_render.ply");
icons[PATTERN] = new Mesh("mesh/icon_gear.ply");
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
icons[SHARE] = new Mesh("mesh/icon_share.ply");
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
icons[CROP] = new Mesh("mesh/icon_rightarrow.ply");
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
icons[GRID] = new Mesh("mesh/icon_grid.ply");
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
shadows[SQUARE_POINT] = nullptr;
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
shadows[IMAGE] = shadow;
icons[SEQUENCE] = new Mesh("mesh/icon_sequence.ply");
shadows[SEQUENCE]= shadow;
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
shadows[VIDEO] = shadow;
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
shadows[SESSION]= shadow;
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
shadows[CLONE] = shadow;
icons[RENDER] = new Mesh("mesh/icon_render.ply");
shadows[RENDER] = shadow;
icons[GROUP] = new Mesh("mesh/icon_group_vimix.ply");
shadows[GROUP] = shadow;
icons[PATTERN] = new Mesh("mesh/icon_gear.ply");
shadows[PATTERN]= shadow;
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
shadows[CAMERA] = shadow;
icons[CUBE] = new Mesh("mesh/icon_cube.ply");
shadows[CUBE] = shadow;
icons[SHARE] = new Mesh("mesh/icon_share.ply");
shadows[SHARE] = shadow;
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
shadows[DOTS] = nullptr;
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
shadows[BUSY] = nullptr;
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
shadows[LOCK] = shadow;
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
shadows[UNLOCK] = shadow;
icons[EYE] = new Mesh("mesh/icon_eye.ply");
shadows[EYE] = shadow;
icons[EYESLASH] = new Mesh("mesh/icon_eye_slash.ply");
shadows[EYESLASH] = shadow;
icons[VECTORSLASH] = new Mesh("mesh/icon_vector_square_slash.ply");
shadows[VECTORSLASH] = shadow;
icons[ARROWS] = new Mesh("mesh/icon_rightarrow.ply");
shadows[ARROWS] = shadow;
icons[ROTATION] = new Mesh("mesh/border_handles_rotation.ply");
shadows[ROTATION] = shadow;
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
shadows[CIRCLE] = nullptr;
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
shadows[CLOCK] = nullptr;
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
shadows[CLOCK_H]= nullptr;
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
shadows[SQUARE] = nullptr;
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
shadows[CROSS] = nullptr;
icons[GRID] = new Mesh("mesh/icon_grid.ply");
shadows[GRID] = nullptr;
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
shadows[EMPTY] = shadow;
}
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
symbol_ = icons[type_];
shadow_ = shadow;
shadow_ = shadows[type_];
translation_ = pos;
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
@@ -397,7 +524,8 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
// generate matrix
ctm = GlmToolkit::transform(tran, rot, sca);
shadow_->draw( ctm, projection );
if (shadow_)
shadow_->draw( ctm, projection );
symbol_->draw( ctm, projection);
}
}
@@ -419,11 +547,6 @@ Disk::Disk() : Node()
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
Disk::~Disk()
{
}
void Disk::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() ) {
@@ -449,55 +572,88 @@ void Disk::accept(Visitor& v)
v.visit(*this);
}
Surface *Glyph::font_ = nullptr;
//SelectionBox::SelectionBox()
//{
//// color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
// color = glm::vec4( 1.f, 0.f, 0.f, 1.f);
// square_ = new LineSquare( 3 );
Glyph::Glyph(int imgui_font_index) : Node(), character_(' '), font_index_(imgui_font_index), baseline_(0.f), shape_(1.f, 1.f)
{
if (Glyph::font_ == nullptr)
Glyph::font_ = new Surface;
//}
uvTransform_ = glm::identity<glm::mat4>();
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
//void SelectionBox::draw (glm::mat4 modelview, glm::mat4 projection)
//{
// if ( !initialized() ) {
// square_->init();
// init();
// }
void Glyph::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() ) {
// if (visible_) {
if (!Glyph::font_->initialized()) {
// // use a visitor bounding box to calculate extend of all selected nodes
// BoundingBoxVisitor vbox;
uint tex = (uint)(intptr_t)ImGui::GetIO().Fonts->TexID;
if ( tex > 0) {
Glyph::font_->init();
font_->setTextureIndex(tex);
}
}
// // visit every child of the selection
// for (NodeSet::iterator node = children_.begin();
// node != children_.end(); node++) {
// // reset the transform before
// vbox.setModelview(glm::identity<glm::mat4>());
// (*node)->accept(vbox);
// }
if (Glyph::font_->initialized()) {
init();
setChar(character_);
}
}
// // get the bounding box
// bbox_ = vbox.bbox();
if ( visible_ ) {
//// Log::Info(" -------- visitor box (%f, %f)-(%f, %f)", bbox_.min().x, bbox_.min().y, bbox_.max().x, bbox_.max().y);
// set color
Glyph::font_->shader()->color = color;
// // set color
// square_->shader()->color = color;
// modify the shader iTransform to select UVs of the desired glyph
Glyph::font_->shader()->iTransform = uvTransform_;
// // compute transformation from bounding box
//// glm::mat4 ctm = modelview * GlmToolkit::transform(glm::vec3(0.f), glm::vec3(0.f), glm::vec3(1.f));
// glm::mat4 ctm = modelview * GlmToolkit::transform(bbox_.center(), glm::vec3(0.f), bbox_.scale());
// rebuild a matrix with rotation (see handles) and translation from modelview + translation_
// and define scale to be 1, 1
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
// extract scaling
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
glm::vec2 sca = glm::vec2(vec.y) * glm::vec2(scale_.y) * shape_;
// extract translation
glm::vec3 tran = glm::vec3(modelview[3][0], modelview[3][1], modelview[3][2]) ;
tran += (translation_ + glm::vec3(0.f, baseline_ * scale_.y, 0.f) ) * glm::vec3(vec);
// apply local rotation
rot.z += rotation_.z;
// generate matrix
ctm = GlmToolkit::transform(tran, rot, glm::vec3(sca, 1.f));
// // draw bbox
//// square_->draw( modelview, projection);
// square_->draw( ctm, projection);
Glyph::font_->draw( ctm, projection);
}
}
// // DEBUG
//// visible_=false;
// }
void Glyph::setChar(char c)
{
character_ = c;
//}
if (initialized()) {
// get from imgui the UVs of the given char
const ImGuiIO& io = ImGui::GetIO();
ImFont* myfont = io.Fonts->Fonts[0];
if (font_index_ > 0 && font_index_ < io.Fonts->Fonts.size() )
myfont = io.Fonts->Fonts[font_index_];
const ImFontGlyph* glyph = myfont->FindGlyph(character_);
if (glyph) {
// create a texture UV transform to get the UV coordinates of the glyph
const glm::vec3 uv_t = glm::vec3( glyph->U0, glyph->V0, 0.f);
const glm::vec3 uv_s = glm::vec3( glyph->U1 - glyph->U0, glyph->V1 - glyph->V0, 1.f);
const glm::vec3 uv_r = glm::vec3(0.f, 0.f, 0.f);
uvTransform_ = GlmToolkit::transform(uv_t, uv_r, uv_s);
// remember glyph shape
shape_ = glm::vec2(glyph->X1 - glyph->X0, glyph->Y1 - glyph->Y0) / myfont->FontSize;
baseline_ = -glyph->Y0 / myfont->FontSize;
}
}
}

View File

@@ -12,7 +12,7 @@ class Frame : public Node
{
public:
typedef enum { ROUND = 0, SHARP } CornerType;
typedef enum { ROUND = 0, SHARP, GROUP } CornerType;
typedef enum { THIN = 0, LARGE } BorderType;
typedef enum { NONE = 0, GLOW, DROP, PERSPECTIVE } ShadowType;
@@ -23,11 +23,11 @@ public:
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
Mesh *border() const { return side_; }
glm::vec4 color;
protected:
Mesh *side_;
Mesh *right_;
Mesh *left_;
Mesh *top_;
Mesh *shadow_;
LineSquare *square_;
@@ -36,7 +36,7 @@ protected:
class Handles : public Node
{
public:
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, MENU } Type;
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, CROP, MENU, LOCKED, UNLOCKED } Type;
Handles(Type type);
~Handles();
@@ -60,9 +60,9 @@ protected:
class Symbol : public Node
{
public:
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, PATTERN, CAMERA, SHARE,
DOTS, BUSY, LOCK, UNLOCK, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE,
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t, glm::vec3 pos = glm::vec3(0.f));
~Symbol();
void draw (glm::mat4 modelview, glm::mat4 projection) override;
@@ -83,7 +83,6 @@ class Disk : public Node
{
public:
Disk();
~Disk();
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
@@ -94,20 +93,26 @@ protected:
static Mesh *disk_;
};
//class SelectionBox : public Group
//{
//public:
// SelectionBox();
class Glyph : public Node
{
public:
Glyph(int imgui_font_index = 0);
void setChar(char c);
// void draw (glm::mat4 modelview, glm::mat4 projection) override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;
// glm::vec4 color;
glm::vec4 color;
//protected:
// LineSquare *square_;
// GlmToolkit::AxisAlignedBoundingBox bbox_;
protected:
//};
char character_;
int font_index_;
float baseline_;
glm::vec2 shape_;
glm::mat4 uvTransform_;
static Surface *font_;
};
#endif // DECORATIONS_H

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
@@ -19,7 +38,7 @@
#ifndef NDEBUG
#define DEVICE_DEBUG
//#define GST_DEVICE_DEBUG
#define GST_DEVICE_DEBUG
#endif
@@ -101,80 +120,99 @@ gboolean
Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
{
GstDevice *device;
gchar *name;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_DEVICE_ADDED: {
case GST_MESSAGE_DEVICE_ADDED:
{
gst_message_parse_device_added (message, &device);
name = gst_device_get_display_name (device);
// ignore if already in the list
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), name) != manager().src_name_.end())
break;
manager().src_name_.push_back(name);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s plugged : %s\n", name, stru);
g_free (stru);
#endif
g_free (name);
std::string p = pipelineForDevice(device, manager().src_description_.size());
manager().src_description_.push_back(p);
DeviceConfigSet confs = getDeviceConfigs(p);
manager().src_config_.push_back(confs);
manager().list_uptodate_ = false;
manager().add(device);
gst_object_unref (device);
}
break;
case GST_MESSAGE_DEVICE_REMOVED: {
case GST_MESSAGE_DEVICE_REMOVED:
{
gst_message_parse_device_removed (message, &device);
name = gst_device_get_display_name (device);
manager().remove(name);
#ifdef GST_DEVICE_DEBUG
g_print("\nDevice %s unplugged\n", name);
#endif
g_free (name);
manager().list_uptodate_ = false;
manager().remove(device);
gst_object_unref (device);
}
break;
default:
default:
break;
}
return G_SOURCE_CONTINUE;
}
void Device::remove(const char *device)
void Device::add(GstDevice *device)
{
if (device==nullptr)
return;
gchar *device_name = gst_device_get_display_name (device);
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), device_name) == manager().src_name_.end()) {
std::string p = pipelineForDevice(device, manager().src_description_.size());
DeviceConfigSet confs = getDeviceConfigs(p);
// add if not in the list and valid
if (!p.empty() && !confs.empty()) {
src_name_.push_back(device_name);
src_description_.push_back(p);
src_config_.push_back(confs);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s plugged : %s\n", device_name, stru);
g_free (stru);
#endif
}
list_uptodate_ = false;
}
g_free (device_name);
}
void Device::remove(GstDevice *device)
{
if (device==nullptr)
return;
gchar *device_name = gst_device_get_display_name (device);
std::vector< std::string >::iterator nameit = src_name_.begin();
std::vector< std::string >::iterator descit = src_description_.begin();
std::vector< DeviceConfigSet >::iterator coit = src_config_.begin();
while (nameit != src_name_.end()){
if ( (*nameit).compare(device) == 0 )
if ( (*nameit).compare(device_name) == 0 )
{
src_name_.erase(nameit);
src_description_.erase(descit);
src_config_.erase(coit);
list_uptodate_ = false;
#ifdef GST_DEVICE_DEBUG
g_print("\nDevice %s unplugged\n", device_name);
#endif
break;
}
nameit++;
descit++;
coit++;
++nameit;
++descit;
++coit;
}
g_free (device_name);
}
Device::Device()
Device::Device(): list_uptodate_(false)
{
}
void Device::init()
{
GstBus *bus;
GstCaps *caps;
@@ -198,24 +236,9 @@ Device::Device()
GList *devices = gst_device_monitor_get_devices(monitor_);
GList *tmp;
for (tmp = devices; tmp ; tmp = tmp->next ) {
GstDevice *device = (GstDevice *) tmp->data;
gchar *name = gst_device_get_display_name (device);
src_name_.push_back(name);
g_free (name);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s already plugged : %s", name, stru);
g_free (stru);
#endif
std::string p = pipelineForDevice(device, src_description_.size());
src_description_.push_back(p);
DeviceConfigSet confs = getDeviceConfigs(p);
src_config_.push_back(confs);
add(device);
gst_object_unref (device);
}
g_list_free(devices);
@@ -257,7 +280,7 @@ struct hasDevice: public std::unary_function<DeviceSource*, bool>
inline bool operator()(const DeviceSource* elem) const {
return (elem && elem->device() == _d);
}
hasDevice(std::string d) : _d(d) { }
explicit hasDevice(const std::string &d) : _d(d) { }
private:
std::string _d;
};
@@ -325,13 +348,14 @@ int Device::index(const std::string &device) const
return i;
}
DeviceSource::DeviceSource() : StreamSource()
DeviceSource::DeviceSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = new Stream;
// set symbol
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.8f, 0.8f, 0.01f));
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
DeviceSource::~DeviceSource()
@@ -341,9 +365,8 @@ DeviceSource::~DeviceSource()
}
void DeviceSource::setDevice(const std::string &devicename)
{
{
device_ = devicename;
Log::Notify("Creating Source with device '%s'", device_.c_str());
int index = Device::manager().index(device_);
if (index > -1) {
@@ -359,32 +382,42 @@ void DeviceSource::setDevice(const std::string &devicename)
DeviceConfigSet confs = Device::manager().config(index);
#ifdef DEVICE_DEBUG
Log::Info("Device %s supported configs:", devicename.c_str());
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); it++ ){
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
}
#endif
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
if (!confs.empty()) {
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
pipeline << " ! " << best.stream;
if (!best.format.empty())
pipeline << ",format=" << best.format;
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
pipeline << ",width=" << best.width;
pipeline << ",height=" << best.height;
pipeline << " ! " << best.stream;
if (!best.format.empty())
pipeline << ",format=" << best.format;
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
pipeline << ",width=" << best.width;
pipeline << ",height=" << best.height;
if ( best.stream.find("jpeg") != std::string::npos )
pipeline << " ! jpegdec";
if ( best.stream.find("jpeg") != std::string::npos )
pipeline << " ! jpegdec";
if ( device_.find("Screen") != std::string::npos )
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
if ( device_.find("Screen") != std::string::npos )
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
pipeline << " ! videoconvert";
pipeline << " ! videoconvert";
stream_->open( pipeline.str(), best.width, best.height);
stream_->play(true);
// resize render buffer
if (renderbuffer_)
renderbuffer_->resize(best.width, best.height);
// open gstreamer
stream_->open( pipeline.str(), best.width, best.height);
stream_->play(true);
}
// will be ready after init and one frame rendered
ready_ = false;
}
else
Log::Warning("No such device '%s'", device_.c_str());
@@ -430,11 +463,11 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
// get the first pad and its content
GstIterator *iter = gst_element_iterate_src_pads(elem);
GValue vPad = G_VALUE_INIT;
GstPad* ret = NULL;
GstPad* pad_ret = NULL;
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
{
ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (ret, NULL);
pad_ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (pad_ret, NULL);
// loop over all caps offered by the pad
int C = gst_caps_get_size(device_caps);
@@ -507,8 +540,8 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
gdouble fps_max = 1.0;
// loop over all fractions
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
const GValue *frac = gst_value_list_get_value(val, n);
for (int i = 0; i < N; ++i ){
const GValue *frac = gst_value_list_get_value(val, i);
// read one fraction in the list
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
int n = gst_value_get_fraction_numerator(frac);
@@ -556,9 +589,14 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
glm::ivec2 DeviceSource::icon() const
{
if ( device_.find("Screen") != std::string::npos )
return glm::ivec2(19, 1);
return glm::ivec2(ICON_SOURCE_DEVICE_SCREEN);
else
return glm::ivec2(2, 14);
return glm::ivec2(ICON_SOURCE_DEVICE);
}
std::string DeviceSource::info() const
{
return std::string("device '") + device_ + "'";
}

View File

@@ -10,7 +10,7 @@
class DeviceSource : public StreamSource
{
public:
DeviceSource();
DeviceSource(uint64_t id = 0);
~DeviceSource();
// Source interface
@@ -25,6 +25,7 @@ public:
inline std::string device() const { return device_; }
glm::ivec2 icon() const override;
std::string info() const override;
private:
std::string device_;
@@ -87,8 +88,8 @@ class Device
friend class DeviceSource;
Device();
Device(Device const& copy); // Not Implemented
Device& operator=(Device const& copy); // Not Implemented
Device(Device const& copy) = delete;
Device& operator=(Device const& copy) = delete;
public:
@@ -99,6 +100,7 @@ public:
return _instance;
}
void init();
int numDevices () const;
std::string name (int index) const;
std::string description (int index) const;
@@ -115,7 +117,8 @@ public:
private:
void remove(const char *device);
void remove(GstDevice *device);
void add(GstDevice *device);
std::vector< std::string > src_name_;
std::vector< std::string > src_description_;

615
DialogToolkit.cpp Normal file
View File

@@ -0,0 +1,615 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
// multiplatform implementation of system dialogs
//
// 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git
// is the prefered solution, but it is not compatible with linux snap packaging
// because of 'zenity' access rights nightmare :(
// Thus this re-implementation of native GTK+ dialogs for linux
#include "defines.h"
#include "Settings.h"
#include "SystemToolkit.h"
#include "DialogToolkit.h"
#if defined(LINUX)
#define USE_TINYFILEDIALOG 0
#else
#define USE_TINYFILEDIALOG 1
#endif
#if USE_TINYFILEDIALOG
#include "tinyfiledialogs.h"
#else
#include <stdio.h>
#include <gtk/gtk.h>
static bool gtk_init_ok = false;
static int window_x = -1, window_y = -1;
void add_filter_any_file_dialog( GtkWidget *dialog)
{
/* append a wildcard option */
GtkFileFilter *filter;
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, "Any file" );
gtk_file_filter_add_pattern( filter, "*" );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
void add_filter_file_dialog( GtkWidget *dialog, int const countfilterPatterns, char const * const * const filterPatterns, char const * const filterDescription)
{
GtkFileFilter *filter;
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, filterDescription );
for (int i = 0; i < countfilterPatterns; i++ ) {
gtk_file_filter_add_pattern( filter, g_ascii_strdown(filterPatterns[i], -1) );
gtk_file_filter_add_pattern( filter, g_ascii_strup(filterPatterns[i], -1) );
}
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
void wait_for_event(void)
{
while ( gtk_events_pending() )
gtk_main_iteration();
}
bool gtk_init()
{
if (!gtk_init_ok) {
if ( gtk_init_check( NULL, NULL ) )
gtk_init_ok = true;
}
return gtk_init_ok;
}
#endif
// globals
const std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
bool DialogToolkit::FileDialog::busy_ = false;
//
// FileDialog common functions
//
DialogToolkit::FileDialog::FileDialog(const std::string &name) : id_(name)
{
if ( Settings::application.dialogRecentFolder.count(id_) == 0 )
Settings::application.dialogRecentFolder[id_] = SystemToolkit::home_path();
}
bool DialogToolkit::FileDialog::closed()
{
if ( !promises_.empty() ) {
// check that file dialog thread finished
if (promises_.back().wait_for(timeout) == std::future_status::ready ) {
// get the filename from this file dialog
path_ = promises_.back().get();
if (!path_.empty()) {
// save path location
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
}
// done with this file dialog
promises_.pop_back();
busy_ = false;
return true;
}
}
return false;
}
//
// type specific implementations
//
std::string openImageFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenImageDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openImageFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openSessionFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenSessionDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openSessionFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openMediaFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenMediaDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openMediaFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string saveSessionFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::SaveSessionDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, saveSessionFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openFolderDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenFolderDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openFolderDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path);
void DialogToolkit::MultipleImagesDialog::open()
{
if ( !busy_ && promisedlist_.empty() ) {
promisedlist_.emplace_back( std::async(std::launch::async, selectImagesFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
bool DialogToolkit::MultipleImagesDialog::closed()
{
if ( !promisedlist_.empty() ) {
// check that file dialog thread finished
if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) {
// get the filename from this file dialog
std::list<std::string> list = promisedlist_.back().get();
if (!list.empty()) {
// selected a filenames
pathlist_ = list;
path_ = list.front();
// save path location
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
}
else {
pathlist_.clear();
path_.clear();
}
// done with this file dialog
promisedlist_.pop_back();
busy_ = false;
return true;
}
}
return false;
}
//
//
// CALLBACKS
//
//
std::string saveSessionFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
char const * save_pattern[1] = { VIMIX_FILES_PATTERN };
#if USE_TINYFILEDIALOG
char const * save_file_name;
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session");
if (save_file_name)
filename = std::string(save_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT, NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
// set file filters
add_filter_file_dialog(dialog, 1, save_pattern, "vimix session");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (save_file_name)
filename = std::string(save_file_name);
g_free( save_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
std::string extension = filename.substr(filename.find_last_of(".") + 1);
if (!filename.empty() && extension != "mix")
filename += ".mix";
return filename;
}
std::string openSessionFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[1] = { VIMIX_FILES_PATTERN };
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 1, open_pattern, "vimix session");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openMediaFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[26] = { MEDIA_FILES_PATTERN };
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 26, open_pattern, "All supported formats", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 26, open_pattern, "Supported formats (videos, images, sessions)");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openImageFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[5] = { IMAGES_FILES_PATTERN };
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openFolderDialog(const std::string &label, const std::string &path)
{
std::string foldername = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
#if USE_TINYFILEDIALOG
char const * open_folder_name;
open_folder_name = tinyfd_selectFolderDialog(label.c_str(), startpath.c_str());
if (open_folder_name)
foldername = std::string(open_folder_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return foldername;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Select", GTK_RESPONSE_ACCEPT, NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_folder_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_folder_name)
foldername = std::string(open_folder_name);
g_free( open_folder_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return foldername;
}
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path)
{
std::list<std::string> files;
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif" };
#if USE_TINYFILEDIALOG
char const * open_file_names;
open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 3, open_pattern, "Images (JPG, PNG, TIF)", 1);
if (open_file_names) {
const std::string& str (open_file_names);
const std::string& delimiters = "|";
// Skip delimiters at beginning.
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first non-delimiter.
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
while (std::string::npos != pos || std::string::npos != lastPos) {
// Found a token, add it to the vector.
files.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters.
lastPos = str.find_first_not_of(delimiters, pos);
// Find next non-delimiter.
pos = str.find_first_of(delimiters, lastPos);
}
}
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return files;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 3, open_pattern, "Images (JPG, PNG, TIF)");
add_filter_any_file_dialog(dialog);
// multiple files
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true );
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
while (open_file_names) {
files.push_back( (char *) open_file_names->data );
open_file_names = open_file_names->next;
// g_free( open_file_names->data );
}
g_slist_free( open_file_names );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return files;
}
void DialogToolkit::ErrorDialog(const char* message)
{
#if USE_TINYFILEDIALOG
tinyfd_messageBox( APP_TITLE, message, "ok", "error", 0);
#else
if (!gtk_init()) {
return;
}
GtkWidget *dialog = gtk_message_dialog_new( NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"Error: %s", message);
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
static int x = 0, y = 0;
if (x != 0)
gtk_window_move( GTK_WINDOW(dialog), x, y);
// show
gtk_dialog_run( GTK_DIALOG(dialog) );
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &x, &y);
// done
gtk_widget_destroy( dialog );
wait_for_event();
#endif
}

90
DialogToolkit.h Normal file
View File

@@ -0,0 +1,90 @@
#ifndef DIALOGTOOLKIT_H
#define DIALOGTOOLKIT_H
#include <string>
#include <list>
#include <vector>
#include <future>
#define VIMIX_FILES_PATTERN "*.mix"
#define MEDIA_FILES_PATTERN "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",\
"*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",\
"*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",\
"*.tif", "*.tiff", "*.webp", "*.bmp", "*.ppm", "*.svg,"
#define IMAGES_FILES_PATTERN "*.jpg", "*.png", "*.bmp", "*.ppm", "*.gif"
namespace DialogToolkit
{
void ErrorDialog(const char* message);
class FileDialog
{
protected:
std::string id_;
std::string directory_;
std::string path_;
std::vector< std::future<std::string> >promises_;
static bool busy_;
public:
FileDialog(const std::string &name);
virtual void open() = 0;
virtual bool closed();
inline std::string path() const { return path_; }
static bool busy() { return busy_; }
};
class OpenImageDialog : public FileDialog
{
public:
OpenImageDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenSessionDialog : public FileDialog
{
public:
OpenSessionDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenMediaDialog : public FileDialog
{
public:
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class SaveSessionDialog : public FileDialog
{
public:
SaveSessionDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenFolderDialog : public FileDialog
{
public:
OpenFolderDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class MultipleImagesDialog : public FileDialog
{
std::list<std::string> pathlist_;
std::vector< std::future< std::list<std::string> > > promisedlist_;
public:
MultipleImagesDialog(const std::string &name) : FileDialog(name) {}
void open() override;
bool closed() override;
inline std::list<std::string> images() const { return pathlist_; }
};
}
#endif // DIALOGTOOLKIT_H

View File

@@ -1,3 +1,23 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <glm/gtc/matrix_transform.hpp>
@@ -8,14 +28,19 @@
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force): force_(force)
{
target_ = nodetodraw;
targets_.push_back(nodetodraw);
modelview_ = glm::identity<glm::mat4>();
projection_ = projection;
done_ = false;
num_duplicat_ = 1;
transform_duplicat_ = glm::identity<glm::mat4>();
}
DrawVisitor::DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force):
modelview_(glm::identity<glm::mat4>()), projection_(projection), targets_(nodestodraw), force_(force),
num_duplicat_(1), transform_duplicat_(glm::identity<glm::mat4>())
{
}
void DrawVisitor::loop(int num, glm::mat4 transform)
{
@@ -25,19 +50,30 @@ void DrawVisitor::loop(int num, glm::mat4 transform)
void DrawVisitor::visit(Node &n)
{
// draw the target
if ( target_ && n.id() == target_->id()) {
// force visible status if required
bool v = n.visible_;
if (force_)
n.visible_ = true;
// find the node with this id
std::vector<Node *>::iterator it = std::find_if(targets_.begin(), targets_.end(), hasId(n.id()));
// found this node in the list of targets: draw it
if (it != targets_.end()) {
targets_.erase(it);
for (int i = 0; i < num_duplicat_; ++i) {
// draw multiple copies if requested
n.draw(modelview_, projection_);
modelview_ *= transform_duplicat_;
}
done_ = true;
}
if (done_) return;
// restore visibility
n.visible_ = v;
if (targets_.empty()) return;
// update transform
modelview_ *= n.transform_;
@@ -47,11 +83,11 @@ void DrawVisitor::visit(Node &n)
void DrawVisitor::visit(Group &n)
{
// no need to traverse deeper if this node was drawn already
if (done_) return;
if (targets_.empty()) return;
// traverse children
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); !done_ && node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
@@ -60,17 +96,22 @@ void DrawVisitor::visit(Group &n)
void DrawVisitor::visit(Scene &n)
{
modelview_ = glm::identity<glm::mat4>();
n.root()->accept(*this);
}
void DrawVisitor::visit(Switch &n)
{
// no need to traverse deeper if this node was drawn already
if (targets_.empty()) return;
// traverse acive child
glm::mat4 mv = modelview_;
n.activeChild()->accept(*this);
if ( n.activeChild()->visible_ || force_)
n.activeChild()->accept(*this);
modelview_ = mv;
}
void DrawVisitor::visit(Primitive &n)
void DrawVisitor::visit(Primitive &)
{
}

View File

@@ -1,27 +1,27 @@
#ifndef DRAWVISITOR_H
#define DRAWVISITOR_H
#include <glm/glm.hpp>
#include "GlmToolkit.h"
#include "Visitor.h"
class DrawVisitor : public Visitor
{
glm::mat4 modelview_;
glm::mat4 projection_;
Node *target_;
bool done_;
std::vector<Node *> targets_;
bool force_;
int num_duplicat_;
glm::mat4 transform_duplicat_;
public:
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force = false);
void loop(int num, glm::mat4 transform);
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Primitive& n) override;
void visit(Primitive& ) override;
void visit(Group& n) override;
void visit(Switch& n) override;
};

View File

@@ -1,14 +1,27 @@
#include "FileDialog.h"
#include "ImGuiToolkit.h"
/*
* This file is part of vimix - Live video mixer
*
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <fstream>
#include <iostream>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <stb_image.h>
#include <glad/glad.h>
#ifdef WIN32
@@ -29,8 +42,8 @@
#endif
#include "imgui_internal.h"
#include <stb_image.h>
#include "ImGuiToolkit.h"
#include "FileDialog.h"
static std::string s_fs_root(1u, PATH_SEP);
static std::string currentFileDialog;
@@ -184,7 +197,7 @@ inline PathStruct ParsePathFileName(const std::string& vPathFileName)
return res;
}
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, std::string vStr)
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string &vStr)
{
std::string st = vStr;
size_t len = vBufferLen - 1u;
@@ -259,8 +272,6 @@ static bool stringComparator(const FileInfoStruct& a, const FileInfoStruct& b)
void FileDialog::ScanDir(const std::string& vPath)
{
struct dirent **files = NULL;
int i = 0;
int n = 0;
std::string path = vPath;
#if defined(LINUX) or defined(APPLE)
@@ -286,12 +297,12 @@ void FileDialog::ScanDir(const std::string& vPath)
path += PATH_SEP;
}
#endif
n = scandir(path.c_str(), &files, NULL, alphaSort);
int n = scandir(path.c_str(), &files, NULL, alphaSort);
if (n > 0)
{
m_FileList.clear();
for (i = 0; i < n; i++)
for (int i = 0; i < n; ++i)
{
struct dirent *ent = files[i];
@@ -334,7 +345,7 @@ void FileDialog::ScanDir(const std::string& vPath)
}
}
for (i = 0; i < n; i++)
for (int i = 0; i < n; ++i)
{
free(files[i]);
}
@@ -440,7 +451,7 @@ void FileDialog::ComposeNewPath(std::vector<std::string>::iterator vIter)
break;
}
vIter--;
--vIter;
}
}
@@ -709,9 +720,9 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
SetPath("."); // Go Home
}
#ifdef WIN32
bool drivesClick = false;
#ifdef WIN32
ImGui::SameLine();
if (ImGui::Button("Drives"))
@@ -871,10 +882,12 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
SetPath(m_CurrentPath);
}
#ifdef WIN32
if (drivesClick == true)
{
GetDrives();
}
#endif
ImGui::EndChild();
ImGui::PopFont();
@@ -1019,12 +1032,12 @@ std::string FileDialog::GetUserString()
return dlg_userString;
}
void FileDialog::SetFilterColor(std::string vFilter, ImVec4 vColor)
void FileDialog::SetFilterColor(const std::string &vFilter, ImVec4 vColor)
{
m_FilterColor[vFilter] = vColor;
}
bool FileDialog::GetFilterColor(std::string vFilter, ImVec4 *vColor)
bool FileDialog::GetFilterColor(const std::string &vFilter, ImVec4 *vColor)
{
if (vColor)
{
@@ -1060,7 +1073,7 @@ inline void InfosPane(std::string vFilter, bool *vCantContinue)
*vCantContinue = canValidateDialog;
}
inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
inline void TextInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
{
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Text");
@@ -1097,7 +1110,7 @@ inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantC
*vCantContinue = text.size() > 0;
}
inline void ImageInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
inline void ImageInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
{
// opengl texture
static GLuint tex = 0;

View File

@@ -6,15 +6,8 @@
#include <vector>
#include <string>
#include <map>
#include <future>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
#include <string>
#include <vector>
#include <list>
#define MAX_FILE_DIALOG_NAME_BUFFER 1024
@@ -78,8 +71,8 @@ public:
protected:
FileDialog(); // Prevent construction
FileDialog(const FileDialog&) {}; // Prevent construction by copying
FileDialog& operator =(const FileDialog&) { return *this; }; // Prevent assignment
FileDialog(const FileDialog&) = delete; // Prevent construction by copying
FileDialog& operator =(const FileDialog&) = delete; // Prevent assignment
~FileDialog(); // Prevent unwanted destruction
public:
@@ -102,8 +95,8 @@ public:
std::string GetCurrentFilter();
std::string GetUserString();
void SetFilterColor(std::string vFilter, ImVec4 vColor);
bool GetFilterColor(std::string vFilter, ImVec4 *vColor);
void SetFilterColor(const std::string &vFilter, ImVec4 vColor);
bool GetFilterColor(const std::string &vFilter, ImVec4 *vColor);
void ClearFilterColor();
private:

View File

@@ -1,3 +1,24 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <sstream>
#include "FrameBuffer.h"
#include "ImageShader.h"
#include "Resource.h"
@@ -7,28 +28,56 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glad/glad.h>
#include <stb_image.h>
#include <stb_image_write.h>
const char* FrameBuffer::aspect_ratio_name[5] = { "4:3", "3:2", "16:10", "16:9", "21:9" };
glm::vec2 FrameBuffer::aspect_ratio_size[5] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) , glm::vec2(21.f,9.f) };
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "4K" };
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
const char* FrameBuffer::resolution_name[5] = { "720", "1080", "1200", "1440", "2160" };
float FrameBuffer::resolution_height[5] = { 720.f, 1080.f, 1200.f, 1440.f, 2160.f };
glm::vec3 FrameBuffer::getResolutionFromParameters(int ar, int h)
{
float width = aspect_ratio_size[ar].x * resolution_height[h] / aspect_ratio_size[ar].y;
width -= (int)width % 2;
glm::vec3 res = glm::vec3( width, resolution_height[h] , 0.f);
return res;
}
glm::ivec2 FrameBuffer::getParametersFromResolution(glm::vec3 res)
{
glm::ivec2 p = glm::ivec2(-1);
// get aspect ratio parameter
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
float myratio = res.x / res.y;
for(int ar = 0; ar < num_ar; ar++) {
if ( myratio - (FrameBuffer::aspect_ratio_size[ar].x / FrameBuffer::aspect_ratio_size[ar].y ) < EPSILON){
p.x = ar;
break;
}
}
// get height parameter
static int num_height = ((int)(sizeof(FrameBuffer::resolution_height) / sizeof(*FrameBuffer::resolution_height)));
for(int h = 0; h < num_height; h++) {
if ( res.y - FrameBuffer::resolution_height[h] < 1){
p.y = h;
break;
}
}
return p;
}
FrameBuffer::FrameBuffer(glm::vec3 resolution, bool useAlpha, bool multiSampling):
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
{
attrib_.viewport = glm::ivec2(resolution);
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
setProjectionArea(glm::vec2(1.f, 1.f));
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
}
FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampling):
@@ -36,8 +85,8 @@ FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampl
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
{
attrib_.viewport = glm::ivec2(width, height);
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
setProjectionArea(glm::vec2(1.f, 1.f));
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
}
void FrameBuffer::init()
@@ -102,6 +151,12 @@ FrameBuffer::~FrameBuffer()
{
if (framebufferid_)
glDeleteFramebuffers(1, &framebufferid_);
if (intermediate_framebufferid_)
glDeleteFramebuffers(1, &intermediate_framebufferid_);
if (textureid_)
glDeleteTextures(1, &textureid_);
if (intermediate_textureid_)
glDeleteTextures(1, &intermediate_textureid_);
}
@@ -121,20 +176,14 @@ float FrameBuffer::aspectRatio() const
std::string FrameBuffer::info() const
{
std::string s = "";
glm::ivec2 p = FrameBuffer::getParametersFromResolution(resolution());
std::ostringstream info;
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
float myratio = aspectRatio();
for(int i= 0; i < num_ar; i++) {
if ( myratio - (FrameBuffer::aspect_ratio_size[i].x / FrameBuffer::aspect_ratio_size[i].y ) < EPSILON)
{
s += std::string( FrameBuffer::aspect_ratio_name[i]) + ", ";
break;
}
}
info << attrib_.viewport.x << "x" << attrib_.viewport.y;
if (p.x > -1)
info << "px, " << FrameBuffer::aspect_ratio_name[p.x];
s += std::to_string(width()) + "x" + std::to_string(height()) + " px";
return s;
return info.str();
}
glm::vec3 FrameBuffer::resolution() const
@@ -142,7 +191,32 @@ glm::vec3 FrameBuffer::resolution() const
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
}
void FrameBuffer::begin()
void FrameBuffer::resize(int width, int height)
{
if (framebufferid_) {
if (attrib_.viewport.x != width || attrib_.viewport.y != height)
{
// de-init
glDeleteFramebuffers(1, &framebufferid_);
framebufferid_ = 0;
if (intermediate_framebufferid_)
glDeleteFramebuffers(1, &intermediate_framebufferid_);
intermediate_framebufferid_ = 0;
if (textureid_)
glDeleteTextures(1, &textureid_);
textureid_ = 0;
if (intermediate_textureid_)
glDeleteTextures(1, &intermediate_textureid_);
intermediate_textureid_ = 0;
// change resolution
attrib_.viewport = glm::ivec2(width, height);
}
}
}
void FrameBuffer::begin(bool clear)
{
if (!framebufferid_)
init();
@@ -151,7 +225,8 @@ void FrameBuffer::begin()
Rendering::manager().pushAttrib(attrib_);
glClear(GL_COLOR_BUFFER_BIT);
if (clear)
glClear(GL_COLOR_BUFFER_BIT);
}
void FrameBuffer::end()
@@ -176,7 +251,7 @@ void FrameBuffer::release()
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void FrameBuffer::readPixels()
void FrameBuffer::readPixels(uint8_t *target_data)
{
if (!framebufferid_)
return;
@@ -191,21 +266,28 @@ void FrameBuffer::readPixels()
else
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, 0);
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, target_data);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
bool FrameBuffer::blit(FrameBuffer *other)
bool FrameBuffer::blit(FrameBuffer *destination)
{
if (!framebufferid_ || !other || !other->framebufferid_)
if (!framebufferid_ || !destination || (use_alpha_ != destination->use_alpha_) )
return false;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, other->framebufferid_);
if (!destination->framebufferid_)
destination->init();
if (use_multi_sampling_)
glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_framebufferid_);
else
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebufferid_);
// blit to the frame buffer object
glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y,
0, 0, other->width(), other->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
0, 0, destination->width(), destination->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
@@ -240,7 +322,7 @@ void FrameBuffer::checkFramebufferStatus()
break;
case GL_FRAMEBUFFER_COMPLETE:
#ifndef NDEBUG
Log::Info("Framebuffer created %d x %d.", width(), height());
g_print("Framebuffer created %d x %d\n", width(), height());
#endif
break;
}
@@ -252,16 +334,142 @@ glm::mat4 FrameBuffer::projection() const
return projection_;
}
glm::vec2 FrameBuffer::projectionArea() const
{
return projection_crop_;
return projection_area_;
}
void FrameBuffer::setProjectionArea(glm::vec2 c)
{
projection_crop_.x = CLAMP(c.x, 0.1f, 1.f);
projection_crop_.y = CLAMP(c.y, 0.1f, 1.f);
projection_ = glm::ortho(-projection_crop_.x, projection_crop_.x, projection_crop_.y, -projection_crop_.y, -1.f, 1.f);
projection_area_.x = CLAMP(c.x, 0.1f, 1.f);
projection_area_.y = CLAMP(c.y, 0.1f, 1.f);
projection_ = glm::ortho(-projection_area_.x, projection_area_.x, projection_area_.y, -projection_area_.y, -1.f, 1.f);
}
FrameBufferImage::FrameBufferImage(int w, int h) :
rgb(nullptr), width(w), height(h)
{
if (width>0 && height>0)
rgb = new uint8_t[width*height*3];
}
FrameBufferImage::FrameBufferImage(jpegBuffer jpgimg) :
rgb(nullptr), width(0), height(0)
{
int c = 0;
if (jpgimg.buffer != nullptr && jpgimg.len >0)
rgb = stbi_load_from_memory(jpgimg.buffer, jpgimg.len, &width, &height, &c, 3);
}
FrameBufferImage::FrameBufferImage(const std::string &filename) :
rgb(nullptr), width(0), height(0)
{
int c = 0;
if (!filename.empty())
rgb = stbi_load(filename.c_str(), &width, &height, &c, 3);
}
FrameBufferImage::~FrameBufferImage()
{
if (rgb!=nullptr)
delete rgb;
}
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg() const
{
jpegBuffer jpgimg;
// if we hold a valid image
if (rgb!=nullptr && width>0 && height>0) {
// allocate JPEG buffer
// (NB: JPEG will need less than this but we can't know before...)
jpgimg.buffer = (unsigned char *) malloc( width * height * 3 * sizeof(unsigned char));
stbi_write_jpg_to_func( [](void *context, void *data, int size)
{
memcpy(((FrameBufferImage::jpegBuffer*)context)->buffer + ((FrameBufferImage::jpegBuffer*)context)->len, data, size);
((FrameBufferImage::jpegBuffer*)context)->len += size;
}
,&jpgimg, width, height, 3, rgb, FBI_JPEG_QUALITY);
}
return jpgimg;
}
FrameBufferImage *FrameBuffer::image(){
FrameBufferImage *img = nullptr;
// not ready
if (!framebufferid_)
return img;
// only compatible for RGB FrameBuffers
if (use_alpha_ || use_multi_sampling_)
return img;
// allocate image
img = new FrameBufferImage(attrib_.viewport.x, attrib_.viewport.y);
// get pixels into image
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // set buffer target readpixel
readPixels(img->rgb);
return img;
}
bool FrameBuffer::fill(FrameBufferImage *image)
{
if (!framebufferid_)
init();
// only compatible for RGB FrameBuffers
if (use_alpha_ || use_multi_sampling_)
return false;
// invalid image
if ( image == nullptr ||
image->rgb==nullptr ||
image->width < 1 ||
image->height < 1 )
return false;
// is it same size ?
if (image->width == attrib_.viewport.x && image->height == attrib_.viewport.y ) {
// directly fill texture with image
glBindTexture(GL_TEXTURE_2D, textureid_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
glBindTexture(GL_TEXTURE_2D, 0);
}
else {
uint textureimage, framebufferimage;
// generate texture
glGenTextures(1, &textureimage);
glBindTexture(GL_TEXTURE_2D, textureimage);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
glBindTexture(GL_TEXTURE_2D, 0);
// create a framebuffer object
glGenFramebuffers(1, &framebufferimage);
glBindFramebuffer(GL_FRAMEBUFFER, framebufferimage);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureimage, 0);
// blit to the frame buffer object with interpolation
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferimage);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferid_);
glBlitFramebuffer(0, 0, image->width, image->height,
0, 0, attrib_.viewport.x, attrib_.viewport.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// cleanup
glDeleteFramebuffers(1, &framebufferimage);
glDeleteTextures(1, &textureimage);
}
return true;
}

View File

@@ -1,35 +1,66 @@
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
#include "Scene.h"
#include "RenderingManager.h"
#define FBI_JPEG_QUALITY 90
/**
* @brief The FrameBufferImage class stores an RGB image in RAM
* Direct access to rgb array, and exchange format to JPEG in RAM
*/
class FrameBufferImage
{
public:
uint8_t *rgb;
int width;
int height;
struct jpegBuffer {
unsigned char *buffer = nullptr;
uint len = 0;
};
jpegBuffer getJpeg() const;
FrameBufferImage(int w, int h);
FrameBufferImage(jpegBuffer jpgimg);
FrameBufferImage(const std::string &filename);
// non assignable class
FrameBufferImage(FrameBufferImage const&) = delete;
FrameBufferImage& operator=(FrameBufferImage const&) = delete;
~FrameBufferImage();
};
/**
* @brief The FrameBuffer class holds an OpenGL Frame Buffer Object.
*/
class FrameBuffer {
public:
// size descriptions
static const char* aspect_ratio_name[5];
static glm::vec2 aspect_ratio_size[5];
static const char* resolution_name[4];
static float resolution_height[4];
static const char* resolution_name[5];
static float resolution_height[5];
static glm::vec3 getResolutionFromParameters(int ar, int h);
static glm::ivec2 getParametersFromResolution(glm::vec3 res);
// unbind any framebuffer object
static void release();
FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(FrameBuffer const&) = delete;
~FrameBuffer();
// Bind & push attribs to prepare draw
void begin();
void begin(bool clear = true);
// pop attrib and unbind to end draw
void end();
// blit copy to another, returns true on success
bool blit(FrameBuffer *other);
bool blit(FrameBuffer *destination);
// bind the FrameBuffer in READ and perform glReadPixels
// return the size of the buffer
void readPixels();
// (to be used after preparing a target PBO)
void readPixels(uint8_t* target_data = 0);
// clear color
inline void setClearColor(glm::vec4 color) { attrib_.clear_color = color; }
@@ -39,31 +70,38 @@ public:
inline uint width() const { return attrib_.viewport.x; }
inline uint height() const { return attrib_.viewport.y; }
glm::vec3 resolution() const;
void resize(int width, int height);
float aspectRatio() const;
std::string info() const;
// projection and crop
// projection area (crop)
glm::mat4 projection() const;
glm::vec2 projectionArea() const;
void setProjectionArea(glm::vec2 c);
// internal pixel format
inline uint opengl_id() const { return framebufferid_; }
inline bool use_alpha() const { return use_alpha_; }
inline bool use_multisampling() const { return use_multi_sampling_; }
// index for texturing
uint texture() const;
// get and fill image
FrameBufferImage *image();
bool fill(FrameBufferImage *image);
private:
void init();
void checkFramebufferStatus();
RenderingAttrib attrib_;
glm::mat4 projection_;
glm::vec2 projection_crop_;
glm::vec2 projection_area_;
uint textureid_, intermediate_textureid_;
uint framebufferid_, intermediate_framebufferid_;
bool use_alpha_, use_multi_sampling_;
};

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
// Desktop OpenGL function loader
@@ -10,13 +29,14 @@
#include "defines.h"
#include "Log.h"
#include "GstToolkit.h"
#include "BaseToolkit.h"
#include "FrameBuffer.h"
#include "FrameGrabber.h"
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(nullptr)
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL)
{
pbo_[0] = 0;
pbo_[1] = 0;
@@ -28,10 +48,10 @@ FrameGrabbing::~FrameGrabbing()
clearAll();
// cleanup
if (caps_!=nullptr)
if (caps_)
gst_caps_unref (caps_);
if (pbo_[0])
glDeleteBuffers(2, pbo_);
// if (pbo_[0] > 0) // automatically deleted at shutdown
// glDeleteBuffers(2, pbo_);
}
void FrameGrabbing::add(FrameGrabber *rec)
@@ -40,12 +60,31 @@ void FrameGrabbing::add(FrameGrabber *rec)
grabbers_.push_back(rec);
}
void FrameGrabbing::chain(FrameGrabber *rec, FrameGrabber *next_rec)
{
if (rec != nullptr && next_rec != nullptr)
{
// add grabber if not yet
if ( std::find(grabbers_.begin(), grabbers_.end(), rec) == grabbers_.end() )
grabbers_.push_back(rec);
grabbers_chain_[next_rec] = rec;
}
}
void FrameGrabbing::verify(FrameGrabber **rec)
{
if ( std::find(grabbers_.begin(), grabbers_.end(), *rec) == grabbers_.end() &&
grabbers_chain_.find(*rec) == grabbers_chain_.end() )
*rec = nullptr;
}
FrameGrabber *FrameGrabbing::front()
{
if (grabbers_.empty())
return nullptr;
else
return grabbers_.front();
return grabbers_.front();
}
struct fgId: public std::unary_function<FrameGrabber*, bool>
@@ -53,7 +92,7 @@ struct fgId: public std::unary_function<FrameGrabber*, bool>
inline bool operator()(const FrameGrabber* elem) const {
return (elem && elem->id() == _id);
}
fgId(uint64_t id) : _id(id) { }
explicit fgId(uint64_t id) : _id(id) { }
private:
uint64_t _id;
};
@@ -73,7 +112,7 @@ FrameGrabber *FrameGrabbing::get(uint64_t id)
void FrameGrabbing::stopAll()
{
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); iter++ )
for (iter=grabbers_.begin(); iter != grabbers_.end(); ++iter )
(*iter)->stop();
}
@@ -84,13 +123,17 @@ void FrameGrabbing::clearAll()
{
FrameGrabber *rec = *iter;
rec->stop();
iter = grabbers_.erase(iter);
delete rec;
if (rec->finished()) {
iter = grabbers_.erase(iter);
delete rec;
}
else
++iter;
}
}
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer)
{
if (frame_buffer == nullptr)
return;
@@ -121,13 +164,12 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
pbo_next_index_ = 0;
// new caps
if (caps_!=nullptr)
if (caps_)
gst_caps_unref (caps_);
caps_ = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
"width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
}
@@ -145,6 +187,7 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
#else
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
// update case ; alternating indices
@@ -179,21 +222,43 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
pbo_index_ = (pbo_index_ + 1) % 2;
// a frame was successfully grabbed
if (buffer != nullptr) {
if ( buffer != nullptr && gst_buffer_get_size(buffer) > 0) {
// give the frame to all recorders
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
while (iter != grabbers_.end())
{
FrameGrabber *rec = *iter;
rec->addFrame(buffer, caps_, dt);
rec->addFrame(buffer, caps_);
// remove finished recorders
if (rec->finished()) {
iter = grabbers_.erase(iter);
delete rec;
}
else
iter++;
++iter;
}
// manage the list of chainned recorder
std::map<FrameGrabber *, FrameGrabber *>::iterator chain = grabbers_chain_.begin();
while (chain != grabbers_chain_.end())
{
// update frame grabber of chain list
chain->first->addFrame(buffer, caps_);
// if the chained recorder is now active
if (chain->first->active_ && chain->first->accept_buffer_){
// add it to main grabbers,
grabbers_.push_back(chain->first);
// stop the replaced grabber
chain->second->stop();
// loop in chain list: done with this chain
chain = grabbers_chain_.erase(chain);
}
else
// loop in chain list
++chain;
}
// unref / free the frame
@@ -206,14 +271,14 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
FrameGrabber::FrameGrabber(): finished_(false), initialized_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr), timer_firstframe_(0),
timestamp_(0), duration_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE), timestamp_on_clock_(false)
{
// unique id
id_ = GlmToolkit::uniqueId();
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
timeframe_ = 2 * frame_duration_;
id_ = BaseToolkit::uniqueId();
// configure default parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // 25 FPS by default
}
FrameGrabber::~FrameGrabber()
@@ -222,8 +287,13 @@ FrameGrabber::~FrameGrabber()
gst_object_unref (src_);
if (caps_ != nullptr)
gst_caps_unref (caps_);
if (timer_)
gst_object_unref (timer_);
if (pipeline_ != nullptr) {
gst_element_set_state (pipeline_, GST_STATE_NULL);
GstState state = GST_STATE_NULL;
gst_element_set_state (pipeline_, state);
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
gst_object_unref (pipeline_);
}
}
@@ -241,24 +311,28 @@ bool FrameGrabber::busy() const
return false;
}
double FrameGrabber::duration() const
uint64_t FrameGrabber::duration() const
{
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
return GST_TIME_AS_MSECONDS(duration_);
}
void FrameGrabber::stop ()
{
// send end of stream
gst_app_src_end_of_stream (src_);
// TODO if not initialized wait for initializer
// stop recording
active_ = false;
// send end of stream
gst_app_src_end_of_stream (src_);
}
std::string FrameGrabber::info() const
{
if (!initialized_)
return "Initializing";
if (active_)
return GstToolkit::time_to_string(timestamp_);
return GstToolkit::time_to_string(duration_);
else
return "Inactive";
}
@@ -275,83 +349,163 @@ void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
if (grabber) {
grabber->accept_buffer_ = false;
#ifndef NDEBUG
Log::Info("Frame capture : Buffer full");
#endif
}
}
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
GstPadProbeReturn FrameGrabber::callback_event_probe(GstPad *, GstPadProbeInfo * info, gpointer p)
{
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
grabber->endofstream_ = true;
}
return GST_PAD_PROBE_OK;
}
std::string FrameGrabber::initialize(FrameGrabber *rec, GstCaps *caps)
{
return rec->init(caps);
}
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps)
{
// ignore
if (buffer == nullptr)
return;
// first time initialization
if (pipeline_ == nullptr)
init(caps);
// cancel if finished
if (finished_)
return;
// stop if an incompatilble frame buffer given
if ( !gst_caps_is_equal( caps_, caps ))
{
stop();
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
Log::Warning("FrameGrabber interrupted because the resolution changed.");
if (pipeline_ == nullptr) {
initializer_ = std::async( FrameGrabber::initialize, this, caps);
}
// store a frame if recording is active
if (active_)
{
// calculate dt in ns
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
// if time is passed one frame duration (with 10% margin)
// and if the encoder accepts data
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
// set timing of buffer
buffer->pts = timestamp_;
buffer->duration = frame_duration_;
// increment ref counter to make sure the frame remains available
gst_buffer_ref(buffer);
// push
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
accept_buffer_ = false;
// next timestamp
timestamp_ += frame_duration_;
// restart frame counter
timeframe_ = 0;
}
}
// did the recording terminate with sink receiving end-of-stream ?
else {
if (!finished_)
// initializer ongoing in separate thread
if (initializer_.valid()) {
// try to get info from initializer
if (initializer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
{
// Wait for EOS message
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
// received EOS
if (msg) {
// stop the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_FAILURE)
Log::Warning("FrameGrabber Could not stop.");
// done initialization
std::string msg = initializer_.get();
// if initialization succeeded
if (initialized_) {
// attach EOS detector
GstPad *pad = gst_element_get_static_pad (gst_bin_get_by_name (GST_BIN (pipeline_), "sink"), "sink");
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, FrameGrabber::callback_event_probe, this, NULL);
gst_object_unref (pad);
// start recording
active_ = true;
// inform
Log::Info("%s", msg.c_str());
}
// else show warning
else {
// end frame grabber
finished_ = true;
// inform
Log::Warning("%s", msg.c_str());
}
}
}
if (finished_)
terminate();
// stop if an incompatilble frame buffer given after initialization
if (initialized_ && !gst_caps_is_subset( caps_, caps ))
{
stop();
Log::Warning("Frame capture interrupted because the resolution changed.");
}
// store a frame if recording is active and if the encoder accepts data
if (active_)
{
if (accept_buffer_) {
GstClockTime t = 0;
// initialize timer on first occurence
if (timer_ == nullptr) {
timer_ = gst_pipeline_get_clock ( GST_PIPELINE(pipeline_) );
timer_firstframe_ = gst_clock_get_time(timer_);
}
else
// time since timer starts (first frame registered)
t = gst_clock_get_time(timer_) - timer_firstframe_;
// if time is zero (first frame) or if delta time is passed one frame duration (with a margin)
if ( t == 0 || (t - duration_) > (frame_duration_ - 3000) ) {
// count frames
frame_count_++;
// set duration to an exact multiples of frame duration
duration_ = ( t / frame_duration_) * frame_duration_;
if (timestamp_on_clock_)
// automatic frame presentation time stamp
// set time to actual time
// & round t to a multiples of frame duration
timestamp_ = duration_;
else {
// monotonic time increment to keep fixed FPS
timestamp_ += frame_duration_;
// force frame presentation time stamp
buffer->pts = timestamp_;
// set frame duration
buffer->duration = frame_duration_;
}
// when buffering is (almost) full, refuse buffer 1 frame over 2
if (buffering_full_)
accept_buffer_ = frame_count_%2;
else
{
// enter buffering_full_ mode if the space left in buffering is for only few frames
// (this prevents filling the buffer entirely)
if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < MIN_BUFFER_SIZE ) {
#ifndef NDEBUG
Log::Info("Frame capture : Using %s of %s Buffer.",
BaseToolkit::byte_to_string(gst_app_src_get_current_level_bytes(src_)).c_str(),
BaseToolkit::byte_to_string(buffering_size_).c_str());
#endif
buffering_full_ = true;
}
}
// increment ref counter to make sure the frame remains available
gst_buffer_ref(buffer);
// push frame
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
}
}
}
// if we received and end of stream (from callback_event_probe)
if (endofstream_)
{
// try to stop properly when interrupted
if (active_) {
// de-activate and re-send EOS
stop();
// inform
Log::Info("Frame capture : Unnexpected EOF signal (no space left on drive? File deleted?)");
Log::Warning("Frame capture : Failed after %s.", GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str());
}
// terminate properly if finished
else
{
terminate();
finished_ = true;
}
}
}

View File

@@ -2,18 +2,21 @@
#define FRAMEGRABBER_H
#include <atomic>
#include <future>
#include <list>
#include <map>
#include <string>
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include "GlmToolkit.h"
// use glReadPixel or glGetTextImage
// read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL
#define DEFAULT_GRABBER_FPS 30
#define MIN_BUFFER_SIZE 33177600 // 33177600 bytes = 1 frames 4K, 9 frames 720p
class FrameBuffer;
@@ -40,36 +43,49 @@ public:
virtual void stop();
virtual std::string info() const;
virtual double duration() const;
virtual uint64_t duration() const;
virtual bool finished() const;
virtual bool busy() const;
protected:
// only FrameGrabbing manager can add frame
virtual void addFrame(GstBuffer *buffer, GstCaps *caps, float dt);
virtual void addFrame(GstBuffer *buffer, GstCaps *caps);
// only addFrame method shall call those
virtual void init(GstCaps *caps) = 0;
virtual std::string init(GstCaps *caps) = 0;
virtual void terminate() = 0;
// thread-safe testing termination
std::atomic<bool> finished_;
std::atomic<bool> initialized_;
std::atomic<bool> active_;
std::atomic<bool> endofstream_;
std::atomic<bool> accept_buffer_;
std::atomic<bool> buffering_full_;
// gstreamer pipeline
GstElement *pipeline_;
GstAppSrc *src_;
GstCaps *caps_;
GstClockTime timeframe_;
GstClock *timer_;
GstClockTime timer_firstframe_;
GstClockTime timestamp_;
GstClockTime duration_;
GstClockTime frame_duration_;
guint64 frame_count_;
guint64 buffering_size_;
bool timestamp_on_clock_;
// async threaded initializer
std::future<std::string> initializer_;
static std::string initialize(FrameGrabber *rec, GstCaps *caps);
// gstreamer callbacks
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
static GstPadProbeReturn callback_event_probe(GstPad *, GstPadProbeInfo *info, gpointer user_data);
};
/**
@@ -80,12 +96,12 @@ protected:
*/
class FrameGrabbing
{
friend class Session;
friend class Mixer;
// Private Constructor
FrameGrabbing();
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
FrameGrabbing(FrameGrabbing const& copy) = delete;
FrameGrabbing& operator=(FrameGrabbing const& copy) = delete;
public:
@@ -101,6 +117,8 @@ public:
inline uint height() const { return height_; }
void add(FrameGrabber *rec);
void chain(FrameGrabber *rec, FrameGrabber *new_rec);
void verify(FrameGrabber **rec);
FrameGrabber *front();
FrameGrabber *get(uint64_t id);
void stopAll();
@@ -109,10 +127,11 @@ public:
protected:
// only for friend Session
void grabFrame(FrameBuffer *frame_buffer, float dt);
void grabFrame(FrameBuffer *frame_buffer);
private:
std::list<FrameGrabber *> grabbers_;
std::map<FrameGrabber *, FrameGrabber *> grabbers_chain_;
guint pbo_[2];
guint pbo_index_;
guint pbo_next_index_;

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - Live video mixer
*
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include "Log.h"
@@ -58,7 +77,7 @@ void GarbageVisitor::visit(Group &n)
// loop over members of a group
// and stop when found
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); ++node) {
// visit the child node
(*node)->accept(*this);
// un-stack recursive browsing
@@ -82,78 +101,3 @@ void GarbageVisitor::visit(Primitive &n)
}
void GarbageVisitor::visit(Surface &n)
{
}
void GarbageVisitor::visit(ImageSurface &n)
{
}
void GarbageVisitor::visit(FrameBufferSurface &n)
{
}
void GarbageVisitor::visit(MediaSurface &n)
{
}
void GarbageVisitor::visit(MediaPlayer &n)
{
}
void GarbageVisitor::visit(Shader &n)
{
}
void GarbageVisitor::visit(ImageShader &n)
{
}
void GarbageVisitor::visit(ImageProcessingShader &n)
{
}
void GarbageVisitor::visit(LineStrip &n)
{
}
void GarbageVisitor::visit(LineSquare &)
{
}
void GarbageVisitor::visit(LineCircle &n)
{
}
void GarbageVisitor::visit(Mesh &n)
{
}
void GarbageVisitor::visit(Frame &n)
{
}
void GarbageVisitor::visit (Source& s)
{
}
void GarbageVisitor::visit (MediaSource& s)
{
}

View File

@@ -24,24 +24,6 @@ public:
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(Surface& n) override;
void visit(ImageSurface& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface& n) override;
void visit(LineStrip& n) override;
void visit(LineSquare&) override;
void visit(LineCircle& n) override;
void visit(Mesh& n) override;
void visit(Frame& n) override;
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (Source& s) override;
void visit (MediaSource& s) override;
};
#endif // GARBAGEVISITOR_H

1148
GeometryView.cpp Normal file

File diff suppressed because it is too large Load Diff

49
GeometryView.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef GEOMETRYVIEW_H
#define GEOMETRYVIEW_H
#include "View.h"
class GeometryView : public View
{
public:
GeometryView();
// non assignable class
GeometryView(GeometryView const&) = delete;
GeometryView& operator=(GeometryView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
bool canSelect(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void terminate() override;
void arrow (glm::vec2) override;
private:
Surface *output_surface_;
Node *overlay_position_;
Node *overlay_position_cross_;
Symbol *overlay_rotation_;
Symbol *overlay_rotation_fix_;
Group *overlay_rotation_clock_;
Symbol *overlay_rotation_clock_tic_;
Node *overlay_rotation_clock_hand_;
Symbol *overlay_scaling_;
Symbol *overlay_scaling_cross_;
Node *overlay_scaling_grid_;
Node *overlay_crop_;
void updateSelectionOverlay() override;
bool overlay_selection_active_;
Group *overlay_selection_stored_status_;
Handles *overlay_selection_scale_;
Handles *overlay_selection_rotate_;
void applySelectionTransform(glm::mat4 M);
};
#endif // GEOMETRYVIEW_H

View File

@@ -1,21 +1,32 @@
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
#include "GlmToolkit.h"
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/random.hpp>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <chrono>
#include <ctime>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtc/random.hpp>
#include "GlmToolkit.h"
uint64_t GlmToolkit::uniqueId()
{
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
// 64-bit int 18446744073709551615UL
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
}
glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale)
{
@@ -27,10 +38,52 @@ glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::
return View * Model;
}
void GlmToolkit::inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale)
{
// extract rotation from modelview
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = M * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
rotation = rot;
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() {
mMin = glm::vec3(1.f);
mMax = glm::vec3(-1.f);
// extract scaling
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * M ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
scale = glm::vec3(vec.x, vec.y, 1.f);
// extract translation
vec = M * glm::vec4(0.f, 0.f, 0.f, 1.f);
translation = glm::vec3(vec);
}
//float rewrapAngleRestricted(float angle)
//// This function takes an angle in the range [-3*pi, 3*pi] and
//// wraps it to the range [-pi, pi].
//{
// if (angle > glm::pi<float>() )
// return angle - glm::two_pi<float>();
// else if (angle < - glm::pi<float>())
// return angle + glm::two_pi<float>();
// else
// return angle;
//}
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() :
mMin(glm::vec3(1.f)), mMax(glm::vec3(-1.f))
{
}
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox(const GlmToolkit::AxisAlignedBoundingBox &D) :
mMin(D.mMin), mMax(D.mMax)
{
}
void GlmToolkit::AxisAlignedBoundingBox::operator = (const GlmToolkit::AxisAlignedBoundingBox &D ) {
mMin = D.mMin;
mMax = D.mMax;
}
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
@@ -48,7 +101,7 @@ void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
void GlmToolkit::AxisAlignedBoundingBox::extend(std::vector<glm::vec3> points)
{
for (auto p = points.begin(); p != points.end(); p++)
for (auto p = points.begin(); p != points.end(); ++p)
extend(*p);
}
@@ -175,10 +228,10 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
glm::vec4 vec;
// Apply transform to all four corners (can be rotated) and update bbox accordingly
vec = m * glm::vec4(mMin, 1.f);
vec = m * glm::vec4(mMin.x, mMin.y, 0.f, 1.f);
bb.extend(glm::vec3(vec));
vec = m * glm::vec4(mMax, 1.f);
vec = m * glm::vec4(mMax.x, mMax.y, 0.f, 1.f);
bb.extend(glm::vec3(vec));
vec = m * glm::vec4(mMin.x, mMax.y, 0.f, 1.f);
@@ -190,6 +243,14 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
return bb;
}
bool GlmToolkit::operator< (const GlmToolkit::AxisAlignedBoundingBox& A, const GlmToolkit::AxisAlignedBoundingBox& B )
{
if (A.isNull())
return true;
if (B.isNull())
return false;
return ( glm::length2(A.mMax-A.mMin) < glm::length2(B.mMax-B.mMin) );
}
glm::ivec2 GlmToolkit::resolutionFromDescription(int aspectratio, int height)
{

View File

@@ -1,31 +1,22 @@
#ifndef GLMTOOLKIT_H
#define GLMTOOLKIT_H
#include <vector>
#include <glm/glm.hpp>
namespace GlmToolkit
{
// get integer with unique id
uint64_t uniqueId();
// get Matrix for these transformation components
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
void inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale);
class AxisAlignedBoundingBox
{
glm::vec3 mMin;
glm::vec3 mMax;
public:
AxisAlignedBoundingBox();
void operator = (const AxisAlignedBoundingBox &D ) {
mMin = D.mMin;
mMax = D.mMax;
}
AxisAlignedBoundingBox(const AxisAlignedBoundingBox &D);
void operator = (const AxisAlignedBoundingBox &D );
// test
inline bool isNull() const { return mMin.x > mMax.x || mMin.y > mMax.y || mMin.z > mMax.z;}
@@ -45,8 +36,28 @@ public:
AxisAlignedBoundingBox translated(glm::vec3 t) const;
AxisAlignedBoundingBox scaled(glm::vec3 s) const;
AxisAlignedBoundingBox transformed(glm::mat4 m) const;
friend bool operator<(const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
protected:
glm::vec3 mMin;
glm::vec3 mMax;
};
// a bbox A is < from bbox B if its diagonal is shorter
bool operator< (const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
class OrientedBoundingBox
{
public:
OrientedBoundingBox() : orientation(glm::vec3(0.f,0.f,0.f)) {}
AxisAlignedBoundingBox aabb;
glm::vec3 orientation;
};
static const char* aspect_ratio_names[6] = { "1:1", "4:3", "3:2", "16:10", "16:9", "21:9" };
static const char* height_names[10] = { "16", "64", "200", "320", "480", "576", "720p", "1080p", "1440", "4K" };

View File

@@ -1,7 +1,28 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <sstream>
#include <iomanip>
using namespace std;
#include <gst/gl/gl.h>
#include "GstToolkit.h"
string GstToolkit::time_to_string(guint64 t, time_string_mode m)
@@ -12,6 +33,8 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
return "00:00:00.00";
case TIME_STRING_MINIMAL:
return "0.0";
case TIME_STRING_READABLE:
return "0 second";
default:
return "00.00";
}
@@ -21,23 +44,52 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
guint s = ms / 1000;
ostringstream oss;
// MINIMAL: keep only the 2 higher values (most significant)
if (m == TIME_STRING_MINIMAL) {
// READABLE : long format
if (m == TIME_STRING_READABLE) {
int count = 0;
if (s / 3600) {
oss << s / 3600 << ':';
oss << s / 3600 << " h ";
count++;
}
if ((s % 3600) / 60) {
oss << (s % 3600) / 60 << ':';
oss << (s % 3600) / 60 << " min ";
count++;
}
if (count < 2) {
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
count++;
if (count < 2 )
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 100 << " sec";
else
oss << " s";
}
if (count < 2 )
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 10;
}
// MINIMAL: keep only the 2 higher values (most significant)
else if (m == TIME_STRING_MINIMAL) {
int count = 0;
// hours
if (s / 3600) {
oss << s / 3600 << ':';
count++;
}
// minutes
if (count > 0) {
oss << setw(2) << setfill('0') << (s % 3600) / 60 << ':';
count++;
}
else if ((s % 3600) / 60)
{
oss << (s % 3600) / 60 << ':';
count++;
}
// seconds
{
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
count++;
}
if (count < 2)
oss << '.'<< setw((ms % 1000) / 100 ? 2 : 1) << setfill('0') << (ms % 1000) / 10;
}
else {
// TIME_STRING_FIXED : fixed length string (11 chars) HH:mm:ss.ii"
@@ -54,6 +106,18 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
}
std::string GstToolkit::filename_to_uri(std::string path)
{
if (path.empty())
return path;
// set uri to open
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
std::string uri( uritmp );
g_free(uritmp);
return uri;
}
list<string> GstToolkit::all_plugins()
{
list<string> pluginlist;
@@ -107,6 +171,31 @@ bool GstToolkit::enable_feature (string name, bool enable) {
}
gst_registry_add_feature (registry, GST_PLUGIN_FEATURE (factory));
gst_object_unref (factory);
return true;
}
bool GstToolkit::has_feature (string name)
{
if (name.empty())
return false;
GstRegistry *registry = NULL;
GstElementFactory *factory = NULL;
registry = gst_registry_get();
if (!registry) return false;
factory = gst_element_factory_find (name.c_str());
if (!factory) return false;
GstElement *elem = gst_element_factory_create (factory, NULL);
gst_object_unref (factory);
if (!elem) return false;
gst_object_unref (elem);
return true;
}
@@ -121,3 +210,99 @@ string GstToolkit::gst_version()
return oss.str();
}
#if GST_GL_HAVE_PLATFORM_GLX
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
// list ordered with higher priority first (e.g. nvidia proprietary before vaapi)
const char *plugins[11] = { "nvh264dec", "nvh265dec", "nvmpeg2videodec", "nvmpeg4videodec", "nvvp8dec", "nvvp9dec",
"vaapidecodebin", "omxmpeg4videodec", "omxmpeg2dec", "omxh264dec", "vdpaumpegdec",
};
const int N = 11;
#elif GST_GL_HAVE_PLATFORM_CGL
const char *plugins[2] = { "vtdec_hw", "vtdechw" };
const int N = 2;
#else
const char *plugins[0] = { };
const int N = 0;
#endif
// see https://developer.ridgerun.com/wiki/index.php?title=GStreamer_modify_the_elements_rank
std::list<std::string> GstToolkit::enable_gpu_decoding_plugins(bool enable)
{
list<string> plugins_list_;
static GstRegistry* plugins_register = nullptr;
if ( plugins_register == nullptr )
plugins_register = gst_registry_get();
static bool enabled_ = false;
if (enabled_ != enable) {
enabled_ = enable;
for (int i = 0; i < N; i++) {
GstPluginFeature* feature = gst_registry_lookup_feature(plugins_register, plugins[i]);
if(feature != NULL) {
plugins_list_.push_front( string( plugins[i] ) );
gst_plugin_feature_set_rank(feature, enable ? GST_RANK_PRIMARY + (N-i) : GST_RANK_MARGINAL);
gst_object_unref(feature);
}
}
}
return plugins_list_;
}
std::string GstToolkit::used_gpu_decoding_plugins(GstElement *gstbin)
{
std::string found = "";
GstIterator* it = gst_bin_iterate_recurse(GST_BIN(gstbin));
GValue value = G_VALUE_INIT;
for(GstIteratorResult r = gst_iterator_next(it, &value); r != GST_ITERATOR_DONE; r = gst_iterator_next(it, &value))
{
if ( r == GST_ITERATOR_OK )
{
GstElement *e = static_cast<GstElement*>(g_value_peek_pointer(&value));
if (e) {
const gchar *name = gst_element_get_name(e);
for (int i = 0; i < N; i++) {
if (std::string(name).find(plugins[i]) != std::string::npos) {
found = plugins[i];
break;
}
}
}
}
g_value_unset(&value);
}
gst_iterator_free(it);
return found;
}
std::string GstToolkit::used_decoding_plugins(GstElement *gstbin)
{
std::string found = "";
GstIterator* it = gst_bin_iterate_recurse(GST_BIN(gstbin));
GValue value = G_VALUE_INIT;
for(GstIteratorResult r = gst_iterator_next(it, &value); r != GST_ITERATOR_DONE; r = gst_iterator_next(it, &value))
{
if ( r == GST_ITERATOR_OK )
{
GstElement *e = static_cast<GstElement*>(g_value_peek_pointer(&value));
if (e) {
const gchar *name = gst_element_get_name(e);
found += std::string(name) + ", ";
}
}
g_value_unset(&value);
}
gst_iterator_free(it);
return found;
}

View File

@@ -12,15 +12,22 @@ namespace GstToolkit
typedef enum {
TIME_STRING_FIXED = 0,
TIME_STRING_ADJUSTED,
TIME_STRING_MINIMAL
TIME_STRING_MINIMAL,
TIME_STRING_READABLE
} time_string_mode;
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
std::string filename_to_uri(std::string filename);
std::string gst_version();
std::list<std::string> all_plugins();
std::list<std::string> all_plugin_features(std::string pluginname);
std::list<std::string> all_plugins();
std::list<std::string> enable_gpu_decoding_plugins(bool enable = true);
std::string used_gpu_decoding_plugins(GstElement *gstbin);
std::string used_decoding_plugins(GstElement *gstbin);
std::list<std::string> all_plugin_features(std::string pluginname);
bool has_feature (std::string name);
bool enable_feature (std::string name, bool enable);
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,33 +3,56 @@
#include <glib.h>
#include <string>
#include <list>
#include <vector>
#include <utility>
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui_internal.h"
#include "rsc/fonts/IconsFontAwesome5.h"
namespace ImGuiToolkit
{
// Icons from resource icon.dds
void Icon(int i, int j);
void Icon (int i, int j, bool enabled = true);
bool IconButton (int i, int j, const char *tooltips = nullptr);
bool IconButton (const char* icon, const char *tooltips = nullptr);
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
void ShowIconsWindow(bool* p_open);
// utility buttons
// buttons and gui items with icon
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state);
bool ButtonToggle(const char* label, bool* toggle);
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltip = nullptr);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state, const char* tooltip = nullptr);
bool MenuItemIcon (int i, int j, const char* label, bool selected = false, bool enabled = true);
bool SelectableIcon(const char* label, int i, int j, bool selected = false);
bool ComboIcon (std::vector<std::pair<int, int> > icons, std::vector<std::string> labels, int* state);
bool ComboIcon (const char* label, std::vector<std::pair<int, int> > icons, std::vector<std::string> items, int* i);
void HelpMarker (const char* desc);
// buttons
bool ButtonToggle (const char* label, bool* toggle, const char *tooltip = nullptr);
bool ButtonSwitch (const char* label, bool* toggle, const char *shortcut = nullptr);
void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0));
// utility sliders
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
// tooltip and mouse over help
void setToolTipsEnabled (bool on);
bool toolTipsEnabled ();
void ToolTip (const char* desc, const char* shortcut = nullptr);
void HelpToolTip(const char* desc, const char* shortcut = nullptr);
void Indication (const char* desc, const char* icon, const char* shortcut = nullptr);
void Indication (const char* desc, int i, int j, const char* shortcut = nullptr);
// sliders
bool SliderTiming (const char* label, uint *ms, uint v_min, uint v_max, uint v_step, const char* text_max = nullptr);
bool TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width, double tempo = 0, double quantum = 0);
void RenderTimeline (struct ImGuiWindow* window, struct ImRect timeline_bbox, guint64 begin, guint64 end, guint64 step, bool verticalflip = false);
void RenderTimelineBPM (struct ImGuiWindow* window, struct ImRect timeline_bbox, double tempo, double quantum, guint64 begin, guint64 end, guint64 step, bool verticalflip = false);
bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, guint64 begin, guint64 end, bool cut, bool *released, const ImVec2 size);
void ShowPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size);
// fonts from ressources 'fonts/'
typedef enum {
@@ -41,22 +64,30 @@ namespace ImGuiToolkit
} font_style;
void SetFont (font_style type, const std::string &ttf_font_name, int pointsize, int oversample = 2);
void PushFont (font_style type);
void ImageGlyph(font_style type, char c, float h = 60);
void Spacing();
void WindowText(const char* window_name, ImVec2 window_pos, const char* text);
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
// text input
bool InputText(const char* label, std::string* str);
bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0));
void TextMultiline(const char* label, const std::string &str, float width);
bool InputCodeMultiline(const char* label, std::string *str, const ImVec2& size = ImVec2(0, 0), int *numline = NULL);
void CodeMultiline(const char* label, const std::string &str, float width);
// color of gui items
// accent color of UI
typedef enum {
ACCENT_BLUE =0,
ACCENT_ORANGE,
ACCENT_GREY
} accent_color;
void SetAccentColor (accent_color color);
struct ImVec4 GetHighlightColor ();
struct ImVec4 HighlightColor (bool active = true);
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
// varia
void WindowText(const char* window_name, ImVec2 window_pos, const char* text);
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,34 +2,38 @@
#define IMGUIVISITOR_H
#include "Visitor.h"
#include "InfoVisitor.h"
class ImGuiVisitor: public Visitor
{
InfoVisitor info;
public:
ImGuiVisitor();
// Elements of Scene
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Group& n) override;
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface& n) override;
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
void visit (FrameBufferSurface& n) override;
// Elements with attributes
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (MediaPlayer& n) override;
void visit (Shader& n) override;
void visit (ImageProcessingShader& n) override;
void visit (Source& s) override;
void visit (MediaSource& s) override;
void visit (SessionSource& s) override;
void visit (SessionFileSource& s) override;
void visit (SessionGroupSource& s) override;
void visit (RenderSource& s) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
void visit (GenericStreamSource& s) override;
};
#endif // IMGUIVISITOR_H

View File

@@ -1,6 +1,26 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "defines.h"
#include "Visitor.h"
#include "Log.h"
#include "ImageProcessingShader.h"
ShadingProgram imageProcessingShadingProgram("shaders/image.vs", "shaders/imageprocessing.fs");
@@ -12,26 +32,7 @@ const char* ImageProcessingShader::filter_names[12] = { "None", "Blur", "Sharpen
ImageProcessingShader::ImageProcessingShader(): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
}
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
brightness = S.brightness;
contrast = S.contrast;
saturation = S.saturation;
hueshift = S.hueshift;
threshold = S.threshold;
lumakey = S.lumakey;
nbColors = S.nbColors;
invert = S.invert;
filterid = S.filterid;
gamma = S.gamma;
levels = S.levels;
chromakey = S.chromakey;
chromadelta = S.chromadelta;
ImageProcessingShader::reset();
}
void ImageProcessingShader::use()
@@ -56,7 +57,6 @@ void ImageProcessingShader::use()
}
void ImageProcessingShader::reset()
{
Shader::reset();
@@ -78,10 +78,8 @@ void ImageProcessingShader::reset()
}
void ImageProcessingShader::operator = (const ImageProcessingShader &S )
void ImageProcessingShader::copy(ImageProcessingShader const& S)
{
Shader::operator =(S);
brightness = S.brightness;
contrast = S.contrast;
saturation = S.saturation;

View File

@@ -11,13 +11,12 @@ class ImageProcessingShader : public Shader
public:
ImageProcessingShader();
ImageProcessingShader(const ImageProcessingShader &model);
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const ImageProcessingShader &S);
void copy(ImageProcessingShader const& S);
// color effects
float brightness; // [-1 1]

View File

@@ -1,71 +1,85 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glad/glad.h>
#include "defines.h"
#include "Visitor.h"
#include "ImageShader.h"
#include "Resource.h"
#include "rsc/fonts/IconsFontAwesome5.h"
static ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
#include "ImageShader.h"
const char* ImageShader::mask_names[11] = { "None", "Glow", "Halo", "Circle", "Round", "Vignette", "Top", "Botton", "Left", "Right", "Custom" };
std::vector< uint > ImageShader::mask_presets;
ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
ShadingProgram imageAlphaProgram ("shaders/image.vs", "shaders/imageblending.fs");
std::vector< ShadingProgram > maskPrograms = {
ShadingProgram("shaders/simple.vs", "shaders/simple.fs"),
ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs")
};
ImageShader::ImageShader(): Shader(), mask(0), custom_textureindex(0), stipple(0.0)
const char* MaskShader::mask_names[3] = { ICON_FA_EXPAND, ICON_FA_EDIT, ICON_FA_SHAPES };
const char* MaskShader::mask_shapes[5] = { "Elipse", "Oblong", "Rectangle", "Horizontal", "Vertical" };
ImageShader::ImageShader(): Shader(), stipple(0.f), mask_texture(0)
{
// first initialization
if ( mask_presets.empty() ) {
mask_presets.push_back(Resource::getTextureWhite());
mask_presets.push_back(Resource::getTextureImage("images/mask_glow.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_halo.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_circle.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_roundcorner.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_vignette.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_top.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_bottom.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_left.png"));
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_right.png"));
}
// static program shader
program_ = &imageShadingProgram;
// reset instance
reset();
ImageShader::reset();
}
void ImageShader::use()
{
Shader::use();
// set stippling
program_->setUniform("stipple", stipple);
// default mask
if (mask_texture == 0)
mask_texture = Resource::getTextureWhite();
// setup mask texture
glActiveTexture(GL_TEXTURE1);
if ( mask < 10 )
glBindTexture(GL_TEXTURE_2D, mask_presets[mask]);
else
glBindTexture(GL_TEXTURE_2D, custom_textureindex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture (GL_TEXTURE_2D, mask_texture);
glActiveTexture(GL_TEXTURE0);
}
void ImageShader::reset()
{
Shader::reset();
mask_texture = 0;
// default mask
mask = 0;
custom_textureindex = mask_presets[0];
// no stippling
stipple = 0.f;
}
void ImageShader::operator = (const ImageShader &S )
void ImageShader::copy(ImageShader const& S)
{
Shader::operator =(S);
mask = S.mask;
custom_textureindex = S.custom_textureindex;
mask_texture = S.mask_texture;
stipple = S.stipple;
}
@@ -74,3 +88,80 @@ void ImageShader::accept(Visitor& v) {
Shader::accept(v);
v.visit(*this);
}
AlphaShader::AlphaShader(): ImageShader()
{
// to inverse alpha mode, use dedicated shading program
program_ = &imageAlphaProgram;
// reset instance
reset();
blending = Shader::BLEND_NONE;
}
MaskShader::MaskShader(): Shader(), mode(0)
{
// reset instance
MaskShader::reset();
// static program shader
program_ = &maskPrograms[0];
}
void MaskShader::use()
{
// select program to use
mode = MINI(mode, 2);
shape = MINI(shape, 4);
program_ = mode < 2 ? &maskPrograms[mode] : &maskPrograms[shape+2] ;
// actual use of shader program
Shader::use();
// shape parameters
size = shape < HORIZONTAL ? glm::max(glm::abs(size), glm::vec2(0.2)) : size;
program_->setUniform("size", size);
program_->setUniform("blur", blur);
// brush parameters
program_->setUniform("cursor", cursor);
program_->setUniform("brush", brush);
program_->setUniform("option", option);
program_->setUniform("effect", effect);
}
void MaskShader::reset()
{
Shader::reset();
// default mask
mode = 0;
// default shape
shape = 0;
blur = 0.5f;
size = glm::vec2(1.f, 1.f);
// default brush
cursor = glm::vec4(-10.f, -10.f, 1.f, 1.f);
brush = glm::vec3(0.5f, 0.1f, 0.f);
option = 0;
effect = 0;
}
void MaskShader::copy(MaskShader const& S)
{
mode = S.mode;
shape = S.shape;
blur = S.blur;
size = S.size;
}
void MaskShader::accept(Visitor& v) {
Shader::accept(v);
v.visit(*this);
}

View File

@@ -1,4 +1,4 @@
#ifndef IMAGESHADER_H
#ifndef IMAGESHADER_H
#define IMAGESHADER_H
#include <string>
@@ -14,21 +14,66 @@ class ImageShader : public Shader
{
public:
ImageShader();
void use() override;
void reset() override;
void accept(Visitor& v) override;
void copy(ImageShader const& S);
void operator = (const ImageShader &S);
uint mask_texture;
uint mask;
uint custom_textureindex;
// uniforms
float stipple;
};
static const char* mask_names[11];
static std::vector< uint > mask_presets;
class AlphaShader : public ImageShader
{
public:
AlphaShader();
};
class MaskShader : public Shader
{
public:
MaskShader();
void use() override;
void reset() override;
void accept(Visitor& v) override;
void copy(MaskShader const& S);
enum Modes {
NONE = 0,
PAINT = 1,
SHAPE = 2
};
uint mode;
enum Shapes {
ELIPSE = 0,
OBLONG = 1,
RECTANGLE = 2,
HORIZONTAL = 3,
VERTICAL = 4
};
uint shape;
// uniforms
glm::vec2 size;
float blur;
int option;
int effect;
glm::vec4 cursor;
glm::vec3 brush;
static const char* mask_names[3];
static const char* mask_shapes[5];
};
#endif // IMAGESHADER_H

298
InfoVisitor.cpp Normal file
View File

@@ -0,0 +1,298 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <vector>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <tinyxml2.h>
#include "tinyxml2Toolkit.h"
#include "defines.h"
#include "Log.h"
#include "Scene.h"
#include "Primitives.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
#include "MediaSource.h"
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "MultiFileSource.h"
#include "SessionCreator.h"
#include "SessionVisitor.h"
#include "Settings.h"
#include "Mixer.h"
#include "ActionManager.h"
#include "BaseToolkit.h"
#include "UserInterfaceManager.h"
#include "SystemToolkit.h"
#include "InfoVisitor.h"
InfoVisitor::InfoVisitor() : brief_(true), current_id_(0)
{
}
void InfoVisitor::visit(Node &)
{
}
void InfoVisitor::visit(Group &)
{
}
void InfoVisitor::visit(Switch &)
{
}
void InfoVisitor::visit(Scene &)
{
}
void InfoVisitor::visit(Primitive &)
{
}
void InfoVisitor::visit(MediaPlayer &mp)
{
// do not ask twice
if (current_id_ == mp.id())
return;
std::ostringstream oss;
if (brief_) {
oss << SystemToolkit::filename(mp.filename()) << std::endl;
oss << mp.width() << " x " << mp.height() << ", ";
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,"));
if (!mp.isImage())
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
}
else {
oss << mp.filename() << std::endl;
oss << mp.media().codec_name << std::endl;
oss << mp.width() << " x " << mp.height() ;
if (!mp.isImage())
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
}
information_ = oss.str();
// remember (except if codec was not identified yet)
if ( !mp.media().codec_name.empty() )
current_id_ = mp.id();
}
void InfoVisitor::visit(Stream &n)
{
std::ostringstream oss;
if (brief_) {
oss << BaseToolkit::splitted(n.description(), '!').front();
}
else {
oss << n.description();
}
information_ = oss.str();
}
void InfoVisitor::visit (MediaSource& s)
{
s.mediaplayer()->accept(*this);
}
void InfoVisitor::visit (SessionFileSource& s)
{
if (current_id_ == s.id() || s.session() == nullptr)
return;
std::ostringstream oss;
if (brief_) {
oss << SystemToolkit::filename(s.path()) << " (";
oss << s.session()->numSource() << " sources)" << std::endl;
}
else
oss << s.path() << std::endl;
if (s.session()->frame()){
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
oss << "RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (SessionGroupSource& s)
{
if (current_id_ == s.id() || s.session() == nullptr)
return;
std::ostringstream oss;
oss << s.session()->numSource() << " sources in group" << std::endl;
if (s.session()->frame()){
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
oss << "RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (RenderSource& s)
{
if (current_id_ == s.id())
return;
information_ = "Rendering Output";
current_id_ = s.id();
}
void InfoVisitor::visit (CloneSource& s)
{
if (current_id_ == s.id())
return;
information_ = "Clone of " + s.origin()->name();
current_id_ = s.id();
}
void InfoVisitor::visit (PatternSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
oss << Pattern::get(s.pattern()->type()).label << std::endl;
if (s.pattern()) {
oss << s.pattern()->width() << " x " << s.pattern()->height();
oss << ", RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (DeviceSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
if ( !confs.empty()) {
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
if (brief_) {
oss << best.width << " x " << best.height << ", ";
oss << best.stream << " " << best.format << ", ";
oss << std::fixed << std::setprecision(1) << fps << " fps";
}
else {
oss << s.device() << std::endl;
oss << best.width << " x " << best.height << ", ";
oss << best.stream << " " << best.format << ", ";
oss << std::fixed << std::setprecision(1) << fps << " fps";
}
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (NetworkSource& s)
{
if (current_id_ == s.id())
return;
NetworkStream *ns = s.networkStream();
std::ostringstream oss;
if (brief_) {
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
oss << NetworkToolkit::protocol_name[ns->protocol()] << std::endl;
oss << "IP " << ns->serverAddress();
}
else {
oss << s.connection() << " (IP " << ns->serverAddress() << ")" << std::endl;
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
oss << NetworkToolkit::protocol_name[ns->protocol()];
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (MultiFileSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
if (brief_) {
oss << s.sequence().width << " x " << s.sequence().height << ", ";
oss << s.sequence().codec << std::endl;
oss << s.sequence().max - s.sequence().min + 1 << " images [";
oss << s.sequence().min << " - " << s.sequence().max << "]";
}
else {
oss << s.sequence().location << " [";
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
oss << s.sequence().width << " x " << s.sequence().height << ", ";
oss << s.sequence().codec << " (";
oss << s.sequence().max - s.sequence().min + 1 << " images)";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (GenericStreamSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
if (s.stream()) {
std::string src_element = s.gstElements().front();
src_element = src_element.substr(0, src_element.find(" "));
oss << "gstreamer '" << src_element << "'" << std::endl;
oss << s.stream()->width() << " x " << s.stream()->height();
oss << ", RGB";
}
else
oss << "Undefined";
information_ = oss.str();
current_id_ = s.id();
}

41
InfoVisitor.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef INFOVISITOR_H
#define INFOVISITOR_H
#include "Visitor.h"
class InfoVisitor : public Visitor
{
std::string information_;
bool brief_;
uint64_t current_id_;
public:
InfoVisitor();
inline void setBriefStringMode () { brief_ = true; current_id_ = 0; }
inline void setExtendedStringMode () { brief_ = false; current_id_ = 0; }
inline void reset () { current_id_ = 0; }
inline std::string str () const { return information_; }
// Elements of Scene
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
// Elements with attributes
void visit (Stream& n) override;
void visit (MediaPlayer& n) override;
void visit (MediaSource& s) override;
void visit (SessionFileSource& s) override;
void visit (SessionGroupSource& s) override;
void visit (RenderSource& s) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
void visit (GenericStreamSource& s) override;
};
#endif // INFOVISITOR_H

189
Interpolator.cpp Normal file
View File

@@ -0,0 +1,189 @@
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "defines.h"
#include "Log.h"
#include "Source.h"
#include "ImageProcessingShader.h"
#include "UpdateCallback.h"
#include "Interpolator.h"
SourceInterpolator::SourceInterpolator(Source *subject, const SourceCore &target) :
subject_(subject), from_(static_cast<SourceCore>(*subject)), to_(target), current_cursor_(0.f)
{
}
void SourceInterpolator::interpolateGroup(View::Mode m)
{
current_state_.group(m)->translation_ =
(1.f - current_cursor_) * from_.group(m)->translation_
+ current_cursor_ * to_.group(m)->translation_;
current_state_.group(m)->scale_ =
(1.f - current_cursor_) * from_.group(m)->scale_
+ current_cursor_ * to_.group(m)->scale_;
current_state_.group(m)->rotation_ =
(1.f - current_cursor_) * from_.group(m)->rotation_
+ current_cursor_ * to_.group(m)->rotation_;
current_state_.group(m)->crop_ =
(1.f - current_cursor_) * from_.group(m)->crop_
+ current_cursor_ * to_.group(m)->crop_;
CopyCallback *anim = new CopyCallback( current_state_.group(m) );
subject_->group(m)->update_callbacks_.clear();
subject_->group(m)->update_callbacks_.push_back(anim);
}
void SourceInterpolator::interpolateImageProcessing()
{
current_state_.processingShader()->brightness =
(1.f - current_cursor_) * from_.processingShader()->brightness
+ current_cursor_ * to_.processingShader()->brightness;
current_state_.processingShader()->contrast =
(1.f - current_cursor_) * from_.processingShader()->contrast
+ current_cursor_ * to_.processingShader()->contrast;
current_state_.processingShader()->saturation =
(1.f - current_cursor_) * from_.processingShader()->saturation
+ current_cursor_ * to_.processingShader()->saturation;
current_state_.processingShader()->hueshift =
(1.f - current_cursor_) * from_.processingShader()->hueshift
+ current_cursor_ * to_.processingShader()->hueshift;
current_state_.processingShader()->threshold =
(1.f - current_cursor_) * from_.processingShader()->threshold
+ current_cursor_ * to_.processingShader()->threshold;
current_state_.processingShader()->lumakey =
(1.f - current_cursor_) * from_.processingShader()->lumakey
+ current_cursor_ * to_.processingShader()->lumakey;
current_state_.processingShader()->nbColors =
(1.f - current_cursor_) * from_.processingShader()->nbColors
+ current_cursor_ * to_.processingShader()->nbColors;
current_state_.processingShader()->gamma =
(1.f - current_cursor_) * from_.processingShader()->gamma
+ current_cursor_ * to_.processingShader()->gamma;
current_state_.processingShader()->levels =
(1.f - current_cursor_) * from_.processingShader()->levels
+ current_cursor_ * to_.processingShader()->levels;
current_state_.processingShader()->chromakey =
(1.f - current_cursor_) * from_.processingShader()->chromakey
+ current_cursor_ * to_.processingShader()->chromakey;
current_state_.processingShader()->chromadelta =
(1.f - current_cursor_) * from_.processingShader()->chromadelta
+ current_cursor_ * to_.processingShader()->chromadelta;
subject_->processingShader()->copy( *current_state_.processingShader() );
// not interpolated : invert , filterid
}
float SourceInterpolator::current() const
{
return current_cursor_;
}
void SourceInterpolator::apply(float percent)
{
percent = CLAMP( percent, 0.f, 1.f);
if ( subject_ && ABS_DIFF(current_cursor_, percent) > EPSILON)
{
current_cursor_ = percent;
if (current_cursor_ < EPSILON) {
current_cursor_ = 0.f;
current_state_ = from_;
subject_->copy(current_state_);
}
else if (current_cursor_ > 1.f - EPSILON) {
current_cursor_ = 1.f;
current_state_ = to_;
subject_->copy(current_state_);
}
else {
interpolateGroup(View::MIXING);
interpolateGroup(View::GEOMETRY);
interpolateGroup(View::LAYER);
interpolateGroup(View::TEXTURE);
interpolateImageProcessing();
// Log::Info("SourceInterpolator::update %f", cursor);
}
subject_->touch();
}
}
Interpolator::Interpolator()
{
}
Interpolator::~Interpolator()
{
clear();
}
void Interpolator::clear()
{
for (auto i = interpolators_.begin(); i != interpolators_.end(); ) {
delete *i;
i = interpolators_.erase(i);
}
}
void Interpolator::add (Source *s, const SourceCore &target)
{
SourceInterpolator *i = new SourceInterpolator(s, target);
interpolators_.push_back(i);
}
float Interpolator::current() const
{
float ret = 0.f;
if (interpolators_.size() > 0)
ret = interpolators_.front()->current();
return ret;
}
void Interpolator::apply(float percent)
{
for (auto i = interpolators_.begin(); i != interpolators_.end(); ++i)
(*i)->apply( percent );
}

44
Interpolator.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef INTERPOLATOR_H
#define INTERPOLATOR_H
#include "Source.h"
#include "SourceList.h"
class SourceInterpolator
{
public:
SourceInterpolator(Source *subject, const SourceCore &target);
void apply (float percent);
float current() const;
protected:
Source *subject_;
SourceCore from_;
SourceCore to_;
SourceCore current_state_;
float current_cursor_;
void interpolateGroup (View::Mode m);
void interpolateImageProcessing ();
};
class Interpolator
{
public:
Interpolator();
~Interpolator();
void clear ();
void add (Source *s, const SourceCore &target );
void apply (float percent);
float current() const;
protected:
std::list<SourceInterpolator *> interpolators_;
};
#endif // INTERPOLATOR_H

410
LayerView.cpp Normal file
View File

@@ -0,0 +1,410 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "ImGuiToolkit.h"
#include <string>
#include <sstream>
#include <iomanip>
#include "Mixer.h"
#include "defines.h"
#include "Source.h"
#include "Settings.h"
#include "Decorations.h"
#include "UserInterfaceManager.h"
#include "BoundingBoxVisitor.h"
#include "ActionManager.h"
#include "Log.h"
#include "LayerView.h"
LayerView::LayerView() : View(LAYER), aspect_ratio(1.f)
{
scene.root()->scale_ = glm::vec3(LAYER_DEFAULT_SCALE, LAYER_DEFAULT_SCALE, 1.0f);
scene.root()->translation_ = glm::vec3(2.2f, 1.2f, 0.0f);
// read default settings
if ( Settings::application.views[mode_].name.empty() ) {
// no settings found: store application default
Settings::application.views[mode_].name = "Layer";
saveSettings();
}
else {
restoreSettings();
}
// Geometry Scene background
frame_ = new Group;
Surface *rect = new Surface;
rect->shader()->color.a = 0.3f;
frame_->attach(rect);
Frame *border = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
border->color = glm::vec4( COLOR_FRAME, 0.95f );
frame_->attach(border);
scene.bg()->attach(frame_);
persp_left_ = new Mesh("mesh/perspective_axis_left.ply");
persp_left_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
persp_left_->scale_.x = LAYER_PERSPECTIVE;
persp_left_->translation_.z = -0.1f;
persp_left_->translation_.x = -1.f;
scene.bg()->attach(persp_left_);
persp_right_ = new Mesh("mesh/perspective_axis_right.ply");
persp_right_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
persp_right_->scale_.x = LAYER_PERSPECTIVE;
persp_right_->translation_.z = -0.1f;
persp_right_->translation_.x = 1.f;
scene.bg()->attach(persp_right_);
}
void LayerView::draw()
{
View::draw();
// initialize the verification of the selection
static bool candidate_flatten_group = false;
// display popup menu
if (show_context_menu_ == MENU_SELECTION) {
// initialize the verification of the selection
candidate_flatten_group = true;
// start loop on selection
SourceList::iterator it = Mixer::selection().begin();
float depth_first = (*it)->depth();
for (; it != Mixer::selection().end(); ++it) {
// test if selection is contiguous in layer (i.e. not interrupted)
SourceList::iterator inter = Mixer::manager().session()->find(depth_first, (*it)->depth());
if ( inter != Mixer::manager().session()->end() && !Mixer::selection().contains(*inter)){
// NOT a group: there is a source in the session that
// - is between two selected sources (in depth)
// - is not part of the selection
candidate_flatten_group = false;
break;
}
}
ImGui::OpenPopup( "LayerSelectionContextMenu" );
show_context_menu_ = MENU_NONE;
}
if (ImGui::BeginPopup("LayerSelectionContextMenu")) {
// colored context menu
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
// special action of Mixing view
if (candidate_flatten_group){
if (ImGui::Selectable( ICON_FA_DOWNLOAD " Flatten" )) {
Mixer::manager().groupSelection();
}
}
else {
ImGui::TextDisabled( ICON_FA_DOWNLOAD " Flatten" );
}
ImGui::Separator();
// manipulation of sources in Mixing view
if (ImGui::Selectable( ICON_FA_ALIGN_CENTER " Distribute" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
float depth = (*it)->depth();
float depth_inc = (dsl.back()->depth() - depth) / static_cast<float>(Mixer::selection().size()-1);
for (++it; it != dsl.end(); ++it) {
depth += depth_inc;
(*it)->call( new SetDepth(depth, 80.f) );
}
Action::manager().store(std::string("Selection: Layer Distribute"));
}
if (ImGui::Selectable( ICON_FA_RULER_HORIZONTAL " Compress" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
float depth = (*it)->depth();
for (++it; it != dsl.end(); ++it) {
depth += LAYER_STEP;
(*it)->call( new SetDepth(depth, 80.f) );
}
Action::manager().store(std::string("Selection: Layer Compress"));
}
if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT " Reverse order" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
SourceList::reverse_iterator rit = dsl.rbegin();
for (; it != dsl.end(); ++it, ++rit) {
(*it)->call( new SetDepth((*rit)->depth(), 80.f) );
}
Action::manager().store(std::string("Selection: Layer Reverse order"));
}
ImGui::PopStyleColor(2);
ImGui::EndPopup();
}
}
void LayerView::update(float dt)
{
View::update(dt);
// a more complete update is requested
if (View::need_deep_update_ > 0) {
// update rendering of render frame
FrameBuffer *output = Mixer::manager().session()->frame();
if (output){
// correct with aspect ratio
aspect_ratio = output->aspectRatio();
frame_->scale_.x = aspect_ratio;
persp_left_->translation_.x = -aspect_ratio;
persp_right_->translation_.x = aspect_ratio + 0.06;
}
// prevent invalid scaling
float s = CLAMP(scene.root()->scale_.x, LAYER_MIN_SCALE, LAYER_MAX_SCALE);
scene.root()->scale_.x = s;
scene.root()->scale_.y = s;
}
if (Mixer::manager().view() == this )
{
// update the selection overlay
updateSelectionOverlay();
}
}
bool LayerView::canSelect(Source *s) {
return ( View::canSelect(s) && s->active() );
}
void LayerView::resize ( int scale )
{
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
z *= z;
z *= LAYER_MAX_SCALE - LAYER_MIN_SCALE;
z += LAYER_MIN_SCALE;
scene.root()->scale_.x = z;
scene.root()->scale_.y = z;
// Clamp translation to acceptable area
glm::vec3 border(2.f, 1.f, 0.f);
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border * 2.f);
}
int LayerView::size ()
{
float z = (scene.root()->scale_.x - LAYER_MIN_SCALE) / (LAYER_MAX_SCALE - LAYER_MIN_SCALE);
return (int) ( sqrt(z) * 100.f);
}
std::pair<Node *, glm::vec2> LayerView::pick(glm::vec2 P)
{
// get picking from generic View
std::pair<Node *, glm::vec2> pick = View::pick(P);
// deal with internal interactive objects
if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
openContextMenu(MENU_SELECTION);
}
else {
// get if a source was picked
Source *s = Mixer::manager().findSource(pick.first);
if (s != nullptr) {
// pick on the lock icon; unlock source
if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
lock(s, false);
// pick = { s->locker_, pick.second };
pick = { nullptr, glm::vec2(0.f) };
}
// pick on the open lock icon; lock source and cancel pick
else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) {
lock(s, true);
pick = { nullptr, glm::vec2(0.f) };
}
// pick a locked source; cancel pick
else if ( s->locked() ) {
pick = { nullptr, glm::vec2(0.f) };
}
// pick the symbol: ask to show editor
else if ( pick.first == s->symbol_ ) {
UserInterface::manager().showSourceEditor(s);
}
}
else
pick = { nullptr, glm::vec2(0.f) };
}
return pick;
}
float LayerView::setDepth(Source *s, float d)
{
if (!s)
return -1.f;
// move the layer node of the source
Group *sourceNode = s->group(mode_);
float depth = d < 0.f ? sourceNode->translation_.z : d;
// negative or no depth given; find the front most depth
if ( depth < 0.f ) {
// default to place visible in front of background
depth = LAYER_BACKGROUND + LAYER_STEP;
// find the front-most souce in the workspace (behind FOREGROUND)
for (NodeSet::iterator node = scene.ws()->begin(); node != scene.ws()->end(); ++node) {
// place in front of previous sources
depth = MAX(depth, (*node)->translation_.z + LAYER_STEP);
// in case node is already at max depth
if ((*node)->translation_.z + DELTA_DEPTH > MAX_DEPTH )
(*node)->translation_.z -= DELTA_DEPTH;
}
}
// change depth
sourceNode->translation_.z = CLAMP( depth, MIN_DEPTH, MAX_DEPTH);
// request reordering of scene at next update
View::need_deep_update_++;
// request update of source
s->touch();
return sourceNode->translation_.z;
}
View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>)
{
if (!s)
return Cursor();
// unproject
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
// compute delta translation
glm::vec3 dest_translation = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
// discretized translation with ALT
if (UserInterface::manager().altModifier())
dest_translation.x = ROUND(dest_translation.x, 5.f);
// apply change
float d = setDepth( s, MAX( -dest_translation.x, 0.f) );
// store action in history
std::ostringstream info;
info << "Depth " << std::fixed << std::setprecision(2) << d << " ";
current_action_ = s->name() + ": " + info.str();
return Cursor(Cursor_ResizeNESW, info.str() );
}
void LayerView::arrow (glm::vec2 movement)
{
static float accumulator = 0.f;
accumulator += dt_;
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
bool first = true;
glm::vec3 delta_translation(0.f);
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
// individual move with SHIFT
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
continue;
Group *sourceNode = (*it)->group(mode_);
glm::vec3 dest_translation(0.f);
if (first) {
// dest starts at current
dest_translation = sourceNode->translation_;
// + ALT : discrete displacement
if (UserInterface::manager().altModifier()) {
if (accumulator > 100.f) {
dest_translation += glm::sign(gl_delta) * 0.21f;
dest_translation.x = ROUND(dest_translation.x, 10.f);
accumulator = 0.f;
}
else
break;
}
else {
// normal case: dest += delta
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
accumulator = 0.f;
}
// store action in history
std::ostringstream info;
info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
current_action_ = (*it)->name() + ": " + info.str();
// delta for others to follow
delta_translation = dest_translation - sourceNode->translation_;
}
else {
// dest = current + delta from first
dest_translation = sourceNode->translation_ + delta_translation;
}
// apply & request update
setDepth( *it, MAX( -dest_translation.x, 0.f) );
first = false;
}
}
void LayerView::updateSelectionOverlay()
{
View::updateSelectionOverlay();
if (overlay_selection_->visible_) {
// calculate bbox on selection
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
overlay_selection_->scale_ = selection_box.scale();
overlay_selection_->translation_ = selection_box.center();
// slightly extend the boundary of the selection
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.07f, 0.07f, 1.f) / overlay_selection_->scale_;
}
}

35
LayerView.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef LAYERVIEW_H
#define LAYERVIEW_H
#include "View.h"
class LayerView : public View
{
public:
LayerView();
// non assignable class
LayerView(LayerView const&) = delete;
LayerView& operator=(LayerView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
bool canSelect(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void arrow (glm::vec2) override;
float setDepth (Source *, float d = -1.f);
private:
void updateSelectionOverlay() override;
float aspect_ratio;
Mesh *persp_left_, *persp_right_;
Group *frame_;
};
#endif // LAYERVIEW_H

101
Log.cpp
View File

@@ -1,22 +1,33 @@
#include "Log.h"
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui_internal.h"
#include "ImGuiToolkit.h"
#include "defines.h"
// multiplatform
#include <tinyfiledialogs.h>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <string>
#include <list>
#include <mutex>
using namespace std;
#include "defines.h"
#include "ImGuiToolkit.h"
#include "DialogToolkit.h"
#include "Log.h"
static std::mutex mtx;
struct AppLog
@@ -43,6 +54,7 @@ struct AppLog
{
mtx.lock();
int old_size = Buf.size();
Buf.appendf("%04d ", LineOffsets.size()); // this adds 6 characters to show line number
Buf.appendfv(fmt, args);
Buf.append("\n");
@@ -58,7 +70,7 @@ struct AppLog
ImGui::SetNextWindowPos(ImVec2(430, 660), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(1150, 220), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(600, 180), ImVec2(FLT_MAX, FLT_MAX));
if (!ImGui::Begin(title, p_open))
if ( !ImGui::Begin(title, p_open))
{
ImGui::End();
return;
@@ -66,6 +78,9 @@ struct AppLog
// window
ImGui::SameLine(0, 0);
static bool numbering = true;
ImGuiToolkit::ButtonIconToggle(4, 12, 4, 12, &numbering );
ImGui::SameLine();
bool clear = ImGui::Button( ICON_FA_BACKSPACE " Clear");
ImGui::SameLine();
bool copy = ImGui::Button( ICON_FA_COPY " Copy");
@@ -73,7 +88,12 @@ struct AppLog
Filter.Draw("Filter", -60.0f);
ImGui::Separator();
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar);
if ( !ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar) )
{
ImGui::EndChild();
ImGui::End();
return;
}
if (clear)
Clear();
@@ -118,7 +138,7 @@ struct AppLog
{
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
{
const char* line_start = buf + LineOffsets[line_no];
const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6);
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
ImGui::TextUnformatted(line_start, line_end);
}
@@ -140,7 +160,10 @@ struct AppLog
}
};
static AppLog logs;
AppLog logs;
list<string> notifications;
list<string> warnings;
float notifications_timeout = 0.f;
void Log::Info(const char* fmt, ...)
{
@@ -153,12 +176,9 @@ void Log::Info(const char* fmt, ...)
void Log::ShowLogWindow(bool* p_open)
{
ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver);
logs.Draw( ICON_FA_LIST_UL " Logs", p_open);
logs.Draw( IMGUI_TITLE_LOGS, p_open);
}
static list<string> notifications;
static float notifications_timeout = 0.f;
void Log::Notify(const char* fmt, ...)
{
ImGuiTextBuffer buf;
@@ -173,12 +193,9 @@ void Log::Notify(const char* fmt, ...)
notifications_timeout = 0.f;
// always log
Log::Info("%s", buf.c_str());
Log::Info(ICON_FA_INFO_CIRCLE " %s", buf.c_str());
}
static list<string> warnings;
void Log::Warning(const char* fmt, ...)
{
ImGuiTextBuffer buf;
@@ -192,18 +209,18 @@ void Log::Warning(const char* fmt, ...)
warnings.push_back(buf.c_str());
// always log
Log::Info("Warning - %s\n", buf.c_str());
Log::Info(ICON_FA_EXCLAMATION_TRIANGLE " Warning - %s", buf.c_str());
}
void Log::Render(bool showNofitications, bool showWarnings)
void Log::Render(bool *showWarnings)
{
bool show_warnings = !warnings.empty() & showWarnings;
bool show_notification = !notifications.empty() & showNofitications;
bool show_warnings = !warnings.empty();
bool show_notification = !notifications.empty();
if (!show_notification && !show_warnings)
return;
ImGuiIO& io = ImGui::GetIO();
const ImGuiIO& io = ImGui::GetIO();
float width = io.DisplaySize.x * 0.4f;
float pos = io.DisplaySize.x * 0.3f;
@@ -238,12 +255,13 @@ void Log::Render(bool showNofitications, bool showWarnings)
notifications.clear();
}
if (show_warnings) {
ImGui::OpenPopup("Warning");
if (ImGui::BeginPopupModal("Warning", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGuiToolkit::Icon(9, 4);
ImGui::SameLine(0, 10);
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImGui::SetNextItemWidth(width);
ImGui::TextColored(ImVec4(1.0f,0.6f,0.0f,1.0f), "%ld error(s) occured.\n\n", warnings.size());
ImGui::Dummy(ImVec2(width, 0));
@@ -255,8 +273,21 @@ void Log::Render(bool showNofitications, bool showWarnings)
}
ImGui::PopTextWrapPos();
ImGui::Dummy(ImVec2(width * 0.8f, 0)); ImGui::SameLine(); // right align
if (ImGui::Button(" Ok ", ImVec2(width * 0.2f, 0))) {
bool close = false;
ImGui::Spacing();
if (ImGui::Button("Show logs", ImVec2(width * 0.2f, 0))) {
close = true;
if (showWarnings!= nullptr)
*showWarnings = true;
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(width * 0.6f, 0)); // right align
ImGui::SameLine();
if (ImGui::Button(" Ok ", ImVec2(width * 0.2f, 0)))
close = true;
if (close) {
ImGui::CloseCurrentPopup();
// messages have been seen
warnings.clear();
@@ -267,7 +298,6 @@ void Log::Render(bool showNofitications, bool showWarnings)
}
}
}
void Log::Error(const char* fmt, ...)
@@ -279,7 +309,8 @@ void Log::Error(const char* fmt, ...)
buf.appendfv(fmt, args);
va_end(args);
tinyfd_messageBox( APP_TITLE, buf.c_str(), "ok", "error", 0);
DialogToolkit::ErrorDialog(buf.c_str());
Log::Info("Error - %s\n", buf.c_str());
}

2
Log.h
View File

@@ -12,7 +12,7 @@ namespace Log
// Draw logs
void ShowLogWindow(bool* p_open = nullptr);
void Render(bool showNofitications = true, bool showWarnings = true);
void Render(bool *showWarnings = nullptr);
}
#endif // __LOG_H_

View File

@@ -1,4 +1,21 @@
#include <thread>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
// Desktop OpenGL function loader
#include <glad/glad.h>
@@ -151,20 +168,17 @@ bool Loopback::systemLoopbackInitialized()
Loopback::Loopback() : FrameGrabber()
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // fixed 30 FPS
}
void Loopback::init(GstCaps *caps)
std::string Loopback::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
return std::string("Invalid caps");
if (!Loopback::systemLoopbackInitialized()){
Log::Warning("Loopback system shall be initialized first.");
finished_ = true;
return;
return std::string("Loopback system shall be initialized first.");
}
// create a gstreamer pipeline
@@ -174,10 +188,9 @@ void Loopback::init(GstCaps *caps)
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("Loopback Could not construct pipeline %s:\n%s", description.c_str(), error->message);
std::string msg = std::string("Loopback : Could not construct pipeline ") + description + "\n" + std::string(error->message);
g_clear_error (&error);
finished_ = true;
return;
return msg;
}
// setup device sink
@@ -190,49 +203,51 @@ void Loopback::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ );
// specify streaming framerate in the given caps
GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, 30, 1); // fixed 30 FPS
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
gst_app_src_set_callbacks( src_, &callbacks, this, NULL);
}
else {
Log::Warning("Loopback Could not configure source");
finished_ = true;
return;
return std::string("Loopback : Could not configure source.");
}
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("Loopback Could not open %s", Loopback::system_loopback_name.c_str());
finished_ = true;
return;
return std::string("Loopback : Could not open ") + Loopback::system_loopback_name;
}
// all good
#if defined(LINUX)
Log::Notify("Loopback started (v4l2loopback on %s)", Loopback::system_loopback_name.c_str());
#else
Log::Notify("Loopback started (%s)", Loopback::system_loopback_name.c_str());
#endif
// start
active_ = true;
initialized_ = true;
return std::string("Loopback started on ") + Loopback::system_loopback_name;
}
void Loopback::terminate()

View File

@@ -15,7 +15,7 @@ class Loopback : public FrameGrabber
static std::string system_loopback_name;
static bool system_loopback_initialized;
void init(GstCaps *caps) override;
std::string init(GstCaps *caps) override;
void terminate() override;
public:

File diff suppressed because it is too large Load Diff

View File

@@ -12,17 +12,17 @@
#include <gst/app/gstappsink.h>
#include "Timeline.h"
#include "Metronome.h"
// Forward declare classes referenced
class Visitor;
#define MAX_PLAY_SPEED 20.0
#define MIN_PLAY_SPEED 0.1
#define N_VFRAME 3
#define N_VFRAME 5
struct MediaInfo {
Timeline timeline;
guint width;
guint par_width; // width to match pixel aspect ratio
guint height;
@@ -34,6 +34,8 @@ struct MediaInfo {
bool interlaced;
bool seekable;
bool valid;
GstClockTime dt;
GstClockTime end;
MediaInfo() {
width = par_width = 640;
@@ -41,19 +43,20 @@ struct MediaInfo {
bitrate = 0;
framerate_n = 1;
framerate_d = 25;
codec_name = "unknown";
codec_name = "";
isimage = false;
interlaced = false;
seekable = false;
valid = false;
dt = GST_CLOCK_TIME_NONE;
end = GST_CLOCK_TIME_NONE;
}
inline MediaInfo& operator = (const MediaInfo& b)
{
if (this != &b) {
this->timeline.setEnd( b.timeline.end() );
this->timeline.setStep( b.timeline.step() );
this->timeline.setFirst( b.timeline.first() );
this->dt = b.dt;
this->end = b.end;
this->width = b.width;
this->par_width = b.par_width;
this->height = b.height;
@@ -89,7 +92,8 @@ public:
/**
* Open a media using gstreamer URI
* */
void open( std::string path);
void open ( const std::string &filename, const std::string &uri = "");
void reopen ();
/**
* Get name of the media
* */
@@ -101,7 +105,7 @@ public:
/**
* Get name of Codec of the media
* */
std::string codec() const;
MediaInfo media() const;
/**
* True if a media was oppenned
* */
@@ -184,7 +188,15 @@ public:
/**
* Seek to zero
* */
void rewind();
void rewind(bool force = false);
/**
* pending
* */
bool pending() const { return pending_; }
/**
* Get position time
* */
GstClockTime position();
/**
* go to a valid position in media timeline
* pos in nanoseconds.
@@ -204,13 +216,9 @@ public:
* - frame duration : timeline.step()
*/
Timeline *timeline();
void setTimeline(Timeline tl);
void setTimeline(const Timeline &tl);
float currentTimelineFading();
/**
* Get position time
* */
GstClockTime position();
/**
* Get framerate of the media
* */
@@ -229,7 +237,7 @@ public:
* */
guint height() const;
/**
* Get frames displayt aspect ratio
* Get frames display aspect ratio
* NB: can be different than width() / height()
* */
float aspectRatio() const;
@@ -238,9 +246,32 @@ public:
* Must be called in OpenGL context
* */
guint texture() const;
/**
* Get the name of the decoder used,
* return 'software' if no hardware decoder is used
* NB: perform request on pipeline on first call
* */
std::string decoderName();
/**
* Forces open using software decoding
* (i.e. without hadrware decoding)
* NB: this reopens the video and reset decoder name
* */
void setSoftwareDecodingForced(bool on);
bool softwareDecodingForced();
/**
* Option to automatically rewind each time the player is disabled
* (i.e. when enable(false) is called )
* */
inline void setRewindOnDisabled(bool on) { rewind_on_disable_ = on; }
inline bool rewindOnDisabled() const { return rewind_on_disable_; }
/**
* Option to synchronize with metronome
* */
inline void setSyncToMetronome(Metronome::Synchronicity s) { metro_sync_ = s; }
inline Metronome::Synchronicity syncToMetronome() const { return metro_sync_; }
/**
* Accept visitors
* Used for saving session file
* */
void accept(Visitor& v);
/**
@@ -251,6 +282,8 @@ public:
static std::list<MediaPlayer*>::const_iterator begin() { return registered_.cbegin(); }
static std::list<MediaPlayer*>::const_iterator end() { return registered_.cend(); }
static MediaInfo UriDiscoverer(const std::string &uri);
private:
// video player description
@@ -261,6 +294,7 @@ private:
// general properties of media
MediaInfo media_;
Timeline timeline_;
std::future<MediaInfo> discoverer_;
// GST & Play status
@@ -270,24 +304,26 @@ private:
GstState desired_state_;
GstElement *pipeline_;
GstVideoInfo v_frame_video_info_;
std::atomic<bool> ready_;
std::atomic<bool> opened_;
std::atomic<bool> failed_;
bool force_update_;
bool pending_;
bool seeking_;
bool enabled_;
bool rewind_on_disable_;
bool force_software_decoding_;
std::string decoder_name_;
Metronome::Synchronicity metro_sync_;
// fps counter
struct TimeCounter {
GstClockTime last_time;
GstClockTime tic_time;
int nbFrames;
GTimer *timer;
gdouble fps;
public:
TimeCounter();
GstClockTime dt();
~TimeCounter();
void tic();
void reset();
gdouble frameRate() const;
inline gdouble frameRate() const { return fps; }
};
TimeCounter timecount_;
@@ -311,6 +347,7 @@ private:
status = INVALID;
position = GST_CLOCK_TIME_NONE;
}
void unmap();
};
Frame frame_[N_VFRAME];
guint write_index_;
@@ -324,8 +361,9 @@ private:
// gst pipeline control
void execute_open();
void execute_play_command(bool on);
void execute_loop_command();
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE);
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE, bool force = false);
// gst frame filling
void init_texture(guint index);

View File

@@ -1,6 +1,24 @@
#include <glm/gtc/matrix_transform.hpp>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "MediaSource.h"
#include <glm/gtc/matrix_transform.hpp>
#include "defines.h"
#include "ImageShader.h"
@@ -11,7 +29,9 @@
#include "Visitor.h"
#include "Log.h"
MediaSource::MediaSource() : Source(), path_("")
#include "MediaSource.h"
MediaSource::MediaSource(uint64_t id) : Source(id), path_("")
{
// create media player
mediaplayer_ = new MediaPlayer;
@@ -25,11 +45,14 @@ MediaSource::~MediaSource()
void MediaSource::setPath(const std::string &p)
{
Log::Notify("Creating Source with media '%s'", p.c_str());
path_ = p;
// open gstreamer
mediaplayer_->open(path_);
mediaplayer_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
std::string MediaSource::path() const
@@ -45,9 +68,14 @@ MediaPlayer *MediaSource::mediaplayer() const
glm::ivec2 MediaSource::icon() const
{
if (mediaplayer_->isImage())
return glm::ivec2(2, 9);
return glm::ivec2(ICON_SOURCE_IMAGE);
else
return glm::ivec2(18, 13);
return glm::ivec2(ICON_SOURCE_VIDEO);
}
std::string MediaSource::info() const
{
return std::string("media '") + path_ + "'";
}
bool MediaSource::failed() const
@@ -79,20 +107,22 @@ void MediaSource::init()
// icon in mixing view
if (mediaplayer_->isImage())
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f));
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.75f, 0.75f, 0.01f));
else
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f));
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// done init
initialized_ = true;
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
// force update of activation mode
active_ = true;
touch();
// deep update to reorder
++View::need_deep_update_;
// done init
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
}
}
@@ -102,14 +132,48 @@ void MediaSource::setActive (bool on)
{
bool was_active = active_;
// try to activate (may fail if source is cloned)
Source::setActive(on);
// change status of media player (only if status changed)
if ( active_ != was_active ) {
if ( active_ != was_active )
mediaplayer_->enable(active_);
// change visibility of active surface (show preview of media when inactive)
if (activesurface_) {
if (active_)
activesurface_->setTextureIndex(Resource::getTextureTransparent());
else
activesurface_->setTextureIndex(mediaplayer_->texture());
}
}
bool MediaSource::playing () const
{
return mediaplayer_->isPlaying();
}
void MediaSource::play (bool on)
{
mediaplayer_->play(on);
}
bool MediaSource::playable () const
{
return !mediaplayer_->isImage();
}
void MediaSource::replay ()
{
mediaplayer_->rewind();
}
guint64 MediaSource::playtime () const
{
return mediaplayer_->position();
}
void MediaSource::update(float dt)
{
Source::update(dt);
@@ -120,21 +184,16 @@ void MediaSource::update(float dt)
void MediaSource::render()
{
if (!initialized_)
if ( renderbuffer_ == nullptr )
init();
else {
// blendingshader_->color.r = mediaplayer_->currentTimelineFading();
// blendingshader_->color.g = mediaplayer_->currentTimelineFading();
// blendingshader_->color.b = mediaplayer_->currentTimelineFading();
// render the media player into frame buffer
renderbuffer_->begin();
// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading();
// apply fading
texturesurface_->shader()->color = glm::vec4( glm::vec3(mediaplayer_->currentTimelineFading()), 1.f);
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
ready_ = true;
}
}

View File

@@ -8,12 +8,17 @@ class MediaPlayer;
class MediaSource : public Source
{
public:
MediaSource();
MediaSource(uint64_t id = 0);
~MediaSource();
// implementation of source API
void update (float dt) override;
void setActive (bool on) override;
bool playing () const override;
void play (bool) override;
bool playable () const override;
void replay () override;
guint64 playtime () const override;
void render() override;
bool failed() const override;
uint texture() const override;
@@ -25,6 +30,7 @@ public:
MediaPlayer *mediaplayer() const;
glm::ivec2 icon() const override;
std::string info() const override;
protected:

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <iostream>
#include <sstream>
#include <istream>
@@ -16,9 +35,10 @@
#include "Resource.h"
#include "ImageShader.h"
#include "Visitor.h"
#include "Log.h"
#include "Mesh.h"
#include "GlmToolkit.h"
#include "Log.h"
#include "Mesh.h"
using namespace std;
using namespace glm;
@@ -31,11 +51,8 @@ typedef struct prop {
std::string name;
bool is_float;
bool is_list;
prop(std::string n, bool t, bool l = false){
name = n;
is_float = t;
is_list = l;
}
prop(const std::string &n, bool t, bool l = false) :
name(n), is_float(t), is_list(l) { }
} plyProperty;
typedef std::map<std::string, std::vector<plyProperty> > plyElementProperties;
@@ -45,9 +62,9 @@ template <typename T>
T parseValue(std::istream& istream) {
T v;
char space = ' ';
istream >> v;
if (!istream.eof()) {
char space = ' ';
istream >> space >> std::ws;
}
@@ -230,7 +247,6 @@ bool parsePLY(string ascii,
// a numerical property
if ( ! prop.is_list ) {
float value;
switch ( prop.name[0] ) {
case 'x':
point.x = parseValue<float>(stringstream);
@@ -266,7 +282,7 @@ bool parsePLY(string ascii,
break;
default:
// ignore normals or other types
value = parseValue<float>(stringstream);
parseValue<float>(stringstream);
break;
}
}

1
Mesh.h
View File

@@ -18,6 +18,7 @@ public:
Mesh(const std::string& ply_path, const std::string& tex_path = "");
void setTexture(uint textureindex);
inline uint texture() const { return textureindex_; }
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;

253
Metronome.cpp Normal file
View File

@@ -0,0 +1,253 @@
#include <atomic>
#include <iomanip>
#include <iostream>
#include <thread>
/// Ableton Link is a technology that synchronizes musical beat, tempo,
/// and phase across multiple applications running on one or more devices.
/// Applications on devices connected to a local network discover each other
/// automatically and form a musical session in which each participant can
/// perform independently: anyone can start or stop while still staying in time.
/// Anyone can change the tempo, the others will follow.
/// Anyone can join or leave without disrupting the session.
///
/// https://ableton.github.io/link/
///
#include <ableton/Link.hpp>
#include "Settings.h"
#include "Metronome.h"
#include "Log.h"
namespace ableton
{
/// Inspired from Dummy audio platform example
/// https://github.com/Ableton/link/blob/master/examples/linkaudio/AudioPlatform_Dummy.hpp
class Engine
{
public:
Engine(Link& link)
: mLink(link)
, mQuantum(4.)
{
}
void startPlaying()
{
auto sessionState = mLink.captureAppSessionState();
sessionState.setIsPlayingAndRequestBeatAtTime(true, now(), 0., mQuantum);
mLink.commitAppSessionState(sessionState);
}
void stopPlaying()
{
auto sessionState = mLink.captureAppSessionState();
sessionState.setIsPlaying(false, now());
mLink.commitAppSessionState(sessionState);
}
bool isPlaying() const
{
return mLink.captureAppSessionState().isPlaying();
}
double beatTime() const
{
auto sessionState = mLink.captureAppSessionState();
return sessionState.beatAtTime(now(), mQuantum);
}
std::chrono::microseconds timeNextBeat() const
{
auto sessionState = mLink.captureAppSessionState();
double beat = ceil(sessionState.beatAtTime(now(), mQuantum));
return sessionState.timeAtBeat(beat, mQuantum);
}
double phaseTime() const
{
auto sessionState = mLink.captureAppSessionState();
return sessionState.phaseAtTime(now(), mQuantum);
}
std::chrono::microseconds timeNextPhase() const
{
auto sessionState = mLink.captureAppSessionState();
double phase = ceil(sessionState.phaseAtTime(now(), mQuantum));
double beat = ceil(sessionState.beatAtTime(now(), mQuantum));
return sessionState.timeAtBeat(beat + (mQuantum-phase), mQuantum);
}
double tempo() const
{
auto sessionState = mLink.captureAppSessionState();
return sessionState.tempo();
}
double setTempo(double tempo)
{
auto sessionState = mLink.captureAppSessionState();
sessionState.setTempo(tempo, now());
mLink.commitAppSessionState(sessionState);
return sessionState.tempo();
}
double quantum() const
{
return mQuantum;
}
void setQuantum(double quantum)
{
mQuantum = quantum;
}
bool isStartStopSyncEnabled() const
{
return mLink.isStartStopSyncEnabled();
}
void setStartStopSyncEnabled(bool enabled)
{
mLink.enableStartStopSync(enabled);
}
std::chrono::microseconds now() const
{
return mLink.clock().micros();
}
private:
Link& mLink;
double mQuantum;
};
} // namespace ableton
ableton::Link link_(120.);
ableton::Engine engine_(link_);
Metronome::Metronome()
{
}
bool Metronome::init()
{
// set parameters
setEnabled(Settings::application.timer.link_enabled);
setTempo(Settings::application.timer.link_tempo);
setQuantum(Settings::application.timer.link_quantum);
setStartStopSync(Settings::application.timer.link_start_stop_sync);
// no reason for failure?
return true;
}
void Metronome::terminate()
{
// save current tempo
Settings::application.timer.link_tempo = tempo();
// disconnect
link_.enable(false);
}
void Metronome::setEnabled (bool on)
{
link_.enable(on);
Settings::application.timer.link_enabled = link_.isEnabled();
Log::Info("Metronome Ableton Link %s", Settings::application.timer.link_enabled ? "Enabled" : "Disabled");
}
bool Metronome::enabled () const
{
return link_.isEnabled();
}
double Metronome::beats() const
{
return engine_.beatTime();
}
double Metronome::phase() const
{
return engine_.phaseTime();
}
void Metronome::setQuantum(double q)
{
engine_.setQuantum(q);
Settings::application.timer.link_quantum = engine_.quantum();
}
double Metronome::quantum() const
{
return engine_.quantum();
}
void Metronome::setTempo(double t)
{
// set the tempo to t
// OR
// adopt the last tempo value that have been proposed on the network
Settings::application.timer.link_tempo = engine_.setTempo(t);
}
double Metronome::tempo() const
{
return engine_.tempo();
}
void Metronome::setStartStopSync (bool on)
{
engine_.setStartStopSyncEnabled(on);
Settings::application.timer.link_start_stop_sync = engine_.isStartStopSyncEnabled();
Log::Info("Metronome Ableton Link start & stop sync %s", Settings::application.timer.link_start_stop_sync ? "Enabled" : "Disabled");
}
bool Metronome::startStopSync () const
{
return engine_.isStartStopSyncEnabled();
}
void Metronome::restart()
{
engine_.startPlaying();
}
std::chrono::microseconds Metronome::timeToBeat()
{
return engine_.timeNextBeat() - engine_.now();
}
std::chrono::microseconds Metronome::timeToPhase()
{
return engine_.timeNextPhase() - engine_.now();
}
void delay(std::function<void()> f, std::chrono::microseconds us)
{
std::this_thread::sleep_for(us);
f();
}
void Metronome::executeAtBeat( std::function<void()> f )
{
std::thread( delay, f, timeToBeat() ).detach();
}
void Metronome::executeAtPhase( std::function<void()> f )
{
std::thread( delay, f, timeToPhase() ).detach();
}
size_t Metronome::peers() const
{
return link_.numPeers();
}

72
Metronome.h Normal file
View File

@@ -0,0 +1,72 @@
#ifndef METRONOME_H
#define METRONOME_H
#include <chrono>
#include <functional>
class Metronome
{
// Private Constructor
Metronome();
Metronome(Metronome const& copy) = delete;
Metronome& operator=(Metronome const& copy) = delete;
public:
typedef enum {
SYNC_NONE = 0,
SYNC_BEAT,
SYNC_PHASE
} Synchronicity;
static Metronome& manager ()
{
// The only instance
static Metronome _instance;
return _instance;
}
bool init ();
void terminate ();
void setEnabled (bool on);
bool enabled () const;
void setTempo (double t);
double tempo () const;
void setQuantum (double q);
double quantum () const;
void setStartStopSync (bool on);
bool startStopSync () const;
void restart();
// get beat and phase
double beats () const;
double phase () const;
// mechanisms to delay execution to next beat
std::chrono::microseconds timeToBeat();
void executeAtBeat( std::function<void()> f );
// mechanisms to delay execution to next phase
std::chrono::microseconds timeToPhase();
void executeAtPhase( std::function<void()> f );
size_t peers () const;
};
/// Example calls to executeAtBeat
///
/// With a Lamda function calling a member function of an object
/// - without parameter
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p) { p->rewind(); }, mediaplayer_) );
///
/// - with parameter
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, bool o) { p->play(o); }, mediaplayer_, on) );
///
#endif // METRONOME_H

848
Mixer.cpp

File diff suppressed because it is too large Load Diff

65
Mixer.h
View File

@@ -1,16 +1,26 @@
#ifndef MIXER_H
#define MIXER_H
#include "View.h"
#include "GeometryView.h"
#include "MixingView.h"
#include "LayerView.h"
#include "TextureView.h"
#include "TransitionView.h"
#include "Session.h"
#include "Selection.h"
namespace tinyxml2 {
class XMLElement;
}
class SessionSource;
class Mixer
{
// Private Constructor
Mixer();
Mixer(Mixer const& copy); // Not Implemented
Mixer& operator=(Mixer const& copy); // Not Implemented
Mixer(Mixer const& copy) = delete;
Mixer& operator=(Mixer const& copy) = delete;
public:
@@ -30,27 +40,32 @@ public:
// update session and all views
void update();
inline float dt() const { return dt_;}
inline float dt() const { return dt_;} // in miliseconds
inline int fps() const { return int(roundf(1000.f/dt__));}
// draw session and current view
void draw();
// creation of sources
Source * createSourceFile (const std::string &path);
Source * createSourceMultifile(const std::list<std::string> &list_files, uint fps);
Source * createSourceClone (const std::string &namesource = "");
Source * createSourceRender ();
Source * createSourceStream (const std::string &gstreamerpipeline);
Source * createSourcePattern(uint pattern, glm::ivec2 res);
Source * createSourceDevice (const std::string &namedevice);
Source * createSourceNetwork(const std::string &nameconnection);
Source * createSourceGroup ();
// operations on sources
void addSource (Source *s);
void deleteSource (Source *s, bool withundo=true);
void renameSource (Source *s, const std::string &newname);
void deleteSource (Source *s);
void renameSource (Source *s, const std::string &newname = "");
void attach (Source *s);
void detach (Source *s);
void deselect (Source *s);
void deleteSelection();
void groupSelection();
// current source
Source * currentSource ();
@@ -59,41 +74,53 @@ public:
void setCurrentSource (Node *node);
void setCurrentSource (uint64_t id);
void setCurrentNext ();
void setCurrentPrevious ();
void unsetCurrentSource ();
Source *sourceAtIndex (int index);
void setCurrentIndex (int index);
int indexCurrentSource ();
void moveIndex (int current_index, int target_index);
int indexCurrentSource () const;
int count() const;
// browsing into sources
Source * findSource (Node *node);
Source * findSource (std::string name);
Source * findSource (uint64_t id);
SourceList findSources (float depth_from, float depth_to);
SourceList validate(const SourceList &list);
// management of view
View *view (View::Mode m = View::INVALID);
void setView (View::Mode m);
void conceal(Source *s);
void uncover(Source *s);
void conceal (Source *s);
void uncover (Source *s);
bool concealed(Source *s);
// manipulate, load and save sessions
inline Session *session () const { return session_; }
void clear ();
void save ();
void saveas (const std::string& filename);
void save (bool with_version = false);
void saveas (const std::string& filename, bool with_version = false);
void load (const std::string& filename);
void import (const std::string& filename);
void merge (Session *s);
void set (Session *s);
void import (SessionSource *source);
void merge (Session *session);
void merge (SessionSource *source);
void set (Session *session);
void setResolution(glm::vec3 res);
// operations depending on transition mode
void close ();
void open (const std::string& filename);
void close (bool smooth = false);
void open (const std::string& filename, bool smooth = false);
// create sources if clipboard contains well-formed xml text
void paste (const std::string& clipboard);
// version and undo management
void restore(tinyxml2::XMLElement *sessionNode);
protected:
Session *session_;
@@ -104,7 +131,9 @@ protected:
SourceList candidate_sources_;
SourceList stash_;
void insertSource(Source *s, View::Mode m = View::INVALID);
void insertSource (Source *s, View::Mode m = View::INVALID);
bool replaceSource (Source *from, Source *to);
bool recreateSource(Source *s);
void setCurrentSource(SourceList::iterator it);
SourceList::iterator current_source_;
@@ -114,11 +143,11 @@ protected:
MixingView mixing_;
GeometryView geometry_;
LayerView layer_;
AppearanceView appearance_;
TextureView appearance_;
TransitionView transition_;
guint64 update_time_;
float dt_;
float dt__;
};
#endif // MIXER_H

392
MixingGroup.cpp Normal file
View File

@@ -0,0 +1,392 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include "defines.h"
#include "Source.h"
#include "Decorations.h"
#include "Visitor.h"
#include "BaseToolkit.h"
#include "Log.h"
#include "MixingGroup.h"
MixingGroup::MixingGroup (SourceList sources) : parent_(nullptr), root_(nullptr), lines_(nullptr), center_(nullptr),
center_pos_(glm::vec2(0.f, 0.f)), active_(true), update_action_(ACTION_NONE), updated_source_(nullptr)
{
// create unique id
id_ = BaseToolkit::uniqueId();
// fill the vector of sources with the given list
for (auto it = sources.begin(); it != sources.end(); ++it){
// add only if not linked already
if ((*it)->mixinggroup_ == nullptr) {
(*it)->mixinggroup_ = this;
sources_.push_back(*it);
}
}
// scene elements
root_ = new Group;
root_->visible_ = false;
center_ = new Symbol(Symbol::CIRCLE_POINT);
center_->visible_ = false;
center_->color = glm::vec4(COLOR_MIXING_GROUP, 0.75f);
center_->scale_ = glm::vec3(0.6f, 0.6f, 1.f);
root_->attach(center_);
// create
recenter();
createLineStrip();
}
MixingGroup::~MixingGroup ()
{
for (auto it = sources_.begin(); it != sources_.end(); ++it)
(*it)->clearMixingGroup();
if (parent_)
parent_->detach( root_ );
delete root_;
}
void MixingGroup::accept(Visitor& v)
{
v.visit(*this);
}
void MixingGroup::attachTo( Group *parent )
{
if (parent_ != nullptr)
parent_->detach( root_ );
parent_ = parent;
if (parent_ != nullptr)
parent_->attach(root_);
}
void MixingGroup::recenter()
{
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
for (auto it = sources_.begin(); it != sources_.end(); ++it){
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
// set center
center_->translation_ = glm::vec3(center_pos_, 0.f);
}
void MixingGroup::setAction (Action a)
{
if (a == ACTION_UPDATE) {
// accept UPDATE action only if no other action is ongoing
if (update_action_ == ACTION_NONE)
update_action_ = ACTION_UPDATE;
}
else if (a == ACTION_FINISH) {
// only needs to finish if an action was done
if (update_action_ != ACTION_NONE)
update_action_ = ACTION_FINISH;
}
else
update_action_ = a;
}
void MixingGroup::update (float)
{
// after creation, root is not visible: wait that all sources are initialized to make it visible
if (!root_->visible_) {
auto unintitializedsource = std::find_if_not(sources_.begin(), sources_.end(), Source::isInitialized);
root_->visible_ = (unintitializedsource == sources_.end());
}
// group is active if one source in the group is current
auto currentsource = std::find_if(sources_.begin(), sources_.end(), Source::isCurrent);
setActive(currentsource != sources_.end());
// perform action
if (update_action_ == ACTION_FINISH ) {
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
// update only once
update_action_ = ACTION_NONE;
}
else if (update_action_ == ACTION_UPDATE ) {
std::vector<glm::vec2> p = lines_->path();
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
center_->translation_ = glm::vec3(center_pos_, 0.f);
// update path
lines_->changePath(p);
// update only once
update_action_ = ACTION_NONE;
}
else if (update_action_ != ACTION_NONE && updated_source_ != nullptr) {
if (update_action_ == ACTION_GRAB_ONE ) {
// update path
move(updated_source_);
recenter();
}
else if (update_action_ == ACTION_GRAB_ALL ) {
std::vector<glm::vec2> p = lines_->path();
glm::vec2 displacement = glm::vec2(updated_source_->group(View::MIXING)->translation_);
displacement -= p[ index_points_[updated_source_] ];
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// modify all but the already updated source
if ( *it != updated_source_ && !(*it)->locked() ) {
(*it)->group(View::MIXING)->translation_.x += displacement.x;
(*it)->group(View::MIXING)->translation_.y += displacement.y;
(*it)->touch();
}
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
center_->translation_ = glm::vec3(center_pos_, 0.f);
// update path
lines_->changePath(p);
}
else if (update_action_ == ACTION_ROTATE_ALL ) {
std::vector<glm::vec2> p = lines_->path();
// get angle rotation and distance scaling
glm::vec2 pos_first = glm::vec2(updated_source_->group(View::MIXING)->translation_) -center_pos_;
float angle = glm::orientedAngle( glm::normalize(pos_first), glm::vec2(1.f, 0.f) );
float dist = glm::length( pos_first );
glm::vec2 pos_second = glm::vec2(p[ index_points_[updated_source_] ]) -center_pos_;
angle -= glm::orientedAngle( glm::normalize(pos_second), glm::vec2(1.f, 0.f) );
dist /= glm::length( pos_second );
int numactions = 0;
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// modify all but the already updated source
if ( *it != updated_source_ && !(*it)->locked() ) {
glm::vec2 vec = glm::vec2((*it)->group(View::MIXING)->translation_) -center_pos_;
vec = glm::rotate(vec, -angle) * dist;
vec += center_pos_;
(*it)->group(View::MIXING)->translation_.x = vec.x;
(*it)->group(View::MIXING)->translation_.y = vec.y;
(*it)->touch();
numactions++;
}
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
}
// update path
lines_->changePath(p);
// no source was rotated? better action is thus to grab
if (numactions<1)
update_action_ = ACTION_GRAB_ALL;
}
// done
updated_source_ = nullptr;
}
}
void MixingGroup::setActive (bool on)
{
active_ = on;
// overlays
lines_->shader()->color.a = active_ ? 0.96f : 0.5f;
center_->visible_ = update_action_ > ACTION_GRAB_ONE;
}
void MixingGroup::detach (Source *s)
{
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
// ok, its in the list !
if (its != sources_.end()) {
// tell the source
(*its)->clearMixingGroup();
// erase the source from the list
sources_.erase(its);
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
}
void MixingGroup::detach (SourceList l)
{
for (auto sit = l.begin(); sit != l.end(); ++sit) {
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), *sit);
// ok, its in the list !
if (its != sources_.end()) {
// tell the source
(*its)->clearMixingGroup();
// erase the source from the list
sources_.erase(its);
}
}
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
void MixingGroup::attach (Source *s)
{
// if source is not already in a group (this or other)
if (s->mixinggroup_ == nullptr) {
// tell the source
s->mixinggroup_ = this;
// add the source
sources_.push_back(s);
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
}
void MixingGroup::attach (SourceList l)
{
for (auto sit = l.begin(); sit != l.end(); ++sit) {
if ( (*sit)->mixinggroup_ == nullptr) {
// tell the source
(*sit)->mixinggroup_ = this;
// add the source
sources_.push_back(*sit);
}
}
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
uint MixingGroup::size()
{
return sources_.size();
}
SourceList MixingGroup::getCopy() const
{
SourceList sl = sources_;
return sl;
}
SourceList::iterator MixingGroup::begin ()
{
return sources_.begin();
}
SourceList::iterator MixingGroup::end ()
{
return sources_.end();
}
bool MixingGroup::contains (Source *s)
{
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
// in the list ?
return its != sources_.end();
}
void MixingGroup::move (Source *s)
{
if (contains(s) && lines_) {
// modify one point in the path
lines_->editPath(index_points_[s], glm::vec2(s->group(View::MIXING)->translation_));
}
}
void MixingGroup::createLineStrip()
{
if (sources_.size() > 1) {
if (lines_) {
root_->detach(lines_);
delete lines_;
}
// sort the vector of sources in clockwise order around the center pos_
sources_ = mixing_sorted( sources_, center_pos_);
// start afresh list of indices
index_points_.clear();
// path linking all sources
std::vector<glm::vec2> path;
for (auto it = sources_.begin(); it != sources_.end(); ++it){
index_points_[*it] = path.size();
path.push_back(glm::vec2((*it)->group(View::MIXING)->translation_));
}
// create
lines_ = new LineLoop(path, 1.5f);
lines_->shader()->color = glm::vec4(COLOR_MIXING_GROUP, 0.96f);
root_->attach(lines_);
}
}

86
MixingGroup.h Normal file
View File

@@ -0,0 +1,86 @@
#ifndef MIXINGGROUP_H
#define MIXINGGROUP_H
#include <map>
#include "View.h"
#include "SourceList.h"
class LineLoop;
class Symbol;
class MixingGroup
{
public:
MixingGroup (SourceList sources);
// non assignable class
MixingGroup(MixingGroup const&) = delete;
MixingGroup& operator=(MixingGroup const&) = delete;
virtual ~MixingGroup ();
// Get unique id
inline uint64_t id () const { return id_; }
// Source list manipulation
SourceList getCopy() const;
SourceList::iterator begin ();
SourceList::iterator end ();
uint size ();
bool contains (Source *s);
void detach (Source *s);
void detach (SourceList l);
void attach (Source *s);
void attach (SourceList l);
// actions for update
typedef enum {
ACTION_NONE = 0,
ACTION_UPDATE = 1,
ACTION_GRAB_ONE = 2,
ACTION_GRAB_ALL = 3,
ACTION_ROTATE_ALL = 4,
ACTION_FINISH = 5
} Action;
void setAction (Action a) ;
inline Action action () { return update_action_; }
inline void follow (Source *s) { updated_source_ = s; }
// to update in Session
void update (float);
// active if one of its source is current
inline bool active () const { return active_; }
void setActive (bool on);
// attach node to draw in Mixing View
void attachTo( Group *parent );
// accept all kind of visitors
virtual void accept (Visitor& v);
private:
// Drawing elements
Group *parent_;
Group *root_;
LineLoop *lines_;
Symbol *center_;
void createLineStrip();
void recenter();
void move (Source *s);
// properties linked to sources
glm::vec2 center_pos_;
SourceList sources_;
std::map< Source *, uint> index_points_;
// status and actions
uint64_t id_;
bool active_;
Action update_action_;
Source *updated_source_;
};
#endif // MIXINGGROUP_H

777
MixingView.cpp Normal file
View File

@@ -0,0 +1,777 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <string>
#include <sstream>
#include <iomanip>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "ImGuiToolkit.h"
#include "Mixer.h"
#include "defines.h"
#include "Source.h"
#include "Settings.h"
#include "Decorations.h"
#include "UserInterfaceManager.h"
#include "BoundingBoxVisitor.h"
#include "ActionManager.h"
#include "MixingGroup.h"
#include "Log.h"
#include "MixingView.h"
// internal utility
float sin_quad_texture(float x, float y);
uint textureMixingQuadratic();
MixingView::MixingView() : View(MIXING), limbo_scale_(MIXING_MIN_THRESHOLD)
{
scene.root()->scale_ = glm::vec3(MIXING_DEFAULT_SCALE, MIXING_DEFAULT_SCALE, 1.0f);
scene.root()->translation_ = glm::vec3(0.0f, 0.0f, 0.0f);
// read default settings
if ( Settings::application.views[mode_].name.empty() ) {
// no settings found: store application default
Settings::application.views[mode_].name = "Mixing";
saveSettings();
}
else
restoreSettings();
// Mixing scene background
limbo_ = new Mesh("mesh/disk.ply");
limbo_->scale_ = glm::vec3(limbo_scale_, limbo_scale_, 1.f);
limbo_->shader()->color = glm::vec4( COLOR_LIMBO_CIRCLE, 1.f );
scene.bg()->attach(limbo_);
// interactive limbo scaling slider
limbo_slider_root_ = new Group;
limbo_slider_root_->translation_ = glm::vec3(0.0f, limbo_scale_, 0.f);
scene.bg()->attach(limbo_slider_root_);
limbo_slider_ = new Disk();
limbo_slider_->translation_ = glm::vec3(0.f, -0.01f, 0.f);
limbo_slider_->scale_ = glm::vec3(0.1f, 0.1f, 1.f);
limbo_slider_->color = glm::vec4( COLOR_LIMBO_CIRCLE, 1.f );
limbo_slider_root_->attach(limbo_slider_);
limbo_up_ = new Mesh("mesh/triangle_point.ply");
limbo_up_->scale_ = glm::vec3(0.8f, 0.8f, 1.f);
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
limbo_slider_root_->attach(limbo_up_);
limbo_down_ = new Mesh("mesh/triangle_point.ply");
limbo_down_->translation_ = glm::vec3(0.f, -0.02f, 0.f);
limbo_down_->scale_ = glm::vec3(0.8f, 0.8f, 1.f);
limbo_down_->rotation_ = glm::vec3(0.f, 0.f, M_PI);
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
limbo_slider_root_->attach(limbo_down_);
mixingCircle_ = new Mesh("mesh/disk.ply");
mixingCircle_->shader()->color = glm::vec4( 1.f, 1.f, 1.f, 1.f );
scene.bg()->attach(mixingCircle_);
circle_ = new Mesh("mesh/circle.ply");
circle_->shader()->color = glm::vec4( COLOR_CIRCLE, 1.0f );
scene.bg()->attach(circle_);
// Mixing scene foreground
// button frame (non interactive; no picking detected on Mesh)
Mesh *tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
tmp->translation_ = glm::vec3(0.f, 1.f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
scene.fg()->attach(tmp);
// interactive button
button_white_ = new Disk();
button_white_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
button_white_->translation_ = glm::vec3(0.f, 1.f, 0.f);
button_white_->color = glm::vec4( 0.85f, 0.85f, 0.85f, 1.0f );
scene.fg()->attach(button_white_);
// button frame
tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
tmp->translation_ = glm::vec3(0.f, -1.f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
scene.fg()->attach(tmp);
// interactive button
button_black_ = new Disk();
button_black_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
button_black_->translation_ = glm::vec3(0.f, -1.f, 0.f);
button_black_->color = glm::vec4( 0.1f, 0.1f, 0.1f, 1.0f );
scene.fg()->attach(button_black_);
// moving slider
slider_root_ = new Group;
scene.fg()->attach(slider_root_);
// interactive slider
slider_ = new Disk();
slider_->scale_ = glm::vec3(0.08f, 0.08f, 1.f);
slider_->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
slider_root_->attach(slider_);
// dark mask in front
tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.075f, 0.075f, 1.f);
tmp->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_SLIDER_CIRCLE, 1.0f );
slider_root_->attach(tmp);
// stashCircle_ = new Disk();
// stashCircle_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
// stashCircle_->translation_ = glm::vec3(2.f, -1.0f, 0.f);
// stashCircle_->color = glm::vec4( COLOR_STASH_CIRCLE, 0.6f );
// scene.bg()->attach(stashCircle_);
}
void MixingView::draw()
{
// set texture
if (mixingCircle_->texture() == 0)
mixingCircle_->setTexture(textureMixingQuadratic());
// temporarily force shaders to use opacity blending for rendering icons
Shader::force_blending_opacity = true;
// draw scene of this view
View::draw();
// restore state
Shader::force_blending_opacity = false;
// display popup menu
if (show_context_menu_ == MENU_SELECTION) {
ImGui::OpenPopup( "MixingSelectionContextMenu" );
show_context_menu_ = MENU_NONE;
}
if (ImGui::BeginPopup("MixingSelectionContextMenu")) {
// colored context menu
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
// special action of Mixing view: link or unlink
SourceList selected = Mixer::selection().getCopy();
if ( Mixer::manager().session()->canlink( selected )) {
// the selected sources can be linked
if (ImGui::Selectable( ICON_FA_LINK " Link" )){
// assemble a MixingGroup
Mixer::manager().session()->link(selected, scene.fg() );
Action::manager().store(std::string("Sources linked."));
// clear selection and select one of the sources of the group
Source *cur = Mixer::selection().front();
Mixer::manager().unsetCurrentSource();
Mixer::selection().clear();
Mixer::manager().setCurrentSource( cur );
}
}
else {
// the selected sources cannot be linked: offer to unlink!
if (ImGui::Selectable( ICON_FA_UNLINK " Unlink" )){
// dismantle MixingGroup(s)
Mixer::manager().session()->unlink(selected);
Action::manager().store(std::string("Sources unlinked."));
}
}
ImGui::Separator();
// manipulation of sources in Mixing view
if (ImGui::Selectable( ICON_FA_CROSSHAIRS " Center" )){
glm::vec2 center = glm::vec2(0.f, 0.f);
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// compute barycenter (1)
center += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center /= Mixer::selection().size();
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
(*it)->group(View::MIXING)->translation_ -= glm::vec3(center, 0.f);
(*it)->touch();
}
Action::manager().store(std::string("Selection: Mixing Center"));
}
if (ImGui::Selectable( ICON_FA_HAYKAL " Dispatch" )){
glm::vec2 center = glm::vec2(0.f, 0.f);
// distribute with equal angle
float angle = 0.f;
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, 1.2), angle);
(*it)->group(View::MIXING)->translation_.x = P.x;
(*it)->group(View::MIXING)->translation_.y = P.y;
(*it)->touch();
angle -= glm::two_pi<float>() / float(Mixer::selection().size());
}
Action::manager().store(std::string("Selection: Mixing Dispatch"));
}
if (ImGui::Selectable( ICON_FA_FAN " Distribute" )){
SourceList list;
glm::vec2 center = glm::vec2(0.f, 0.f);
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
list.push_back(*it);
// compute barycenter (1)
center += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center /= list.size();
// sort the vector of sources in clockwise order around the center pos_
list = mixing_sorted( list, center);
// average distance
float d = 0.f;
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
d += glm::distance(glm::vec2((*it)->group(View::MIXING)->translation_), center);
}
d /= list.size();
// distribute with equal angle
float angle = 0.f;
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, d), angle);
(*it)->group(View::MIXING)->translation_.x = P.x;
(*it)->group(View::MIXING)->translation_.y = P.y;
(*it)->touch();
angle -= glm::two_pi<float>() / float(list.size());
}
Action::manager().store(std::string("Selection: Mixing Distribute in circle"));
}
if (ImGui::Selectable( ICON_FA_ELLIPSIS_V " Align & Distribute" )){
SourceList list;
glm::vec2 center = glm::vec2(0.f, 0.f);
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
list.push_back(*it);
// compute barycenter (1)
center += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center /= list.size();
// distribute with equal angle
float i = 0.f;
float sign = -1.f;
for (SourceList::iterator it = list.begin(); it != list.end(); ++it, sign*=-1.f) {
(*it)->group(View::MIXING)->translation_.x = center.x;
(*it)->group(View::MIXING)->translation_.y = center.y + sign * i;
if (sign<0) i+=0.32f;
(*it)->touch();
}
Action::manager().store(std::string("Selection: Mixing Align & Distribute"));
}
if (ImGui::Selectable( ICON_FA_CLOUD_SUN " Expand & hide" )){
SourceList::iterator it = Mixer::selection().begin();
for (; it != Mixer::selection().end(); ++it) {
(*it)->call( new SetAlpha(0.f) );
}
Action::manager().store(std::string("Selection: Mixing Expand & hide"));
}
if (ImGui::Selectable( ICON_FA_SUN " Compress & show" )){
SourceList::iterator it = Mixer::selection().begin();
for (; it != Mixer::selection().end(); ++it) {
(*it)->call( new SetAlpha(0.999f) );
}
Action::manager().store(std::string("Selection: Mixing Compress & show"));
}
ImGui::PopStyleColor(2);
ImGui::EndPopup();
}
}
void MixingView::resize ( int scale )
{
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
z *= z;
z *= MIXING_MAX_SCALE - MIXING_MIN_SCALE;
z += MIXING_MIN_SCALE;
scene.root()->scale_.x = z;
scene.root()->scale_.y = z;
// Clamp translation to acceptable area
glm::vec2 res = resolution();
glm::vec3 border(2.3f * res.x/res.y, 2.3f, 0.f);
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
}
int MixingView::size ()
{
float z = (scene.root()->scale_.x - MIXING_MIN_SCALE) / (MIXING_MAX_SCALE - MIXING_MIN_SCALE);
return (int) ( sqrt(z) * 100.f);
}
void MixingView::centerSource(Source *s)
{
// calculate screen area visible in the default view
GlmToolkit::AxisAlignedBoundingBox view_box;
glm::mat4 modelview = GlmToolkit::transform(scene.root()->translation_, scene.root()->rotation_, scene.root()->scale_);
view_box.extend( Rendering::manager().unProject(glm::vec2(0.f, Rendering::manager().mainWindow().height()), modelview) );
view_box.extend( Rendering::manager().unProject(glm::vec2(Rendering::manager().mainWindow().width(), 0.f), modelview) );
// check if upper-left corner of source is in view box
glm::vec3 pos_source = s->group(mode_)->translation_ + glm::vec3( -s->group(mode_)->scale_.x, s->group(mode_)->scale_.y, 0.f);
if ( !view_box.contains(pos_source)) {
// not visible so shift view
glm::vec2 screenpoint = glm::vec2(500.f, 20.f) * Rendering::manager().mainWindow().dpiScale();
glm::vec3 pos_to = Rendering::manager().unProject(screenpoint, scene.root()->transform_);
glm::vec4 pos_delta = glm::vec4(pos_to.x, pos_to.y, 0.f, 0.f) - glm::vec4(pos_source.x, pos_source.y, 0.f, 0.f);
pos_delta = scene.root()->transform_ * pos_delta;
scene.root()->translation_ += glm::vec3(pos_delta);
}
}
void MixingView::update(float dt)
{
View::update(dt);
// // always update the mixinggroups
// for (auto g = groups_.begin(); g != groups_.end(); g++)
// (*g)->update(dt);
// a more complete update is requested
// for mixing, this means restore position of the fading slider
if (View::need_deep_update_ > 0) {
//
// Set limbo scale according to session
//
const float p = CLAMP(Mixer::manager().session()->activationThreshold(), MIXING_MIN_THRESHOLD, MIXING_MAX_THRESHOLD);
limbo_slider_root_->translation_.y = p;
limbo_->scale_ = glm::vec3(p, p, 1.f);
//
// prevent invalid scaling
//
float s = CLAMP(scene.root()->scale_.x, MIXING_MIN_SCALE, MIXING_MAX_SCALE);
scene.root()->scale_.x = s;
scene.root()->scale_.y = s;
}
// the current view is the mixing view
if (Mixer::manager().view() == this ){
//
// Set slider to match the actual fading of the session
//
float f = Mixer::manager().session()->fading();
// reverse calculate angle from fading & move slider
slider_root_->rotation_.z = SIGN(-slider_root_->rotation_.z) * asin(f) * -2.f;
// visual feedback on mixing circle
f = 1.f - f;
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
// update the selection overlay
updateSelectionOverlay();
}
}
std::pair<Node *, glm::vec2> MixingView::pick(glm::vec2 P)
{
// get picking from generic View
std::pair<Node *, glm::vec2> pick = View::pick(P);
// deal with internal interactive objects
if ( pick.first == button_white_ || pick.first == button_black_ ) {
// animate clic
pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f));
// animated fading in session
if (pick.first == button_white_)
Mixer::manager().session()->setFadingTarget(0.f, 500.f);
else
Mixer::manager().session()->setFadingTarget(1.f, 500.f);
}
else if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
openContextMenu(MENU_SELECTION);
}
else {
// get if a source was picked
Source *s = Mixer::manager().findSource(pick.first);
if (s != nullptr) {
// pick on the lock icon; unlock source
if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
lock(s, false);
// pick = { s->locker_, pick.second };
pick = { nullptr, glm::vec2(0.f) };
}
// pick on the open lock icon; lock source and cancel pick
else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) {
lock(s, true);
pick = { nullptr, glm::vec2(0.f) };
}
// pick a locked source ; cancel pick
else if ( s->locked() ) {
pick = { nullptr, glm::vec2(0.f) };
}
// pick the symbol: ask to show editor
else if ( pick.first == s->symbol_ ) {
UserInterface::manager().showSourceEditor(s);
}
// pick on the mixing group rotation icon
else if ( pick.first == s->rotation_mixingroup_ ) {
if (UserInterface::manager().shiftModifier())
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
else
s->mixinggroup_->setAction( MixingGroup::ACTION_ROTATE_ALL );
}
// pick source of a mixing group
else if ( s->mixinggroup_ != nullptr ) {
if (UserInterface::manager().ctrlModifier()) {
SourceList linked = s->mixinggroup_->getCopy();
linked.remove(s);
if (Mixer::selection().empty())
Mixer::selection().add(linked);
}
else if (UserInterface::manager().shiftModifier())
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
else
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ALL );
}
}
}
return pick;
}
View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
{
View::Cursor ret = Cursor();
ret.type = Cursor_ResizeAll;
// unproject
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
// No source is given
if (!s) {
// if interaction with slider
if (pick.first == slider_) {
// apply rotation to match angle with mouse cursor
float angle = glm::orientedAngle( glm::normalize(glm::vec2(0.f, 1.0)), glm::normalize(glm::vec2(gl_Position_to)));
// snap on 0 and PI angles
if ( ABS_DIFF(angle, 0.f) < 0.05)
angle = 0.f;
else if ( ABS_DIFF(angle, M_PI) < 0.05)
angle = M_PI;
// animate slider (rotation angle on its parent)
slider_root_->rotation_.z = angle;
// calculate fading from angle
float f = sin( ABS(angle) * 0.5f);
Mixer::manager().session()->setFadingTarget(f);
// cursor feedback
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
std::ostringstream info;
info << "Output " << 100 - int(f * 100.0) << " %";
return Cursor(Cursor_Hand, info.str() );
}
else if (pick.first == limbo_slider_) {
// move slider scaling limbo area
const float p = CLAMP(gl_Position_to.y, MIXING_MIN_THRESHOLD, MIXING_MAX_THRESHOLD);
limbo_slider_root_->translation_.y = p;
limbo_->scale_ = glm::vec3(p, p, 1.f);
Mixer::manager().session()->setActivationThreshold(p);
// color change of arrow indicators
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, p < MIXING_MAX_THRESHOLD ? 0.15f : 0.01f );
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, p > MIXING_MIN_THRESHOLD ? 0.15f : 0.01f );
std::ostringstream info;
info << ICON_FA_SNOWFLAKE " Deactivation limit";
return Cursor(Cursor_Hand, info.str() );
}
// nothing to do
return Cursor();
}
//
// Interaction with source
//
// compute delta translation
s->group(mode_)->translation_ = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
// manage mixing group
if (s->mixinggroup_ != nullptr ) {
// inform mixing groups to follow the current source
if (Source::isCurrent(s) && s->mixinggroup_->action() > MixingGroup::ACTION_UPDATE) {
s->mixinggroup_->follow(s);
// special cursor for rotation
if (s->mixinggroup_->action() == MixingGroup::ACTION_ROTATE_ALL)
ret.type = Cursor_Hand;
}
else
s->mixingGroup()->setAction(MixingGroup::ACTION_NONE);
}
// request update
s->touch();
// // trying to enter stash
// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) {
// // refuse to put an active source in stash
// if (s->active())
// s->group(mode_)->translation_ = s->stored_status_->translation_;
// else {
// Mixer::manager().conceal(s);
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE) - glm::vec3(0.1f, 0.1f, 0.f);
// }
// }
// else if ( Mixer::manager().concealed(s) ) {
// Mixer::manager().uncover(s);
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE);
// }
std::ostringstream info;
if (s->active()) {
info << "Alpha " << std::fixed << std::setprecision(3) << s->blendingShader()->color.a << " ";
info << ( (s->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
}
else
info << "Inactive " << ICON_FA_SNOWFLAKE;
// store action in history
current_action_ = s->name() + ": " + info.str();
// update cursor
ret.info = info.str();
return ret;
}
void MixingView::terminate()
{
View::terminate();
// terminate all mixing group actions
for (auto g = Mixer::manager().session()->beginMixingGroup();
g != Mixer::manager().session()->endMixingGroup(); ++g)
(*g)->setAction( MixingGroup::ACTION_FINISH );
}
View::Cursor MixingView::over (glm::vec2 pos)
{
View::Cursor ret = Cursor();
std::pair<Node *, glm::vec2> pick = View::pick(pos);
// deal with internal interactive objects
if ( pick.first == slider_ ) {
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
ret.type = Cursor_Hand;
}
else
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
if ( pick.first == limbo_slider_ ) {
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, limbo_slider_root_->translation_.y < MIXING_MAX_THRESHOLD ? 0.1f : 0.01f );
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, limbo_slider_root_->translation_.y > MIXING_MIN_THRESHOLD ? 0.1f : 0.01f );
ret.type = Cursor_Hand;
}
else {
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
}
return ret;
}
void MixingView::arrow (glm::vec2 movement)
{
static float accumulator = 0.f;
accumulator += dt_;
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
bool first = true;
glm::vec3 delta_translation(0.f);
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// individual move with SHIFT
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
continue;
Group *sourceNode = (*it)->group(mode_);
glm::vec3 dest_translation(0.f);
if (first) {
// dest starts at current
dest_translation = sourceNode->translation_;
// + ALT : discrete displacement
if (UserInterface::manager().altModifier()) {
if (accumulator > 100.f) {
dest_translation += glm::sign(gl_delta) * 0.1f;
dest_translation.x = ROUND(dest_translation.x, 10.f);
dest_translation.y = ROUND(dest_translation.y, 10.f);
accumulator = 0.f;
}
else
break;
}
else {
// normal case: dest += delta
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
accumulator = 0.f;
}
// store action in history
std::ostringstream info;
if ((*it)->active()) {
info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
}
else
info << "Inactive " << ICON_FA_SNOWFLAKE;
current_action_ = (*it)->name() + ": " + info.str();
// delta for others to follow
delta_translation = dest_translation - sourceNode->translation_;
}
else {
// dest = current + delta from first
dest_translation = sourceNode->translation_ + delta_translation;
}
// apply & request update
sourceNode->translation_ = dest_translation;
(*it)->touch();
first = false;
}
}
void MixingView::setAlpha(Source *s)
{
if (!s)
return;
// move the layer node of the source
Group *sourceNode = s->group(mode_);
glm::vec2 mix_pos = glm::vec2(DEFAULT_MIXING_TRANSLATION);
for(NodeSet::iterator it = scene.ws()->begin(); it != scene.ws()->end(); ++it) {
// avoid superposing icons: distribute equally
if ( glm::distance(glm::vec2((*it)->translation_), mix_pos) < DELTA_ALPHA) {
mix_pos += glm::vec2(-0.03f, 0.03f);
}
}
sourceNode->translation_.x = mix_pos.x;
sourceNode->translation_.y = mix_pos.y;
// request update
s->touch();
}
void MixingView::updateSelectionOverlay()
{
View::updateSelectionOverlay();
if (overlay_selection_->visible_) {
// calculate bbox on selection
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
overlay_selection_->scale_ = selection_box.scale();
overlay_selection_->translation_ = selection_box.center();
// slightly extend the boundary of the selection
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.01f, 0.01f, 1.f) / overlay_selection_->scale_;
}
}
#define CIRCLE_PIXELS 64
#define CIRCLE_PIXEL_RADIUS 1024.f
//#define CIRCLE_PIXELS 256
//#define CIRCLE_PIXEL_RADIUS 16384.0
//#define CIRCLE_PIXELS 1024
//#define CIRCLE_PIXEL_RADIUS 262144.0
float sin_quad_texture(float x, float y) {
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ) / CIRCLE_PIXEL_RADIUS, 0.f, 1.f ) );
float D = sqrt( ( x * x )/ CIRCLE_PIXEL_RADIUS + ( y * y )/ CIRCLE_PIXEL_RADIUS );
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
}
uint textureMixingQuadratic()
{
static GLuint texid = 0;
if (texid == 0) {
// generate the texture with alpha exactly as computed for sources
GLubyte matrix[CIRCLE_PIXELS*CIRCLE_PIXELS * 4];
int l = -CIRCLE_PIXELS / 2 + 1;
for (int i = 0; i < CIRCLE_PIXELS ; ++i) {
int c = -CIRCLE_PIXELS / 2 + 1;
for (int j = 0; j < CIRCLE_PIXELS ; ++j) {
// distance to the center
GLfloat distance = sin_quad_texture( (float) c , (float) l );
// distance = 1.f - (GLfloat) ((c * c) + (l * l)) / CIRCLE_PIXEL_RADIUS; // quadratic
// distance = 1.f - (GLfloat) sqrt( (GLfloat) ((c * c) + (l * l))) / (GLfloat) sqrt(CIRCLE_PIXEL_RADIUS); // linear
// transparency
GLfloat alpha = 255.f * CLAMP( distance , 0.f, 1.f);
GLubyte A = static_cast<GLubyte>(alpha);
// luminance adjustment
GLfloat luminance = 255.f * CLAMP( 0.2f + 0.75f * distance, 0.f, 1.f);
GLubyte L = static_cast<GLubyte>(luminance);
// fill pixel RGBA
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 0 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 1 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 2 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 3 ] = A;
++c;
}
++l;
}
// setup texture
glGenTextures(1, &texid);
glBindTexture(GL_TEXTURE_2D, texid);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, CIRCLE_PIXELS, CIRCLE_PIXELS);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, CIRCLE_PIXELS, CIRCLE_PIXELS, GL_BGRA, GL_UNSIGNED_BYTE, matrix);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
}
return texid;
}

49
MixingView.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef MIXINGVIEW_H
#define MIXINGVIEW_H
#include "View.h"
//class MixingGroup;
class MixingView : public View
{
public:
MixingView();
// non assignable class
MixingView(MixingView const&) = delete;
MixingView& operator=(MixingView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
void centerSource(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>) override;
void terminate() override;
Cursor over (glm::vec2) override;
void arrow (glm::vec2) override;
void setAlpha (Source *s);
private:
void updateSelectionOverlay() override;
float limbo_scale_;
Group *slider_root_;
Disk *slider_;
Disk *button_white_;
Disk *button_black_;
// Disk *stashCircle_;
Mesh *mixingCircle_;
Mesh *circle_;
Mesh *limbo_;
Group *limbo_slider_root_;
Mesh *limbo_up_, *limbo_down_;
Disk *limbo_slider_;
};
#endif // MIXINGVIEW_H

272
MultiFileSource.cpp Normal file
View File

@@ -0,0 +1,272 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <sstream>
#include <algorithm>
#include "defines.h"
#include "ImageShader.h"
#include "Resource.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "GstToolkit.h"
#include "BaseToolkit.h"
#include "SystemToolkit.h"
#include "MediaPlayer.h"
#include "Log.h"
#include "MultiFileSource.h"
// example test gstreamer pipelines
//
// multifile : sequence of numbered images
// gst-launch-1.0 multifilesrc location="/home/bhbn/Images/sequence/frames%03d.png" caps="image/png,framerate=\(fraction\)12/1" loop=1 ! decodebin ! videoconvert ! autovideosink
//
// imagesequencesrc : sequence of numbered images (cannot loop)
// gst-launch-1.0 imagesequencesrc location=frames%03d.png start-index=1 framerate=24/1 ! decodebin ! videoconvert ! autovideosink
//
MultiFileSequence::MultiFileSequence() : width(0), height(0), min(0), max(0)
{
}
MultiFileSequence::MultiFileSequence(const std::list<std::string> &list_files)
{
location = BaseToolkit::common_numbered_pattern(list_files, &min, &max);
// sanity check: the location pattern looks like a filename and seems consecutive numbered
if ( SystemToolkit::extension_filename(location).empty() ||
SystemToolkit::path_filename(location) != SystemToolkit::path_filename(list_files.front()) ||
list_files.size() != (size_t) (max - min) + 1 ) {
Log::Info("MultiFileSequence '%s' invalid.", location.c_str());
location.clear();
}
if ( !location.empty() ) {
MediaInfo media = MediaPlayer::UriDiscoverer( GstToolkit::filename_to_uri( list_files.front() ) );
if (media.valid && media.isimage) {
codec.resize(media.codec_name.size());
std::transform(media.codec_name.begin(), media.codec_name.end(), codec.begin(), ::tolower);
width = media.width;
height = media.height;
}
else
Log::Info("MultiFileSequence '%s' does not list images.", location.c_str());
}
}
bool MultiFileSequence::valid() const
{
return !( location.empty() || codec.empty() || width < 1 || height < 1 || max == min);
}
inline MultiFileSequence& MultiFileSequence::operator = (const MultiFileSequence& b)
{
if (this != &b) {
this->width = b.width;
this->height = b.height;
this->min = b.min;
this->max = b.max;
this->location = b.location;
this->codec = b.codec;
}
return *this;
}
bool MultiFileSequence::operator != (const MultiFileSequence& b)
{
return ( location != b.location || codec != b.codec || width != b.width ||
height != b.height || min != b.min || max != b.max );
}
MultiFile::MultiFile() : Stream(), src_(nullptr)
{
}
void MultiFile::open (const MultiFileSequence &sequence, uint framerate )
{
if (sequence.location.empty())
return;
std::ostringstream gstreamer_pipeline;
gstreamer_pipeline << "multifilesrc name=src location=\"";
gstreamer_pipeline << sequence.location;
gstreamer_pipeline << "\" caps=\"image/";
gstreamer_pipeline << sequence.codec;
gstreamer_pipeline << ",framerate=(fraction)";
gstreamer_pipeline << framerate;
gstreamer_pipeline << "/1\" loop=1";
gstreamer_pipeline << " start-index=";
gstreamer_pipeline << sequence.min;
gstreamer_pipeline << " stop-index=";
gstreamer_pipeline << sequence.max;
gstreamer_pipeline << " ! decodebin ! videoconvert";
// (private) open stream
Stream::open(gstreamer_pipeline.str(), sequence.width, sequence.height);
// keep multifile source for dynamic properties change
src_ = gst_bin_get_by_name (GST_BIN (pipeline_), "src");
}
void MultiFile::close ()
{
if (src_ != nullptr) {
gst_object_unref (src_);
src_ = nullptr;
}
Stream::close();
}
void MultiFile::setIndex(int val)
{
if (src_) {
g_object_set (src_, "index", val, NULL);
}
}
int MultiFile::index()
{
int val = 0;
if (src_) {
g_object_get (src_, "index", &val, NULL);
}
return val;
}
void MultiFile::setProperties (int begin, int end, int loop)
{
if (src_) {
g_object_set (src_, "start-index", MAX(begin, 0), NULL);
g_object_set (src_, "stop-index", MAX(end, 0), NULL);
g_object_set (src_, "loop", MIN(loop, 1), NULL);
}
}
MultiFileSource::MultiFileSource (uint64_t id) : StreamSource(id), framerate_(0), begin_(-1), end_(INT_MAX), loop_(1)
{
// create stream
stream_ = static_cast<Stream *>( new MultiFile );
// set symbol
symbol_ = new Symbol(Symbol::SEQUENCE, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
void MultiFileSource::setFiles (const std::list<std::string> &list_files, uint framerate)
{
setSequence(MultiFileSequence(list_files), framerate);
}
void MultiFileSource::setSequence (const MultiFileSequence &sequence, uint framerate)
{
framerate_ = CLAMP( framerate, 1, 30);
sequence_ = sequence;
if (sequence_.valid())
{
// open gstreamer
multifile()->open( sequence_, framerate_ );
stream_->play(true);
// validate range and apply loop_
setRange(begin_, end_);
// will be ready after init and one frame rendered
ready_ = false;
}
}
void MultiFileSource::setFramerate (uint framerate)
{
if (multifile()) {
setSequence(sequence_, framerate);
}
}
void MultiFileSource::setLoop (bool on)
{
if (multifile()) {
loop_ = on ? 1 : 0;
multifile()->setProperties (begin_, end_, loop_);
}
}
void MultiFileSource::setRange (int begin, int end)
{
begin_ = glm::clamp( begin, sequence_.min, sequence_.max );
end_ = glm::clamp( end , sequence_.min, sequence_.max );
begin_ = glm::min( begin_, end_ );
end_ = glm::max( begin_, end_ );
if (multifile())
multifile()->setProperties (begin_, end_, loop_);
}
void MultiFileSource::replay ()
{
if (multifile()) {
multifile()->setIndex (begin_);
stream_->rewind();
}
}
guint64 MultiFileSource::playtime () const
{
guint64 time = 0;
if (multifile())
time += multifile()->index();
time *= GST_SECOND;
time /= framerate_;
return time;
}
void MultiFileSource::accept (Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}
MultiFile *MultiFileSource::multifile () const
{
return dynamic_cast<MultiFile *>(stream_);
}
glm::ivec2 MultiFileSource::icon () const
{
return glm::ivec2(ICON_SOURCE_SEQUENCE);
}
std::string MultiFileSource::info() const
{
return std::string("sequence '") + sequence_.location + "'";
}

83
MultiFileSource.h Normal file
View File

@@ -0,0 +1,83 @@
#ifndef MULTIFILESOURCE_H
#define MULTIFILESOURCE_H
#include <string>
#include <list>
#include "StreamSource.h"
struct MultiFileSequence {
std::string location;
std::string codec;
uint width;
uint height;
int min;
int max;
MultiFileSequence ();
MultiFileSequence (const std::list<std::string> &list_files);
bool valid () const;
MultiFileSequence& operator = (const MultiFileSequence& b);
bool operator != (const MultiFileSequence& b);
};
class MultiFile : public Stream
{
public:
MultiFile ();
void open (const MultiFileSequence &sequence, uint framerate = 30);
void close () override;
// dynamic change of gstreamer multifile source properties
void setProperties(int begin, int end, int loop);
// image index
int index();
void setIndex(int val);
protected:
GstElement *src_ ;
};
class MultiFileSource : public StreamSource
{
public:
MultiFileSource (uint64_t id = 0);
// Source interface
void accept (Visitor& v) override;
void replay () override;
guint64 playtime () const override;
// StreamSource interface
Stream *stream () const override { return stream_; }
glm::ivec2 icon() const override;
std::string info() const override;
// specific interface
void setFiles (const std::list<std::string> &list_files, uint framerate);
void setSequence (const MultiFileSequence &sequence, uint framerate);
inline MultiFileSequence sequence () const { return sequence_; }
void setFramerate (uint fps);
inline uint framerate () const { return framerate_; }
void setLoop (bool on);
inline bool loop () const { return loop_ > 0 ? true : false; }
void setRange (int begin, int end);
inline int begin() const { return begin_; }
inline int end () const { return end_; }
MultiFile *multifile () const;
private:
MultiFileSequence sequence_;
uint framerate_;
int begin_, end_, loop_;
};
#endif // MULTIFILESOURCE_H

View File

@@ -1,3 +1,22 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <sstream>
#include <thread>
@@ -5,17 +24,17 @@
#include <future>
#include <glm/gtc/matrix_transform.hpp>
#include <gst/pbutils/pbutils.h>
#include <gst/gst.h>
#include "osc/OscOutboundPacketStream.h"
#include "SystemToolkit.h"
#include "defines.h"
#include "Stream.h"
#include "Decorations.h"
#include "Visitor.h"
#include "Log.h"
#include "Connection.h"
#include "NetworkSource.h"
@@ -25,7 +44,7 @@
// this is called when receiving an answer for streaming request
void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
void NetworkStream::ResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint )
{
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
@@ -67,10 +86,10 @@ void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
}
NetworkStream::NetworkStream(): Stream(), receiver_(nullptr)
NetworkStream::NetworkStream(): Stream(),
receiver_(nullptr), received_config_(false), connected_(false)
{
received_config_ = false;
connected_ = false;
}
glm::ivec2 NetworkStream::resolution() const
@@ -204,7 +223,7 @@ void NetworkStream::update()
{
Stream::update();
if ( !ready_ && !failed_ && received_config_)
if ( !opened_ && !failed_ && received_config_)
{
// only once
received_config_ = false;
@@ -242,22 +261,34 @@ void NetworkStream::update()
// general case : create pipeline and open
if (!failed_) {
// build the pipeline depending on stream info
std::ostringstream pipeline;
// get generic pipeline string
std::string pipelinestring = NetworkToolkit::protocol_receive_pipeline[config_.protocol];
// find placeholder for PORT
int xxxx = pipelinestring.find("XXXX");
// keep beginning of pipeline
pipeline << pipelinestring.substr(0, xxxx);
// Replace 'XXXX' by info on port config
pipeline << parameter;
// keep ending of pipeline
pipeline << pipelinestring.substr(xxxx + 4);
// add a videoconverter
pipeline << " ! videoconvert";
// find placeholder for PORT or SHH socket
size_t xxxx = pipelinestring.find("XXXX");
if (xxxx != std::string::npos)
// Replace 'XXXX' by info on port config
pipelinestring.replace(xxxx, 4, parameter);
// find placeholder for WIDTH
size_t wwww = pipelinestring.find("WWWW");
if (wwww != std::string::npos)
// Replace 'WWWW' by width
pipelinestring.replace(wwww, 4, std::to_string(config_.width) );
// find placeholder for HEIGHT
size_t hhhh = pipelinestring.find("HHHH");
if (hhhh != std::string::npos)
// Replace 'WWWW' by height
pipelinestring.replace(hhhh, 4, std::to_string(config_.height) );
// add a videoconverter
pipelinestring.append(" ! videoconvert ");
#ifdef NETWORK_DEBUG
Log::Info("Openning pipeline %s", pipelinestring.c_str());
#endif
// open the pipeline with generic stream class
Stream::open(pipeline.str(), config_.width, config_.height);
Stream::open(pipelinestring, config_.width, config_.height);
}
}
else {
@@ -268,14 +299,14 @@ void NetworkStream::update()
}
NetworkSource::NetworkSource() : StreamSource()
NetworkSource::NetworkSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = (Stream *) new NetworkStream;
stream_ = static_cast<Stream *>( new NetworkStream );
// set symbol
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.8f, 0.8f, 0.01f));
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
@@ -292,11 +323,13 @@ NetworkStream *NetworkSource::networkStream() const
void NetworkSource::setConnection(const std::string &nameconnection)
{
connection_name_ = nameconnection;
Log::Notify("Network Source connecting to '%s'", connection_name_.c_str());
// open network stream
networkStream()->connect( connection_name_ );
stream_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
@@ -312,5 +345,13 @@ void NetworkSource::accept(Visitor& v)
v.visit(*this);
}
glm::ivec2 NetworkSource::icon() const
{
return glm::ivec2(ICON_SOURCE_NETWORK);
}
std::string NetworkSource::info() const
{
return std::string("connected to '") + connection_name_ + "'";
}

View File

@@ -1,32 +1,13 @@
#ifndef NETWORKSOURCE_H
#define NETWORKSOURCE_H
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "osc/OscOutboundPacketStream.h"
#include "ip/UdpSocket.h"
#include "NetworkToolkit.h"
#include "Connection.h"
#include "StreamSource.h"
class NetworkStream;
class StreamerResponseListener : public osc::OscPacketListener
{
protected:
class NetworkStream *parent_;
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
public:
inline void setParent(NetworkStream *s) { parent_ = s; }
};
class NetworkStream : public Stream
{
friend class StreamerResponseListener;
public:
NetworkStream();
@@ -42,10 +23,22 @@ public:
std::string clientAddress() const;
std::string serverAddress() const;
protected:
class ResponseListener : public osc::OscPacketListener
{
protected:
class NetworkStream *parent_;
virtual void ProcessMessage( const osc::ReceivedMessage& m,
const IpEndpointName& remoteEndpoint );
public:
inline void setParent(NetworkStream *s) { parent_ = s; }
ResponseListener() : parent_(nullptr) {}
};
private:
// connection information
ConnectionInfo streamer_;
StreamerResponseListener listener_;
ResponseListener listener_;
UdpListeningReceiveSocket *receiver_;
std::atomic<bool> received_config_;
std::atomic<bool> connected_;
@@ -59,7 +52,7 @@ class NetworkSource : public StreamSource
std::string connection_name_;
public:
NetworkSource();
NetworkSource(uint64_t id = 0);
~NetworkSource();
// Source interface
@@ -73,7 +66,8 @@ public:
void setConnection(const std::string &nameconnection);
std::string connection() const;
glm::ivec2 icon() const override { return glm::ivec2(18, 11); }
glm::ivec2 icon() const override;
std::string info() const override;
};

View File

@@ -1,3 +1,21 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
@@ -60,32 +78,48 @@
* RCV
* gst-launch-1.0 shmsrc is-live=true socket-path=/tmp/blah ! video/x-raw, format=RGB, framerate=30/1, width=320, height=240 ! videoconvert ! autovideosink
*
* RTP UDP JPEG
*
* SND
* gst-launch-1.0 videotestsrc is-live=true ! video/x-raw, format=RGB, framerate=30/1 ! videoconvert ! video/x-raw, format=I420 ! jpegenc quality=95 ! rtpjpegpay ! udpsink port=5000 host=127.0.0
* RCV
* gst-launch-1.0 udpsrc buffer-size=200000 port=5000 ! application/x-rtp,encoding-name=JPEG ! rtpjpegdepay ! queue max-size-buffers=10 ! jpegdec ! videoconvert ! video/x-raw, format=RGB ! autovideosink
*
* */
const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
"Shared Memory",
"RTP JPEG Stream",
"RTP H264 Stream",
"RTP JPEG Broadcast",
"RTP H264 Broadcast"
"RAW Images",
"JPEG Stream",
"H264 Stream",
"JPEG Broadcast",
"H264 Broadcast",
"RGB Shared Memory"
};
const std::vector<std::string> NetworkToolkit::protocol_send_pipeline {
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc ! rtpjpegpay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink"
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=3 ! rtpvrawpay ! application/x-rtp,sampling=RGB ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc quality=85 ! rtpjpegpay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" pass=4 quantizer=22 speed-preset=2 ! rtph264pay aggregate-mode=1 ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc idct-method=float ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink"
};
const std::vector<std::string> NetworkToolkit::protocol_receive_pipeline {
"udpsrc buffer-size=200000 port=XXXX caps=\"application/x-rtp,media=(string)video,encoding-name=(string)RAW,sampling=(string)RGB,width=(string)WWWW,height=(string)HHHH\" ! rtpvrawdepay ! queue max-size-buffers=10",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG ! rtpjpegdepay ! queue max-size-buffers=10 ! jpegdec",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264 ! rtph264depay ! queue max-size-buffers=10 ! avdec_h264",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264 ! rtpstreamdepay ! rtph264depay ! avdec_h264",
"shmsrc socket-path=XXXX ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG,payload=26,clock-rate=90000 ! queue max-size-buffers=10 ! rtpjpegdepay ! jpegdec",
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264,payload=96,clock-rate=90000 ! queue ! rtph264depay ! avdec_h264",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG,payload=26,clock-rate=90000 ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtph264depay ! avdec_h264"
};
const std::vector< std::pair<std::string, std::string> > NetworkToolkit::protocol_h264_send_pipeline {
// {"vtenc_h264_hw", "video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! rtph264pay aggregate-mode=1 ! udpsink name=sink"},
{"nvh264enc", "video/x-raw, format=RGBA, framerate=30/1 ! queue max-size-buffers=10 ! nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"},
{"vaapih264enc", "video/x-raw, format=NV12, framerate=30/1 ! queue max-size-buffers=10 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"}
};
bool initialized_ = false;
@@ -95,12 +129,11 @@ std::vector<unsigned long> iplongs_;
void add_interface(int fd, const char *name) {
struct ifreq ifreq;
char host[128];
memset(&ifreq, 0, sizeof ifreq);
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
int family;
switch(family=ifreq.ifr_addr.sa_family) {
char host[128];
switch(ifreq.ifr_addr.sa_family) {
case AF_INET:
case AF_INET6:
getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host, 0, 0, NI_NUMERICHOST);
@@ -122,14 +155,14 @@ void add_interface(int fd, const char *name) {
void list_interfaces()
{
struct ifreq *ifreq;
struct ifconf ifconf;
char buf[16384];
int fd=socket(PF_INET, SOCK_DGRAM, 0);
if(fd > -1) {
struct ifconf ifconf;
ifconf.ifc_len=sizeof buf;
ifconf.ifc_buf=buf;
if(ioctl(fd, SIOCGIFCONF, &ifconf)==0) {
struct ifreq *ifreq;
ifreq=ifconf.ifc_req;
for(int i=0;i<ifconf.ifc_len;) {
size_t len;

View File

@@ -4,6 +4,11 @@
#include <string>
#include <vector>
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "ip/UdpSocket.h"
#define OSC_SEPARATOR '/'
#define OSC_PREFIX "/vimix"
#define OSC_PING "/ping"
#define OSC_PONG "/pong"
@@ -12,22 +17,18 @@
#define OSC_STREAM_REJECT "/reject"
#define OSC_STREAM_DISCONNECT "/disconnect"
#define MAX_HANDSHAKE 20
#define HANDSHAKE_PORT 71310
#define STREAM_REQUEST_PORT 71510
#define OSC_DIALOG_PORT 71010
#define IP_MTU_SIZE 1536
namespace NetworkToolkit
{
typedef enum {
SHM_RAW = 0,
UDP_RAW = 0,
UDP_JPEG,
UDP_H264,
TCP_JPEG,
TCP_H264,
SHM_RAW,
DEFAULT
} Protocol;
@@ -66,6 +67,7 @@ struct StreamConfig {
extern const char* protocol_name[DEFAULT];
extern const std::vector<std::string> protocol_send_pipeline;
extern const std::vector< std::pair<std::string, std::string> > protocol_h264_send_pipeline;
extern const std::vector<std::string> protocol_receive_pipeline;
std::string hostname();

23
Overlay.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "Overlay.h"
Overlay::Overlay()
{
}
SnapshotOverlay::SnapshotOverlay() : Overlay()
{
}
void SnapshotOverlay::draw()
{
}
void SnapshotOverlay::update (float dt)
{
}

38
Overlay.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef OVERLAY_H
#define OVERLAY_H
#include "View.h"
class Overlay
{
public:
Overlay();
virtual void update (float dt) = 0;
virtual void draw () = 0;
virtual std::pair<Node *, glm::vec2> pick(glm::vec2) {
return { nullptr, glm::vec2(0.f) };
}
virtual View::Cursor grab (Source*, glm::vec2, glm::vec2, std::pair<Node *, glm::vec2>) {
return View::Cursor ();
}
virtual View::Cursor over (glm::vec2) {
return View::Cursor ();
}
};
class SnapshotOverlay: public Overlay
{
public:
SnapshotOverlay();
void draw () override;
void update (float dt) override;
};
#endif // OVERLAY_H

View File

@@ -1,8 +1,25 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include "PatternSource.h"
#include "defines.h"
#include "ImageShader.h"
#include "Resource.h"
@@ -10,91 +27,70 @@
#include "Stream.h"
#include "Visitor.h"
#include "Log.h"
#include "GstToolkit.h"
#define MAX_PATTERN 23
#include "PatternSource.h"
// smpte (0) SMPTE 100%% color bars
// snow (1) Random (television snow)
// black (2) 100%% Black
// white (3) 100%% White
// red (4) Red
// green (5) Green
// blue (6) Blue
// checkers-1 (7) Checkers 1px
// checkers-2 (8) Checkers 2px
// checkers-4 (9) Checkers 4px
// checkers-8 (10) Checkers 8px
// circular (11) Circular
// blink (12) Blink
// smpte75 (13) SMPTE 75%% color bars
// zone-plate (14) Zone plate
// gamut (15) Gamut checkers
// chroma-zone-plate (16) Chroma zone plate
// solid-color (17) Solid color
// ball (18) Moving ball
// smpte100 (19) SMPTE 100%% color bars
// bar (20) Bar
// pinwheel (21) Pinwheel
// spokes (22) Spokes
// gradient (23) Gradient
// colors (24) Colors
const char* pattern_internal_[24] = { "videotestsrc pattern=black",
"videotestsrc pattern=white",
"videotestsrc pattern=gradient",
"videotestsrc pattern=checkers-1 ! video/x-raw,format=GRAY8 ! videoconvert",
"videotestsrc pattern=checkers-8 ! video/x-raw,format=GRAY8 ! videoconvert",
"videotestsrc pattern=circular",
"frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert",
"videotestsrc pattern=pinwheel",
"videotestsrc pattern=spokes",
"videotestsrc pattern=red",
"videotestsrc pattern=green",
"videotestsrc pattern=blue",
"videotestsrc pattern=smpte100",
"videotestsrc pattern=colors",
"videotestsrc pattern=smpte",
"videotestsrc pattern=snow",
"videotestsrc pattern=blink",
"videotestsrc pattern=zone-plate",
"videotestsrc pattern=chroma-zone-plate",
"videotestsrc pattern=bar horizontal-speed=5",
"videotestsrc pattern=ball",
"frei0r-src-ising0r",
"videotestsrc pattern=black ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ",
"videotestsrc pattern=black ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" "
};
//
// Fill the list of patterns videotestsrc
//
// Label (for display), feature (for test), pipeline (for gstreamer), animated (true/false), available (false by default)
std::vector<pattern_descriptor> Pattern::patterns_ = {
{ "Black", "videotestsrc", "videotestsrc pattern=black", false, false },
{ "White", "videotestsrc", "videotestsrc pattern=white", false, false },
{ "Gradient", "videotestsrc", "videotestsrc pattern=gradient", false, false },
{ "Checkers 1x1 px", "videotestsrc", "videotestsrc pattern=checkers-1 ! videobalance saturation=0 contrast=1.5", false, false },
{ "Checkers 8x8 px", "videotestsrc", "videotestsrc pattern=checkers-8 ! videobalance saturation=0 contrast=1.5", false, false },
{ "Circles", "videotestsrc", "videotestsrc pattern=circular", false, false },
{ "Lissajous", "frei0r-src-lissajous0r", "frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert", false, false },
{ "Pinwheel", "videotestsrc", "videotestsrc pattern=pinwheel", false, false },
{ "Spokes", "videotestsrc", "videotestsrc pattern=spokes", false, false },
{ "Red", "videotestsrc", "videotestsrc pattern=red", false, false },
{ "Green", "videotestsrc", "videotestsrc pattern=green", false, false },
{ "Blue", "videotestsrc", "videotestsrc pattern=blue", false, false },
{ "Color bars", "videotestsrc", "videotestsrc pattern=smpte100", false, false },
{ "RGB grid", "videotestsrc", "videotestsrc pattern=colors", false, false },
{ "SMPTE test pattern", "videotestsrc", "videotestsrc pattern=smpte", true, false },
{ "Television snow", "videotestsrc", "videotestsrc pattern=snow", true, false },
{ "Blink", "videotestsrc", "videotestsrc pattern=blink", true, false },
{ "Fresnel zone plate", "videotestsrc", "videotestsrc pattern=zone-plate kx2=XXX ky2=YYY kt=4", true, false },
{ "Chroma zone plate", "videotestsrc", "videotestsrc pattern=chroma-zone-plate kx2=XXX ky2=YYY kt=4", true, false },
{ "Bar moving", "videotestsrc", "videotestsrc pattern=bar horizontal-speed=5", true, false },
{ "Ball bouncing", "videotestsrc", "videotestsrc pattern=ball", true, false },
{ "Blob", "frei0r-src-ising0r", "frei0r-src-ising0r", true, false },
{ "Timer", "timeoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ", true, false },
{ "Clock", "clockoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ", true, false },
{ "Resolution", "textoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! textoverlay text=\"XXXX x YYYY px\" halignment=center valignment=center font-desc=\"Sans, 52\" ", false, false },
{ "Frame", "videobox", "videotestsrc pattern=solid-color foreground-color=0 ! videobox fill=white top=-10 bottom=-10 left=-10 right=-10", false, false },
{ "Cross", "textoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! textoverlay text=\"+\" halignment=center valignment=center font-desc=\"Sans, 22\" ", false, false },
{ "Grid", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.35", false, false },
{ "Point Grid", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.4", false, false },
{ "Ruler", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.9", false, false },
{ "RGB noise", "frei0r-filter-rgbnoise", "videotestsrc pattern=black ! frei0r-filter-rgbnoise noise=0.6", true, false },
{ "Philips test pattern", "frei0r-src-test-pat-b", "frei0r-src-test-pat-b type=0.7 ", false, false }
};
std::vector<std::string> Pattern::pattern_types = { "Black",
"White",
"Gradient",
"Checkers 1x1 px",
"Checkers 8x8 px",
"Circles",
"Lissajous",
"Pinwheel",
"Spokes",
"Red",
"Green",
"Blue",
"Color bars",
"RGB grid",
"SMPTE test pattern",
"Television snow",
"Blink",
"Fresnel zone plate",
"Chroma zone plate",
"Bar moving",
"Ball bouncing",
"Blob",
"Timer",
"Clock"
};
Pattern::Pattern() : Stream(), type_(MAX_PATTERN+1) // invalid pattern
Pattern::Pattern() : Stream(), type_(UINT_MAX) // invalid pattern
{
}
pattern_descriptor Pattern::get(uint type)
{
// check availability of feature to use this pattern
if (!patterns_[type].available)
patterns_[type].available = GstToolkit::has_feature(patterns_[type].feature);
// return struct
return patterns_[type];
}
uint Pattern::count()
{
return patterns_.size();
}
glm::ivec2 Pattern::resolution()
{
return glm::ivec2( width_, height_);
@@ -103,46 +99,66 @@ glm::ivec2 Pattern::resolution()
void Pattern::open( uint pattern, glm::ivec2 res )
{
type_ = MIN(pattern, MAX_PATTERN);
std::string gstreamer_pattern = pattern_internal_[type_];
// clamp type to be sure
type_ = MIN(pattern, Pattern::patterns_.size()-1);
std::string gstreamer_pattern = Pattern::patterns_[type_].pipeline;
// there is always a special case...
switch(type_)
{
case 18: // zone plates
case 17:
{
std::ostringstream oss;
oss << " kx2=" << (int)(res.x * 10.f / res.y) << " ky2=10 kt=4";
gstreamer_pattern += oss.str(); // Zone plate
}
break;
default:
break;
}
//
// pattern string post-processing: replace placeholders by resolution values
// XXXX, YYYY = resolution x and y
// XXX, YYY = resolution x and y / 10
//
// if there is a XXXX parameter to enter
std::string::size_type xxxx = gstreamer_pattern.find("XXXX");
if (xxxx != std::string::npos)
gstreamer_pattern = gstreamer_pattern.replace(xxxx, 4, std::to_string(res.x));
// if there is a YYYY parameter to enter
std::string::size_type yyyy = gstreamer_pattern.find("YYYY");
if (yyyy != std::string::npos)
gstreamer_pattern = gstreamer_pattern.replace(yyyy, 4, std::to_string(res.y));
// if there is a XXX parameter to enter
std::string::size_type xxx = gstreamer_pattern.find("XXX");
if (xxx != std::string::npos)
gstreamer_pattern = gstreamer_pattern.replace(xxx, 3, std::to_string(res.x/10));
// if there is a YYY parameter to enter
std::string::size_type yyy = gstreamer_pattern.find("YYY");
if (yyy != std::string::npos)
gstreamer_pattern = gstreamer_pattern.replace(yyy, 3, std::to_string(res.y/10));
// all patterns before 'SMPTE test pattern' are single frames (not animated)
single_frame_ = type_ < 14;
// remember if the pattern is to be updated once or animated
single_frame_ = !Pattern::patterns_[type_].animated;
// (private) open stream
Stream::open(gstreamer_pattern, res.x, res.y);
}
PatternSource::PatternSource() : StreamSource()
PatternSource::PatternSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = (Stream *) new Pattern;
stream_ = static_cast<Stream *>( new Pattern );
// set symbol
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.8f, 0.8f, 0.01f));
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
void PatternSource::setPattern(uint type, glm::ivec2 resolution)
{
Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str());
// open gstreamer with pattern
if ( Pattern::get(type).available) {
pattern()->open( (uint) type, resolution );
}
// revert to pattern Black if not available
else {
pattern()->open( 0, resolution );
Log::Warning("Pattern '%s' is not available in this version of vimix.", Pattern::get(type).label.c_str());
}
pattern()->open( (uint) type, resolution );
// play gstreamer
stream_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
void PatternSource::accept(Visitor& v)
@@ -158,3 +174,12 @@ Pattern *PatternSource::pattern() const
}
glm::ivec2 PatternSource::icon() const
{
return glm::ivec2(ICON_SOURCE_PATTERN);
}
std::string PatternSource::info() const
{
return std::string("pattern '") + Pattern::get(pattern()->type()).label + "'";
}

View File

@@ -5,10 +5,22 @@
#include "StreamSource.h"
typedef struct pattern_ {
std::string label;
std::string feature;
std::string pipeline;
bool animated;
bool available;
} pattern_descriptor;
class Pattern : public Stream
{
static std::vector<pattern_descriptor> patterns_;
public:
static std::vector<std::string> pattern_types;
static pattern_descriptor get(uint type);
static uint count();
Pattern();
void open( uint pattern, glm::ivec2 res);
@@ -23,7 +35,7 @@ private:
class PatternSource : public StreamSource
{
public:
PatternSource();
PatternSource(uint64_t id = 0);
// Source interface
void accept (Visitor& v) override;
@@ -35,7 +47,8 @@ public:
Pattern *pattern() const;
void setPattern(uint type, glm::ivec2 resolution);
glm::ivec2 icon() const override { return glm::ivec2(12, 5); }
glm::ivec2 icon() const override;
std::string info() const override;
};

View File

@@ -1,25 +1,44 @@
#include "PickingVisitor.h"
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "Log.h"
#include "Decorations.h"
#include "GlmToolkit.h"
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "Log.h"
#include "Decorations.h"
#include "GlmToolkit.h"
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(), force_(force)
#include "PickingVisitor.h"
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(),
force_(force), modelview_(glm::mat4(1.f))
{
modelview_ = glm::mat4(1.f);
points_.push_back( coordinates );
}
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(), force_(force)
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(),
force_(force), modelview_(glm::mat4(1.f))
{
modelview_ = glm::mat4(1.f);
points_.push_back( selectionstart );
points_.push_back( selection_end );
}
@@ -36,7 +55,7 @@ void PickingVisitor::visit(Group &n)
return;
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
@@ -126,6 +145,15 @@ void PickingVisitor::visit(Handles &n)
glm::vec4 S = glm::inverse(modelview_) * glm::vec4( 0.05f, 0.05f, 0.f, 0.f );
float scale = glm::length( glm::vec2(S) );
// extract rotation from modelview
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview_ * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview_ ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
glm::vec4 mirror = glm::sign(vec);
bool picked = false;
if ( n.type() == Handles::RESIZE ) {
// 4 corners
@@ -146,21 +174,28 @@ void PickingVisitor::visit(Handles &n)
}
else if ( n.type() == Handles::ROTATE ){
// the icon for rotation is on the right top corner at (0.12, 0.12) in scene coordinates
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
float l = glm::length( glm::vec2(vec) );
picked = glm::length( glm::vec2( 1.f + l, 1.f + l) - glm::vec2(P) ) < 1.5f * scale;
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, 0.12f, 0.f, 0.f ) );
picked = glm::length( glm::vec2( 1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::SCALE ){
// the icon for scaling is on the right bottom corner at (0.12, -0.12) in scene coordinates
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
float l = glm::length( glm::vec2(vec) );
picked = glm::length( glm::vec2( 1.f + l, -1.f - l) - glm::vec2(P) ) < 1.5f * scale;
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, -0.12f, 0.f, 0.f ) );
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::CROP ){
// the icon for cropping is on the left bottom corner at (0.12, 0.12) in scene coordinates
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, 0.12f, 0.f, 0.f ) );
picked = glm::length( glm::vec2( -1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::MENU ){
// the icon for restore is on the left top corner at (-0.12, 0.12) in scene coordinates
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
float l = glm::length( glm::vec2(vec) );
picked = glm::length( glm::vec2( -1.f - l, 1.f + l) - glm::vec2(P) ) < 1.5f * scale;
// the icon for menu is on the left top corner at (-0.12, 0.12) in scene coordinates
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( -0.12f, 0.12f, 0.f, 0.f ) );
picked = glm::length( glm::vec2( -1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
else if ( n.type() == Handles::LOCKED || n.type() == Handles::UNLOCKED ){
// the icon for lock is on the right bottom corner at (-0.12, 0.12) in scene coordinates
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( -0.12f, 0.12f, 0.f, 0.f ) );
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
}
if ( picked )

View File

@@ -18,10 +18,10 @@
*/
class PickingVisitor: public Visitor
{
bool force_;
std::vector<glm::vec3> points_;
glm::mat4 modelview_;
std::vector< std::pair<Node *, glm::vec2> > nodes_;
bool force_;
public:

View File

@@ -1,4 +1,33 @@
#include "Primitives.h"
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glad/glad.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtc/constants.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/rotate_vector.hpp>
#include "ImageShader.h"
#include "Resource.h"
#include "FrameBuffer.h"
@@ -6,19 +35,9 @@
#include "Visitor.h"
#include "Log.h"
#include <glad/glad.h>
#include "Primitives.h"
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/rotate_vector.hpp>
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0)
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0), mirror_(true)
{
// geometry for a trianglulated simple rectangle surface with UV
// (0,0) B +---+ D (1,0)
@@ -88,12 +107,8 @@ void Surface::draw(glm::mat4 modelview, glm::mat4 projection)
glActiveTexture(GL_TEXTURE0);
if ( textureindex_ ) {
glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // TODO add user input to select mode
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
}
else
glBindTexture(GL_TEXTURE_2D, Resource::getTextureBlack());
@@ -124,66 +139,8 @@ void ImageSurface::accept(Visitor& v)
v.visit(*this);
}
MediaSurface::MediaSurface(const std::string& p, Shader *s) : Surface(s)
{
path_ = p;
mediaplayer_ = new MediaPlayer;
}
MediaSurface::~MediaSurface()
{
delete mediaplayer_;
}
void MediaSurface::init()
{
Surface::init();
mediaplayer_->open(path_);
mediaplayer_->play(true);
}
void MediaSurface::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() ) {
init();
// set the texture to the media player once openned
if ( mediaplayer_->isOpen() )
textureindex_ = mediaplayer_->texture();
}
Surface::draw(modelview, projection);
}
void MediaSurface::update( float dt )
{
if ( mediaplayer_->isOpen() ) {
mediaplayer_->update();
scale_.x = mediaplayer_->aspectRatio();
}
Primitive::update( dt );
}
void MediaSurface::accept(Visitor& v)
{
Surface::accept(v);
v.visit(*this);
}
FrameBufferSurface::FrameBufferSurface(FrameBuffer *fb, Shader *s) : Surface(s), frame_buffer_(fb)
{
}
void FrameBufferSurface::init()
{
Surface::init();
// set aspect ratio
scale_.x = frame_buffer_->aspectRatio();
}
void FrameBufferSurface::draw(glm::mat4 modelview, glm::mat4 projection)
@@ -204,7 +161,6 @@ void FrameBufferSurface::accept(Visitor& v)
v.visit(*this);
}
Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) : Primitive(new Shader)
{
for(size_t i = 0; i < points.size(); ++i)
@@ -218,8 +174,6 @@ Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) :
pointsize_ = pointsize;
}
void Points::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() )
@@ -238,63 +192,44 @@ void Points::accept(Visitor& v)
v.visit(*this);
}
LineStrip::LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth) : Primitive(new Shader), linewidth_(linewidth)
HLine::HLine(float linewidth): Primitive(new Shader), width(linewidth)
{
for(size_t i = 0; i < points.size(); ++i)
{
points_.push_back( points[i] );
colors_.push_back( colors[i] );
indices_.push_back ( i );
}
drawMode_ = GL_LINE_STRIP;
}
void LineStrip::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() )
init();
// glLineWidth(linewidth_ * 2.f * Rendering::manager().mainWindow().dpiScale());
glm::mat4 mv = modelview;
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.001f, 1.001f, 1.f));
// TODO FIXME drawing multiple times is not correct to draw lines of different width
// TODO Draw LineStrip using polygons
for (uint i = 0 ; i < linewidth_ ; ++i ) {
Primitive::draw(mv, projection);
mv *= scale;
}
// glLineWidth(1);
}
void LineStrip::accept(Visitor& v)
{
Primitive::accept(v);
v.visit(*this);
}
static const std::vector<glm::vec3> square_points {
glm::vec3( -1.f, -1.f, 0.f ), glm::vec3( -1.f, 1.f, 0.f ),
glm::vec3( 1.f, 1.f, 0.f ), glm::vec3( 1.f, -1.f, 0.f ),
glm::vec3( -1.f, -1.f, 0.f )
};
static const std::vector<glm::vec4> square_colors {
// 1 3
// +-------+ ^
// / | / | \ |
// +-----+ => 0 + | / | + 5 | linewidth
// -1 1 \ | / | / |
// +-------+ v
// 2 4
//
points_ = std::vector<glm::vec3> { glm::vec3( -1.f, 0.f, 0.f ),
glm::vec3( -0.999f, 0.001f, 0.f ),
glm::vec3( -0.999f, -0.001f, 0.f ),
glm::vec3( 0.999f, 0.001f, 0.f ),
glm::vec3( 0.999f, -0.001f, 0.f ),
glm::vec3( 1.f, 0.f, 0.f ) };
colors_ = std::vector<glm::vec4> { glm::vec4( 1.f, 1.f, 1.f , 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
glm::vec4( 1.f, 1.f, 1.f, 1.f )
};
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
indices_ = std::vector<uint> { 0, 1, 2, 3, 4, 5 };
drawMode_ = GL_TRIANGLE_STRIP;
LineSquare::LineSquare(uint linewidth) : LineStrip(square_points, square_colors, linewidth)
{
// default scale
scale_.y = width;
//default color
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
void LineSquare::init()
HLine::~HLine()
{
// do NOT delete vao_ (unique)
vao_ = 0;
}
void HLine::init()
{
// use static unique vertex array object
static uint unique_vao_ = 0;
@@ -319,43 +254,59 @@ void LineSquare::init()
// 2. remember global vertex array object
unique_vao_ = vao_;
unique_drawCount = drawCount_;
// 3. unique_vao_ will NOT be deleted
}
}
void LineSquare::accept(Visitor& v)
void HLine::draw(glm::mat4 modelview, glm::mat4 projection)
{
Primitive::accept(v);
v.visit(*this);
// extract pure scaling from modelview (without rotation)
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
// Change transform to use linewidth independently of scale in Y (vertical)
scale_.y = (float) width / vec.y;
update(0);
// change color
shader_->color = color;
Primitive::draw(modelview, projection);
}
LineSquare::~LineSquare()
VLine::VLine(float linewidth): Primitive(new Shader), width(linewidth)
{
points_ = std::vector<glm::vec3> { glm::vec3( 0.f, -1.f, 0.f ),
glm::vec3( 0.001f, -0.999f, 0.f ),
glm::vec3( -0.001f, -0.999f, 0.f ),
glm::vec3( 0.001f, 0.999f, 0.f ),
glm::vec3( -0.001f, 0.999f, 0.f ),
glm::vec3( 0.f, 1.f, 0.f )};
colors_ = std::vector<glm::vec4> { glm::vec4( 1.f, 1.f, 1.f , 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
indices_ = std::vector<uint> { 0, 1, 2, 3, 4, 5 };
drawMode_ = GL_TRIANGLE_STRIP;
// default scale
scale_.x = width;
// default color
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
}
VLine::~VLine()
{
// do NOT delete vao_ (unique)
vao_ = 0;
}
LineCircle::LineCircle(uint linewidth) : LineStrip(std::vector<glm::vec3>(), std::vector<glm::vec4>(), linewidth)
{
static int N = 72;
static float a = glm::two_pi<float>() / static_cast<float>(N);
static glm::vec4 circle_color_points = glm::vec4(1.f, 1.f, 1.f, 1.f);
// loop to build a circle
glm::vec3 P(1.f, 0.f, 0.f);
for (int i = 0; i < N ; i++ ){
points_.push_back( glm::vec3(P) );
colors_.push_back( circle_color_points );
indices_.push_back ( i );
P = glm::rotateZ(P, a);
}
// close loop
points_.push_back( glm::vec3(1.f, 0.f, 0.f) );
colors_.push_back( circle_color_points );
indices_.push_back ( N );
}
void LineCircle::init()
void VLine::init()
{
// use static unique vertex array object
static uint unique_vao_ = 0;
@@ -365,10 +316,10 @@ void LineCircle::init()
Node::init();
// 2. use the global vertex array object
vao_ = unique_vao_;
drawCount_ = unique_drawCount;
// replace AxisAlignedBoundingBox
drawCount_ = unique_drawCount;
// compute AxisAlignedBoundingBox
bbox_.extend(points_);
// arrays of vertices are not needed anymore (STATIC DRAW of vertex object)
// arrays of vertices are not needed anymore
points_.clear();
colors_.clear();
texCoords_.clear();
@@ -380,19 +331,319 @@ void LineCircle::init()
// 2. remember global vertex array object
unique_vao_ = vao_;
unique_drawCount = drawCount_;
// 3. unique_vao_ will NOT be deleted because LineCircle::deleteGLBuffers_() is empty
// 3. unique_vao_ will NOT be deleted
}
}
void VLine::draw(glm::mat4 modelview, glm::mat4 projection)
{
// extract pure scaling from modelview (without rotation)
glm::mat4 ctm;
glm::vec3 rot(0.f);
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
// Change transform to use linewidth independently of scale in X (horizontal)
scale_.x = width / vec.x;
update(0);
// change color
shader_->color = color;
Primitive::draw(modelview, projection);
}
LineSquare::LineSquare(float linewidth) : Group()
{
top_ = new HLine(linewidth);
top_->translation_ = glm::vec3(0.f, 1.f, 0.f);
attach(top_);
bottom_ = new HLine(linewidth);
bottom_->translation_ = glm::vec3(0.f, -1.f, 0.f);
attach(bottom_);
left_ = new VLine(linewidth);
left_->translation_ = glm::vec3(-1.f, 0.f, 0.f);
attach(left_);
right_ = new VLine(linewidth);
right_->translation_ = glm::vec3(1.f, 0.f, 0.f);
attach(right_);
}
LineSquare::LineSquare(const LineSquare &square)
{
top_ = new HLine(square.top_->width);
top_->translation_ = glm::vec3(0.f, 1.f, 0.f);
attach(top_);
bottom_ = new HLine(square.bottom_->width);
bottom_->translation_ = glm::vec3(0.f, -1.f, 0.f);
attach(bottom_);
left_ = new VLine(square.left_->width);
left_->translation_ = glm::vec3(-1.f, 0.f, 0.f);
attach(left_);
right_ = new VLine(square.right_->width);
right_->translation_ = glm::vec3(1.f, 0.f, 0.f);
attach(right_);
setColor(square.color());
}
void LineSquare::setLineWidth(float v)
{
top_->width = v;
bottom_->width = v;
left_->width = v;
right_->width = v;
}
void LineSquare::setColor(glm::vec4 c)
{
top_->color = c;
bottom_->color = c;
left_->color = c;
right_->color = c;
}
LineStrip::LineStrip(const std::vector<glm::vec2> &path, float linewidth) : Primitive(new Shader),
arrayBuffer_(0), path_(path)
{
linewidth_ = 0.002f * linewidth;
for(size_t i = 1; i < path_.size(); ++i)
{
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
glm::vec3 end = glm::vec3(path_[i], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( begin - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
}
drawMode_ = GL_TRIANGLE_STRIP;
}
LineStrip::~LineStrip()
{
// delete buffer
if ( arrayBuffer_ )
glDeleteBuffers ( 1, &arrayBuffer_);
}
void LineStrip::init()
{
if ( vao_ )
glDeleteVertexArrays ( 1, &vao_);
// Vertex Array
glGenVertexArrays( 1, &vao_ );
// Create and initialize buffer objects
if ( arrayBuffer_ )
glDeleteBuffers ( 1, &arrayBuffer_);
glGenBuffers( 1, &arrayBuffer_ );
uint elementBuffer_;
glGenBuffers( 1, &elementBuffer_);
glBindVertexArray( vao_ );
// setup the array buffers for vertices
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors, NULL, GL_DYNAMIC_DRAW);
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
// setup the element array for indices
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(uint) * indices_.size(), &(indices_[0]), GL_STATIC_DRAW);
// explain how to read attributes 0 and 1
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void *)0 );
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void *)(sizeofPoints) );
glEnableVertexAttribArray(1);
// done
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// drawing indications
drawCount_ = indices_.size();
if ( elementBuffer_ )
glDeleteBuffers ( 1, &elementBuffer_);
indices_.clear();
// compute AxisAlignedBoundingBox
bbox_.extend(points_);
Node::init();
}
void LineStrip::updatePath()
{
// redo points_ array
points_.clear();
for(size_t i = 1; i < path_.size(); ++i)
{
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
glm::vec3 end = glm::vec3(path_[i], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
}
// bind the vertex array and change the point coordinates
glBindVertexArray( vao_ );
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// reset and compute AxisAlignedBoundingBox
GlmToolkit::AxisAlignedBoundingBox b;
bbox_ = b;
bbox_.extend(points_);
}
void LineStrip::editPath(uint index, glm::vec2 position)
{
if (index < path_.size()) {
path_[index] = position;
updatePath();
}
}
void LineCircle::accept(Visitor& v)
void LineStrip::changePath(std::vector<glm::vec2> path)
{
// invalid if not enough points given
size_t N = path_.size();
if (path.size() < N)
return;
// replace path but keep number of points
path_ = path;
path_.resize(N);
updatePath();
}
void LineStrip::setLineWidth(float linewidth) {
linewidth_ = 0.002f * linewidth;
updatePath();
}
void LineStrip::accept(Visitor& v)
{
Primitive::accept(v);
v.visit(*this);
}
LineCircle::~LineCircle()
LineLoop::LineLoop(const std::vector<glm::vec2> &path, float linewidth) : LineStrip(path, linewidth)
{
// do NOT delete vao_ (unique)
vao_ = 0;
// close linestrip loop
glm::vec3 begin = glm::vec3(path_[path_.size()-1], 0.f);
glm::vec3 end = glm::vec3(path_[0], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( begin - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
}
void LineLoop::updatePath()
{
glm::vec3 begin;
glm::vec3 end;
glm::vec3 dir;
glm::vec3 perp;
// redo points_ array
points_.clear();
size_t i = 1;
for(; i < path_.size(); ++i)
{
begin = glm::vec3(path_[i-1], 0.f);
end = glm::vec3(path_[i], 0.f);
dir = end - begin;
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
}
// close linestrip loop
begin = glm::vec3(path_[i-1], 0.f);
end = glm::vec3(path_[0], 0.f);
dir = end - begin;
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
// bind the vertex array and change the point coordinates
glBindVertexArray( vao_ );
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// re-compute AxisAlignedBoundingBox
bbox_.extend(points_);
}
#define LINE_CIRCLE_DENSITY 72
LineCircle::LineCircle(float linewidth) : LineLoop(std::vector<glm::vec2>(LINE_CIRCLE_DENSITY), linewidth)
{
static float a = glm::two_pi<float>() / static_cast<float>(LINE_CIRCLE_DENSITY-1);
// loop to build a circle
glm::vec3 P(1.f, 0.f, 0.f);
for (int i = 0; i < LINE_CIRCLE_DENSITY - 1; i++ ){
path_[i] = glm::vec2(P);
P = glm::rotateZ(P, a);
}
updatePath();
}

View File

@@ -30,8 +30,12 @@ public:
inline void setTextureIndex(uint t) { textureindex_ = t; }
inline uint textureIndex() const { return textureindex_; }
inline void setMirrorTexture(bool m) { mirror_ = m; }
inline bool mirrorTexture() { return mirror_; }
protected:
uint textureindex_;
bool mirror_;
};
@@ -58,31 +62,6 @@ protected:
};
/**
* @brief The MediaSurface class is a Surface to draw a video
*
* URI is passed to a Media Player to handle the video playback
* Height = 1.0, Width is set by the aspect ratio of the image
*/
class MediaSurface : public Surface {
public:
MediaSurface(const std::string& p, Shader *s = new ImageShader);
~MediaSurface();
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
void update (float dt) override;
inline std::string path() const { return path_; }
inline MediaPlayer *mediaPlayer() const { return mediaplayer_; }
protected:
std::string path_;
MediaPlayer *mediaplayer_;
};
/**
* @brief The FrameBufferSurface class is a Surface to draw a framebuffer
*
@@ -94,11 +73,11 @@ class FrameBufferSurface : public Surface {
public:
FrameBufferSurface(FrameBuffer *fb, Shader *s = new ImageShader);
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
inline FrameBuffer *getFrameBuffer() const { return frame_buffer_; }
inline void setFrameBuffer(FrameBuffer *fb) { frame_buffer_ = fb; }
inline FrameBuffer *frameBuffer() const { return frame_buffer_; }
protected:
FrameBuffer *frame_buffer_;
@@ -125,54 +104,101 @@ public:
};
class HLine : public Primitive {
public:
HLine(float width = 1.f);
virtual ~HLine();
void init () override;
void draw(glm::mat4 modelview, glm::mat4 projection) override;
glm::vec4 color;
float width;
};
class VLine : public Primitive {
public:
VLine(float width = 1.f);
virtual ~VLine();
void init () override;
void draw(glm::mat4 modelview, glm::mat4 projection) override;
glm::vec4 color;
float width;
};
/**
* @brief The LineSquare class is a group of 4 lines (width & height = 1.0)
*/
class LineSquare : public Group {
HLine *top_, *bottom_;
VLine *left_, *right_;
public:
LineSquare(float linewidth = 1.f);
LineSquare(const LineSquare &square);
void setLineWidth(float v);
inline float lineWidth() const { return top_->width; }
void setColor(glm::vec4 c);
inline glm::vec4 color() const { return top_->color; }
};
/**
* @brief The LineStrip class is a Primitive to draw lines
*/
class LineStrip : public Primitive {
uint linewidth_;
public:
LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth = 1);
LineStrip(const std::vector<glm::vec2> &path, float linewidth = 1.f);
virtual ~LineStrip();
virtual void draw(glm::mat4 modelview, glm::mat4 projection) override;
virtual void init () override;
virtual void accept(Visitor& v) override;
std::vector<glm::vec3> getPoints() { return points_; }
std::vector<glm::vec4> getColors() { return colors_; }
inline std::vector<glm::vec2> path() { return path_; }
inline float lineWidth() const { return linewidth_ * 500.f; }
inline void setLineWidth(uint v) { linewidth_ = v; }
inline uint getLineWidth() const { return linewidth_; }
void changePath(std::vector<glm::vec2> path);
void editPath(uint index, glm::vec2 position);
void setLineWidth(float linewidth);
protected:
float linewidth_;
uint arrayBuffer_;
std::vector<glm::vec2> path_;
virtual void updatePath();
};
/**
* @brief The LineLoop class is a LineStrip with closed path
*/
class LineLoop : public LineStrip {
public:
LineLoop(const std::vector<glm::vec2> &path, float linewidth = 1.f);
protected:
void updatePath() override;
};
/**
* @brief The LineSquare class is a square LineStrip (width & height = 1.0)
* @brief The LineCircle class is a circular LineLoop (diameter = 1.0)
*/
class LineSquare : public LineStrip {
class LineCircle : public LineLoop {
public:
LineSquare(uint linewidth = 1);
LineCircle(float linewidth = 1.f);
void init() override;
void accept(Visitor& v) override;
virtual ~LineSquare();
};
/**
* @brief The LineCircle class is a circular LineStrip (diameter = 1.0)
*/
class LineCircle : public LineStrip {
public:
LineCircle(uint linewidth = 1);
void init() override;
void accept(Visitor& v) override;
virtual ~LineCircle();
};

View File

@@ -12,13 +12,22 @@ monitor or a projector, but can be recorded live (no audio).
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
# License
GPL-3.0-or-later
See [LICENSE](https://github.com/brunoherbelin/vimix/blob/master/LICENSE)
# Install
Check the [Quick Installation Guide](https://github.com/brunoherbelin/vimix/wiki/Quick-Installation-Guide)
### Linux
Download and install a release package from https://snapcraft.io/vimix
snap install vimix
$ snap install vimix
NB: You'll need to setup the snap permissions.
### Mac OSX
@@ -27,17 +36,26 @@ NB: You'll need to accept the exception in OSX security preference.
## Clone
git clone --recursive https://github.com/brunoherbelin/vimix.git
$ git clone --recursive https://github.com/brunoherbelin/vimix.git
This will create the directory 'vimix', download the latest version of vimix code,
and (recursively) clone all the internal git Dependencies.
and (recursively) clone all the internal git dependencies.
To only update a cloned git copy:
$ git pull
## Compile
mkdir vimix-build
cd vimix-build
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
cmake --build .
First time after git clone:
$ mkdir vimix-build
$ cd vimix-build
$ cmake -DCMAKE_BUILD_TYPE=Release ../vimix
Compile (or re-compile after pull):
$ cmake --build .
### Dependencies
@@ -46,20 +64,42 @@ and (recursively) clone all the internal git Dependencies.
- gcc
- make
- cmake
- git
**Libraries:**
- gstreamer
- gst-plugins : base, good, bad & ugly
- libpng
- gst-plugins : libav, base, good, bad & ugly
- libglfw3
- libicu
#### Install Dependencies
**Ubuntu**
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
$ apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav libicu-dev libgtk-3-dev
**OSX with Brew**
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly
$ brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
#### Generate snap
To generate the snap (from vimix directory):
$ snapcraft
To install the locally created snap:
$ snap install --dangerous vimix_0.5_amd64.snap
### Memcheck
To generate memory usage plots in [massif format](https://valgrind.org/docs/manual/ms-manual.html):
$ G_SLICE=always-malloc valgrind --tool=massif ./vimix
To check for memory leaks:
$ G_SLICE=always-malloc valgrind --leak-check=full --log-file=vimix_mem.txt ./vimix

View File

@@ -1,4 +1,25 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <thread>
#include<algorithm> // for copy() and assign()
#include<iterator> // for back_inserter
// Desktop OpenGL function loader
#include <glad/glad.h>
@@ -24,11 +45,11 @@ PNGRecorder::PNGRecorder() : FrameGrabber()
{
}
void PNGRecorder::init(GstCaps *caps)
std::string PNGRecorder::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
return std::string("Invalid caps");
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! pngenc ! filesink name=sink";
@@ -37,10 +58,9 @@ void PNGRecorder::init(GstCaps *caps)
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("PNG Capture Could not construct pipeline %s:\n%s", description.c_str(), error->message);
std::string msg = std::string("PNG Capture Could not construct pipeline ") + description + "\n" + std::string(error->message);
g_clear_error (&error);
finished_ = true;
return;
return msg;
}
// verify location path (path is always terminated by the OS dependent separator)
@@ -60,12 +80,13 @@ void PNGRecorder::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
@@ -82,38 +103,35 @@ void PNGRecorder::init(GstCaps *caps)
}
else {
Log::Warning("PNG Capture Could not configure source");
finished_ = true;
return;
return std::string("PNG Capture : Failed to configure frame grabber.");
}
// start pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("PNG Capture Could not record %s", filename_.c_str());
finished_ = true;
return;
return std::string("PNG Capture : Failed to start frame grabber.");
}
// all good
Log::Info("PNG Capture started.");
initialized_ = true;
// start recording !!
active_ = true;
return std::string("PNG Capture started ");
}
void PNGRecorder::terminate()
{
// remember and inform
Settings::application.recentRecordings.push(filename_);
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
}
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps)
{
FrameGrabber::addFrame(buffer, caps, dt);
FrameGrabber::addFrame(buffer, caps);
// PNG Recorder specific :
// stop after one frame
if (timestamp_ > 0) {
if (frame_count_ > 0) {
stop();
}
}
@@ -123,12 +141,13 @@ const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
"H264 (Realtime)",
"H264 (High 4:4:4)",
"H265 (Realtime)",
"H265 (HQ Animation)",
"H265 (HQ)",
"ProRes (Standard)",
"ProRes (HQ 4444)",
"WebM VP8 (2MB/s)",
"WebM VP8 (Realtime)",
"Multiple JPEG"
};
const std::vector<std::string> VideoRecorder::profile_description {
// Control x264 encoder quality :
// pass
@@ -138,19 +157,17 @@ const std::vector<std::string> VideoRecorder::profile_description {
// The total range is from 0 to 51, where 0 is lossless, 18 can be considered visually lossless,
// and 51 is terrible quality. A sane range is 18-26, and the default is 23.
// speed-preset
// ultrafast (1)
// superfast (2)
// veryfast (3)
// faster (4)
// fast (5)
#ifndef APPLE
// "video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
#else
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 ! h264parse ! ",
#endif
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=16 speed-preset=4 threads=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" pass=4 quantizer=22 speed-preset=2 ! video/x-h264, profile=baseline ! h264parse ! ",
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=18 speed-preset=3 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
// Control x265 encoder quality :
// NB: apparently x265 only accepts I420 format :(
// speed-preset
// superfast (2)
// veryfast (3)
// faster (4)
// fast (5)
@@ -162,56 +179,176 @@ const std::vector<std::string> VideoRecorder::profile_description {
// fastdecode (5)
// animation (6) optimize the encode quality for animation content without impacting the encode speed
// crf Quality-controlled variable bitrate [0 51]
// default 28
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
"video/x-raw, format=I420 ! x265enc tune=4 speed-preset=3 ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
// default 28
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
"video/x-raw, format=I420 ! x265enc tune=2 speed-preset=2 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=2 option-string=\"crf=12\" ! video/x-h265, profile=(string)main ! h265parse ! ",
// Apple ProRes encoding parameters
// pass
// cbr (0) Constant Bitrate Encoding
// quant (2) Constant Quantizer
// pass1 (512) VBR Encoding - Pass 1
// profile
// 0 proxy
// 1 lt
// 2 standard
// 3 hq
// 4 4444
"avenc_prores_ks pass=2 profile=2 quantizer=26 ! ",
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 profile=4 quantizer=12 ! ",
// 0 proxy 45Mbps YUV 4:2:2
// 1 lt 102Mbps YUV 4:2:2
// 2 standard 147Mbps YUV 4:2:2
// 3 hq 220Mbps YUV 4:2:2
// 4 4444 330Mbps YUVA 4:4:4:4
// quant-mat
// -1 auto
// 0 proxy
// 2 lt
// 3 standard
// 4 hq
// 6 default
"video/x-raw, format=I422_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=2 quant-mat=6 quantizer=8 ! ",
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=4 quant-mat=6 quantizer=4 ! ",
// VP8 WebM encoding
"vp8enc end-usage=vbr cpu-used=8 max-quantizer=35 deadline=100000 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=100 ! ",
"jpegenc ! "
// deadline per frame (usec)
// 0=best,
// 1=realtime
// see https://www.webmproject.org/docs/encoder-parameters/
// "vp8enc end-usage=cbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 undershoot=95 "
// "buffer-size=6000 buffer-initial-size=4000 buffer-optimal-size=5000 "
// "keyframe-max-dist=999999 min-quantizer=4 max-quantizer=50 ! ",
"vp8enc end-usage=vbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 keyframe-max-dist=360 "
"token-partitions=2 static-threshold=1000 min-quantizer=4 max-quantizer=20 ! ",
// JPEG encoding
"jpegenc idct-method=float ! "
};
// Too slow
//// WebM VP9 encoding parameters
//// https://www.webmproject.org/docs/encoder-parameters/
//// https://developers.google.com/media/vp9/settings/vod/
//"vp9enc end-usage=vbr end-usage=vbr cpu-used=3 max-quantizer=35 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=1000 ! "
// FAILED
// x265 encoder quality
// string description = "appsrc name=src ! videoconvert ! "
// "x265enc tune=4 speed-preset=2 option-string='crf=28' ! h265parse ! "
// "qtmux ! filesink name=sink";
#if GST_GL_HAVE_PLATFORM_GLX
// under GLX (Linux), gstreamer might have nvidia or vaapi encoders
// the hardware encoder will be filled at first instanciation of VideoRecorder
std::vector<std::string> VideoRecorder::hardware_encoder;
std::vector<std::string> VideoRecorder::hardware_profile_description;
std::vector<std::string> nvidia_encoder = {
"nvh264enc",
"nvh264enc",
"nvh265enc",
"nvh265enc",
"", "", "", ""
};
std::vector<std::string> nvidia_profile_description {
// qp-const Constant quantizer (-1 = from NVENC preset)
// Range: -1 - 51 Default: -1
// rc-mode Rate Control Mode
// (0): default - Default
// (1): constqp - Constant Quantization
// (2): cbr - Constant Bit Rate
// (3): vbr - Variable Bit Rate
// (4): vbr-minqp - Variable Bit Rate (with minimum quantization parameter, DEPRECATED)
// (5): cbr-ld-hq - Low-Delay CBR, High Quality
// (6): cbr-hq - CBR, High Quality (slower)
// (7): vbr-hq - VBR, High Quality (slower)
// Control nvh264enc encoder
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=(string)main ! h264parse ! ",
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 qp-const=18 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
// Control nvh265enc encoder
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 zerolatency=true ! video/x-h265, profile=(string)main-10 ! h265parse ! ",
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 qp-const=18 ! video/x-h265, profile=(string)main-444 ! h265parse ! ",
"", "", "", ""
};
std::vector<std::string> vaapi_encoder = {
"vaapih264enc",
"vaapih264enc",
"vaapih265enc",
"vaapih265enc",
"", "", "", ""
};
std::vector<std::string> vaapi_profile_description {
// Control vaapih264enc encoder
"video/x-raw, format=NV12 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! h264parse ! ",
"video/x-raw, format=NV12 ! vaapih264enc rate-control=cqp init-qp=14 quality-level=4 keyframe-period=0 max-bframes=2 ! video/x-h264, profile=(string)high ! h264parse ! ",
// Control vaapih265enc encoder
"video/x-raw, format=NV12 ! vaapih265enc ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=NV12 ! vaapih265enc rate-control=cqp init-qp=14 quality-level=4 keyframe-period=0 max-bframes=2 ! video/x-h265, profile=(string)main-444 ! h265parse ! ",
"", "", "", ""
};
#elif GST_GL_HAVE_PLATFORM_CGL
// under CGL (Mac), gstreamer might have the VideoToolbox
std::vector<std::string> VideoRecorder::hardware_encoder = {
"vtenc_h264_hw",
"vtenc_h264_hw",
"", "", "", "", "", ""
};
std::vector<std::string> VideoRecorder::hardware_profile_description {
// Control vtenc_h264_hw encoder
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
"video/x-raw, format=UYVY ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 quality=0.9 ! h264parse ! ",
"", "", "", "", "", ""
};
#else
// in other platforms, no hardware encoder
std::vector<std::string> VideoRecorder::hardware_encoder;
std::vector<std::string> VideoRecorder::hardware_profile_description;
#endif
const char* VideoRecorder::buffering_preset_name[6] = { "Minimum", "100 MB", "200 MB", "500 MB", "1 GB", "2 GB" };
const guint64 VideoRecorder::buffering_preset_value[6] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648 };
const char* VideoRecorder::framerate_preset_name[3] = { "15 FPS", "25 FPS", "30 FPS" };
const gint VideoRecorder::framerate_preset_value[3] = { 15, 25, 30 };
VideoRecorder::VideoRecorder() : FrameGrabber()
{
// first run initialization of hardware encoders in linux
#if GST_GL_HAVE_PLATFORM_GLX
if (hardware_encoder.size() < 1) {
// test nvidia encoder
if ( GstToolkit::has_feature(nvidia_encoder[0] ) ) {
// consider that if first nvidia encoder is valid, all others should also be available
hardware_encoder.assign(nvidia_encoder.begin(), nvidia_encoder.end());
hardware_profile_description.assign(nvidia_profile_description.begin(), nvidia_profile_description.end());
}
// test vaapi encoder
else if ( GstToolkit::has_feature(vaapi_encoder[0] ) ) {
hardware_encoder.assign(vaapi_encoder.begin(), vaapi_encoder.end());
hardware_profile_description.assign(vaapi_profile_description.begin(), vaapi_profile_description.end());
}
}
#endif
}
void VideoRecorder::init(GstCaps *caps)
std::string VideoRecorder::init(GstCaps *caps)
{
// ignore
if (caps == nullptr)
return;
return std::string("Invalid caps");
// apply settings
buffering_size_ = MAX( MIN_BUFFER_SIZE, buffering_preset_value[Settings::application.record.buffering_mode]);
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, framerate_preset_value[Settings::application.record.framerate_mode]);
timestamp_on_clock_ = Settings::application.record.priority_mode < 1;
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
Settings::application.record.profile = H264_STANDARD;
description += profile_description[Settings::application.record.profile];
// test for a hardware accelerated encoder
if (Settings::application.render.gpu_decoding && (int) hardware_encoder.size() > 0 &&
GstToolkit::has_feature(hardware_encoder[Settings::application.record.profile]) ) {
description += hardware_profile_description[Settings::application.record.profile];
Log::Info("Video Recording using hardware accelerated encoder (%s)", hardware_encoder[Settings::application.record.profile].c_str());
}
// revert to software encoder
else
description += profile_description[Settings::application.record.profile];
// verify location path (path is always terminated by the OS dependent separator)
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
@@ -238,10 +375,9 @@ void VideoRecorder::init(GstCaps *caps)
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
std::string msg = std::string("Video Recording : Could not construct pipeline ") + description + "\n" + std::string(error->message);
g_clear_error (&error);
finished_ = true;
return;
return msg;
}
// setup file sink
@@ -255,18 +391,32 @@ void VideoRecorder::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
if (timestamp_on_clock_)
g_object_set (G_OBJECT (src_),"do-timestamp", TRUE,NULL);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_);
// specify recorder framerate in the given caps
GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, framerate_preset_value[Settings::application.record.framerate_mode], 1);
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;
@@ -277,35 +427,52 @@ void VideoRecorder::init(GstCaps *caps)
}
else {
Log::Warning("VideoRecorder Could not configure source");
finished_ = true;
return;
return std::string("Video Recording : Failed to configure frame grabber.");
}
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
finished_ = true;
return;
return std::string("Video Recording : Failed to start frame grabber.");
}
// all good
Log::Info("Video Recording started (%s)", profile_name[Settings::application.record.profile]);
initialized_ = true;
return std::string("Video Recording started ") + profile_name[Settings::application.record.profile];
// start recording !!
active_ = true;
}
void VideoRecorder::terminate()
{
// stop the pipeline (again)
gst_element_set_state (pipeline_, GST_STATE_NULL);
// statistics on expected number of frames
guint64 N = MAX( (guint64) duration_ / (guint64) frame_duration_, frame_count_);
float loss = 100.f * ((float) (N - frame_count_) ) / (float) N;
Log::Info("Video Recording : %ld frames captured in %s (aming for %ld, %.0f%% lost)",
frame_count_, GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str(), N, loss);
// warn user if more than 10% lost
if (loss > 10.f) {
if (timestamp_on_clock_)
Log::Warning("Video Recording lost %.0f%% of frames: framerate could not be maintained at %ld FPS.", loss, GST_SECOND / frame_duration_);
else
Log::Warning("Video Recording lost %.0f%% of frames: video is only %s long.",
loss, GstToolkit::time_to_string(timestamp_, GstToolkit::TIME_STRING_READABLE).c_str());
Log::Info("Video Recording : try a lower resolution / a lower framerate / a larger buffer size / a faster codec.");
}
// remember and inform
Settings::application.recentRecordings.push(filename_);
Log::Notify("Video Recording %s is ready.", filename_.c_str());
}
std::string VideoRecorder::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
else
if (initialized_ && !active_ && !endofstream_)
return "Saving file...";
return FrameGrabber::info();
}

View File

@@ -2,6 +2,8 @@
#define RECORDER_H
#include <vector>
#include <string>
#include <gst/pbutils/pbutils.h>
#include <gst/app/gstappsrc.h>
@@ -10,26 +12,26 @@
class PNGRecorder : public FrameGrabber
{
std::string filename_;
std::string filename_;
public:
PNGRecorder();
std::string filename() const { return filename_; }
protected:
void init(GstCaps *caps) override;
std::string init(GstCaps *caps) override;
void terminate() override;
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
void addFrame(GstBuffer *buffer, GstCaps *caps) override;
};
class VideoRecorder : public FrameGrabber
{
std::string filename_;
std::string filename_;
void init(GstCaps *caps) override;
std::string init(GstCaps *caps) override;
void terminate() override;
public:
@@ -45,12 +47,19 @@ public:
JPEG_MULTI,
DEFAULT
} Profile;
static const char* profile_name[DEFAULT];
static const char* profile_name[DEFAULT];
static const std::vector<std::string> profile_description;
static std::vector<std::string> hardware_encoder;
static std::vector<std::string> hardware_profile_description;
static const char* buffering_preset_name[6];
static const guint64 buffering_preset_value[6];
static const char* framerate_preset_name[3];
static const int framerate_preset_value[3];
VideoRecorder();
std::string info() const override;
std::string filename() const { return filename_; }
};

166
RenderView.cpp Normal file
View File

@@ -0,0 +1,166 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <thread>
// Opengl
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "defines.h"
#include "Settings.h"
#include "Decorations.h"
#include "RenderView.h"
RenderView::RenderView() : View(RENDERING), frame_buffer_(nullptr), fading_overlay_(nullptr)
{
}
RenderView::~RenderView()
{
if (frame_buffer_)
delete frame_buffer_;
if (fading_overlay_)
delete fading_overlay_;
}
void RenderView::setFading(float f)
{
if (fading_overlay_ == nullptr)
fading_overlay_ = new Surface;
fading_overlay_->shader()->color.a = CLAMP( f < EPSILON ? 0.f : f, 0.f, 1.f);
}
float RenderView::fading() const
{
if (fading_overlay_)
return fading_overlay_->shader()->color.a;
else
return 0.f;
}
void RenderView::setResolution(glm::vec3 resolution, bool useAlpha)
{
// use default resolution if invalid resolution is given (default behavior)
if (resolution.x < 2.f || resolution.y < 2.f)
resolution = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
// do we need to change resolution ?
if (frame_buffer_ && frame_buffer_->resolution() != resolution) {
// new frame buffer
delete frame_buffer_;
frame_buffer_ = nullptr;
}
if (!frame_buffer_)
// output frame is an RBG Multisamples FrameBuffer
frame_buffer_ = new FrameBuffer(resolution, useAlpha, true);
// reset fading
setFading();
}
void RenderView::draw()
{
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -SCENE_DEPTH, 1.f);
if (frame_buffer_) {
// draw in frame buffer
glm::mat4 P = glm::scale( projection, glm::vec3(1.f / frame_buffer_->aspectRatio(), 1.f, 1.f));
// render the scene normally (pre-multiplied alpha in RGB)
frame_buffer_->begin();
scene.root()->draw(glm::identity<glm::mat4>(), P);
fading_overlay_->draw(glm::identity<glm::mat4>(), projection);
frame_buffer_->end();
}
}
void RenderView::drawThumbnail()
{
if (frame_buffer_) {
// if a thumbnailer is pending
if (thumbnailer_.size() > 0) {
try {
// new thumbnailing framebuffer
FrameBuffer *frame_thumbnail = new FrameBuffer( glm::vec3(SESSION_THUMBNAIL_HEIGHT * frame_buffer_->aspectRatio(), SESSION_THUMBNAIL_HEIGHT, 1.f) );
// render
if (Settings::application.render.blit) {
if ( !frame_buffer_->blit(frame_thumbnail) )
throw std::runtime_error("no blit");
}
else {
FrameBufferSurface *thumb = new FrameBufferSurface(frame_buffer_);
frame_thumbnail->begin();
thumb->draw(glm::identity<glm::mat4>(), frame_thumbnail->projection());
frame_thumbnail->end();
delete thumb;
}
// return valid thumbnail promise
thumbnailer_.back().set_value( frame_thumbnail->image() );
// done with thumbnailing framebuffer
delete frame_thumbnail;
}
catch(...) {
// return failed thumbnail promise
thumbnailer_.back().set_exception(std::current_exception());
}
// done with this promise
thumbnailer_.pop_back();
}
}
}
FrameBufferImage *RenderView::thumbnail ()
{
// by default null image
FrameBufferImage *img = nullptr;
// this function is always called from a parallel thread
// So we wait for a few frames of rendering before trying to capture a thumbnail
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// create and store a promise for a FrameBufferImage
thumbnailer_.emplace_back( std::promise<FrameBufferImage *>() );
// future will return the promised FrameBufferImage
std::future<FrameBufferImage *> ft = thumbnailer_.back().get_future();
try {
// wait for a valid return value from promise
img = ft.get();
}
// catch any failed promise
catch (const std::exception&){
}
return img;
}

45
RenderView.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef RENDERVIEW_H
#define RENDERVIEW_H
#include <vector>
#include <future>
#include "View.h"
class RenderView : public View
{
friend class Session;
// rendering FBO
FrameBuffer *frame_buffer_;
Surface *fading_overlay_;
// promises of returning thumbnails after an update
std::vector< std::promise<FrameBufferImage *> > thumbnailer_;
public:
RenderView ();
~RenderView ();
// render frame (in opengl context)
void draw () override;
bool canSelect(Source *) override { return false; }
void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false);
glm::vec3 resolution() const { return frame_buffer_->resolution(); }
// current frame
inline FrameBuffer *frame () const { return frame_buffer_; }
protected:
void setFading(float f = 0.f);
float fading() const;
// get a thumbnail outside of opengl context; wait for a promise to be fullfiled after draw
void drawThumbnail();
FrameBufferImage *thumbnail ();
};
#endif // RENDERVIEW_H

View File

@@ -1,4 +1,23 @@
#include <cstring>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <cstring>
#include <thread>
#include <mutex>
#include <chrono>
@@ -38,7 +57,7 @@
// standalone image loader
#include <stb_image.h>
// vmix
// vimix
#include "defines.h"
#include "Log.h"
#include "Resource.h"
@@ -48,13 +67,60 @@
#include "SystemToolkit.h"
#include "GstToolkit.h"
#include "UserInterfaceManager.h"
#include "RenderingManager.h"
// local statics
#ifdef USE_GST_OPENGL_SYNC_HANDLER
//
// Discarded because not working under OSX - kept in case it would become useful
//
// Linking pipeline to the rendering instance ensures the opengl contexts
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
//
static GstGLContext *global_gl_context = NULL;
static GstGLDisplay *global_display = NULL;
static std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
static GstBusSyncReply bus_sync_handler( GstBus *, GstMessage * msg, gpointer )
{
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
const gchar* contextType;
gst_message_parse_context_type(msg, &contextType);
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
gst_context_set_gl_display(displayContext, global_display);
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
gst_context_unref (displayContext);
g_info ("Managed %s\n", contextType);
}
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
GstStructure* structure = gst_context_writable_structure(appContext);
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
gst_context_unref (appContext);
g_info ("Managed %s\n", contextType);
}
}
gst_message_unref (msg);
return GST_BUS_DROP;
}
void Rendering::LinkPipeline( GstPipeline *pipeline )
{
// capture bus signals to force a unique opengl context for all GST elements
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
gst_object_unref (m_bus);
}
#endif
std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
static void glfw_error_callback(int error, const char* description)
{
@@ -95,10 +161,9 @@ static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
{
static double seconds = 0.f;
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
{
static double seconds = 0.f;
// detect double clic
if ( glfwGetTime() - seconds < 0.2f ) {
// toggle fullscreen
@@ -154,27 +219,45 @@ bool Rendering::init()
g_setenv ("GST_PLUGIN_SYSTEM_PATH", plugins_path.c_str(), TRUE);
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
}
std::string frei0r_path = SystemToolkit::cwd_path() + "frei0r-1" ;
if ( SystemToolkit::file_exists(frei0r_path)) {
Log::Info("Found Frei0r plugins in %s", frei0r_path.c_str());
g_setenv ("FREI0R_PATH", frei0r_path.c_str(), TRUE);
}
g_setenv ("GST_GL_API", "opengl3", TRUE);
gst_init (NULL, NULL);
// increase selection rank for GPU decoding plugins
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins(Settings::application.render.gpu_decoding);
if (Settings::application.render.gpu_decoding) {
if (gpuplugins.size() > 0) {
Log::Info("Found the following GPU decoding plugin(s):");
int i = 1;
for(auto it = gpuplugins.rbegin(); it != gpuplugins.rend(); it++, ++i)
Log::Info("%d. %s", i, (*it).c_str());
}
else {
Log::Info("No GPU decoding plugin found.");
}
}
#ifdef SYNC_GSTREAMER_OPENGL_CONTEXT
#if GST_GL_HAVE_PLATFORM_WGL
global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
#elif GST_GL_HAVE_PLATFORM_CGL
// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
//#if GST_GL_HAVE_PLATFORM_WGL
// global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
// GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
//#elif GST_GL_HAVE_PLATFORM_CGL
//// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
// global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
// global_gl_context = gst_gl_context_new_wrapped (global_display,
// (guintptr) 0,
// GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
//#elif GST_GL_HAVE_PLATFORM_GLX
// global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
// global_gl_context = gst_gl_context_new_wrapped (global_display,
// (guintptr) glfwGetGLXContext(main_.window()),
// GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
//#endif
global_gl_context = gst_gl_context_new_wrapped (global_display,
(guintptr) 0,
GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
#elif GST_GL_HAVE_PLATFORM_GLX
global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
global_gl_context = gst_gl_context_new_wrapped (global_display,
(guintptr) glfwGetGLXContext(main_.window()),
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
#endif
#endif
//
// output window
@@ -186,9 +269,6 @@ bool Rendering::init()
glfwSetKeyCallback( output_.window(), WindowEscapeFullscreen);
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
// GstDeviceMonitor *dm = GstToolkit::setup_raw_video_source_device_monitor();
return true;
}
@@ -223,22 +303,26 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
void Rendering::draw()
{
// change windows fullscreen mode if requested
main_.toggleFullscreen_();
output_.toggleFullscreen_();
// change main window title if requested
if (!main_new_title_.empty()) {
main_.setTitle(main_new_title_);
main_new_title_.clear();
}
// operate on main window context
main_.makeCurrent();
// User Interface step 1
UserInterface::manager().NewFrame();
// Custom draw
// draw
std::list<Rendering::RenderingCallback>::iterator iter;
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); iter++)
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); ++iter)
{
(*iter)();
}
// User Interface step 2
UserInterface::manager().Render();
// perform screenshot if requested
if (request_screenshot_) {
// glfwMakeContextCurrent(main_window_);
@@ -246,12 +330,20 @@ void Rendering::draw()
request_screenshot_ = false;
}
// swap GL buffers
glfwSwapBuffers(main_.window());
// draw output window (and swap buffer output)
output_.draw( Mixer::manager().session()->frame() );
// swap GL buffers
glfwSwapBuffers(main_.window());
glfwSwapBuffers(output_.window());
// software framerate limiter 60FPS if not v-sync
if ( Settings::application.render.vsync < 1 ) {
static GTimer *timer = g_timer_new ();
double elapsed = g_timer_elapsed (timer, NULL) * 1000000.0;
if ( (elapsed < 16000.0) && (elapsed > 0.0) )
g_usleep( 16000 - (gulong)elapsed );
g_timer_start(timer);
}
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
@@ -260,10 +352,6 @@ void Rendering::draw()
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
glfwPollEvents();
// change windows
main_.toggleFullscreen_();
output_.toggleFullscreen_();
// no g_main_loop_run(loop) : update global GMainContext
g_main_context_iteration(NULL, FALSE);
@@ -275,7 +363,7 @@ void Rendering::terminate()
// close window
glfwDestroyWindow(output_.window());
glfwDestroyWindow(main_.window());
glfwTerminate();
// glfwTerminate();
}
@@ -356,14 +444,15 @@ glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bo
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
{
for (int i = 0; i < path_count; ++i) {
int i = 0;
for (; i < path_count; ++i) {
std::string filename(paths[i]);
if (filename.empty())
break;
// try to create a source
Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) );
}
if (path_count>0) {
if (i>0) {
UserInterface::manager().showPannel();
Rendering::manager().mainWindow().show();
}
@@ -416,9 +505,11 @@ RenderingWindow::~RenderingWindow()
void RenderingWindow::setTitle(const std::string &title)
{
std::string fulltitle = Settings::application.windows[index_].name;
if ( !title.empty() )
fulltitle += " -- " + title;
std::string fulltitle;
if ( title.empty() )
fulltitle = Settings::application.windows[index_].name;
else
fulltitle = title + std::string(" - " APP_NAME);
glfwSetWindowTitle(window_, fulltitle.c_str());
}
@@ -514,7 +605,7 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
// done request
request_toggle_fullscreen_ = false;
// if in fullscreen mode
// disable fullscreen mode
if (mo == nullptr) {
// store fullscreen mode
Settings::application.windows[index_].fullscreen = false;
@@ -526,7 +617,7 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
Settings::application.windows[index_].w,
Settings::application.windows[index_].h, 0 );
}
// not in fullscreen mode
// set fullscreen mode
else {
// store fullscreen mode
Settings::application.windows[index_].fullscreen = true;
@@ -536,12 +627,11 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
const GLFWvidmode * mode = glfwGetVideoMode(mo);
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
// Enable vsync on output window only (i.e. not 0 if has a master)
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
}
// Enable vsync on output window only (i.e. not 0 if has a master)
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
}
@@ -620,23 +710,25 @@ bool RenderingWindow::init(int index, GLFWwindow *share)
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
// create the window normal
// create the window
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
if (window_ == NULL){
Log::Error("Failed to create GLFW Window %d", index_);
return false;
}
// set position
// ensure minimal window size
glfwSetWindowSizeLimits(window_, 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
// set initial position
glfwSetWindowPos(window_, winset.x, winset.y);
/// CALLBACKS
// store global ref to pointers (used by callbacks)
GLFW_window_[window_] = this;
// window position and resize callbacks
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
// glfwSetFramebufferSizeCallback( window_, WindowResizeCallback );
glfwSetWindowPosCallback( window_, WindowMoveCallback );
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
// take opengl context ownership
glfwMakeContextCurrent(window_);
@@ -659,11 +751,15 @@ bool RenderingWindow::init(int index, GLFWwindow *share)
// DPI scaling (retina)
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
// We decide for byte aligned textures all over
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
// This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
//
// fast mipmaps (we are not really using mipmaps anyway)
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
// acurate derivative for shader
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
// if not main window
if ( master_ != NULL ) {
@@ -753,39 +849,45 @@ void RenderingWindow::draw(FrameBuffer *fb)
// attach the 2D texture to local FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
#ifndef NDEBUG
Log::Info("Blit to output window enabled.");
#endif
}
// calculate scaling factor of frame buffer inside window
int rx, ry, rw, rh;
float renderingAspectRatio = fb->aspectRatio();
if (aspectRatio() < renderingAspectRatio) {
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
rx = 0;
ry = (window_attributes_.viewport.y - nh) / 2;
rw = window_attributes_.viewport.x;
rh = (window_attributes_.viewport.y + nh) / 2;
} else {
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
rx = (window_attributes_.viewport.x - nw) / 2;
ry = 0;
rw = (window_attributes_.viewport.x + nw) / 2;
rh = window_attributes_.viewport.y;
// if not disabled
if (!Settings::application.render.disabled) {
// calculate scaling factor of frame buffer inside window
int rx, ry, rw, rh;
float renderingAspectRatio = fb->aspectRatio();
if (aspectRatio() < renderingAspectRatio) {
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
rx = 0;
ry = (window_attributes_.viewport.y - nh) / 2;
rw = window_attributes_.viewport.x;
rh = (window_attributes_.viewport.y + nh) / 2;
} else {
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
rx = (window_attributes_.viewport.x - nw) / 2;
ry = 0;
rw = (window_attributes_.viewport.x + nw) / 2;
rh = window_attributes_.viewport.y;
}
// select fbo texture read target
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
// select screen target
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit operation from fbo (containing texture) to screen
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
// select fbo texture read target
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
// select screen target
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit operation from fbo (containing texture) to screen
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
// draw geometry
else
else if (!Settings::application.render.disabled)
{
// VAO is not shared between multiple contexts of different windows
// so we have to create a new VAO for rendering the surface in this window
@@ -815,64 +917,14 @@ void RenderingWindow::draw(FrameBuffer *fb)
glBindTexture(GL_TEXTURE_2D, 0);
}
// restore attribs
Rendering::manager().popAttrib();
// swap buffer
glfwSwapBuffers(window_);
}
// restore attribs
Rendering::manager().popAttrib();
// give back context ownership
glfwMakeContextCurrent(master_);
}
//
// Discarded because not working under OSX - kept in case it would become useful
//
// Linking pipeline to the rendering instance ensures the opengl contexts
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
//
static GstBusSyncReply
bus_sync_handler (GstBus *, GstMessage * msg, gpointer )
{
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
const gchar* contextType;
gst_message_parse_context_type(msg, &contextType);
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
gst_context_set_gl_display(displayContext, global_display);
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
gst_context_unref (displayContext);
g_info ("Managed %s\n", contextType);
}
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
GstStructure* structure = gst_context_writable_structure(appContext);
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
gst_context_unref (appContext);
g_info ("Managed %s\n", contextType);
}
}
gst_message_unref (msg);
return GST_BUS_DROP;
}
void Rendering::LinkPipeline( GstPipeline *pipeline )
{
// capture bus signals to force a unique opengl context for all GST elements
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
gst_object_unref (m_bus);
// GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
// gst_bus_enable_sync_message_emission (m_bus);
// g_signal_connect (m_bus, "sync-message", G_CALLBACK (bus_sync_handler), pipeline);
// gst_object_unref (m_bus);
}

View File

@@ -10,6 +10,7 @@
#include "Screenshot.h"
//#define USE_GST_OPENGL_SYNC_HANDLER
typedef struct GLFWmonitor GLFWmonitor;
typedef struct GLFWwindow GLFWwindow;
@@ -28,6 +29,7 @@ class RenderingWindow
GLFWwindow *window_, *master_;
RenderingAttrib window_attributes_;
std::string title_changed_;
int index_;
float dpi_scale_;
@@ -91,8 +93,8 @@ class Rendering
// Private Constructor
Rendering();
Rendering(Rendering const& copy); // Not Implemented
Rendering& operator=(Rendering const& copy); // Not Implemented
Rendering(Rendering const& copy) = delete;
Rendering& operator=(Rendering const& copy) = delete;
public:
@@ -105,9 +107,8 @@ public:
// Initialization OpenGL and GLFW window creation
bool init();
// show windows and reset views
void show();
// true if active rendering window
bool isActive();
// draw one frame
@@ -130,6 +131,7 @@ public:
// get hold on the windows
inline RenderingWindow& mainWindow() { return main_; }
inline RenderingWindow& outputWindow() { return output_; }
inline void setMainWindowTitle(const std::string t) { main_new_title_ = t; }
// request screenshot
void requestScreenshot();
@@ -143,6 +145,11 @@ public:
// project from scene coordinate to window
glm::vec2 project(glm::vec3 scene_coordinate, glm::mat4 modelview = glm::mat4(1.f), bool to_framebuffer = true);
#ifdef USE_GST_OPENGL_SYNC_HANDLER
// for opengl pipeline in gstreamer
static void LinkPipeline( GstPipeline *pipeline );
#endif
private:
std::string glsl_version;
@@ -154,6 +161,7 @@ private:
std::list<RenderingCallback> draw_callbacks_;
RenderingWindow main_;
std::string main_new_title_;
RenderingWindow output_;
// file drop callback
@@ -162,8 +170,6 @@ private:
Screenshot screenshot_;
bool request_screenshot_;
// for opengl pipeline in gstreamer
void LinkPipeline( GstPipeline *pipeline );
};

View File

@@ -1,6 +1,21 @@
#include "defines.h"
#include "Resource.h"
#include "Log.h"
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <fstream>
#include <sstream>
@@ -10,16 +25,17 @@
// Desktop OpenGL function loader
#include <glad/glad.h>
// multiplatform message box
#include <tinyfiledialogs.h>
// standalone image loader
#include "stb_image.h"
#include <stb_image.h>
// CMake Ressource Compiler
#include <cmrc/cmrc.hpp>
CMRC_DECLARE(vmix);
#include "defines.h"
#include "Log.h"
#include "Resource.h"
std::map<std::string, uint> textureIndex;
std::map<std::string, float> textureAspectRatio;
@@ -41,6 +57,7 @@ uint Resource::getTextureBlack()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_black;
@@ -62,6 +79,7 @@ uint Resource::getTextureWhite()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_white;
@@ -83,6 +101,7 @@ uint Resource::getTextureTransparent()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_transparent;
@@ -102,7 +121,7 @@ const char *Resource::getData(const std::string& path, size_t* out_file_size){
cmrc::file::iterator it = file.begin();
data = static_cast<const char *>(it);
}
catch (std::system_error e) {
catch (const std::system_error &e) {
Log::Error("Could not access ressource %s", std::string(path).c_str());
}
@@ -118,7 +137,7 @@ std::string Resource::getText(const std::string& path){
file = fs.open(path.c_str());
file_stream << std::string(file.begin(), file.end()) << std::endl;
}
catch (std::system_error e) {
catch (const std::system_error &e) {
Log::Error("Could not access ressource %s", std::string(path).c_str());
}
@@ -162,9 +181,8 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
uint mipMapCount = *(uint*)&(header[24]);
uint fourCC = *(uint*)&(header[80]);
// how big is it going to be including all mipmaps?
uint bufsize;
bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
// how big is it going to be including all mipmaps?
uint bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
// get the buffer = bytes [128 - ]
const char *buffer = fp + 128;
@@ -188,7 +206,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
}
}
if (height == 0){
if (height == 0 || bufsize == 0){
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
return 0;
}
@@ -209,11 +227,11 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
// load the mipmaps
for (uint level = 0; level < mipMapCount && (width || height); ++level)
{
uint size = ((width+3)/4)*((height+3)/4)*blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset);
uint s = ((width+3)/4)*((height+3)/4)*blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, s, buffer + offset);
glGenerateMipmap(GL_TEXTURE_2D);
offset += size;
offset += s;
width /= 2;
height /= 2;
@@ -222,6 +240,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
if(height < 1) height = 1;
}
glBindTexture(GL_TEXTURE_2D, 0);
// remember to avoid openning the same resource twice
textureIndex[path] = textureID;
@@ -236,10 +255,9 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
{
std::string ext = path.substr(path.find_last_of(".") + 1);
if (ext=="dds")
if (ext=="dds"){
return getTextureDDS(path, aspect_ratio);
GLuint textureID = 0;
}
// return previously openned resource if already openned before
if (textureIndex.count(path) > 0) {
@@ -247,6 +265,7 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
return textureIndex[path];
}
GLuint textureID = 0;
float ar = 1.0;
int w, h, n;
unsigned char* img = nullptr;
@@ -266,19 +285,20 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
}
if (h == 0){
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
stbi_image_free(img);
return 0;
}
ar = static_cast<float>(w) / static_cast<float>(h);
glGenTextures(1, &textureID);
glBindTexture( GL_TEXTURE_2D, textureID);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glBindTexture( GL_TEXTURE_2D, textureID);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img);
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);
glBindTexture(GL_TEXTURE_2D, 0);
// free memory
stbi_image_free(img);

View File

@@ -1,37 +1,70 @@
#include "defines.h"
#include "Scene.h"
#include "Shader.h"
#include "Primitives.h"
#include "Visitor.h"
#include "GarbageVisitor.h"
#include "Log.h"
#include "GlmToolkit.h"
#include "SessionVisitor.h"
#include <glad/glad.h>
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/random.hpp>
#include <glad/glad.h>
#include <algorithm>
#include "defines.h"
#include "Shader.h"
#include "Primitives.h"
#include "Visitor.h"
#include "GarbageVisitor.h"
#include "Log.h"
#include "BaseToolkit.h"
#include "GlmToolkit.h"
#include "SessionVisitor.h"
#include "Scene.h"
#define DEBUG_SCENE 0
#if DEBUG_SCENE
static int num_nodes_ = 0;
#endif
// Node
Node::Node() : initialized_(false), visible_(true), refcount_(0)
{
// create unique id
id_ = GlmToolkit::uniqueId();
id_ = BaseToolkit::uniqueId();
transform_ = glm::identity<glm::mat4>();
scale_ = glm::vec3(1.f);
rotation_ = glm::vec3(0.f);
translation_ = glm::vec3(0.f);
crop_ = glm::vec3(1.f);
#if DEBUG_SCENE
num_nodes_++;
#endif
}
Node::~Node ()
{
clearCallbacks();
#if DEBUG_SCENE
num_nodes_--;
#endif
}
void Node::clearCallbacks()
@@ -45,7 +78,7 @@ void Node::clearCallbacks()
}
}
void Node::copyTransform(Node *other)
void Node::copyTransform(const Node *other)
{
if (!other)
return;
@@ -53,25 +86,23 @@ void Node::copyTransform(Node *other)
scale_ = other->scale_;
rotation_ = other->rotation_;
translation_ = other->translation_;
crop_ = other->crop_;
}
void Node::update( float dt)
{
std::list<UpdateCallback *>::iterator iter;
for (iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
{
UpdateCallback *callback = *iter;
if (callback->enabled())
callback->update(this, dt);
callback->update(this, dt);
if (callback->finished()) {
iter = update_callbacks_.erase(iter);
delete callback;
}
else {
iter++;
}
else
++iter;
}
// update transform matrix from attributes
@@ -109,14 +140,14 @@ void Primitive::init()
glGenBuffers( 1, &elementBuffer_);
glBindVertexArray( vao_ );
// compute the memory needs for points normals and indicies
// compute the memory needs for points
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
std::size_t sizeofTexCoords = sizeof(glm::vec2) * texCoords_.size();
// setup the array buffers for vertices
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
glBufferData(GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
if ( sizeofTexCoords )
@@ -124,7 +155,7 @@ void Primitive::init()
// setup the element array for the triangle indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
int sizeofIndices = indices_.size()*sizeof(uint);
std::size_t sizeofIndices = indices_.size() * sizeof(uint);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeofIndices, &(indices_[0]), GL_STATIC_DRAW);
// explain how to read attributes 0, 1 and 2 (for point, color and textcoord respectively)
@@ -271,7 +302,7 @@ void Group::update( float dt )
// update every child node
for (NodeSet::iterator node = children_.begin();
node != children_.end(); node++) {
node != children_.end(); ++node) {
(*node)->update ( dt );
}
}
@@ -288,7 +319,7 @@ void Group::draw(glm::mat4 modelview, glm::mat4 projection)
// draw every child node
for (NodeSet::iterator node = children_.begin();
node != children_.end(); node++) {
node != children_.end(); ++node) {
(*node)->draw ( ctm, projection );
}
}
@@ -382,13 +413,13 @@ void Switch::accept(Visitor& v)
void Switch::setActive (uint index)
{
active_ = CLAMP(index, 0, children_.size() - 1);
active_ = MINI(index, children_.size() - 1);
}
Node *Switch::child(uint index) const
{
if (!children_.empty()) {
uint i = CLAMP(index, 0, children_.size() - 1);
uint i = MINI(index, children_.size() - 1);
return children_.at(i);
}
return nullptr;
@@ -445,6 +476,9 @@ Scene::~Scene()
clear();
// bg and fg are deleted as children of root
delete root_;
#if DEBUG_SCENE
Log::Info("Total scene nodes %d", num_nodes_);
#endif
}

View File

@@ -13,6 +13,7 @@
#include <map>
#include "UpdateCallback.h"
#include "GlmToolkit.h"
// Forward declare classes referenced
class Shader;
@@ -64,13 +65,13 @@ public:
// accept all kind of visitors
virtual void accept (Visitor& v);
void copyTransform (Node *other);
void copyTransform (const Node *other);
// public members, to manipulate with care
bool visible_;
uint refcount_;
glm::mat4 transform_;
glm::vec3 scale_, rotation_, translation_;
glm::vec3 scale_, rotation_, translation_, crop_;
// animation update callbacks
// list of callbacks to call at each update
@@ -239,6 +240,9 @@ class Scene {
public:
Scene();
// non assignable class
Scene(Scene const&) = delete;
Scene& operator=(Scene const&) = delete;
~Scene();
void accept (Visitor& v);

View File

@@ -1,4 +1,21 @@
#include "Screenshot.h"
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <memory.h>
#include <assert.h>
@@ -11,6 +28,7 @@
#include <stb_image.h>
#include <stb_image_write.h>
#include "Screenshot.h"
Screenshot::Screenshot()
@@ -39,7 +57,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
{
Width = w - x;
Height = h - y;
unsigned int size = Width * Height * 4;
unsigned int size = Width * Height * 3;
// create BPO
if (Pbo == 0)
@@ -58,7 +76,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
// screenshot to PBO (fast)
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, 0);
Pbo_full = true;
// done
@@ -85,6 +103,7 @@ void Screenshot::save(std::string filename)
// ready for next
Pbo_full = false;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
}
@@ -128,11 +147,9 @@ void Screenshot::storeToFile(Screenshot *s, std::string filename)
ScreenshotSavePending_ = true;
// got data to save ?
if (s && s->Data) {
// make it usable
s->RemoveAlpha();
s->FlipVertical();
// save file
stbi_write_png(filename.c_str(), s->Width, s->Height, 4, s->Data, s->Width * 4);
stbi_flip_vertically_on_write(true);
stbi_write_png(filename.c_str(), s->Width, s->Height, 3, s->Data, s->Width * 3);
}
ScreenshotSavePending_ = false;
}

View File

@@ -1,6 +1,30 @@
#include "SearchVisitor.h"
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include "Scene.h"
#include "MediaSource.h"
#include "Session.h"
#include "SessionSource.h"
#include "SearchVisitor.h"
SearchVisitor::SearchVisitor(Node *node) : Visitor(), node_(node), found_(false)
{
@@ -20,7 +44,7 @@ void SearchVisitor::visit(Group &n)
if (found_)
return;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
if (found_)
break;
@@ -39,3 +63,65 @@ void SearchVisitor::visit(Scene &n)
// search only in workspace
n.ws()->accept(*this);
}
SearchFileVisitor::SearchFileVisitor() : Visitor()
{
}
void SearchFileVisitor::visit(Node &)
{
}
void SearchFileVisitor::visit(Group &n)
{
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
}
}
void SearchFileVisitor::visit(Switch &n)
{
if (n.numChildren()>0)
n.activeChild()->accept(*this);
}
void SearchFileVisitor::visit(Scene &n)
{
// search only in workspace
n.ws()->accept(*this);
}
void SearchFileVisitor::visit (MediaSource& s)
{
filenames_.push_back( s.path() );
}
void SearchFileVisitor::visit (SessionFileSource& s)
{
filenames_.push_back( s.path() );
}
std::list<std::string> SearchFileVisitor::parse (Session *se)
{
SearchFileVisitor sv;
for (auto iter = se->begin(); iter != se->end(); iter++){
(*iter)->accept(sv);
}
return sv.filenames();
}
bool SearchFileVisitor::find (Session *se, std::string path)
{
std::list<std::string> filenames = parse (se);
return std::find(filenames.begin(), filenames.end(), path) != filenames.end();
}

View File

@@ -1,8 +1,12 @@
#ifndef SEARCHVISITOR_H
#define SEARCHVISITOR_H
#include <list>
#include <string>
#include "Visitor.h"
class Session;
class SearchVisitor: public Visitor
{
Node *node_;
@@ -14,12 +18,35 @@ public:
inline Node *node() const { return found_ ? node_ : nullptr; }
// Elements of Scene
void visit(Scene& n);
void visit(Node& n);
void visit(Primitive&) {}
void visit(Group& n);
void visit(Switch& n);
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Primitive&) override {}
void visit (Group& n) override;
void visit (Switch& n) override;
};
class SearchFileVisitor: public Visitor
{
std::list<std::string> filenames_;
public:
SearchFileVisitor();
inline std::list<std::string> filenames() const { return filenames_; }
// Elements of Scene
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Primitive&) override {}
void visit (Group& n) override;
void visit (Switch& n) override;
// Sources
void visit (MediaSource& s) override;
void visit (SessionFileSource& s) override;
static std::list<std::string> parse (Session *se);
static bool find (Session *se, std::string path);
};
#endif // SEARCHVISITOR_H

View File

@@ -1,8 +1,27 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include "defines.h"
#include "SessionVisitor.h"
#include <tinyxml2.h>
#include "Source.h"
#include "Selection.h"
@@ -61,7 +80,7 @@ void Selection::set(SourceList l)
{
clear();
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::SELECTED);
l.sort();
@@ -71,7 +90,7 @@ void Selection::set(SourceList l)
void Selection::add(SourceList l)
{
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::SELECTED);
// generate new set as union of current selection and give list
@@ -86,7 +105,7 @@ void Selection::add(SourceList l)
void Selection::remove(SourceList l)
{
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::VISIBLE);
// generate new set as difference of current selection and give list
@@ -98,13 +117,13 @@ void Selection::remove(SourceList l)
void Selection::clear()
{
for(auto it = selection_.begin(); it != selection_.end(); it++)
for(auto it = selection_.begin(); it != selection_.end(); ++it)
(*it)->setMode(Source::VISIBLE);
selection_.clear();
}
uint Selection::size()
uint Selection::size() const
{
return selection_.size();
}
@@ -117,7 +136,21 @@ Source *Selection::front()
return selection_.front();
}
bool Selection::empty()
Source *Selection::back()
{
if (selection_.empty())
return nullptr;
return selection_.back();
}
void Selection::pop_front()
{
if (!selection_.empty()) // TODO set mode ?
selection_.pop_front();
}
bool Selection::empty() const
{
return selection_.empty();
}
@@ -143,29 +176,14 @@ SourceList::iterator Selection::end()
return selection_.end();
}
std::string Selection::xml()
std::string Selection::clipboard() const
{
std::string x = "";
if (!selection_.empty()) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
selectionNode->SetAttribute("size", (int) selection_.size());
xmlDoc.InsertEndChild(selectionNode);
// fill doc
SessionVisitor sv(&xmlDoc, selectionNode);
for (auto iter = selection_.begin(); iter != selection_.end(); iter++, sv.setRoot(selectionNode) )
(*iter)->accept(sv);
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
return SessionVisitor::getClipboard(selection_);
}
SourceList Selection::getCopy() const
{
SourceList dsl = selection_;
return dsl;
}

View File

@@ -1,7 +1,8 @@
#ifndef SELECTION_H
#define SELECTION_H
#include "Source.h"
#include <string>
#include "SourceList.h"
class Selection
{
@@ -9,24 +10,32 @@ class Selection
public:
Selection();
// construct list
void add (Source *s);
void add (SourceList l);
void remove (Source *s);
void remove (SourceList l);
void set (Source *s);
void set (SourceList l);
void toggle (Source *s);
void add (SourceList l);
void remove (SourceList l);
void set (SourceList l);
void clear ();
void pop_front();
// access elements
SourceList::iterator begin ();
SourceList::iterator end ();
Source *front();
bool contains (Source *s);
bool empty();
uint size ();
Source *back();
std::string xml();
// properties
bool contains (Source *s);
bool empty() const;
uint size () const;
// extract
std::string clipboard() const;
SourceList getCopy() const;
protected:
SourceList::iterator find (Source *s);

View File

@@ -1,21 +1,47 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include "defines.h"
#include "BaseToolkit.h"
#include "Source.h"
#include "Settings.h"
#include "FrameBuffer.h"
#include "Session.h"
#include "GarbageVisitor.h"
#include "FrameGrabber.h"
#include "SessionCreator.h"
#include "SessionSource.h"
#include "MixingGroup.h"
#include "Log.h"
Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
{
filename_ = "";
#include "Session.h"
SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_string(BaseToolkit::uniqueId())),
text(t), large(l), stick(s), pos(glm::vec2(520.f, 30.f)), size(glm::vec2(220.f, 220.f))
{
}
Session::Session() : active_(true), activation_threshold_(MIXING_MIN_THRESHOLD),
filename_(""), failedSource_(nullptr), thumbnail_(nullptr)
{
config_[View::RENDERING] = new Group;
config_[View::RENDERING]->scale_ = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
config_[View::GEOMETRY] = new Group;
config_[View::GEOMETRY]->scale_ = Settings::application.views[View::GEOMETRY].default_scale;
@@ -29,14 +55,24 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
config_[View::APPEARANCE] = new Group;
config_[View::APPEARANCE]->scale_ = Settings::application.views[View::APPEARANCE].default_scale;
config_[View::APPEARANCE]->translation_ = Settings::application.views[View::APPEARANCE].default_translation;
config_[View::TEXTURE] = new Group;
config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale;
config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation;
snapshots_.xmlDoc_ = new tinyxml2::XMLDocument;
start_time_ = gst_util_get_timestamp ();
}
Session::~Session()
{
// TODO delete all mixing groups?
auto group_iter = mixing_groups_.begin();
while ( group_iter != mixing_groups_.end() ){
delete (*group_iter);
group_iter = mixing_groups_.erase(group_iter);
}
// delete all sources
for(auto it = sources_.begin(); it != sources_.end(); ) {
// erase this source from the list
@@ -47,14 +83,22 @@ Session::~Session()
delete config_[View::GEOMETRY];
delete config_[View::LAYER];
delete config_[View::MIXING];
delete config_[View::APPEARANCE];
delete config_[View::TEXTURE];
snapshots_.keys_.clear();
delete snapshots_.xmlDoc_;
}
uint64_t Session::runtime() const
{
return gst_util_get_timestamp () - start_time_;
}
void Session::setActive (bool on)
{
if (active_ != on) {
active_ = on;
for(auto it = sources_.begin(); it != sources_.end(); it++) {
for(auto it = sources_.begin(); it != sources_.end(); ++it) {
(*it)->setActive(active_);
}
}
@@ -63,26 +107,71 @@ void Session::setActive (bool on)
// update all sources
void Session::update(float dt)
{
// no update until render view is initialized
if ( render_.frame() == nullptr )
return;
// pre-render all sources
failedSource_ = nullptr;
bool ready = true;
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){
// pre-render of all sources
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); it++){
// ensure the RenderSource is rendering *this* session
RenderSource *rs = dynamic_cast<RenderSource *>( *it );
if ( rs!= nullptr && rs->session() != this )
rs->setSession(this);
// discard failed source
if ( (*it)->failed() ) {
failedSource_ = (*it);
}
// render normally
else {
if ( !(*it)->ready() )
ready = false;
// render the source
(*it)->render();
// update the source
(*it)->setActive(activation_threshold_);
(*it)->update(dt);
}
}
// apply fading (smooth dicotomic reaching)
float f = render_.fading();
if ( ABS_DIFF(f, fading_target_) > EPSILON) {
render_.setFading( f + ( fading_target_ - f ) / 2.f);
// update session's mixing groups
auto group_iter = mixing_groups_.begin();
while ( group_iter != mixing_groups_.end() ){
// update all valid groups
if ((*group_iter)->size() > 1) {
(*group_iter)->update(dt);
group_iter++;
}
else
// delete invalid groups (singletons)
group_iter = deleteMixingGroup(group_iter);
}
// update fading requested
if (fading_.active) {
// animate
fading_.progress += dt;
// update animation
if ( fading_.duration > 0.f && fading_.progress < fading_.duration ) {
// interpolation
float f = fading_.progress / fading_.duration;
f = ( 1.f - f ) * fading_.start + f * fading_.target;
render_.setFading( f );
}
// arrived at target
else {
// set precise value
render_.setFading( fading_.target );
// fading finished
fading_.active = false;
fading_.start = fading_.target;
fading_.duration = fading_.progress = 0.f;
}
}
// update the scene tree
@@ -91,12 +180,11 @@ void Session::update(float dt)
// draw render view in Frame Buffer
render_.draw();
// grab frames to recorders & streamers
FrameGrabbing::manager().grabFrame(render_.frame(), dt);
// draw the thumbnail only after all sources are ready
if (ready)
render_.drawThumbnail();
}
SourceList::iterator Session::addSource(Source *s)
{
// lock before change
@@ -104,21 +192,21 @@ SourceList::iterator Session::addSource(Source *s)
// find the source
SourceList::iterator its = find(s);
// ok, its NOT in the list !
if (its == sources_.end()) {
// insert the source in the rendering
render_.scene.ws()->attach(s->group(View::RENDERING));
// insert the source to the beginning of the list
sources_.push_front(s);
sources_.push_back(s);
// return the iterator to the source created at the beginning
its = sources_.end()--;
}
// unlock access
access_.unlock();
// return the iterator to the source created at the beginning
return sources_.begin();
return its;
}
SourceList::iterator Session::deleteSource(Source *s)
@@ -130,13 +218,13 @@ SourceList::iterator Session::deleteSource(Source *s)
SourceList::iterator its = find(s);
// ok, its in the list !
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// inform group
if (s->mixingGroup() != nullptr)
s->mixingGroup()->detach(s);
// erase the source from the update list & get next element
its = sources_.erase(its);
// delete the source : safe now
delete s;
}
@@ -148,7 +236,6 @@ SourceList::iterator Session::deleteSource(Source *s)
return its;
}
void Session::removeSource(Source *s)
{
// lock before change
@@ -158,10 +245,11 @@ void Session::removeSource(Source *s)
SourceList::iterator its = find(s);
// ok, its in the list !
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// inform group
if (s->mixingGroup() != nullptr)
s->mixingGroup()->detach(s);
// erase the source from the update list & get next element
sources_.erase(its);
}
@@ -170,7 +258,6 @@ void Session::removeSource(Source *s)
access_.unlock();
}
Source *Session::popSource()
{
Source *s = nullptr;
@@ -179,10 +266,8 @@ Source *Session::popSource()
if (its != sources_.end())
{
s = *its;
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// erase the source from the update list & get next element
sources_.erase(its);
}
@@ -190,18 +275,52 @@ Source *Session::popSource()
return s;
}
void Session::setResolution(glm::vec3 resolution)
static void replaceThumbnail(Session *s)
{
render_.setResolution(resolution);
config_[View::RENDERING]->scale_ = resolution;
if (s != nullptr) {
FrameBufferImage *t = s->renderThumbnail();
if (t != nullptr) // avoid recursive infinite loop
s->setThumbnail(t);
}
}
void Session::setFading(float f, bool forcenow)
void Session::setThumbnail(FrameBufferImage *t)
{
if (forcenow)
render_.setFading( f );
resetThumbnail();
// replace with given image
if (t != nullptr)
thumbnail_ = t;
// no thumbnail image given: capture from rendering in a parallel thread
else
std::thread( replaceThumbnail, this ).detach();
}
fading_target_ = CLAMP(f, 0.f, 1.f);
void Session::resetThumbnail()
{
if (thumbnail_ != nullptr)
delete thumbnail_;
thumbnail_ = nullptr;
}
void Session::setResolution(glm::vec3 resolution, bool useAlpha)
{
// setup the render view: if not specified the default config resulution will be used
render_.setResolution( resolution, useAlpha );
// store the actual resolution set in the render view
config_[View::RENDERING]->scale_ = render_.resolution();
}
void Session::setFadingTarget(float f, float duration)
{
// targetted fading value
fading_.target = CLAMP(f, 0.f, 1.f);
// starting point for interpolation
fading_.start = fading();
// initiate animation
fading_.progress = 0.f;
fading_.duration = duration;
// activate update
fading_.active = true;
}
SourceList::iterator Session::begin()
@@ -234,24 +353,39 @@ SourceList::iterator Session::find(Node *node)
return std::find_if(sources_.begin(), sources_.end(), Source::hasNode(node));
}
SourceList::iterator Session::find(float depth_from, float depth_to)
{
return std::find_if(sources_.begin(), sources_.end(), Source::hasDepth(depth_from, depth_to));
}
SourceList Session::getDepthSortedList() const
{
return depth_sorted(sources_);
}
uint Session::numSource() const
{
return sources_.size();
}
std::list<uint64_t> Session::getIdList() const
SourceIdList Session::getIdList() const
{
std::list<uint64_t> idlist;
for( auto sit = sources_.begin(); sit != sources_.end(); sit++)
idlist.push_back( (*sit)->id() );
// make sure no duplicate
idlist.unique();
return idlist;
return ids(sources_);
}
std::list<std::string> Session::getNameList(uint64_t exceptid) const
{
std::list<std::string> namelist;
for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) {
if ( (*it)->id() != exceptid )
namelist.push_back( (*it)->name() );
}
return namelist;
}
bool Session::empty() const
{
return sources_.empty();
@@ -259,14 +393,14 @@ bool Session::empty() const
SourceList::iterator Session::at(int index)
{
if (index<0)
if ( index < 0 || index > (int) sources_.size())
return sources_.end();
int i = 0;
SourceList::iterator it = sources_.begin();
while ( i < index && it != sources_.end() ){
i++;
it++;
++it;
}
return it;
}
@@ -275,7 +409,7 @@ int Session::index(SourceList::iterator it) const
{
int index = -1;
int count = 0;
for(auto i = sources_.begin(); i != sources_.end(); i++, count++) {
for(auto i = sources_.begin(); i != sources_.end(); ++i, ++count) {
if ( i == it ) {
index = count;
break;
@@ -284,6 +418,179 @@ int Session::index(SourceList::iterator it) const
return index;
}
void Session::move(int current_index, int target_index)
{
if ( current_index < 0 || current_index > (int) sources_.size()
|| target_index < 0 || target_index > (int) sources_.size()
|| target_index == current_index )
return;
SourceList::iterator from = at(current_index);
SourceList::iterator to = at(target_index);
if ( target_index > current_index )
++to;
Source *s = (*from);
sources_.erase(from);
sources_.insert(to, s);
}
bool Session::canlink (SourceList sources)
{
bool canlink = true;
// verify that all sources given are valid in the sesion
validate(sources);
for (auto it = sources.begin(); it != sources.end(); ++it) {
// this source is linked
if ( (*it)->mixingGroup() != nullptr ) {
// askt its group to detach it
canlink = false;
}
}
return canlink;
}
void Session::link(SourceList sources, Group *parent)
{
// we need at least 2 sources to make a group
if (sources.size() > 1) {
unlink(sources);
// create and add a new mixing group
MixingGroup *g = new MixingGroup(sources);
mixing_groups_.push_back(g);
// if provided, attach the group to the parent
if (g && parent != nullptr)
g->attachTo( parent );
}
}
void Session::unlink (SourceList sources)
{
// verify that all sources given are valid in the sesion
validate(sources);
// brute force : detach all given sources
for (auto it = sources.begin(); it != sources.end(); ++it) {
// this source is linked
if ( (*it)->mixingGroup() != nullptr ) {
// askt its group to detach it
(*it)->mixingGroup()->detach(*it);
}
}
}
void Session::addNote(SessionNote note)
{
notes_.push_back( note );
}
std::list<SessionNote>::iterator Session::beginNotes ()
{
return notes_.begin();
}
std::list<SessionNote>::iterator Session::endNotes ()
{
return notes_.end();
}
std::list<SessionNote>::iterator Session::deleteNote (std::list<SessionNote>::iterator n)
{
if (n != notes_.end())
return notes_.erase(n);
return notes_.end();
}
std::list<SourceList> Session::getMixingGroups () const
{
std::list<SourceList> lmg;
for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); ++group_it)
lmg.push_back( (*group_it)->getCopy() );
return lmg;
}
std::list<MixingGroup *>::iterator Session::deleteMixingGroup (std::list<MixingGroup *>::iterator g)
{
if (g != mixing_groups_.end()) {
delete (*g);
return mixing_groups_.erase(g);
}
return mixing_groups_.end();
}
std::list<MixingGroup *>::iterator Session::beginMixingGroup()
{
return mixing_groups_.begin();
}
std::list<MixingGroup *>::iterator Session::endMixingGroup()
{
return mixing_groups_.end();
}
size_t Session::numPlayGroups() const
{
return play_groups_.size();
}
void Session::addPlayGroup(const SourceIdList &ids)
{
play_groups_.push_back( ids );
}
void Session::addToPlayGroup(size_t i, Source *s)
{
if (i < play_groups_.size() )
{
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() )
play_groups_[i].push_back(s->id());
}
}
void Session::removeFromPlayGroup(size_t i, Source *s)
{
if (i < play_groups_.size() )
{
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() )
play_groups_[i].remove( s->id() );
}
}
void Session::deletePlayGroup(size_t i)
{
if (i < play_groups_.size() )
play_groups_.erase( play_groups_.begin() + i);
}
SourceList Session::playGroup(size_t i) const
{
SourceList list;
if (i < play_groups_.size() )
{
for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){
SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));;
if ( it != sources_.end())
list.push_back( *it);
}
}
return list;
}
void Session::lock()
{
access_.lock();
@@ -294,12 +601,26 @@ void Session::unlock()
access_.unlock();
}
Session *Session::load(const std::string& filename)
void Session::validate (SourceList &sources)
{
SessionCreator creator;
// verify that all sources given are valid in the sesion
// and remove the invalid sources
for (auto _it = sources.begin(); _it != sources.end(); ) {
SourceList::iterator found = std::find(sources_.begin(), sources_.end(), *_it);
if ( found == sources_.end() )
_it = sources.erase(_it);
else
_it++;
}
}
Session *Session::load(const std::string& filename, uint recursion)
{
// create session
SessionCreator creator(recursion);
creator.load(filename);
// return created session
return creator.session();
}

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