// RAVU 3x Upscaling Shader (R4 Variant, RGB)
// Rapid and Accurate Video Upscaling - Triple scale version
// Edge-adaptive upscaler using radius 4 kernel for 3x scaling, full RGB processing

//!BGFX EFFECT
//!VERSION 1
//!NAME RAVU 3x R4 RGB
//!CATEGORY Upscaling
//!DESCRIPTION 3x edge-adaptive upscaler with radius 4 kernel. Full RGB processing for better color accuracy. Highest quality preset.

//!TEXTURE
Texture2D INPUT;

//!SAMPLER
//!FILTER POINT
SamplerState sam_INPUT;

//!TEXTURE
//!WIDTH  INPUT_WIDTH * 3
//!HEIGHT INPUT_HEIGHT * 3
Texture2D OUTPUT;

//!TEXTURE
//!SOURCE ravu_3x_lut4_f16.dds
//!FORMAT R16G16B16A16_FLOAT
Texture2D ravu_3x_lut4;

//!SAMPLER
//!FILTER LINEAR
SamplerState sam_ravu_3x_lut4;

//!COMMON
#include "prescalers.hlsli"

#define LAST_PASS 1

//!PASS 1
//!DESC RAVU-3x Upscale (rgb, r4)
//!IN INPUT, ravu_3x_lut4
//!OUT OUTPUT
//!BLOCK_SIZE 96, 24
//!NUM_THREADS 32, 8
static const vec3 color_primary = vec3(0.2126, 0.7152, 0.0722);
// HLSL outer product helper
float4x3 outerProduct(float3 l, float4 r) { return mul(float4x1(r), float1x3(l)); }
shared vec3 inp[532];
shared float inp_luma[532];

#define CURRENT_PASS 1

#define GET_SAMPLE(x) x
#define imageStore(out_image, pos, val) imageStoreOverride(pos, val)
void imageStoreOverride(uint2 pos, float4 value) { OUTPUT[pos] = value; }

#define INPUT_tex(pos) GET_SAMPLE(vec4(texture(INPUT, pos)))
static const float2 INPUT_size = float2(GetInputSize());
static const float2 INPUT_pt = float2(GetInputPt());

#define ravu_3x_lut4_tex(pos) (vec4(texture(ravu_3x_lut4, pos)))

#define HOOKED_tex(pos) INPUT_tex(pos)
#define HOOKED_size INPUT_size
#define HOOKED_pt INPUT_pt

