Writing FXPack Shaders: Difference between revisions

From MXWendler Wiki
Jump to navigation Jump to search
Create developer guide: how to write FXPack shaders (bot)
 
Add section: writing a shader with Claude (bot)
Line 298: Line 298:
* Texture coordinates are in '''pixels''', not 0..1 — normalise with <code>mxw_reciprocalU/V_mxw</code> and un-normalise before every <code>texture2D</code> lookup.
* Texture coordinates are in '''pixels''', not 0..1 — normalise with <code>mxw_reciprocalU/V_mxw</code> and un-normalise before every <code>texture2D</code> lookup.
* Target <code>#version 120</code> for portability.
* Target <code>#version 120</code> for portability.
<div class="noprint">
==Writing a shader with Claude==
</div>
Large-language-model assistants such as [https://claude.ai Claude] are very good at GLSL, and the FXPack interface is simple enough that Claude can write a complete, working effect in one go — ''provided you give it the MXWendler-specific rules'', which it cannot know on its own. The trick is to paste the interface contract into the prompt, then describe the look you want.
===A ready-to-use prompt===
Copy this block into Claude, fill in the last line, and it will produce a valid <code>.mxf</code>:
<pre>
You are writing a fragment shader for MXWendler's FXPack effect format. Follow these rules exactly:
- Target GLSL "#version 120".
- The current video frame is a sampler2D declared as:
      uniform sampler2D mxw_tex_plus_0_mxw;
- Texture coordinates from gl_TexCoord[0].xy are in PIXELS (0..width, 0..height), NOT 0..1,
  because the frame is a rectangle texture. Convert with these engine uniforms/helpers:
      uniform float mxw_reciprocalU_mxw; // 1/width
      uniform float mxw_reciprocalV_mxw; // 1/height
      uniform float mxw_maxU_mxw;        // width
      uniform float mxw_maxV_mxw;        // height
      vec2 normalizedTc(vec2 tc)  { return tc * vec2(mxw_reciprocalU_mxw, mxw_reciprocalV_mxw); } // ->0..1
      vec2 unNormalizedTc(vec2 tc) { return tc * vec2(mxw_maxU_mxw, mxw_maxV_mxw); }              // ->pixels
  Always pass PIXEL coordinates to texture2D().
- Animate with time in milliseconds:  uniform float mxw_millis_mxw;
- Expose user controls as sliders. A slider is a float uniform with EXACTLY 6 underscores:
      uniform float mxw_vertslider_<Label>_<lo>_<hi>_<default>_mxw;
  <Label> is a single CamelCase token (no spaces/underscores). Numbers encode '.' as 'x'
  and a leading 'n' for negative:  0.5 -> 0x5,  100.0 -> 100x0,  -0.5 -> n0x5.
- Optional RGBA color picker = vec4 with EXACTLY 7 underscores:
      uniform vec4 mxw_colorcontrol_<Label>_<r>_<g>_<b>_<a>_mxw;
- Always include a Master dry/wet slider and cross-fade to the original at the end:
      uniform float mxw_vertslider_Master_0x0_1x0_1x0_mxw;
      gl_FragColor = mix(texture2D(mxw_tex_plus_0_mxw, gl_TexCoord[0].xy), processed,
                        mxw_vertslider_Master_0x0_1x0_1x0_mxw);
- Do not use any uniforms other than the ones above and your own private ones.
- Output nothing but the shader code.
Now write the effect: <DESCRIBE THE LOOK YOU WANT, e.g. "a horizontal chromatic-aberration
glitch whose strength pulses with time, with a Strength slider 0..20 default 5">
</pre>
===Turning Claude's output into an FXPack===
# Save Claude's code as <code>effects/MyEffect.mxf</code>.
# Optionally add <code>textures/</code> images and a <code>readme.txt</code>.
# ZIP the folders and rename to <code>MyEffect.fxpack</code> (see [[#Packaging and installing|Packaging and installing]]).
# Drop it into MXWendler's <code>effects/</code> folder and restart.
===Iterating===
* If the effect fails to load, MXWendler logs the reason (unknown uniform, wrong underscore count, missing aux texture). Paste that message back to Claude and ask it to fix it.
* To evolve a look, keep the conversation going: ''"add a Speed slider 0..5 default 1"'', ''"make the edges wrap instead of clamp"'', ''"tint the result with a color picker"''. Because the rules stay in context, Claude keeps emitting valid interface uniforms.
* For temporal effects (trails, motion blur, feedback) tell Claude it may also request past frames with <code>uniform sampler2D mxw_tex_minus_N_mxw;</code> (N = frames back) — see [[#Input textures (samplers)|Input textures]].
* Ask for an inline comment on each slider so the resulting shader stays self-documenting.
===Using Claude Code on the effect library===
Because every bundled effect is a plain ZIP, you can point [https://www.anthropic.com/claude-code Claude Code] (or any agent) at the <code>effects/</code> folder and ask it to unzip an existing <code>ag_*</code> effect, study the <code>.mxf</code>, and generate a variation — a fast way to build a family of related looks from a proven starting point.
'''Tip:''' always give the shader a quick visual check in Preload Preview before using it on the Render Output. Claude gets the interface and the maths right the vast majority of the time, but only your eyes can confirm the ''look''.


[[Category:Effects]]
[[Category:Effects]]
[[Category:Reference]]
[[Category:Reference]]

Revision as of 15:23, 8 August 2026

An FXPack is MXWendler's shader-effect container format. It is a plain ZIP archive with the extension .fxpack that bundles one or more GLSL shader stages together with any auxiliary textures and a readme. Dropping an .fxpack file into the effects/ folder makes a new effect available in Preload Preview, the Live Editor and on the Render Output — no recompilation of MXWendler is required.

The clever part of the format is the interface: MXWendler scans your shader source for specially-named uniform declarations. Uniforms whose names start with mxw_ and end with _mxw are recognised automatically and are either fed a live engine value every frame (time, resolution, input frames …) or turned into a UI control (a slider or a color picker) that the operator can automate. You never write any C++ or edit MXWendler itself — the shader is the plugin.

This page documents that interface exactly as the loader implements it (see mxw_effect.cpp, mxw_effectbase::setSource()).

See also: Effects · Tutorial Coupling Video with Effects and Audio Signals

Package structure

An .fxpack is a ZIP file with this internal layout:

MyEffect.fxpack            (a ZIP archive)
├── effects/
│     └── MyEffect.mxf     ← fragment shader (required)
│     └── MyEffect.mxv     ← vertex shader        (optional)
│     └── MyEffect.mxg     ← geometry shader      (optional)
│     └── MyEffect.mxtc    ← tessellation control (optional)
│     └── MyEffect.mxte    ← tessellation eval    (optional)
├── textures/
│     └── myLookup.bmp     ← auxiliary textures   (optional)
└── readme.txt             ← human description    (recommended)

Notes:

  • The effect name shown in the MXWendler UI is taken from the .fxpack file name, not from the file inside.
  • Inside effects/ the loader picks the first file matching each stage extension, so the base name is free — but matching it to the pack name keeps things tidy.
  • At most one file per shader stage. A minimal effect is just a single .mxf fragment shader.
  • Auxiliary textures live in a textures/ sub-folder and are wired up through sampler uniforms (see Auxiliary textures).

Shader stages and file extensions

Each GLSL pipeline stage is a separate file, identified by extension:

Extension Stage Required
.mxv Vertex shader no
.mxf Fragment shader yes (the workhorse)
.mxg Geometry shader no
.mxtc Tessellation control shader no
.mxte Tessellation evaluation shader no

Most effects are pure image processing and only need the fragment shader. If a tessellation stage is present, MXWendler switches the geometry to GL_PATCHES automatically.

Target GLSL version is 120 (#version 120) for maximum cross-platform / macOS compatibility. Higher versions work on desktop GL but may be skipped on older macOS.

The interface: mxw_..._mxw uniforms

The loader tokenises your source, strips comments, and inspects every uniform declaration. A uniform participates in the MXWendler interface only if its name starts with mxw_ and contains _mxw. Everything else is a normal private uniform you set yourself inside the shader.

Which kind of interface a uniform becomes is decided by its GLSL type and by how many underscores its name contains:

Type Underscore count Meaning
sampler2D Input texture (video frame, feedback, or aux image)
float 2 A live engine value (time, resolution …)
float 6 A slider UI control
vec4 7 A color picker UI control
Number encoding

Because GLSL identifiers cannot contain . or -, numeric values baked into uniform names are encoded:

  • x stands for the decimal point → 0x5 = 0.5, 100x0 = 100.0
  • a leading n means negative → n0x5 = -0.5 (slider ranges only)

Live engine values (global uniforms)

Declare a float uniform named mxw_<value>_mxw and MXWendler updates it every frame:

Uniform Value supplied each frame
mxw_millis_mxw Wall-clock time in milliseconds — use for animation
mxw_maxU_mxw Input texture width in pixels
mxw_maxV_mxw Input texture height in pixels
mxw_reciprocalU_mxw 1 / width (multiply texcoords by this to normalise to 0..1)
mxw_reciprocalV_mxw 1 / height
mxw_viewportsizeX_mxw Render viewport width in pixels
mxw_viewportsizeY_mxw Render viewport height in pixels
mxw_footagesizeX_mxw Source footage width in pixels
mxw_footagesizeY_mxw Source footage height in pixels
mxw_footageframelength_mxw Number of frames in the source footage
mxw_framenumber_mxw Current footage frame number
mxw_framecounterenvironment_mxw Global engine frame counter
mxw_outputLuminance1x1_mxw Average luminance of the output (1×1 reduction)

Declaring an unknown mxw_..._mxw float with two underscores is an error and the shader will fail to load.

Coordinate system — important

MXWendler binds video frames as rectangle textures, so gl_TexCoord[0].xy arrives in pixels (0…width, 0…height), not normalised 0…1. Use the reciprocal/max uniforms to convert. The standard helpers used throughout the stock effects are:

uniform float mxw_reciprocalU_mxw;
uniform float mxw_reciprocalV_mxw;
uniform float mxw_maxU_mxw;
uniform float mxw_maxV_mxw;

// pixel coords -> 0..1
vec2 normalizedTc(vec2 tc) {
    return tc * vec2(mxw_reciprocalU_mxw, mxw_reciprocalV_mxw);
}
// 0..1 -> pixel coords (needed before texture2D lookups)
vec2 unNormalizedTc(vec2 tc) {
    return tc * vec2(mxw_maxU_mxw, mxw_maxV_mxw);
}

Input textures (samplers)

Video input arrives through sampler2D uniforms. The name selects which frame:

Uniform pattern Delivers
mxw_tex_plus_0_mxw The current input frame (the normal case)
mxw_tex_plus_N_mxw The frame N steps in the future (look-ahead)
mxw_tex_minus_N_mxw The frame N steps in the past — enables trails / motion blur / temporal feedback
mxw_tex_render_prefinalfx_minus_N_mxw Feedback of the render output (before final FX), N frames back
mxw_accum_buffer_N_mxw Multi-Render-Target accumulation buffer N (advanced ping-pong)

Sample any of them with texture2D(sampler, pixelCoords) where the coordinates are in pixels (use unNormalizedTc() if you computed a 0..1 coordinate). Requesting past/future frames (minus/plus with N>0) tells the engine to keep a rolling history of frames for this effect.

UI controls

Sliders

A float uniform with six underscores becomes an operator-facing, automatable slider:

uniform float mxw_vertslider_<Label>_<lo>_<hi>_<default>_mxw;
  • vertslider — the widget type (currently the only slider type)
  • <Label> — a single-token name shown in the UI (no spaces/underscores; use CamelCase)
  • <lo> <hi> — slider range (encoded numbers, may be negative with n)
  • <default> — start value

Example — a "Resolution" knob from 0.1 to 10.0 starting at 0.5:

uniform float mxw_vertslider_Resolution_0x1_10x0_0x5_mxw;
The Master convention

By convention almost every stock effect exposes a Master slider (0…1, default 1) and uses it to cross-fade between the untouched input and the processed result, so the effect can be dialled in smoothly and automated:

uniform float mxw_vertslider_Master_0x0_1x0_1x0_mxw;
...
gl_FragColor = mix(
    texture2D(mxw_tex_plus_0_mxw, gl_TexCoord[0].xy),  // original
    processedColor,                                    // your result
    mxw_vertslider_Master_0x0_1x0_1x0_mxw);            // dry/wet

Following this convention is strongly recommended — it makes your effect behave like the built-ins.

Color pickers

A vec4 uniform with seven underscores becomes a color picker (RGBA):

uniform vec4 mxw_colorcontrol_<Label>_<r>_<g>__<a>_mxw;

Each component is an encoded number in 0…1. Example — a tint control defaulting to opaque dark blue:

uniform vec4 mxw_colorcontrol_Tint_0x0_0x1_0x25_1x0_mxw;  // rgba = (0.0, 0.1, 0.25, 1.0)

Auxiliary textures

To ship a fixed lookup image (gradient, mask, logo, LUT strip …) with your effect, place the file in the textures/ folder of the ZIP and reference it with a sampler whose name encodes the file name. The last underscore before _mxw separates the extension:

textures/myGradient.bmp   ⇄   uniform sampler2D mxw_tex_myGradient_bmp_mxw;
textures/mixred.bmp       ⇄   uniform sampler2D mxw_tex_mixred_bmp_mxw;

Rules:

  • Pattern: mxw_tex_<basename>_<ext>_mxw maps to textures/<basename>.<ext>.
  • The base name must not start with plus, minus, render or accum — those prefixes are reserved for the frame samplers above.
  • A missing texture file makes the effect fail to load.

A minimal fragment shader

The smallest useful effect — a brightness knob that fades to the original via Master:

#version 120

// input frame
uniform sampler2D mxw_tex_plus_0_mxw;

// dry/wet
uniform float mxw_vertslider_Master_0x0_1x0_1x0_mxw;

// user control: brightness 0..2, default 1
uniform float mxw_vertslider_Brightness_0x0_2x0_1x0_mxw;

void main(void)
{
    vec2 tc  = gl_TexCoord[0].xy;               // pixel coordinates
    vec4 src = texture2D(mxw_tex_plus_0_mxw, tc);

    vec4 processed = vec4(src.rgb * mxw_vertslider_Brightness_0x0_2x0_1x0_mxw, src.a);

    gl_FragColor = mix(src, processed, mxw_vertslider_Master_0x0_1x0_1x0_mxw);
}

Worked example: a time-animated distortion

This annotated version of the stock ag_Sine effect shows the coordinate helpers, a user slider and the Master mix working together:

#version 120

uniform sampler2D mxw_tex_plus_0_mxw;                          // current frame
uniform float     mxw_vertslider_Master_0x0_1x0_1x0_mxw;       // dry/wet
uniform float     mxw_vertslider_Resolution_0x1_10x0_0x5_mxw;  // user knob

uniform float mxw_reciprocalU_mxw;
uniform float mxw_reciprocalV_mxw;
uniform float mxw_maxU_mxw;
uniform float mxw_maxV_mxw;

vec2 normalizedTc(vec2 tc)   { return tc * vec2(mxw_reciprocalU_mxw, mxw_reciprocalV_mxw); }
vec2 unNormalizedTc(vec2 tc) { return tc * vec2(mxw_maxU_mxw, mxw_maxV_mxw); }

void main(void)
{
    vec2 tc = normalizedTc(gl_TexCoord[0].xy);   // -> 0..1
    tc = tc * 2.0 - 1.0;                         // -> -1..1

    vec2 xy = abs(tc) * mxw_vertslider_Resolution_0x1_10x0_0x5_mxw;
    vec2 val = fract(vec2(xy.x + sin(xy.y), xy.y + sin(xy.x)));

    vec4 processed = texture2D(mxw_tex_plus_0_mxw, unNormalizedTc(val));

    gl_FragColor = mix(texture2D(mxw_tex_plus_0_mxw, gl_TexCoord[0].xy),
                       processed,
                       mxw_vertslider_Master_0x0_1x0_1x0_mxw);
}

Packaging and installing

  1. Put your shader(s) in an effects/ folder and any images in a textures/ folder.
  2. Add a readme.txt describing what the effect does.
  3. ZIP those folders together (the folders must be at the root of the archive).
  4. Rename the resulting archive to YourEffectName.fxpack.
  5. Copy it into MXWendler's effects/ directory. It appears in the effect list on next start.

Because an .fxpack is just a ZIP, you can inspect or fork any of the 130+ bundled effects: copy one, rename to .zip, unzip, edit the .mxf, re-zip. The bundled ag_* effects are an excellent library of worked examples.

Rules and gotchas

  • Interface uniforms are matched by name, so spelling and underscore count matter. A slider needs exactly 6 underscores; a color picker exactly 7; a live value exactly 2.
  • Labels are single tokens — no spaces or underscores inside <Label>.
  • Encode decimals with x and negatives with a leading n. -0.5n0x5.
  • Do not declare the same interface uniform in two stages of one effect (e.g. mxw_millis_mxw in both the vertex and fragment shader). Declare it in one stage and pass the value on — this is also faster.
  • Commented-out uniforms are ignored (comments are stripped before scanning), so you can safely park declarations behind // or /* */.
  • Texture coordinates are in pixels, not 0..1 — normalise with mxw_reciprocalU/V_mxw and un-normalise before every texture2D lookup.
  • Target #version 120 for portability.

Writing a shader with Claude

Large-language-model assistants such as Claude are very good at GLSL, and the FXPack interface is simple enough that Claude can write a complete, working effect in one go — provided you give it the MXWendler-specific rules, which it cannot know on its own. The trick is to paste the interface contract into the prompt, then describe the look you want.

A ready-to-use prompt

Copy this block into Claude, fill in the last line, and it will produce a valid .mxf:

You are writing a fragment shader for MXWendler's FXPack effect format. Follow these rules exactly:

- Target GLSL "#version 120".
- The current video frame is a sampler2D declared as:
      uniform sampler2D mxw_tex_plus_0_mxw;
- Texture coordinates from gl_TexCoord[0].xy are in PIXELS (0..width, 0..height), NOT 0..1,
  because the frame is a rectangle texture. Convert with these engine uniforms/helpers:
      uniform float mxw_reciprocalU_mxw; // 1/width
      uniform float mxw_reciprocalV_mxw; // 1/height
      uniform float mxw_maxU_mxw;        // width
      uniform float mxw_maxV_mxw;        // height
      vec2 normalizedTc(vec2 tc)   { return tc * vec2(mxw_reciprocalU_mxw, mxw_reciprocalV_mxw); } // ->0..1
      vec2 unNormalizedTc(vec2 tc) { return tc * vec2(mxw_maxU_mxw, mxw_maxV_mxw); }               // ->pixels
  Always pass PIXEL coordinates to texture2D().
- Animate with time in milliseconds:  uniform float mxw_millis_mxw;
- Expose user controls as sliders. A slider is a float uniform with EXACTLY 6 underscores:
      uniform float mxw_vertslider_<Label>_<lo>_<hi>_<default>_mxw;
  <Label> is a single CamelCase token (no spaces/underscores). Numbers encode '.' as 'x'
  and a leading 'n' for negative:  0.5 -> 0x5,  100.0 -> 100x0,  -0.5 -> n0x5.
- Optional RGBA color picker = vec4 with EXACTLY 7 underscores:
      uniform vec4 mxw_colorcontrol_<Label>_<r>_<g>_<b>_<a>_mxw;
- Always include a Master dry/wet slider and cross-fade to the original at the end:
      uniform float mxw_vertslider_Master_0x0_1x0_1x0_mxw;
      gl_FragColor = mix(texture2D(mxw_tex_plus_0_mxw, gl_TexCoord[0].xy), processed,
                         mxw_vertslider_Master_0x0_1x0_1x0_mxw);
- Do not use any uniforms other than the ones above and your own private ones.
- Output nothing but the shader code.

Now write the effect: <DESCRIBE THE LOOK YOU WANT, e.g. "a horizontal chromatic-aberration
glitch whose strength pulses with time, with a Strength slider 0..20 default 5">

Turning Claude's output into an FXPack

  1. Save Claude's code as effects/MyEffect.mxf.
  2. Optionally add textures/ images and a readme.txt.
  3. ZIP the folders and rename to MyEffect.fxpack (see Packaging and installing).
  4. Drop it into MXWendler's effects/ folder and restart.

Iterating

  • If the effect fails to load, MXWendler logs the reason (unknown uniform, wrong underscore count, missing aux texture). Paste that message back to Claude and ask it to fix it.
  • To evolve a look, keep the conversation going: "add a Speed slider 0..5 default 1", "make the edges wrap instead of clamp", "tint the result with a color picker". Because the rules stay in context, Claude keeps emitting valid interface uniforms.
  • For temporal effects (trails, motion blur, feedback) tell Claude it may also request past frames with uniform sampler2D mxw_tex_minus_N_mxw; (N = frames back) — see Input textures.
  • Ask for an inline comment on each slider so the resulting shader stays self-documenting.

Using Claude Code on the effect library

Because every bundled effect is a plain ZIP, you can point Claude Code (or any agent) at the effects/ folder and ask it to unzip an existing ag_* effect, study the .mxf, and generate a variation — a fast way to build a family of related looks from a proven starting point.

Tip: always give the shader a quick visual check in Preload Preview before using it on the Render Output. Claude gets the interface and the maths right the vast majority of the time, but only your eyes can confirm the look.