foo_vis_milk2 Preset Debugging Guide

Reference for debugging preset rendering differences between the original MilkDrop 2 (DirectX 9) and foo_vis_milk2 (DirectX 11.1).


Rendering Pipeline Overview

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

Shader Wrapping

foo_vis_milk2 assembles shader text by combining include headers + defines + the preset shader body + wrapping code.

Defines

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/Last Lines

// 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 Lines

In .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 ;.

The backtick is optional on read but always written for backward compatibility.


DX9 to DX11 IEEE 754 Differences

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

HLSL Language Fixes

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

Audio Data Path

Float Samples from foobar2000

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.


Custom Shape Rendering

Shapes

Waveforms


Common Debugging Scenarios

Use this checklist when a preset differs between MilkDrop 2 (DX9) and foo_vis_milk2 (DX11).

Colors Wrong

  1. Check for IEEE 754 issues. NaN propagation through the feedback loop is one of the most common causes of color divergence between DX9 and DX11.
  2. Trace the shader math in the dumped source. Inspect generated shader text around the ret assignment to catch unexpected transformations.
  3. Check shape alpha values. Low shape alpha produces small VS[1] contributions, and comp shader math can amplify tiny per-channel differences.
  4. Toggle "Fix legacy shaders" in advanced preferences to compare behavior with and without automatic compatibility rewrites.

Preset Works in MilkDrop 2 (DX9) but not foo_vis_milk2 (DX11)

  1. Check shader model selection. Use Shader Model 2.0 or Shader Model 2.x options in preferences (these map to DX11 compatibility profiles), not legacy ps_2_* assumptions.
  2. Check EEL syntax compatibility. EEL1 compatibility mode is disabled; presets using constructs such as assign, if, equal, above, or below may require EEL2-style conversion.
  3. Verify texture availability and naming. Missing textures can fall back differently than in DX9.
  4. Check for HLSL variable shadowing and compiler-fix side effects. foo_vis_milk2 may rename symbols that shadow built-in functions.

Shapes Wrong Size

  1. Check aspect ratio handling. DX11 path and DX9 path can differ in edge-case preset configurations.
  2. Verify mesh size. Default mesh size is 48 × 36 (DX9 MilkDrop used 32 × 24 or 48 × 36 depending on configuration).
  1. In Advanced > Visualization > MilkDrop 2, turn Debug output on to dump shader source to milkdrop2\debug\.
  2. Reproduce the issue and inspect dumped warp/comp shader text.
  3. Toggle Fix legacy shaders off to isolate whether a compatibility rewrite is involved.
  4. Enable Treat shader warnings as errors for strict compile mode (/WX equivalent) during investigation.

FXC-Equivalent Compile Commands Used by foo_vis_milk2

foo_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.hlsl

Notes:


Key Constants Reference

All constants, samplers, defines, and helper functions are declared in include.fx, which is prepended to every assembled shader.

Constant Buffers

_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)

Random Values

rand_frame    float4 random values updated each frame
rand_preset   float4 random values set once at preset load

Q Variables (EEL ↔︎ Shader Bridge)

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).

Rotation Matrices

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

Predefined Macros

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))

Texture Helper Macros

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

Audio Texture Macros

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.

Safe Intrinsic Wrappers

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.

Texture Lookup Aliases

Case-insensitive texture lookup aliases:

tex1d     → tex1D
tex2d     → tex2D
tex3d     → tex3D
texcube   → texCUBE

Sampler Register Map

Up to 16 sampler slots are available:

Previous-Frame Image Samplers

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).

Built-in Noise Samplers

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).

Blur Samplers

sampler_blur1    blur level 1
sampler_blur2    blur level 2
sampler_blur3    blur level 3

Audio Samplers

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).