foo_vis_milk2
Preset Debugging GuideReference for debugging preset rendering differences between the
original MilkDrop 2 (DirectX 9) and foo_vis_milk2 (DirectX
11.1).
VS[0] (previous frame)
|
v
Warp Pass ──> VS[1] Reads VS[0], applies warp mesh distortion, writes VS[1]
|
v
Blur Passes Reads from frame, generates Gaussian blur levels
|
v
Custom Shapes/Waves ──> VS[1] Drawn directly into VS[1] (alpha-blended)
|
v
Comp Pass ──> Backbuffer Reads VS[1] + blur textures, applies comp shader
foo_vis_milk2 assembles shader text by combining include
headers + defines + the preset shader body + wrapping code.
Warp: #define uv _uv.xy / #define uv_orig _uv.zw (float4 _uv : TEXCOORD0)
Comp: #define uv _uv.xy / #define uv_orig _uv.xy (float2 _uv : TEXCOORD0)
Note: comp uses float2 _uv (no zw), so
uv_orig = uv by design.
// First line (both warp and comp):
float3 ret = 0;
// Last line:
_return_value = float4(ret.xyz, _vDiffuse.w);
.milk
File Format: Backtick Prefix on Shader LinesIn .milk preset files, shader code is stored as numbered
INI-style key=value lines:
warp_1=`float3 ret = 0;
warp_2=`ret = tex2D(sampler_main, uv).xyz;
comp_1=`float3 ret = 0;
comp_2=`ret = GetMain(uv);
per_frame_1=wave_r = 0.5 + 0.5*sin(time);The leading backtick (`) on warp_ and
comp_ lines is a legacy escape character. Original MilkDrop
used the Windows GetPrivateProfileString()
INI API, which treats ; as a comment delimiter. Since HLSL
shader lines contain semicolons, the backtick was prepended to prevent
the INI parser from truncating lines at the first ;.
WriteCode): The backtick is
always prepended to warp_ and comp_ shader
lines. EEL expression lines (per_frame_,
per_pixel_, etc.) are written without it.ReadCode): If the first
character is a backtick, it is stripped (&szLine[1]).
If absent, the line is used as-is. Both forms are accepted.The backtick is optional on read but always written for backward compatibility.
The most common source of rendering differences between DX9-based
MilkDrop and foo_vis_milk2 is NaN
propagation. DX9 hardware (especially NVIDIA) handled edge
cases in non-standard ways, while DX11 follows IEEE 754 strictly.
foo_vis_milk2 applies safe shader intrinsic wrappers to
prevent NaN from entering the feedback loop:
| Function | DX9 Behavior | DX11 Behavior | Applied Fix |
|---|---|---|---|
normalize(0) |
Returns 0 | NaN | Guard zero-length vectors |
sqrt(negative) |
sqrt(abs(x)) (positive) |
NaN | sqrt(abs(x)) always positive |
log(x ≤ 0) |
Large negative | -Infinity / NaN | Clamp to tiny positive |
tan() at poles |
Infinity × 0 = 0 | Infinity × 0 = NaN | Clamp to large finite |
pow(negative, non-int) |
abs(x)^y or 0 |
NaN | Guard negative bases |
asin/acos outside [-1,1] |
Clamps internally | NaN | Clamp input range |
atan2(0, 0) |
Returns 0 | May return NaN | Offset by epsilon |
0 / 0 |
Returns 0 | NaN | Replace zero denominators with epsilon |
x / 0 |
±FLT_MAX | ±Infinity | Replace zero denominators with epsilon |
| Issue | DX9 Behavior | DX11 Problem | Fix Applied |
|---|---|---|---|
bool as l-value |
Works freely | X4555 error on assignment | Convert to int |
| Double literals | Implicit float | Warning on narrowing | Explicit float casts |
| Variable self-shadowing | Allowed | Compilation error | Rename conflicting variables |
| User functions shadow builtins | Allowed | Undefined behavior | Rename user identifiers |
| Macro collisions | Often tolerated | Compile failure | Auto-insert #undef |
foobar2000 float [-1.0, +1.0]
|
v
Waveform buffer (576 samples, stereo)
|
v
FFT (512 frequency bins)
|
v
3-band analysis (bass, mid, treb)
|
v
Available as EEL variables and shader constants
This differs from the original MilkDrop 2 which received 8-bit unsigned PCM from Winamp.
bSpectrum=1)Use this checklist when a preset differs between MilkDrop 2 (DX9) and
foo_vis_milk2 (DX11).
ret assignment to catch
unexpected transformations.VS[1] contributions, and comp shader math can amplify
tiny per-channel differences.foo_vis_milk2 (DX11)ps_2_* assumptions.assign,
if, equal, above, or
below may require EEL2-style conversion.foo_vis_milk2 may rename symbols that
shadow built-in functions.milkdrop2\debug\./WX equivalent) during investigation.foo_vis_milk2foo_vis_milk2 compiles through
D3DCompile(), but these are the equivalent fxc
command lines:
# Vertex shader
fxc /E VS /T vs_4_0_level_9_1 /Gec your_shader.hlsl
# Pixel shader profiles selected by shader mode
fxc /E PS /T ps_4_0_level_9_1 /Gec your_shader.hlsl # SM 2.0 compatibility path
fxc /E PS /T ps_4_0_level_9_3 /Gec your_shader.hlsl # SM 2.x compatibility path
fxc /E PS /T ps_4_0 /Gec your_shader.hlsl
fxc /E PS /T ps_5_0 /Gec your_shader.hlsl
# Strict mode when "Treat shader warnings as errors" is enabled
fxc /E PS /T ps_4_0_level_9_3 /Gec /WX your_shader.hlslNotes:
VS and PS./Gec corresponds to
D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY./WX corresponds to Treat shader warnings as
errors.ps_4_0_level_9_1 /
ps_4_0_level_9_3 for SM 2.x-style compatibility instead of
direct ps_2_* targets.All constants, samplers, defines, and helper functions are declared
in include.fx, which is prepended to every assembled
shader.
_c0 = aspect ratio (.xy = UV multiplier for fullscreen aspect-aware paste; .zw = inverse)
_c1 = (reserved)
_c2 = (.x = time, .y = fps, .z = frame, .w = progress)
_c3 = (.x = bass, .y = mid, .z = treb, .w = vol)
_c4 = (.x = bass_att, .y = mid_att, .z = treb_att, .w = vol_att)
_c5 = (.xy = blur1 scale+bias, .zw = blur2 scale+bias)
_c6 = (.x = blur3 scale, .y = blur3 bias, .z = blur1_min, .w = blur1_max)
_c7 = texsize (.x = width, .y = height, .z = 1/width, .w = 1/height)
_c8 = roam_cos ~= 0.5 + 0.5 * cos(time * float4(~0.3, ~1.3, ~5, ~20))
_c9 = roam_sin ~= 0.5 + 0.5 * sin(time * float4(~0.3, ~1.3, ~5, ~20))
_c10 = slow_roam_cos ~= 0.5 + 0.5 * cos(time * float4(~0.005, ~0.008, ~0.013, ~0.022))
_c11 = slow_roam_sin ~= 0.5 + 0.5 * sin(time * float4(~0.005, ~0.008, ~0.013, ~0.022))
_c12 = (.x = mip_x, .y = mip_y, .z = mip_avg, .w = unused)
_c13 = (.x = blur2_min, .y = blur2_max, .z = blur3_min, .w = blur3_max)
Additional constants for foo_vis_milk2:
_c14 = mouse (.xy = position, .z = button hold, .w = mouse click)
_c15 = (.x = hour, .y = minute, .z = second, .w = total seconds)
_c16 = (.x = year, .y = month, .z = day, .w = weekday 1-7)
rand_frame float4 random values updated each frame
rand_preset float4 random values set once at preset load
Q variables are set in EEL per-frame/per-vertex code and passed to
shaders as packed float4 banks:
_qa = q1–q4 _qi = q33–q36
_qb = q5–q8 _qj = q37–q40
_qc = q9–q12 _qk = q41–q44
_qd = q13–q16 _ql = q45–q48
_qe = q17–q20 _qm = q49–q52
_qf = q21–q24 _qn = q53–q56
_qg = q25–q28 _qo = q57–q60
_qh = q29–q32 _qp = q61–q64
Individual q values are #defined (e.g.
#define q1 _qa.x).
24 float4x3 rotation matrices with minor translation are
available:
rot_s1–rot_s4 static: randomized at preset load time
rot_d1–rot_d4 dynamic: slowly changing
rot_f1–rot_f4 fast: faster changing
rot_vf1–rot_vf4 very fast
rot_uf1–rot_uf4 ultra fast
rot_rand1–rot_rand4 random: re-randomized every frame
| Macro | Expands To |
|---|---|
M_PI |
3.14159265359 |
M_PI_2 |
6.28318530718 (2π) |
M_INV_PI_2 |
0.159154943091895 (1/2π) |
time |
_c2.x |
fps |
_c2.y |
frame |
_c2.z |
progress |
_c2.w |
bass / mid / treb /
vol |
_c3.xyzw |
bass_att / mid_att / treb_att
/ vol_att |
_c4.xyzw |
aspect |
_c0 |
texsize |
_c7 |
roam_cos / roam_sin |
_c8 / _c9 |
slow_roam_cos / slow_roam_sin |
_c10 / _c11 |
mip_x / mip_y / mip_avg |
_c12.x / .y / .z |
blur1_min / blur1_max |
_c6.zw |
blur2_min / blur2_max |
_c13.xy |
blur3_min / blur3_max |
_c13.zw |
mouse |
_c14 (also mouse_x, mouse_y,
mouse_pos, mouse_hold,
mouse_click) |
hour / minute / second /
total_seconds |
_c15.xyzw |
year / month / day /
weekday |
_c16.xyzw |
lum(x) |
dot(x, float3(0.32, 0.49, 0.29)) |
GetMain(uv) tex2D(sampler_main, uv).xyz
GetPixel(uv) tex2D(sampler_main, uv).xyz // alias for GetMain
GetBlur1(uv) tex2D(sampler_blur1, uv).xyz * scale + bias
GetBlur2(uv) tex2D(sampler_blur2, uv).xyz * scale + bias
GetBlur3(uv) tex2D(sampler_blur3, uv).xyz * scale + bias
get_fft(p) FFT magnitude at normalized position p ∈ [0,1]
get_fft_peak(p) FFT peak-hold at normalized position p
get_fft_hz(hz) FFT magnitude at frequency in Hz (0–22050)
get_fft_peak_hz(hz) FFT peak at frequency in Hz
get_wave(p) Waveform sample (average of L+R) at position p
get_wave_left(p) Left channel waveform at position p
get_wave_right(p) Right channel waveform at position p
Case-insensitive aliases are also defined: GetFFT,
GetFFTPeak, GetFFTHz,
GetFFTPeakHz, GetWave,
GetWaveLeft, GetWaveRight.
foo_vis_milk2 automatically rewrites shader intrinsic
calls to use safe wrappers that prevent NaN/Infinity propagation under
DX11 IEEE 754 rules. The replacement is done during shader assembly (in
shader.cpp), so preset code calling e.g.
normalize(x) actually executes
_safe_normalize(x).
All safe functions are overloaded for float,
float2, float3, and float4:
| Original Call | Safe Wrapper | Implementation |
|---|---|---|
normalize(x) |
_safe_normalize(x) |
Returns 0 if dot(x,x) == 0, else
x * rsqrt(dot) |
sqrt(x) |
_safe_sqrt(x) |
sqrt(abs(x)) |
log(x) |
_safe_log(x) |
log(max(1e-30, x)) |
tan(x) |
_safe_tan(x) |
clamp(tan(x), -1e4, 1e4) |
pow(x, y) |
_safe_pow(x, y) |
pow(abs(x), y) |
asin(x) |
_safe_asin(x) |
asin(clamp(x, -1, 1)) |
acos(x) |
_safe_acos(x) |
acos(clamp(x, -1, 1)) |
atan2(y, x) |
_safe_atan2(y, x) |
atan2(y, x + 1e-20) |
Additionally, _safe_denom(x) replaces zero denominators
with 1e-30 to prevent 0/0 → NaN and
x/0 → Infinity. Division wrapping is applied during shader
compilation when _safe_denom is not already present.
Note: these compatibility functions can be disabled in Advanced preferences by turning off Fix legacy shaders.
Case-insensitive texture lookup aliases:
tex1d → tex1D
tex2d → tex2D
tex3d → tex3D
texcube → texCUBE
Up to 16 sampler slots are available:
sampler_main default (bilinear, wrap)
sampler_fw_main bilinear, wrap
sampler_fc_main bilinear, clamp
sampler_pw_main point, wrap
sampler_pc_main point, clamp
All five sample from PrevFrameImage (VS[1] for comp,
VS[0] for warp). The prefix encodes filter (F=bilinear, P=point) and
address mode (W=wrap, C=clamp). Case-insensitive aliases are defined
(e.g. sampler_FW_main).
sampler_noise_lq 256×256 noise (bilinear)
sampler_noise_lq_lite 32×32 noise (bilinear)
sampler_noise_mq 256×256 noise (4× cubic)
sampler_noise_hq 256×256 noise (8× cubic)
sampler_noisevol_lq 32×32×32 3D noise
sampler_noisevol_hq 32×32×32 3D noise (4× cubic)
Each noise sampler has a matching texsize_*
float4 with
(width, height, 1/width, 1/height).
sampler_blur1 blur level 1
sampler_blur2 blur level 2
sampler_blur3 blur level 3
sampler_fft FFT audio spectrum texture (row 0.25 = magnitude, row 0.75 = peak)
sampler_wave Waveform audio texture (row 0.25 = left, row 0.75 = right)
User textures from disk are bound to available slots with prefix-based filtering and wrapping modes (FW/FC/PW/PC).