void Pass1(uint2 blockStart, uint3 threadId) {
	ivec2 group_base = ivec2(gl_WorkGroupID) * ivec2(gl_WorkGroupSize);
	int local_pos = int(gl_LocalInvocationID.x) * 14 + int(gl_LocalInvocationID.y);
	for (int id = int(gl_LocalInvocationIndex); id < 532; id += int(gl_WorkGroupSize.x * gl_WorkGroupSize.y)) {
		uint x = (uint)id / 14, y = (uint)id % 14;
		inp[id] = HOOKED_tex(HOOKED_pt * vec2(float(group_base.x + x) + (-2.5), float(group_base.y + y) + (-2.5))).xyz;
		inp_luma[id] = dot(inp[id], color_primary);
	}
	barrier();
#if CURRENT_PASS == LAST_PASS
	uint2 destPos = blockStart + threadId.xy * 3;
	uint2 outputSize = GetOutputSize();
	if (destPos.x >= outputSize.x || destPos.y >= outputSize.y) {
		return;
	}
#endif

	// Sample luminance values for edge detection
	float luma1 = inp_luma[local_pos + 1];
	float luma2 = inp_luma[local_pos + 2];
	float luma3 = inp_luma[local_pos + 3];
	float luma4 = inp_luma[local_pos + 4];
	float luma5 = inp_luma[local_pos + 5];
	float luma7 = inp_luma[local_pos + 14];
	float luma8 = inp_luma[local_pos + 15];
	float luma9 = inp_luma[local_pos + 16];
	float luma10 = inp_luma[local_pos + 17];
	float luma11 = inp_luma[local_pos + 18];
	float luma12 = inp_luma[local_pos + 19];
	float luma13 = inp_luma[local_pos + 20];
	float luma14 = inp_luma[local_pos + 28];
	float luma15 = inp_luma[local_pos + 29];
	float luma16 = inp_luma[local_pos + 30];
	float luma17 = inp_luma[local_pos + 31];
	float luma18 = inp_luma[local_pos + 32];
	float luma19 = inp_luma[local_pos + 33];
	float luma20 = inp_luma[local_pos + 34];
	float luma21 = inp_luma[local_pos + 42];
	float luma22 = inp_luma[local_pos + 43];
	float luma23 = inp_luma[local_pos + 44];
	float luma24 = inp_luma[local_pos + 45];
	float luma25 = inp_luma[local_pos + 46];
	float luma26 = inp_luma[local_pos + 47];
	float luma27 = inp_luma[local_pos + 48];
	float luma28 = inp_luma[local_pos + 56];
	float luma29 = inp_luma[local_pos + 57];
	float luma30 = inp_luma[local_pos + 58];
	float luma31 = inp_luma[local_pos + 59];
	float luma32 = inp_luma[local_pos + 60];
	float luma33 = inp_luma[local_pos + 61];
	float luma34 = inp_luma[local_pos + 62];
	float luma35 = inp_luma[local_pos + 70];
	float luma36 = inp_luma[local_pos + 71];
	float luma37 = inp_luma[local_pos + 72];
	float luma38 = inp_luma[local_pos + 73];
	float luma39 = inp_luma[local_pos + 74];
	float luma40 = inp_luma[local_pos + 75];
	float luma41 = inp_luma[local_pos + 76];
	float luma43 = inp_luma[local_pos + 85];
	float luma44 = inp_luma[local_pos + 86];
	float luma45 = inp_luma[local_pos + 87];
	float luma46 = inp_luma[local_pos + 88];
	float luma47 = inp_luma[local_pos + 89];

	// Structure tensor computation for edge detection
	vec3 abd = vec3(0.0, 0.0, 0.0);
	float gx, gy;
	gx = (luma15 - luma1) / 2.0;
	gy = (luma9 - luma7) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.02324683987829437;
	gx = (luma16 - luma2) / 2.0;
	gy = (luma10 - luma8) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma17 - luma3) / 2.0;
	gy = (luma11 - luma9) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.038327559383903906;
	gx = (luma18 - luma4) / 2.0;
	gy = (luma12 - luma10) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma19 - luma5) / 2.0;
	gy = (luma13 - luma11) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.02324683987829437;
	gx = (luma22 - luma8) / 2.0;
	gy = (luma16 - luma14) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma23 - luma9) / 2.0;
	gy = (luma17 - luma15) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.04921356040854137;
	gx = (luma24 - luma10) / 2.0;
	gy = (luma18 - luma16) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.055766269846849466;
	gx = (luma25 - luma11) / 2.0;
	gy = (luma19 - luma17) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.04921356040854137;
	gx = (luma26 - luma12) / 2.0;
	gy = (luma20 - luma18) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma29 - luma15) / 2.0;
	gy = (luma23 - luma21) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.038327559383903906;
	gx = (luma30 - luma16) / 2.0;
	gy = (luma24 - luma22) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.055766269846849466;
	gx = (luma31 - luma17) / 2.0;
	gy = (luma25 - luma23) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.06319146241026467;
	gx = (luma32 - luma18) / 2.0;
	gy = (luma26 - luma24) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.055766269846849466;
	gx = (luma33 - luma19) / 2.0;
	gy = (luma27 - luma25) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.038327559383903906;
	gx = (luma36 - luma22) / 2.0;
	gy = (luma30 - luma28) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma37 - luma23) / 2.0;
	gy = (luma31 - luma29) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.04921356040854137;
	gx = (luma38 - luma24) / 2.0;
	gy = (luma32 - luma30) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.055766269846849466;
	gx = (luma39 - luma25) / 2.0;
	gy = (luma33 - luma31) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.04921356040854137;
	gx = (luma40 - luma26) / 2.0;
	gy = (luma34 - luma32) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma43 - luma29) / 2.0;
	gy = (luma37 - luma35) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.02324683987829437;
	gx = (luma44 - luma30) / 2.0;
	gy = (luma38 - luma36) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma45 - luma31) / 2.0;
	gy = (luma39 - luma37) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.038327559383903906;
	gx = (luma46 - luma32) / 2.0;
	gy = (luma40 - luma38) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.033823952439922346;
	gx = (luma47 - luma33) / 2.0;
	gy = (luma41 - luma39) / 2.0;
	abd += vec3(gx * gx, gx * gy, gy * gy) * 0.02324683987829437;

	// Eigenvalue decomposition
	float a = abd.x, b = abd.y, d = abd.z;
	float T = a + d, D = a * d - b * b;
	float delta = sqrt(max(T * T / 4.0 - D, 0.0));
	float L1 = T / 2.0 + delta, L2 = T / 2.0 - delta;
	float sqrtL1 = sqrt(L1), sqrtL2 = sqrt(L2);
	float theta = mix(mod(atan(L1 - a, b) + 3.141592653589793, 3.141592653589793), 0.0, abs(b) < 1.192092896e-7);
	float lambda = sqrtL1;
	float mu = mix((sqrtL1 - sqrtL2) / (sqrtL1 + sqrtL2), 0.0, sqrtL1 + sqrtL2 < 1.192092896e-7);

	// LUT coordinate calculation
	float angle = floor(theta * 24.0 / 3.141592653589793);
	float strength = mix(mix(0.0, 1.0, lambda >= 0.005), 2.0, lambda >= 0.02);
	float coherence = mix(mix(0.0, 1.0, mu >= 0.25), 2.0, mu >= 0.5);
	float coord_y = ((angle * 3.0 + strength) * 3.0 + coherence + 0.5) / 216.0;

	// Weighted sample accumulation from LUT (RGB processing)
	mat4x3 res0 = 0.0, res1 = 0.0;
	vec4 w0, w1;
	w0 = texture(ravu_3x_lut4, vec2(0.01, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.03, coord_y));
	res0 += outerProduct(inp[local_pos + 0], w0) + outerProduct(inp[local_pos + 90], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 0], w1) + outerProduct(inp[local_pos + 90], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.05, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.07, coord_y));
	res0 += outerProduct(inp[local_pos + 1], w0) + outerProduct(inp[local_pos + 89], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 1], w1) + outerProduct(inp[local_pos + 89], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.09, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.11, coord_y));
	res0 += outerProduct(inp[local_pos + 2], w0) + outerProduct(inp[local_pos + 88], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 2], w1) + outerProduct(inp[local_pos + 88], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.13, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.15, coord_y));
	res0 += outerProduct(inp[local_pos + 3], w0) + outerProduct(inp[local_pos + 87], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 3], w1) + outerProduct(inp[local_pos + 87], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.17, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.19, coord_y));
	res0 += outerProduct(inp[local_pos + 4], w0) + outerProduct(inp[local_pos + 86], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 4], w1) + outerProduct(inp[local_pos + 86], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.21, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.23, coord_y));
	res0 += outerProduct(inp[local_pos + 5], w0) + outerProduct(inp[local_pos + 85], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 5], w1) + outerProduct(inp[local_pos + 85], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.25, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.27, coord_y));
	res0 += outerProduct(inp[local_pos + 6], w0) + outerProduct(inp[local_pos + 84], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 6], w1) + outerProduct(inp[local_pos + 84], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.29, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.31, coord_y));
	res0 += outerProduct(inp[local_pos + 14], w0) + outerProduct(inp[local_pos + 76], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 14], w1) + outerProduct(inp[local_pos + 76], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.33, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.35, coord_y));
	res0 += outerProduct(inp[local_pos + 15], w0) + outerProduct(inp[local_pos + 75], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 15], w1) + outerProduct(inp[local_pos + 75], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.37, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.39, coord_y));
	res0 += outerProduct(inp[local_pos + 16], w0) + outerProduct(inp[local_pos + 74], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 16], w1) + outerProduct(inp[local_pos + 74], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.41, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.43, coord_y));
	res0 += outerProduct(inp[local_pos + 17], w0) + outerProduct(inp[local_pos + 73], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 17], w1) + outerProduct(inp[local_pos + 73], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.45, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.47, coord_y));
	res0 += outerProduct(inp[local_pos + 18], w0) + outerProduct(inp[local_pos + 72], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 18], w1) + outerProduct(inp[local_pos + 72], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.49, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.51, coord_y));
	res0 += outerProduct(inp[local_pos + 19], w0) + outerProduct(inp[local_pos + 71], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 19], w1) + outerProduct(inp[local_pos + 71], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.53, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.55, coord_y));
	res0 += outerProduct(inp[local_pos + 20], w0) + outerProduct(inp[local_pos + 70], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 20], w1) + outerProduct(inp[local_pos + 70], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.57, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.59, coord_y));
	res0 += outerProduct(inp[local_pos + 28], w0) + outerProduct(inp[local_pos + 62], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 28], w1) + outerProduct(inp[local_pos + 62], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.61, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.63, coord_y));
	res0 += outerProduct(inp[local_pos + 29], w0) + outerProduct(inp[local_pos + 61], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 29], w1) + outerProduct(inp[local_pos + 61], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.65, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.67, coord_y));
	res0 += outerProduct(inp[local_pos + 30], w0) + outerProduct(inp[local_pos + 60], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 30], w1) + outerProduct(inp[local_pos + 60], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.69, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.71, coord_y));
	res0 += outerProduct(inp[local_pos + 31], w0) + outerProduct(inp[local_pos + 59], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 31], w1) + outerProduct(inp[local_pos + 59], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.73, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.75, coord_y));
	res0 += outerProduct(inp[local_pos + 32], w0) + outerProduct(inp[local_pos + 58], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 32], w1) + outerProduct(inp[local_pos + 58], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.77, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.79, coord_y));
	res0 += outerProduct(inp[local_pos + 33], w0) + outerProduct(inp[local_pos + 57], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 33], w1) + outerProduct(inp[local_pos + 57], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.81, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.83, coord_y));
	res0 += outerProduct(inp[local_pos + 34], w0) + outerProduct(inp[local_pos + 56], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 34], w1) + outerProduct(inp[local_pos + 56], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.85, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.87, coord_y));
	res0 += outerProduct(inp[local_pos + 42], w0) + outerProduct(inp[local_pos + 48], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 42], w1) + outerProduct(inp[local_pos + 48], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.89, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.91, coord_y));
	res0 += outerProduct(inp[local_pos + 43], w0) + outerProduct(inp[local_pos + 47], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 43], w1) + outerProduct(inp[local_pos + 47], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.93, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.95, coord_y));
	res0 += outerProduct(inp[local_pos + 44], w0) + outerProduct(inp[local_pos + 46], w1.wzyx);
	res1 += outerProduct(inp[local_pos + 44], w1) + outerProduct(inp[local_pos + 46], w0.wzyx);
	w0 = texture(ravu_3x_lut4, vec2(0.97, coord_y));
	w1 = texture(ravu_3x_lut4, vec2(0.99, coord_y));
	res0 += outerProduct(inp[local_pos + 45], w0);
	res1 += outerProduct(inp[local_pos + 45], w1);
	res0[0] = clamp(res0[0], 0.0, 1.0);
	res0[1] = clamp(res0[1], 0.0, 1.0);
	res0[2] = clamp(res0[2], 0.0, 1.0);
	res0[3] = clamp(res0[3], 0.0, 1.0);
	res1[0] = clamp(res1[0], 0.0, 1.0);
	res1[1] = clamp(res1[1], 0.0, 1.0);
	res1[2] = clamp(res1[2], 0.0, 1.0);
	res1[3] = clamp(res1[3], 0.0, 1.0);

	// Output 3x3 block
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(0, 0), vec4(res0[0], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(0, 1), vec4(res0[1], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(0, 2), vec4(res0[2], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(1, 0), vec4(res0[3], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(1, 1), vec4(inp[local_pos + 45], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(1, 2), vec4(res1[0], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(2, 0), vec4(res1[1], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(2, 1), vec4(res1[2], 1.0));
	imageStore(out_image, ivec2(gl_GlobalInvocationID) * 3 + ivec2(2, 2), vec4(res1[3], 1.0));
}
