// Shared viewport scaling and temporal jitter helpers.
#ifndef NOISE_RENDER_SCALE_GLSL
#define NOISE_RENDER_SCALE_GLSL

#include "/lib/noise_options.glsl"

// This pack's reduced viewport depends on Iris custom images. OptiFine supports
// compute shaders, but not those images (and loads only vsh/fsh from the current
// dimension folders), so keep its fragment fallback at native resolution.
#if UPSCALE_MODE == 1 && defined IRIS_FEATURE_COMPUTE_SHADERS && defined IRIS_FEATURE_CUSTOM_IMAGES
    #define NOISE_RENDER_SCALE RENDER_SCALE
#else
    #define NOISE_RENDER_SCALE 1.0
#endif

float noiseRenderScale() {
    return float(NOISE_RENDER_SCALE);
}

ivec2 noiseRenderViewportSize(ivec2 fullSize) {
    #if UPSCALE_MODE == 1 && defined IRIS_FEATURE_COMPUTE_SHADERS && defined IRIS_FEATURE_CUSTOM_IMAGES
        return max(ivec2(1), ivec2(floor(vec2(fullSize) * float(NOISE_RENDER_SCALE))));
    #else
        return max(fullSize, ivec2(1));
    #endif
}

vec2 noiseRenderScale2(vec2 fullSize) {
    vec2 safeSize = max(fullSize, vec2(1.0));
    #if UPSCALE_MODE == 1 && defined IRIS_FEATURE_COMPUTE_SHADERS && defined IRIS_FEATURE_CUSTOM_IMAGES
        vec2 viewportSize = max(vec2(1.0), floor(safeSize * float(NOISE_RENDER_SCALE)));
        return viewportSize / safeSize;
    #else
        return vec2(1.0);
    #endif
}

vec2 noiseTaaJitterPixels(int frameIndex) {
	// Minecraft/Iris does not expose complete per-object motion vectors. Even a
	// small projection phase therefore moves distant thin geometry faster than
	// history can validate it. Keep the camera projection fixed; the temporal
	// compute pass still performs full-resolution reprojection and accumulation.
	return vec2(0.0);
}

void noiseTransformScenePosition(inout vec4 clipPosition, vec2 viewSize, int frameIndex) {
    vec2 scale = noiseRenderScale2(viewSize);
    #if AA_MODE == 2
        vec2 jitterNdc = noiseTaaJitterPixels(frameIndex) * 2.0 / max(viewSize * scale, vec2(1.0));
        clipPosition.xy += jitterNdc * clipPosition.w;
    #endif
    #if UPSCALE_MODE == 1 && defined IRIS_FEATURE_COMPUTE_SHADERS && defined IRIS_FEATURE_CUSTOM_IMAGES
        // Keep the sign of W. Using abs(W) here mirrors vertices behind the
        // camera and lets near-plane triangles explode across the viewport.
        float safeW = abs(clipPosition.w) > 1e-6
            ? clipPosition.w
            : (clipPosition.w < 0.0 ? -1e-6 : 1e-6);
        vec2 ndc = clipPosition.xy / safeW;
        ndc = ndc * scale - (1.0 - scale);
        clipPosition.xy = ndc * clipPosition.w;
    #endif
}

void noiseTransformPostPosition(inout vec4 clipPosition, inout vec2 uv, vec2 viewSize) {
    #if UPSCALE_MODE == 1 && defined IRIS_FEATURE_COMPUTE_SHADERS && defined IRIS_FEATURE_CUSTOM_IMAGES
        vec2 scale = noiseRenderScale2(viewSize);
        float safeW = abs(clipPosition.w) > 1e-6
            ? clipPosition.w
            : (clipPosition.w < 0.0 ? -1e-6 : 1e-6);
        vec2 ndc = clipPosition.xy / safeW;
        ndc = ndc * scale - (1.0 - scale);
        clipPosition.xy = ndc * clipPosition.w;
        uv *= scale;
    #endif
}

vec2 noiseLogicalUv(vec2 physicalUv, vec2 viewSize) {
    return physicalUv / noiseRenderScale2(viewSize);
}

vec2 noisePhysicalUv(vec2 logicalUv, vec2 viewSize) {
    return logicalUv * noiseRenderScale2(viewSize);
}

vec2 noiseJitterLogicalUv(vec2 viewSize, int frameIndex) {
    #if AA_MODE == 2
        return noiseTaaJitterPixels(frameIndex) / max(viewSize * noiseRenderScale2(viewSize), vec2(1.0));
    #else
        return vec2(0.0);
    #endif
}

vec2 noiseUnjitterPhysicalUv(vec2 physicalUv, vec2 viewSize, int frameIndex) {
    return noiseLogicalUv(physicalUv, viewSize) - noiseJitterLogicalUv(viewSize, frameIndex);
}

vec2 noiseProjectPhysicalUv(vec2 logicalUv, vec2 viewSize, int frameIndex) {
    return noisePhysicalUv(logicalUv + noiseJitterLogicalUv(viewSize, frameIndex), viewSize);
}

#endif
