-
-
Notifications
You must be signed in to change notification settings - Fork 376
Milkdrop Compatibility
While the goal of projectM is to achieve the best Milkdrop compatibility possible, there still are - and always be - some differences when it comes to rendering presets. This document gives you an overview over all known issues and whether they can be solved or not. The document reflects the state of the current development in the master branch, and not necessarily the latest stable release.
Most of the still unsupported functionality is related to Milkdrop 2 shaders in presets.
If you find any differences in the master branch not already covered by this document, please open a bug report in our issue tracker.
The preset file parser has been rewritten and vastly simplified for the libprojectM 4.1 release. It now uses a very similar approach to reading a .milk file as Milkdrop and uses the same default and fallback values as the original implementation.
The file parser should now be fully compatible with the Milkdrop 2.2 source code release.
Equations are the CPU-calculated part of Milkdrop presets, e.g. the "per-point"/"per-frame" code, shapecode and wavecode. Equation code can be identified in preset files not having a backtick (`) after the equal sign.
In release 4.1, the equation compiler has been reimplemented from scratch in
a separate evaluation library, making all well-formatted
equation code fully compatible with the original Milkdrop ns-eel2 compiler. It supports all features present in the last
source code release, including loops, regXX vars, (g)megabuf, all documented and undocumented functions, the
undocumented $PI
, $PHI
and other constants.
It also implements as under-the-hood quirks like if()
returning variable references and the ability to use such
expressions on the left side of assignments.
There also is a full documentation of all available functions in the library repository.
The only real difference between both compilers is the way errors in the code are handled.
In Milkdrop, the compiler does ignore some syntax errors in the code. Depending on where the error is located in the program and how it syntactically breaks the parser, only a single expression (up to the next ;), a whole block or the remaining program can cease to work. The author doesn't get any information other than the code not working as intended.
The libprojectM compiler will always reject the whole code block. Currently, it is silently discarded, but there will be proper error handling in a future version, so preset authors can get proper feedback in which line/column of their expression code they've made an error.
This difference can lead to a few presets not running properly in projectM while they seem to work correctly in Milkdrop. Implementing the same fuzzy error handling as in Milkdrop would have required a completely different approach to parsing/compiling the code, and as it only affects a handful of presets, fixing the milk files is regarded to be the better solution.
Milkdrop presets use HLSL shader syntax for both composite and warp shaders. projectM on the other hand, as a cross-platform library, currently uses OpenGL (or OpenGL ES) for rendering and thus needs to convert HLSL language into GLSL.
projectM uses Unknownworld's hlslparser with a few fixes or this task. While most of the shaders in presets can be translated correctly, there are still some known issues. Some could be fixed in the transpiler, but HLSL has some unique features which are not supported by GLSL and cannot be translated without manually rewriting the preset shader code.
HLSL supports "extern" shader variables, which are in theory meant to be set from the outside like read/write uniforms, but can be used as a kind of global variable. GLSL in contrast does not allow for global variables in shader code and all uniforms are read-only. Since global variables cannot be easily turned into local variables, presets using this feature will not work correctly in projectM, forever.
Presets using global variables can be identified by looking for variables declared outside any function body, e.g.:
float count;
float SomeFunc()
{
count += 1;
return 2.5;
}
shader_body {
count = 1;
float val = SomeFunc();
}
Presets can in some cases be fixed manually by moving variables into the functions and pass them to other functions
as Inout
parameters:
float SomeFunc(Inout float count)
{
count += 1;
return 2.5;
}
shader_body {
float count = 1;
float val = SomeFunc(count);
}
It might theoretically be possible to automatically pre-process the shader code in such a way that global variables are
moved to the top of the shader_body
function and then all functions using one of those get the required Inout
parameters added. The current transpiler does not support such a complex operation though, so this will require a large
amount of work.
Another possible mitigation would be allowing preset authors to add GLSL shader code directly in the preset, e.g. with a
different prefix like gl_comp_#
and gl_warp_#
. Such presets would then use the GL shaders in projectM and the
original code in Milkdrop. The drawback here is that the preset author has to write code in both shader languages and
make sure the code does not diverge.
The HLSL transpiler should support most HLSL functions, but it was originally written for an older DirectX version and not updated much since then. Presets can use shader language levels supported by DirectX 9 in Milkdrop.
There is no list of known unsupported functions right now. Please inform the projectM team if you have identified an unsupported intrinsic.
Some presets use the SamplerState
struct in HLSL shaders to override some sampler settings like the repeat mode or the
min/mag filter modes. While it is perfectly valid in DirectX 9, newer DirectX versions and OpenGL do not allow to change
these parameters on the GPU side.
This means that presets using this struct will not have their shader compile and a loading error will be issued. There are only a few presets using it, so it's very rare to encounter this specific issue.
While the above issues will naturally affect the preset rendering and visual quality, there are other issues in emulating Milkdrop's waveform and shape rendering code not related to shaders or equation parsing.
Currently, random texture selection (e.g. by using sampler_rand00
in shaders) does not work and will fail to load any
texture, resulting in the preset being mostly black.
Due to the issue that texture/screen coordinates work differently in DirectX and OpenGL, all drawing has to be done upside down and is then flipped when applying the warp shader. As classic presets use different effects, the image is not flipped properly and thus ends up flipped on the Y axis when displayed.
Some effects also don't line up properly, e.g. custom waves and shapes, so it's not just the final image flip that is broken.
Some presets will render quite dimly in higher resolutions like 4K and above. This is not exactly a difference to Milkdrop, which has the exact same issue, but a result of the way waveforms, shape outlines and the "motion vector grid" is being rendered. This is not expected to change in future versions. Continue reading for a technical explanation.
All lines and points are currently rendered at physical 1px line width or point size, with no exceptions. The "thick" drawing flag will cause the line to be rendered 4 times, translated onto a 2x2 pixel grid. This will result in dots being 2x2 pixels in size and outlines 2 pixels thick, also regardless of the actual screen resolution.
Since the waveforms, shapes and motion vector grid sizes scale to the screen size, the dots and lines won't.
In the past, projectM used a line thickness relative to the screen size in the past by calling glLineWidth
with a
value larger than
one. Besides not being supported by all drivers/platforms, this caused some weird rendering artifacts like gaps and
bright sport at corners, while anything rendered as dots was always 1px in size, no exceptions.
The (FFT) spectrum analyzer used in projectM creates a very different range of values as Milkdrop does. Specifically, all values have very high peak numbers, resulting in similarly high values in the bass/mid/treb (and their "att" versions) expression variables. The same is true for waveforms using spectrum data, e.g. custom waves or the default waveform with mode 8.
Some presets, for example "shifter - robotopia.milk", make use of the mentioned values under the assumption they stay in the documented 0.7 to 1.3 range. With the values being way too high and almost never falling below 1, the robotopia preset for example renders a very small shape in the center of the screen. In other affected presets, where these values are used for different calculations, it may result in superfast movement, much more scaling of waves and shapes on beats as intended and all kinds of other glitches.
As of version 4.2, libprojectM supports rendering of Milkdrop user sprites.
There are two key differences to how Milkdrop parses and displays sprites:
- The projectM sprite API only takes a single sprite as code in the API function. Applications using projectM therefore
must read any pre-existing sprites INI file and split it up using the
[imgNN]
section headers. Passing the actual section header is supported, but completely optional as it is ignored by projectM. - projectM does not support color keys with blend mode 4. Color keyed textures is a feature solely supported by DirectX. As projectM uses OpenGL, reimplementing this feature wasn't worth the effort. Instead, blend mode 4 in projectM will use the texture alpha channel for transparency. This is even superior to color keying as it allows smooth transparency gradients.
In future releases, additional sprite types might be supported. The API expects a type string to determine the sprite
variant, with milkdrop
currently being the only supported type. Trying to show a sprite with an unknown type name will
simply be ignored by the library, not causing any issues except for the sprite not being displayed.