shader

Run your own pixel shader over the captured picture before it is drawn: anti-aliasing, sharpening, a colour fix, a CRT look. The engine knows none of these. It runs the shader your mod hands it, on the GPU, at the card's own resolution, before the picture is scaled into the window, so your shader sees the console's real pixels and never a stretched copy.

You ship it compiled. Write it in HLSL, compile it once with fxc (Microsoft's shader compiler, part of the Windows SDK), and put the .cso file in your mod. MCCP contains no shader compiler. It hands your bytes to Direct3D, which checks them.

Windows, Direct3D 11. On any other renderer shader.available() is false and shader.load says why, so a mod can leave its setting off instead of failing. While no shader is on, nothing extra runs at all. Workers have no shader.

functionshader.available() → boolean

Whether frame shaders can run here.

functionshader.load(path) → Shader | nil, why

Loads a compiled pixel shader and switches it on at once. Shaders run one after another, in the order they were loaded, across all mods: each one reads what the one before it wrote. Returns nil and a sentence when the file is missing, is not a compiled shader, is refused by Direct3D, or your mod already has 4.

ParamTypeNotes
pathstringLooked up like your mod's images ("addons/mymod/fx.cso", also inside a packaged mod). Inside the app's folder only. At most 1 MB.

Keep a reference. A shader your mod drops all references to is removed when the collector takes it, and all of your mod's shaders go when it is unloaded.

local fx, why = shader.load("addons/mymod/fx.cso")
if not fx then print("no shader: " .. why) end
methodfx:set(slot, value) → ok, why

Hands your shader a number. Slots 1 to 32 land in mccp_params: slot 1 is mccp_params[0].x, slot 2 .y, slot 5 mccp_params[1].x, and so on. Every slot starts at 0 and keeps its value until you set it again, so set them when your settings change, not every frame.

methodfx:enable(on) → boolean

Switches it on or off without unloading it. Off costs nothing, and it keeps its place in the order.

methodfx:remove()

Unloads it. The object does nothing afterwards.

guideWriting one

A shader is one main function that returns the colour of one pixel. It gets the picture as t0 and two samplers: s0 blends between pixels, s1 takes the nearest one. Copy this header exactly, because the engine fills it in this layout:

cbuffer MCCP : register(b0) {
    float2 mccp_size;       // the picture, in pixels (1920, 1080)
    float2 mccp_texel;      // one pixel in uv: 1 / mccp_size
    float  mccp_time;       // seconds since MCCP started
    float  mccp_frame_no;   // pictures drawn through shaders so far
    float2 mccp_pad;
    float4 mccp_params[8];  // fx:set(1..32)
};
Texture2D    t0 : register(t0);   // the picture
SamplerState s0 : register(s0);   // linear, clamped at the edges
SamplerState s1 : register(s1);   // nearest, clamped at the edges

// pos: this pixel's centre in pixels. uv: 0..1, top-left is (0, 0).
float4 main(float4 pos : SV_Position, float2 uv : TEXCOORD0) : SV_Target {
    float strength = mccp_params[0].x;                    // fx:set(1, ...)
    float3 c = t0.Sample(s1, uv).rgb;
    float3 g = dot(c, float3(0.299, 0.587, 0.114));
    return float4(lerp(c, g, strength), 1.0);             // fade to grey
}

Compile it with fxc, which is in C:\Program Files (x86)\Windows Kits\10\bin\<version>\x64\:

fxc /nologo /O3 /T ps_5_0 /E main /Fo fx.cso fx.hlsl

Colours are as the card sends them (0 to 1, not linearised). The alpha you return is ignored. ps_4_0 works too.

What it changes: the picture in the window, and the clean and overlay screenshots, which are taken from the window. It does not change what mods read from the frame (Frame, buffer, and recordings made from them) or the raw screenshots, which stay the card's own pixels.
One page of the MCCP modding reference. Every section is its own document; search covers all of them.