@@ -3174,6 +3174,238 @@ void main(uint3 GTid : SV_GroupThreadID)
31743174 m_effects.push_back (std::move (desc));
31753175 }
31763176
3177+ // ---- ShaderLab Scale (Resampling) ----
3178+ // D3D11 compute scale effect with filter algorithms beyond what D2D's
3179+ // CLSID_D2D1Scale natively offers (Lanczos-3, Mitchell-Netravali,
3180+ // Catmull-Rom, Box / area, Gaussian). Useful as a hand-inserted
3181+ // resolution cap in heavy graphs: insert between Source and the
3182+ // visual branch and the entire downstream chain renders at the
3183+ // smaller resolution.
3184+ //
3185+ // Output dimensions are explicitly OutputWidth + OutputHeight. To
3186+ // drive them from source dimensions, add an Image Info node on the
3187+ // same input and route ImageInfo.Width / .Height through Numeric
3188+ // Expression nodes (e.g. expr "A * 0.5") into Scale.OutputWidth /
3189+ // .OutputHeight.
3190+ //
3191+ // OutputWidth = 0 or OutputHeight = 0 falls back to the source's
3192+ // bounds (no scaling).
3193+ {
3194+ static const std::string scaleHLSL = R"HLSL(
3195+ // ShaderLab Scale -- D3D11 compute resampler with multi-tap filters.
3196+ // Output pixel (x,y) reads a windowed neighborhood around the corresponding
3197+ // source location and combines the samples with the chosen kernel.
3198+
3199+ Texture2D<float4> Source : register(t0);
3200+ RWTexture2D<float4> ImageOutput : register(u1);
3201+
3202+ cbuffer constants : register(b0) {
3203+ uint Width; // source dims (auto-injected from input #0)
3204+ uint Height;
3205+ uint OutputWidth; // 0 = pass-through (= source Width)
3206+ uint OutputHeight; // 0 = pass-through (= source Height)
3207+ uint FilterMode; // 0=Bilinear 1=CatmullRom 2=Mitchell 3=Lanczos3 4=Box 5=Gaussian
3208+ };
3209+
3210+ // ---- Filter kernels (1D weights; separable for 2D) ---------------------
3211+
3212+ float W_bilinear(float t) { return max(0.0, 1.0 - abs(t)); }
3213+
3214+ float W_catmullrom(float t) {
3215+ t = abs(t);
3216+ if (t < 1.0) return 1.5*t*t*t - 2.5*t*t + 1.0;
3217+ if (t < 2.0) return -0.5*t*t*t + 2.5*t*t - 4.0*t + 2.0;
3218+ return 0.0;
3219+ }
3220+
3221+ // Mitchell-Netravali B=1/3, C=1/3 -- balanced sharpness/ringing default.
3222+ float W_mitchell(float t) {
3223+ t = abs(t);
3224+ const float B = 1.0/3.0, C = 1.0/3.0;
3225+ if (t < 1.0) {
3226+ return ((12.0 - 9.0*B - 6.0*C)*t*t*t
3227+ + (-18.0 + 12.0*B + 6.0*C)*t*t
3228+ + (6.0 - 2.0*B)) / 6.0;
3229+ }
3230+ if (t < 2.0) {
3231+ return ((-B - 6.0*C)*t*t*t
3232+ + (6.0*B + 30.0*C)*t*t
3233+ + (-12.0*B - 48.0*C)*t
3234+ + (8.0*B + 24.0*C)) / 6.0;
3235+ }
3236+ return 0.0;
3237+ }
3238+
3239+ float W_lanczos3(float t) {
3240+ t = abs(t);
3241+ if (t < 1e-5) return 1.0;
3242+ if (t >= 3.0) return 0.0;
3243+ float pt = 3.14159265 * t;
3244+ return 3.0 * sin(pt) * sin(pt / 3.0) / (pt * pt);
3245+ }
3246+
3247+ float W_box(float t, float halfWidth) {
3248+ return abs(t) <= halfWidth ? 1.0 : 0.0;
3249+ }
3250+
3251+ float W_gauss(float t) {
3252+ // sigma = 0.5 -- tight kernel. Effective radius widens with downscale
3253+ // ratio via the support multiplier in main().
3254+ return exp(-(t*t) * 2.0); // = exp(-t^2 / (2*0.5^2))
3255+ }
3256+
3257+ [numthreads(8, 8, 1)]
3258+ void main(uint3 dtid : SV_DispatchThreadID)
3259+ {
3260+ // Pass-through when OutputWidth/Height not set: act as identity.
3261+ uint outW = (OutputWidth > 0) ? OutputWidth : Width;
3262+ uint outH = (OutputHeight > 0) ? OutputHeight : Height;
3263+ if (dtid.x >= outW || dtid.y >= outH) return;
3264+
3265+ // Output pixel center -> source-pixel-center coords.
3266+ float2 outCenter = float2(dtid.xy) + 0.5;
3267+ float2 outSize = float2(outW, outH);
3268+ float2 srcSize = float2(Width, Height);
3269+ float2 ratio = srcSize / outSize; // src per dst pixel
3270+ float2 srcCenter = outCenter * ratio - 0.5;
3271+
3272+ // For downscale (ratio > 1) we widen the kernel by the ratio so the
3273+ // filter integrates over the *destination footprint* in source pixels;
3274+ // otherwise samples alias. For upscale (ratio < 1) we keep support = 1.
3275+ float2 support = max(ratio, float2(1.0, 1.0));
3276+
3277+ // Per-mode base radius in kernel domain (half-width of the kernel).
3278+ uint mode = FilterMode;
3279+ float baseRadius = 1.0;
3280+ if (mode == 1) baseRadius = 2.0; // Catmull-Rom
3281+ else if (mode == 2) baseRadius = 2.0; // Mitchell
3282+ else if (mode == 3) baseRadius = 3.0; // Lanczos-3
3283+ else if (mode == 4) baseRadius = 0.5; // Box (half-width)
3284+ else if (mode == 5) baseRadius = 2.0; // Gaussian (truncated)
3285+
3286+ // Effective radius in source pixels after widening for downscale.
3287+ float2 effR = baseRadius * support;
3288+ int2 r = int2(ceil(effR));
3289+ r = clamp(r, int2(1, 1), int2(12, 12)); // safety cap
3290+
3291+ int2 ic = int2(floor(srcCenter));
3292+
3293+ float4 sum = float4(0, 0, 0, 0);
3294+ float wsum = 0.0;
3295+
3296+ [loop]
3297+ for (int dy = -r.y; dy <= r.y; ++dy)
3298+ {
3299+ [loop]
3300+ for (int dx = -r.x; dx <= r.x; ++dx)
3301+ {
3302+ int2 sxy = clamp(ic + int2(dx, dy),
3303+ int2(0, 0),
3304+ int2(int(Width) - 1, int(Height) - 1));
3305+ float2 t = (float2(sxy) + 0.5 - srcCenter) / support;
3306+
3307+ float wx, wy;
3308+ if (mode == 0) { wx = W_bilinear(t.x); wy = W_bilinear(t.y); }
3309+ else if (mode == 1) { wx = W_catmullrom(t.x); wy = W_catmullrom(t.y); }
3310+ else if (mode == 2) { wx = W_mitchell(t.x); wy = W_mitchell(t.y); }
3311+ else if (mode == 3) { wx = W_lanczos3(t.x); wy = W_lanczos3(t.y); }
3312+ else if (mode == 4) { wx = W_box(t.x, 0.5); wy = W_box(t.y, 0.5); }
3313+ else { wx = W_gauss(t.x); wy = W_gauss(t.y); }
3314+
3315+ float w = wx * wy;
3316+ sum += Source.Load(int3(sxy, 0)) * w;
3317+ wsum += w;
3318+ }
3319+ }
3320+
3321+ ImageOutput[dtid.xy] = (wsum > 1e-6) ? (sum / wsum) : float4(0, 0, 0, 0);
3322+ }
3323+ )HLSL" ;
3324+ ShaderLabEffectDescriptor desc;
3325+ desc.name = L" Scale" ;
3326+ desc.effectId = L" ShaderLab Scale" ; desc.effectVersion = 1 ;
3327+ desc.category = L" Color" ;
3328+ desc.subcategory = L" Resampling" ;
3329+ desc.shaderType = Graph::CustomShaderType::D3D11ComputeShader;
3330+ desc.hasImageOutput = true ;
3331+ desc.threadGroupX = 8 ;
3332+ desc.threadGroupY = 8 ;
3333+ desc.threadGroupZ = 1 ;
3334+ desc.hlslSource = scaleHLSL;
3335+ desc.inputNames = { L" Source" };
3336+ desc.parameters = {
3337+ // OutputWidth / OutputHeight are the primary controls.
3338+ // 0 means "pass through at source dims". To drive them
3339+ // from source dimensions, bind from an Image Info node
3340+ // (see catalog) through a Numeric Expression.
3341+ { L" OutputWidth" , L" uint" , 0 .0f , 0 .0f , 16384 .0f , 1 .0f },
3342+ { L" OutputHeight" , L" uint" , 0 .0f , 0 .0f , 16384 .0f , 1 .0f },
3343+ { L" FilterMode" , L" float" , 3 .0f , 0 .0f , 5 .0f , 1 .0f ,
3344+ { L" Bilinear" , L" Catmull-Rom Bicubic" , L" Mitchell-Netravali" ,
3345+ L" Lanczos-3" , L" Box (Area)" , L" Gaussian" } },
3346+ };
3347+ m_effects.push_back (std::move (desc));
3348+ }
3349+
3350+ // ---- Image Info ----
3351+ // Analysis-only compute that exposes its source's dimensions and a
3352+ // few derived quantities as typed analysis fields. The runner
3353+ // auto-injects Width / Height into the cbuffer based on the input
3354+ // texture; this shader just copies them into the result buffer
3355+ // alongside two convenience derivations.
3356+ //
3357+ // Composes naturally with Numeric Expression to drive parameters
3358+ // that should track the source -- e.g. ImageInfo.Width through
3359+ // "A * 0.5" into Scale.OutputWidth for a half-resolution pass.
3360+ {
3361+ static const std::string imageInfoHLSL = R"HLSL(
3362+ // Image Info -- D3D11 compute, exposes source dims as analysis fields.
3363+
3364+ Texture2D<float4> Source : register(t0);
3365+ RWStructuredBuffer<float4> Result : register(u0);
3366+
3367+ cbuffer constants : register(b0) {
3368+ uint Width;
3369+ uint Height;
3370+ };
3371+
3372+ [numthreads(1, 1, 1)]
3373+ void main()
3374+ {
3375+ float w = float(Width);
3376+ float h = float(Height);
3377+ float aspect = (h > 0.5) ? (w / h) : 0.0;
3378+ float pixels = w * h;
3379+ // Each analysis field maps to its own Result[i].x slot.
3380+ Result[0] = float4(w, 0, 0, 0); // Width
3381+ Result[1] = float4(h, 0, 0, 0); // Height
3382+ Result[2] = float4(aspect, 0, 0, 0); // AspectRatio
3383+ Result[3] = float4(pixels, 0, 0, 0); // PixelCount
3384+ }
3385+ )HLSL" ;
3386+ ShaderLabEffectDescriptor desc;
3387+ desc.name = L" Image Info" ;
3388+ desc.effectId = L" Image Info" ; desc.effectVersion = 1 ;
3389+ desc.category = L" Analysis" ;
3390+ desc.subcategory = L" Statistics" ;
3391+ desc.shaderType = Graph::CustomShaderType::D3D11ComputeShader;
3392+ desc.dataOnly = true ;
3393+ desc.hasImageOutput = false ;
3394+ desc.threadGroupX = 1 ;
3395+ desc.threadGroupY = 1 ;
3396+ desc.threadGroupZ = 1 ;
3397+ desc.hlslSource = imageInfoHLSL;
3398+ desc.inputNames = { L" Source" };
3399+ desc.analysisOutputType = Graph::AnalysisOutputType::Typed;
3400+ desc.analysisFields = {
3401+ { L" Width" , Graph::AnalysisFieldType::Float },
3402+ { L" Height" , Graph::AnalysisFieldType::Float },
3403+ { L" AspectRatio" , Graph::AnalysisFieldType::Float },
3404+ { L" PixelCount" , Graph::AnalysisFieldType::Float },
3405+ };
3406+ m_effects.push_back (std::move (desc));
3407+ }
3408+
31773409 // ---- Working Space Parameter Node ----
31783410 // A first-class, host-driven parameter node that mirrors the active
31793411 // display profile (live or simulated) as 14 typed analysis output
0 commit comments