Merge remote-tracking branch 'origin/beta'

This commit is contained in:
Bruno Herbelin
2026-05-01 23:27:49 +02:00
10 changed files with 246 additions and 51 deletions
+14 -13
View File
@@ -1,16 +1,16 @@
vimix performs **graphical mixing and blending** of several movie clips and
computer generated graphics, with image processing effects in real-time.
computer generated graphics, with image processing effects in real-time. Vimix supports GPU accelerated decoding and encoding of videos.
Its intuitive and hands-on user interface gives direct control on image opacity and
shape for producing live graphics during concerts and VJ-ing sessions.
The output image is typically projected full-screen on external monitors
or projectors, but can be streamed live (SRT) or recorded (without audio).
Video mapping can be configured for projection on all connected monitors.
The output can also be live streamed (SRT, shared memory) or recorded.
![screenshot](vimix_screenshot.png)
![screenshot_vimix](https://github.com/brunoherbelin/vimix/blob/22653d5367e296f977c75e3717cb5e9065c6e09f/docs/vimix_screenshot.png)
Check the [Graphical User Manual](https://github.com/brunoherbelin/vimix/wiki/User-manual) or [demo videos](https://vimeo.com/vimix) to discover vimix.
Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871359) to see what vimix can do.
Check the [User Manual](https://github.com/brunoherbelin/vimix/wiki/User-manual) or [demo videos](https://vimeo.com/vimix) to discover vimix.
Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871359) or [video tutorials](https://github.com/brunoherbelin/vimix/wiki/Video-tutorials) to see what vimix can do.
## Install vimix
@@ -20,9 +20,11 @@ Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871
Install the [flathub](https://flathub.org/apps/details/io.github.brunoherbelin.Vimix), [snap](https://snapcraft.io/vimix) or [Deb](https://tracker.debian.org/pkg/vimix) package.
Get Latest Beta version: [build your own flatpak](https://github.com/brunoherbelin/vimix/blob/master/flatpak/README.md).
### Mac OSX
Download package from [Github Releases](https://github.com/brunoherbelin/vimix/releases).
Download packages from [Github Releases](https://github.com/brunoherbelin/vimix/releases).
### Windows
@@ -31,19 +33,18 @@ Download package from [Github Releases](https://github.com/brunoherbelin/vimix/r
## Control vimix with OSC
You can control remotely vimix with [OSC](https://en.wikipedia.org/wiki/Open_Sound_Control), using [TouchOSC Mk1](https://github.com/brunoherbelin/vimix/wiki/TouchOSC-companion)
or using the [vimix OSC API](https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API) from your OSC applications.
or using the [vimix OSC API](https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API) to connect from [OSC applications](https://github.com/brunoherbelin/vimix/wiki/OSC-Mapping).
## About
vimix is free and open source (GPL3+).
Vimix is free and open source (GPL3+).
vimix is the successor of [GLMixer](https://sourceforge.net/projects/glmixer/), benefiting
Vimix is the successor of [GLMixer](https://sourceforge.net/projects/glmixer/), benefiting
from 10 years of refinement of User-Experience design since its [first draft](https://sourceforge.net/p/glmixer/wiki/GLMixer%20History/).
vimix is open to [feature requests and bug reports](https://github.com/brunoherbelin/vimix/issues).
vimix welcomes contributions and support: check the [wiki](https://github.com/brunoherbelin/vimix/wiki) for more info.
Vimix is open to [feature requests and bug reports](https://github.com/brunoherbelin/vimix/issues).
Vimix welcomes contributions and support: check the [wiki](https://github.com/brunoherbelin/vimix/wiki) for more info.
+13 -1
View File
@@ -1162,7 +1162,19 @@ bool Control::receiveSourceAttribute(Source *target, const std::string &attribut
else
arguments >> t >> osc::EndMessage;
if (str && uniform_value != NAN) {
target->call(new SetFilterUniform(std::string(str), uniform_value, t), true);
target->call(new SetUniform(std::string(str), uniform_value, t), true);
}
}
/// e.g. '/vimix/current/code s 'void mainImage(out vec4 o, in vec2 i) { o = vec4( i / iResolution.xy,1,1); }'
else if (attribute.compare(OSC_SOURCE_CODE) == 0) {
std::string code;
const char *str = nullptr;
arguments >> str >> osc::EndMessage;
if (str && strlen(str) > 0) {
target->call(new SetCode(std::string(str)), true);
// show and refresh source editor if source is currently selected
if (target == Mixer::manager().currentSource())
UserInterface::manager().showSourceEditor(target);
}
}
/// e.g. '/vimix/current/filter sf blur 0.5'
+1
View File
@@ -77,6 +77,7 @@
#define OSC_SOURCE_TEXANGLE "/texture_angle"
#define OSC_SOURCE_FILTER "/filter"
#define OSC_SOURCE_UNIFORM "/uniform"
#define OSC_SOURCE_CODE "/code"
#define OSC_SOURCE_BLENDING "/blending"
#define OSC_SOURCE_FLAG "/flag"
+27 -2
View File
@@ -130,9 +130,9 @@ FilteringProgram::FilteringProgram(const std::string &name, const std::string &f
}
FilteringProgram::FilteringProgram(const FilteringProgram &other) :
name_(other.name_), filename_(other.filename_), code_(other.code_),
name_(other.name_), filename_(other.filename_), code_(other.code_),
two_pass_filter_(other.two_pass_filter_), parameters_(other.parameters_),
textures_(other.textures_)
ranges_(other.ranges_), textures_(other.textures_)
{
}
@@ -145,6 +145,8 @@ FilteringProgram& FilteringProgram::operator= (const FilteringProgram& other)
this->code_ = other.code_;
this->parameters_.clear();
this->parameters_ = other.parameters_;
this->ranges_.clear();
this->ranges_ = other.ranges_;
this->textures_.clear();
this->textures_ = other.textures_;
this->two_pass_filter_ = other.two_pass_filter_;
@@ -185,6 +187,15 @@ void FilteringProgram::removeParameter(const std::string &p)
parameters_.erase(p);
}
std::pair< float, float> FilteringProgram::getParameterRange(const std::string &p)
{
std::pair< float, float> default_range(0.f, 1.f);
if (ranges_.find(p) != ranges_.end())
return ranges_[p];
else
return default_range;
}
bool FilteringProgram::hasTexture(const std::string &t)
{
return textures_.find(t) != textures_.end();
@@ -502,6 +513,7 @@ FilteringProgram ImageFilter::program () const
#define REGEX_VARIABLE_NAME "[a-zA-Z_][\\w]+"
#define REGEX_UNIFORM_VALUE "(\\s*=\\s*[[:digit:]]+(\\.[[:digit:]]*)?)?\\s*\\;"
#define REGEX_SAMPLER_DECLARATION "uniform\\s+sampler2D\\s+"
#define REGEX_RANGE_COMMENT "\\/\\/\\s*range\\s*\\[\\s*([+-]?[[:digit:]]+(\\.[[:digit:]]*)?)\\s+([+-]?[[:digit:]]+(\\.[[:digit:]]*)?)\\s*\\]"
void ImageFilter::setProgram(const FilteringProgram &f, std::promise<std::string> *ret)
{
@@ -568,6 +580,19 @@ void ImageFilter::setProgram(const FilteringProgram &f, std::promise<std::string
else
program_.setParameter(varname, 0.f);
}
// Check for range comment on the same line: // range [min max]
std::string line_suffix = found_uniform.suffix().str();
auto newline_pos = line_suffix.find('\n');
if (newline_pos != std::string::npos)
line_suffix = line_suffix.substr(0, newline_pos);
std::smatch found_range;
std::regex is_a_range(REGEX_RANGE_COMMENT);
if (std::regex_search(line_suffix, found_range, is_a_range)) {
float rmin = 0.f, rmax = 1.f;
BaseToolkit::is_a_value(found_range[1].str(), &rmin);
BaseToolkit::is_a_value(found_range[3].str(), &rmax);
program_.setParameterRange(varname, rmin, rmax);
}
// keep parsing
glslcode = found_uniform.suffix().str();
}
+4
View File
@@ -26,6 +26,7 @@ class FilteringProgram
// list of parameters : uniforms names and values
std::map< std::string, float > parameters_;
std::map< std::string, std::pair<float, float> > ranges_;
// list of texture inputs : uniform sampler2D names and source id
std::map< std::string, uint64_t > textures_;
@@ -71,6 +72,9 @@ public:
bool hasParameter(const std::string &p);
void removeParameter(const std::string &p);
void setParameterRange(const std::string &p, float min, float max) { ranges_[p] = std::make_pair(min, max); }
std::pair< float, float> getParameterRange(const std::string &p);
// set the list of textures
inline void setTextures(const std::map< std::string, uint64_t > &textures) { textures_ = textures; }
+108 -20
View File
@@ -23,6 +23,7 @@
#include "Source.h"
#include "ImageProcessingShader.h"
#include "CloneSource.h"
#include "ShaderSource.h"
#include "Filter/ImageFilter.h"
#include "Filter/DelayFilter.h"
#include "MediaSource.h"
@@ -1298,24 +1299,23 @@ void SetFilter::update(Source *s, float dt)
Log::Info("Filter Alpha: unknown operation '%s'", target_method_.c_str());
} break;
case FrameBufferFilter::FILTER_IMAGE: {
// Open the file
ImageFilter *__f = dynamic_cast<ImageFilter *>(clonesrc->filter());
if (__f == nullptr) {
Log::Info("Filter Image: failed to get image filter");
break;
}
FilteringProgram prog;
prog.setName( __f->program().name() );
// try to open a file
std::ifstream file(target_method_);
// Check if the file is opened successfully
if (file.is_open()) {
// Read the content of the file into an std::string
std::string fileContent((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
FilteringProgram prog;
prog.setName(target_method_);
prog.setCode({fileContent, ""});
// get alpha filter
ImageFilter *__f = dynamic_cast<ImageFilter *>(clonesrc->filter());
__f->setProgram(prog);
prog.setFilename(target_method_);
__f->setProgram(prog);
file.close();
}
else
Log::Info("Filter Custom: can't read file '%s'", target_method_.c_str());
// Close the file
file.close();
Log::Info("Filter Image: failed to open file '%s'", target_method_.c_str());
} break;
default:
break;
@@ -1409,7 +1409,7 @@ void SetFilter::accept(Visitor& v)
v.visit(*this);
}
SetFilterUniform::SetFilterUniform(const std::string &uniform, float value, float ms)
SetUniform::SetUniform(const std::string &uniform, float value, float ms)
: SourceCallback()
, uniform_(uniform)
, target_(value)
@@ -1419,12 +1419,13 @@ SetFilterUniform::SetFilterUniform(const std::string &uniform, float value, floa
imagefilter = nullptr;
}
void SetFilterUniform::update(Source *s, float dt)
void SetUniform::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
CloneSource *clonesrc = dynamic_cast<CloneSource *>(s);
ShaderSource *shadersrc = dynamic_cast<ShaderSource *>(s);
if (s->locked() || !clonesrc)
if (s->locked() || ( !clonesrc && !shadersrc ) )
status_ = FINISHED;
@@ -1474,22 +1475,109 @@ void SetFilterUniform::update(Source *s, float dt)
}
void SetFilterUniform::multiply (float factor)
void SetUniform::multiply (float factor)
{
target_ *= factor;
}
SourceCallback *SetFilterUniform::clone() const
SourceCallback *SetUniform::clone() const
{
return new SetFilterUniform(uniform_, target_, duration_);
return new SetUniform(uniform_, target_, duration_);
}
void SetFilterUniform::accept(Visitor& v)
void SetUniform::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
SetCode::SetCode(const std::string &code) : SourceCallback(),
code_(code), compilation_(nullptr)
{
}
void SetCode::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
CloneSource *clonesrc = dynamic_cast<CloneSource *>(s);
ShaderSource *shadersrc = dynamic_cast<ShaderSource *>(s);
if (s->locked() || ( !clonesrc && !shadersrc ) )
status_ = FINISHED;
// update if there is an ongoing compilation
if (status_ == ACTIVE && compilation_ != nullptr ) {
static std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
if (compilation_return_.wait_for(timeout) == std::future_status::ready )
{
// get message returned from compilation
std::string status_ = compilation_return_.get();
Log::Info("setCode: %s", status_.c_str());
// end compilation promise
delete compilation_;
compilation_ = nullptr;
// done
status_ = FINISHED;
}
}
// apply when ready
if (status_ == READY) {
// if there is an image filter in the source
ImageFilter *__f = nullptr;
if (clonesrc)
__f = dynamic_cast<ImageFilter *>(clonesrc->filter());
else
__f = dynamic_cast<ImageFilter *>(shadersrc->filter());
if (__f) {
// set code to the image filter and start compilation
FilteringProgram prog;
prog.setName( __f->program().name() );
// try to open a file
std::ifstream file(code_);
// Check if the file is opened successfully
if (file.is_open()) {
prog.setFilename(code_);
}
else {
prog.resetFilename();
prog.setCode({code_, ""});
}
file.close(); // close anyways
// compile new code
compilation_ = new std::promise<std::string>();
__f->setProgram(prog, compilation_);
compilation_return_ = compilation_->get_future();
// building
status_ = ACTIVE;
}
else {
Log::Info("setCode: failed to get image filter");
status_ = FINISHED;
}
}
}
SourceCallback *SetCode::clone() const
{
return new SetCode(code_);
}
void SetCode::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
SetBlending::SetBlending(const std::string &method)
: SourceCallback()
, target_method_(method)
+24 -4
View File
@@ -3,6 +3,7 @@
#include <cmath>
#include <string>
#include <future>
#include <glm/glm.hpp>
class Visitor;
@@ -51,7 +52,8 @@ public:
CALLBACK_INVERT,
CALLBACK_POSTERIZE,
CALLBACK_FILTER,
CALLBACK_FILTER_UNIFORM,
CALLBACK_UNIFORM,
CALLBACK_CODE,
CALLBACK_BLENDING,
CALLBACK_INVALID
} CallbackType;
@@ -508,7 +510,7 @@ public:
void accept (Visitor& v) override;
};
class SetFilterUniform : public SourceCallback
class SetUniform : public SourceCallback
{
std::string uniform_;
float target_;
@@ -517,7 +519,7 @@ class SetFilterUniform : public SourceCallback
class ImageFilter *imagefilter;
public:
SetFilterUniform (const std::string &uniform = std::string(),
SetUniform (const std::string &uniform = std::string(),
float value = NAN, float ms = 0.f);
float value () const { return target_; }
@@ -528,7 +530,25 @@ public:
void update (Source *s, float) override;
void multiply (float factor) override;
SourceCallback *clone () const override;
CallbackType type () const override { return CALLBACK_FILTER_UNIFORM; }
CallbackType type () const override { return CALLBACK_UNIFORM; }
void accept (Visitor& v) override;
};
class SetCode : public SourceCallback
{
std::string code_;
std::promise<std::string> *compilation_;
std::future<std::string> compilation_return_;
public:
SetCode (const std::string &code = std::string());
const std::string &code () const { return code_; }
void setCode (const std::string &c) { code_ = c; }
void update (Source *s, float) override;
SourceCallback *clone () const override;
CallbackType type () const override { return CALLBACK_CODE; }
void accept (Visitor& v) override;
};
+50 -7
View File
@@ -1143,21 +1143,27 @@ void ImGuiVisitor::visit (ResampleFilter& f)
}
}
void list_parameters_(ImageFilter &f, std::ostringstream &oss)
void ImGuiVisitor::list_parameters_(ImageFilter &f, std::ostringstream &oss, bool editrange)
{
ImGuiIO& io = ImGui::GetIO();
std::map<std::string, float> filter_parameters = f.program().parameters();
FilteringProgram prog = f.program();
std::map<std::string, float> filter_parameters = prog.parameters();
for (auto param = filter_parameters.rbegin(); param != filter_parameters.rend(); ++param)
{
ImGui::PushID( param->first.c_str() );
float v = param->second;
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::SliderFloat( "##ImageFilterParameterEdit", &v, 0.f, 1.f, "%.2f")) {
auto range = prog.getParameterRange(param->first);
float rmin = range.first, rmax = range.second;
if (editrange)
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN - 1.6f * ImGui::GetTextLineHeightWithSpacing());
else
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::SliderFloat( "##ImageFilterParameterEdit", &v, rmin, rmax, "%.2f")) {
f.setProgramParameter(param->first, v);
}
if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){
v = CLAMP( v + 0.01f * io.MouseWheel, 0.f, 1.f);
v = CLAMP( v + 0.01f * (rmax - rmin) * io.MouseWheel, rmin, rmax);
f.setProgramParameter(param->first, v);
oss << " " << param->first << " " << std::setprecision(3) << v;
Action::manager().store(oss.str());
@@ -1166,9 +1172,46 @@ void list_parameters_(ImageFilter &f, std::ostringstream &oss)
oss << " " << param->first << " " << std::setprecision(3) <<param->second;
Action::manager().store(oss.str());
}
if (editrange) {
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGui::Button( "[ ]", ImVec2(1.6f * ImGui::GetTextLineHeight(), 0)) ) {
//
// Add '// range [ min max ]' to uniform declaration in shader code
//
// 1 get code
std::pair< std::string, std::string > code = prog.code();
std::string &glsl = code.first;
// 2 find uniform declaration for this parameter : "uniform float " + param->first
std::string decl_search = std::string("uniform float ") + param->first;
auto decl_pos = glsl.find(decl_search);
if (decl_pos != std::string::npos) {
auto semi_pos = glsl.find(';', decl_pos);
auto eol_pos = glsl.find('\n', decl_pos);
if (semi_pos != std::string::npos && (eol_pos == std::string::npos || semi_pos < eol_pos)) {
// 4 add comment with range at the end of the line
char range_comment[64];
snprintf(range_comment, sizeof(range_comment), " // range [%.6g %.6g]", rmin, rmax);
auto replace_start = semi_pos + 1;
auto replace_end = (eol_pos != std::string::npos) ? eol_pos : glsl.size();
glsl.replace(replace_start, replace_end - replace_start, range_comment);
}
}
// 3 set code with new comment
prog.setCode(code);
f.setProgram(prog);
// 4 show in GUI
UserInterface::manager().shadercontrol.setVisible(true);
}
if (ImGui::IsItemHovered()) {
char text_buf[512];
ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "// range [%.6g %.6g]\nEdit code to modify", rmin, rmax);
ImGuiToolkit::ToolTip(text_buf);
}
}
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::TextButton( param->first.c_str() )) {
v = 0.5f;
v = (rmax - rmin) * 0.5f + rmin;
f.setProgramParameter(param->first, v);
oss << " " << param->first << " " << std::setprecision(3) << v;
Action::manager().store(oss.str());
@@ -1480,7 +1523,7 @@ void ImGuiVisitor::visit (ImageFilter& f)
// List of parameters & textures
oss << "Custom ";
list_parameters_(f, oss);
list_parameters_(f, oss, true);
list_textures_(f, oss);
}
+2
View File
@@ -11,6 +11,8 @@ class ImGuiVisitor: public Visitor
InfoVisitor info;
std::ostringstream oss;
void list_parameters_(class ImageFilter &f, std::ostringstream &oss, bool editrange = false);
public:
ImGuiVisitor();
inline void reset () { info.reset(); }
+3 -4
View File
@@ -70,7 +70,7 @@ void saveEditorText(const std::string &filename)
/// SHADER EDITOR
///
///
ShaderEditWindow::ShaderEditWindow() : WorkspaceWindow("Shader"), _cs_id(0), current_(nullptr), show_shader_inputs_(false)
ShaderEditWindow::ShaderEditWindow() : WorkspaceWindow("Shader"), _cs_id(0), current_(nullptr), show_shader_inputs_(false), compilation_(nullptr)
{
auto lang = TextEditor::LanguageDefinition::GLSL();
@@ -142,9 +142,8 @@ void ShaderEditWindow::setVisible(bool on)
Settings::application.widget.shader_editor = on;
// reset current
current_ = nullptr;
_cs_id = 0;
// force refresh
Refresh();
}
bool ShaderEditWindow::Visible() const