Skip to content

Milkdrop Compatibility

Kai Blaschke edited this page Sep 15, 2023 · 4 revisions

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.

.milk Parser state

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.

Preset equations

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.

Error handling

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.

Shaders

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.

Global shader variables

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.

Unsupported intrinsics

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.

SamplerState structs

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.

Rendering differences

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.

Random texture selection does not work

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.

Related issues

Old-style (Milkdrop 1.4) presets render upside down

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.

Related issues

Presets are too dark in higher resolutions

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.

Related issues

Audio analyzer differences

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.

Related issues