-
-
Notifications
You must be signed in to change notification settings - Fork 382
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.
Most unsupported functionality is related to "recent" additions in Milkdrop 2.2, especially the new ns-eel2 parser. projectM's equation parser code was written in the early Milkdrop 1.x era, and hasn't seen any substantial updates since then.
If you find any differences not already covered by this document, please open a bug report in our issue tracker.
The current preset parser uses a completely different approach for parsing presets than Milkdrop. Parsing is done in a straightforward way, ignoring line numbers and expecting everything to be in the correct order. This is not an issue for the majority of presets, but if there are even tiny errors in the file, the parser might read it in a different way than Milkdrop.
As mentioned above, line numbers are not honored. Milkdrop will search for a certain prefix, e.g. per_frame_
, and then
start counting at 1, adding the number to the prefix and try to find a matching line in the file. The first line found
is loaded, then the number is incremented by 1 and so on. If the parser does not find the key, it aborts. projectM's
parser in contrast will read the file from start to end and just reads the prefix of a line key, then adds the code in
the line to the specific equation block, no matter if there are gaps in the numbers, the line order is wrong etc.
In addition to the above issue, the parser only understands equation code in the form var = expr;
, where var
is the
name of a variable/parameter and expr
is any equation returning a value. Some Milkdrop features also allow functions
on the left-hand side of an assignment, or no assignment at all. Some examples are:
megabuf(some_index) = sin(x);
loop(count, x = x + sin(aaa); y = y - cos(bbb));
Besides the actual functions not being supported (see below), the parser isn't able to read such lines without major changes.
There is an effort to completely rewrite the preset parser, adding a preprocessor (similar to how the Milkdrop parser works) and adding support for the missing constructs below. If you are interested in the implementation progress, please follow this branch.
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.
Currently, projectM does not implement or support the following functions in equations:
-
loop(count, statement1; statement2; ...)
- loopscount
number of times over the given statements. -
while(expr)
- executesexpr
at least once and until it returns 0 (aborts after 0x100000 (1048576) loop iterations). -
megabuf(index)
- returns (or assigns if used as an lvalue) a floating-point number in the equation-local memory buffer. -
gmegabuf(index)
- returns (or assigns if used as an lvalue) a floating-point number in the global memory buffer. -
freembuf(block)
- frees all megabuf RAM from the given memory buffer block until the end of allocated RAM. -
memcpy(dstoffset, srcoffset, count)
- copiescount
values from source to destination offset in megabuf RAM. -
memset(offset, value, count)
- setscount
number of values beginning atoffset
tovalue
. -
exec2(stmt1, stmt2)
- executes both statements in order and returns the return value of the second statement. -
exec3(stmt1, stmt2, stmt3)
- executes all three statements in order and returns the return value of the third statement. -
invsqrt(num)
- returns the fast inverse square root of the given number.
Any preset using one or more of these functions will not work properly as the code block using those cannot be parsed.
Most of these functions are undocumented and thus rarely or never used, but loop
and [g]megabuf
are heavily
utilized in a good umber of presets.
Milkdrop supports a few additional operators since one of the last official releases which projectM currently does not understand:
- Multiple assignment operators in one line:
a = b = 1;
- All compound assignment operators:
+=
,-=
,*=
,/=
,%=
,|=
,&=
,^=
- Equality/comparison operators
==
,!=
,<=
,>=
,<
,>
- Boolean operators:
||
,&&
- Power operator:
^
- Negation operator:
!
- Ternary operator (short-hand if):
cond ? trueexp : falseexp
- Memory access operator:
gmem[index]
,index[offset]
andindex[]
(which is short forindex[0]
)
While those operators are undocumented in any official Milkdrop guides, there are presets using them. These won't work in projectM.
Milkdrop has a few mathematical constants and expressions which can be used in equation code. projectM currently doesn't support any of these:
-
$PI
- evaluates to 3.141592653589793 -
$E
- evaluates to 2.71828183 -
$PHI
- evaluates to 1.61803399 -
$'c'
- evaluates to the numerical ASCII (ordinal) value ofc
-
$Xn
- evaluates to the decimal value of the hexadecimal numbern
(can be up to 16 hex digits long)
These expressions are not documented, and currently there are no presets known to use them. If they are used though, projectM will currently not be able to parse such presets.
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.
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.
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.
Some blending modes work slightly differently between OpenGL and Direct3D. This results in some built-in blend modes to either darken or brighten the image too much between frames.
We are looking into these issues in future versions and will try to mitigate them, e.g. by using custom fragment shaders instead of built-in OpenGL blend modes to get a better result.
Some presets, for example "shifter - robotopia.milk", only render a very small shape in the center of the screen. In some affected presets, the first one or two frames show correctly, but quickly shrink into the center.
The issue has not been investigated thoroughly yet, but seems to be related to some equation variables not properly being propagated between code blocks, reset at the wrong time or not being transferred to the next frame. A large number of presets might be affected, but not all with the same visible effect as in the example above.