--!strict
--!native
--[[ Requires ]]
local Settings = 	require(`../`)()
local Deepfreeze =	require( `./deepfreeze` )
local VM =			require( `../../plugin/process` )
local Task =		require( `../../plugin/task` )
local Textures =	require( `./textures` )

-- Types/Export
local Types =			require( "@self/types" )
export type Shader = 	Types.Shader
export type ShaderModule = Types.ShaderModule
export type Swapchain = Types.Swapchain
export type Program = 	Types.Program

--[[ Locals ]]
local Pipeline = {}
local SyncList = { "Sync" }

--[[ Functions ]]
-- Functions for generating types of buffers; all initialized to opaque/white when applicable
Pipeline.Buffers = table.freeze( {
	RGBAu8_Slice = function(): buffer
		local b = buffer.create( Settings.Actor_Res.x * Settings.Actor_Res.y * 4)
		buffer.fill( b, 0, 0xFF )
		return b
	end,
	RGBAu8_Display = function(): buffer
		local b = buffer.create( Settings.Display_Res.x * Settings.Display_Res.y * 4)
		buffer.fill( b, 0, 0xFF )
		return b
	end,
	RGBAu8_Length = function()
		return Settings.Actor_Res.x * Settings.Actor_Res.y * 4
	end,
	Vector = function(): {vector}
		return table.create( Settings.Actor_Res.x * Settings.Actor_Res.y, vector.one )
	end
} )

local PipelineExecutionThread: thread = coroutine.running()
local FB_Slice_Count: number = Pipeline.Buffers.RGBAu8_Length()
local FB_Full_Slice_Cursor: number = FB_Slice_Count * (Settings.Actor_RenderNum-1)
local FB_Full_Sync: Textures.Texture
local Shared_T: VM.SharedData = VM.RequireShared("FrameTime")
local Sync = {
	Slice = function(FB_Slice: buffer, Present: boolean)
		VM.MainProcess:Send(
			if Present == true then "FB_Slice_Sync_Present" else "FB_Slice_Sync",
			FB_Full_Slice_Cursor,
			FB_Slice
		)
		-- Gets resumed by 'main.luau' invoking Sync.Full
		coroutine.yield(PipelineExecutionThread)
	end,
	-- Update full framebuffer & resume PipelineExecutionThread
	Full = function(FB_Full_In: buffer)
		-- Assign full framebuffer and make texture from it
		FB_Full_Sync = Textures.Load.FromBuffer( "FB", FB_Full_In, Settings.Display_Res )
		-- Resume execution of shader program
		coroutine.resume(PipelineExecutionThread)
	end
}
Sync.Full(Pipeline.Buffers.RGBAu8_Display())
Pipeline.Sync = Sync

local FB_Full_Start: Textures.Texture = Textures.Load.FromBuffer(
	"FB",
	Pipeline.Buffers.RGBAu8_Display(),
	Settings.Display_Res
)
-- Set to black, but provide full alpha
for c = 0, buffer.len(FB_Full_Start.Buffer)-1, 4 do
	buffer.writeu32(FB_Full_Start.Buffer, c, 0x000000FF)
end

-- ExecuteProgram: Executes a program (Collection of shaders) before returning output data.
local FB_Slice_Read: buffer = Pipeline.Buffers.RGBAu8_Slice()
local function ExecuteProgram( self: Program, In: buffer? )
	-- Parallel is a MUST for performance, this affirms for sanity.
	Task.desynchronize()
	-- Grab the thread by which this program is being executed.
	PipelineExecutionThread = coroutine.running()
	-- Update then read frametime from shared buffer, set by main.luau
	local T: number = buffer.readf64( Shared_T:SyncFrom().Buffer, 0 )
	-- Localise starting sliced framebuffer as it will be updated after executing a shader.
	local FB_Slice_Read: buffer = FB_Slice_Read
	-- Execute shaders in order
	for i: number, Shader: Shader in self.Shaders do
		Shader.Run(
			T,									-- Shader deltaTime; Value is provided by main.luau via Process.SharedData .
			FB_Slice_Read,						-- For start of program, provides a blank or explicitly-provided starting sliced framebuffer.
			Shader.FB_Slice_Write,				-- The shaders own sliced framebuffer that will be written to.
			true,								-- Tells shader that, if supported, we would prefer to use 2D interlacing for performance.
			if i == 1 then FB_Full_Start		-- For start of program, provides a blank or explicitly-provided full framebuffer .
			else FB_Full_Sync					-- For sub-sequent shaders, provide the synchronized full framebuffer from main.luau.
		)
		Sync.Slice(
			Shader.FB_Slice_Write,				-- Passes shaders own sliced framebuffer which was written to, for syncing with full framebuffer.
			i >= #self.Shaders					-- Lets Sync.Slice decide if PipelineExecutionThread needs to yield for main.luau to perform syncing.
		)
		FB_Slice_Read = Shader.FB_Slice_Write	-- Pass Assign shader's FB_Slice_Write to FB_Slice_Read
	end
end

-- Iterates through given profile to create shader array.
local function FetchShaders(Profile: {[number]: string}): {[number]: Shader}
	local Shaders: {Shader} = {}
	for _, Filter: string in Profile do
		local Shader_New = {
			Run = require( `@self/shaders/{ Filter }` ) :: ShaderModule,
			FB_Slice_Write = Pipeline.Buffers.RGBAu8_Slice()
		}
		table.freeze( Shader_New )
		table.insert( Shaders, Shader_New :: Shader )
	end
	return table.freeze( Shaders )
end

-- Creates a program with the given shader profile
local ProgramIDsCreated: number = 0
function Pipeline.CreateProgram( Profile: {string} ): Program
	local NewProgram: Program = {
		Name = `Program`,
		Execute = ExecuteProgram,
		WriteBuffer = Pipeline.Buffers.RGBAu8_Slice(),
		Shaders = FetchShaders( Profile )
	}
	return NewProgram
end

return Pipeline


--!strict
--!native
return { "frag/cave", "filter/blur_box", "filter/unsharp_gaussian" }


--!nocheck
--!nolint
--/INLINE
DoInterlace = false
local function Sampler_BoxBlur(t: Textures.Texture, UV: vector): vector
	local tW:number, tH:number = t.Width, t.Height
	UV = (UV * vector.create(tW,tH) + vector.create(.5,.5) ) // 1
	local x0:number	= UV.x % tW 			* u32B	-- Wrap U to texture width
	local y0:number	= ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	UV += vec_1										-- Get bottom-right
	local x1:number	= UV.x % tW 			* u32B	-- Wrap U to texture width
	local y1:number	= ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	return (
		buffer_vec3(t.Buffer, x0+y0) +				-- Sample top-left
			buffer_vec3(t.Buffer, x1+y0) +			-- Sample top-right
			buffer_vec3(t.Buffer, x0+y1) +			-- Sample bottom-left
			buffer_vec3(t.Buffer, x1+y1)			-- Sample bottom-right
	) * .25
end
local texture = Sampler_BoxBlur

local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local pixel_to_uv: vec = fragCoord/iRes;
	return texture(FB_Full, pixel_to_uv)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Gaussian Unsharp Mask 3x3 Kernel
local function Sampler_Unsharp(t: Textures.Texture, UV: vector): vector
	local b = t.Buffer
	local tW:number, tH:number = t.Width, t.Height
	UV = (UV * vector.create(tW,tH) + vector.create(.5,.5) ) // 1
	-- X1Y1
	local x:number	= UV.x % tW 			* u32B	-- Wrap U to texture width
	local y:number	= ( UV.y % tH ) * tW	* u32B	-- Wrap V to texture height
	local OG:vector = buffer_vec3(b, x+y)
	local S:vector = OG * 13
	-- X0Y0
	UV += vector.create(-1, -1)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)
	-- X1Y0
	UV += vector.create(1, 0)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)* 2
	-- X2Y0
	UV += vector.create(1, 0)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)
	-- X0Y1
	UV += vector.create(-2, 1)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)* 2
	-- X0Y1
	UV += vector.create(2, 0)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)* 2
	-- X0Y2
	UV += vector.create(-2, 1)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)
	-- X1Y2
	UV += vector.create(1, 0)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)* 2
	-- X2Y2
	UV += vector.create(1, 0)
	x = UV.x % tW 			* u32B	-- Wrap U to texture width
	y = ( UV.y % tH ) * tW 	* u32B	-- Wrap V to texture height
	S -= buffer_vec3(b, x+y)
	-- Return
	return vector.lerp( OG, vector.clamp( S, vec_0, vec_1 ), .2 )
end
local texture = Sampler_Unsharp

local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local pixel_to_uv: vec = fragCoord/iRes;
	return texture(FB_Full, pixel_to_uv)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
local iChannel0: Textures.Texture = Textures.Load.Base( {}, "rustymetal" )
local texture = Sampler_Nearest -- Sampler_Nearest_IL, Sampler_Linear, Sampler_BoxBlur, Sampler_Unsharp

local PI: f64 = math.pi
local ro: vec = vec(0.0, 2.0, 14.0)
local lookAt: vec = vec(0.0, 0.0, 0.0)
local fwd: vec = vec(0,0,-1)
local right: vec = vec(1,0,0)
local up: vec = vec(0,1,0)
local vec_up: vec = vec(0,1,0)
local a = 2.51
local b_coeff = vec(0.0762, 0.0762, 0.0762) -- 2.54 * 0.03
local c_val = 2.43
local d_coeff = vec(0.59, 0.59, 0.59)
local e_coeff = vec(0.14, 0.14, 0.14)

local function mix(a: vec, b: vec, t: f64): vec
	return vector.lerp(a, b, t)
end

local function fract(x: f64): f64
	return x - floor(x)
end

local function smoothstep(edge0: f64, edge1: f64, x: f64): f64
	local t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
	return t * t * (3.0 - 2.0 * t)
end

local function hash12(x: f64, y: f64): f64
	local vx = fract(x * 0.1031)
	local vy = fract(y * 0.1031)
	local dot_val = vx * (vy + 33.33) + vy * (vx + 33.33)
	return fract((vx + vy) * dot_val)
end

local function noise(x: f64, y: f64): f64
	local i_x = floor(x)
	local i_y = floor(y)
	local f_x = fract(x)
	local f_y = fract(y)

	local u_x = f_x * f_x * (3.0 - 2.0 * f_x)
	local u_y = f_y * f_y * (3.0 - 2.0 * f_y)

	local a = hash12(i_x, i_y)
	local b = hash12(i_x + 1.0, i_y)
	local c = hash12(i_x, i_y + 1.0)
	local d = hash12(i_x + 1.0, i_y + 1.0)

	return math.lerp(math.lerp(a, b, u_x), math.lerp(c, d, u_x), u_y)
end

local function rotateY(v: vec, a: f64): vec
	local c, s = cos(a), sin(a)
	return vec(v.x*c + v.z*s, v.y, -v.x*s + v.z*c)
end

local function rotateX(v: vec, a: f64): vec
	local c, s = cos(a), sin(a)
	return vec(v.x, v.y*c - v.z*s, v.y*s + v.z*c)
end

local function blackbody(temp: f64): vec
	local t = temp * 3.0
	local r = clamp(t * 1.5, 0.0, 1.0) + 0.3 * exp(-pow(t - 3.5, 2.0))
	local g = clamp(t - 0.8, 0.0, 1.0)
	local b = clamp(t - 1.8, 0.0, 1.0)

	local col = vec(r, g, b)
	col *= col

	local orange = vec(1.0, 0.6, 0.1)
	local mix_amt = smoothstep(0.0, 0.5, abs(temp - 0.4))
	return mix(col, orange, mix_amt * 0.5)
end

local function skyColor(rd: vec): vec
	local bg = vec(0.005, 0.005, 0.01)

	local scaled = rd * 350.0
	local sid = vec(floor(scaled.x), floor(scaled.y), floor(scaled.z))
	local h = hash12(sid.x + sid.y * 113.0, sid.z * 57.0)

	local star = 0.0
	if h > 0.992 then
		local b = smoothstep(0.992, 1.0, h)
		star = b * 3.0
	end

	return bg + vec(star, star, star)
end

local function traceBlackHole(ro0: vec, rd0: vec, iTime: f64): vec
	local BH_MASS = 1.0
	local RS = 2.0 * BH_MASS
	local PHOTON_SPHERE = 1.5 * RS
	local ACCRETION_IN = 3.0 * RS
	local ACCRETION_OUT = 14.0 * RS

	local diskTilt = 0.3 -- radians
	local cosT, sinT = cos(diskTilt), sin(diskTilt)

	local p = ro0
	local v = rd0
	local col = vec_0
	local trans = 1.0 -- transmittance
	local min_dist = 1e9

	local iter = 0
	local max_iter = 128 -- steps
	local step_size = 0.3

	local escape_limit = 40.0 * RS

	local function toDiskSpace(pt: vec): vec
		return vec(pt.x, pt.y * cosT - pt.z * sinT, pt.y * sinT + pt.z * cosT)
	end

	while iter < max_iter do
		iter += 1

		local r2 = dot(p, p)
		local r = sqrt(r2)
		min_dist = min(min_dist, r)

		if r < RS * 1.01 then
			trans = 0.0
			break
		end

		if r > escape_limit then
			col += skyColor(v) * trans
			break
		end

		--a = -1.5 * Rs * (h^2 / r^5) * r_vec
		--where h = |r x v| (angular momentum per unit mass)
		local crossP = cross(p, v)
		local h2 = dot(crossP, crossP) -- magnitude squared of angular momentum

		--divide by r^5. Since we have r2 (r^2), r^5 = r2 * r2 * r
		local r5 = r2 * r2 * r
		local geodesicTerm = -1.5 * RS * h2 / max(r5, 1e-4)

		local acc = p * geodesicTerm

		local dt = step_size * r * 0.12

		dt = max(dt, 0.005)

		v = normalize(v + acc * dt)
		p += v * dt

		local pd = toDiskSpace(p)
		local diskH = abs(pd.y)
		local diskR = sqrt(pd.x*pd.x + pd.z*pd.z)

		local thickness = 0.25 + (diskR * 0.06)

		if diskH < thickness and diskR > ACCRETION_IN and diskR < ACCRETION_OUT then
			local phi = math.atan2(pd.z, pd.x)

			local angularSpeed = 1.5 / sqrt(diskR)
			local rotPhi = phi + iTime * angularSpeed

			local noiseScale = 4.0
			local n1 = noise(diskR * 0.5 - iTime * 0.5, rotPhi * 3.0 + diskR * 0.2)
			local n2 = noise(diskR * 1.5, rotPhi * 12.0 - diskR * 0.5)
			local plasma = n1 * 0.7 + n2 * 0.3

			local fadeInner = smoothstep(ACCRETION_IN, ACCRETION_IN * 1.1, diskR)
			local fadeOuter = smoothstep(ACCRETION_OUT, ACCRETION_OUT * 0.5, diskR)
			local verticalFade = smoothstep(thickness, 0.0, diskH)

			local density = plasma * fadeInner * fadeOuter * verticalFade * 0.8

			if density > 0.01 then
				local temp = pow(ACCRETION_IN / diskR, 1.3)

				local redshift = sqrt((r - RS) / r)
				redshift = clamp(redshift, 0.05, 1.0)
				temp *= redshift

				local baseColor = blackbody(temp)

				local velDirDisk = vec(-sin(phi), 0, cos(phi))
				local velDirWorld = vec(velDirDisk.x, velDirDisk.y * cosT + velDirDisk.z * sinT, -velDirDisk.y * sinT + velDirDisk.z * cosT)

				local viewDotVel = dot(v, velDirWorld)
				--  dt = 1 / (g * (1 - b * cos(t)))
				local doppler = pow(1.0 / (1.0 - 0.45 * viewDotVel), 3.5)

				local emission = baseColor * density * doppler * 1.5 * redshift

				local stepAbsorb = density * 0.5 * dt
				local w = min(1.0, stepAbsorb)

				col += emission * (trans * w)
				trans *= (1.0 - w)

				if trans < 0.01 then break end
			end
		end
	end

	local photonSphere = PHOTON_SPHERE
	local ringWidth = 0.2
	if trans > 0.0 and min_dist > photonSphere and min_dist < photonSphere + ringWidth then
		local intensity = smoothstep(photonSphere + ringWidth, photonSphere, min_dist)
		col += vec(1.0, 0.95, 0.85) * intensity * 0.4 * trans
	end

	return col
end

local function main(_: vec, fragCoord: vec, iTime: f64): vec
	-- [-1, 1]
	local uv = vec(-1, -1, 0) + (vec(2,2,0) * fragCoord) / vec(iRes.x, iRes.y, 1)
	uv = vec(uv.x * (iRes.x / iRes.y), uv.y, 0)

	local camPos = ro
	local camDist = 16.0
	local camSpeed = 0.08
	camPos = vec(sin(iTime * camSpeed) * camDist, 3.5 + cos(iTime * 0.15) * 1.5, cos(iTime * camSpeed) * camDist)

	local fwd = normalize(vec(0,0,0) - camPos)
	local right = normalize(cross(fwd, vec(0,1,0)))
	local up = normalize(cross(right, fwd))

	local rd = normalize(fwd + right * uv.x + up * uv.y)
	local col = traceBlackHole(camPos, rd, iTime)
	col *= .6 -- exposure
	-- (x * (a * x + b)) / (x * (c * x + d) + e)
	local num = col * (col * a + b_coeff)
	local den = col * (col * c_val + d_coeff) + e_coeff
	return vector.clamp(num / den, vec_0, vec_1)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Card Game [Source: https://www.shadertoy.com/view/XXtBRr]
local texture = Sampler_Nearest -- Sampler_Nearest_IL, Sampler_Linear
local SPIN_ROTATION:f64 = -2.0;
local SPIN_SPEED:f64 = 7.0;
local SPIN_EASE:f64 = 1.0;
local SPIN_AMOUNT:f64 = 0.25;
local OFFSET:vec = vec(0, 0);
local CONTRAST:f64 = 3.5;
local LIGHTING:f64 = 0.4;
local COLOUR_1:vec = vec(0.871, 0.267, 0.231);
local COLOUR_2:vec = vec(0.0, 0.42, 0.706);
local COLOUR_3:vec = vec(0.086, 0.137, 0.145);
local PIXEL_FILTER:f64 = 745.0;
local speed:f64 = SPIN_ROTATION*SPIN_EASE*0.2
local mul_vec2:vec = vec(1,1)

local function main( O:vec, fragCoord:vec, iTime:f64 ):vector
	local uv0:vec = fragCoord/iRes;
	uv0 = vec(uv0.x,uv0.y);
	local screenSize:vec = iRes;
	local screen_coords:vec = uv0 * iRes;

	local pixel_size:f64 = length(screenSize) / PIXEL_FILTER;

	local uv:vec = (vector.floor(screen_coords*(1/pixel_size))*pixel_size - 0.5*screenSize)/length(screenSize) - OFFSET;
	local uv_len:f64 = length(uv);

	local speed:f64 = speed;
	--speed = iTime * speed; -- For rotation
	speed += 302.2;
	local new_pixel_angle:f64 = math.atan2(uv.y, uv.x) + speed - SPIN_EASE*20.*(1.*SPIN_AMOUNT*uv_len + (1. - 1.*SPIN_AMOUNT));

	local mid:vec = (screenSize/length(screenSize))/2.;

	uv = (vec((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);

	uv *= 30.;
	speed = iTime*(SPIN_SPEED);
	local uv2:vec = mul_vec2 * (uv.x+uv.y);

	for i:f64 = 0, 4 do
		uv2 += (mul_vec2 * sin(max(uv.x, uv.y))) + uv;
		uv += (mul_vec2*.5)*vec(cos(5.1123314 + 0.353*uv2.y + speed*0.131121),sin(uv2.x - 0.113*speed));
		uv -= mul_vec2 * ( 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y) );
	end

	local contrast_mod:f64 = (0.25*CONTRAST + 0.5*SPIN_AMOUNT + 1.2);
	local paint_res:f64 = min(2., max(0.,length(uv)*(0.035)*contrast_mod));
	local c1p:f64 = max(0.,1. - contrast_mod*abs(1.-paint_res));
	local c2p:f64 = max(0.,1. - contrast_mod*abs(paint_res));
	local c3p:f64 = 1. - min(1., c1p + c2p);
	local light:f64 = (LIGHTING - 0.2)*max(c1p*5. - 4., 0.) + LIGHTING*max(c2p*5. - 4., 0.);
	O = (0.3/CONTRAST)*COLOUR_1 + ( vec_1 * (1. - 0.3/CONTRAST) ) * (COLOUR_1*c1p + COLOUR_2*c2p + ( COLOUR_3*c3p*c3p ) ) + (vec_1 * light);
	return vector.clamp(O, vec_0, vec_1);
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Fragment Shader - Cloudy Tunnel [Source: https://www.shadertoy.com/view/llSfRK]

--// origo and lookat points
local o:vec
local t:vec
local oSquareTotal:f64
local sampleCount:f64 = 2
local W:f64 = 4.0
local T:f64 = 0.05
local texture = Sampler_Nearest_IL

-- Values
local iChannel0: Textures.Texture = Textures.Load.Base(
	{ { Name = "From_BT709" }, { Name = "To_sRGB" }, { Name = "VFlip" }, { Name = "GaussianBlur" } },
	"clouds_1024"
)

-- Functions
local function smoothstep(e0:f64,e1:f64,x:f64):f64
	--// Scale, and clamp x to 0..1 range
	x = clamp((x-e0)/(e1-e0),0,1)
	return x*x*(3-2*x)
end

local function filmic_reinhard_curve(x:f64):f64
	local q:f64 = (T+1.0)*x*x
	return q / (q + x + T);
end

local function filmic_reinhard(x:vec):vec
	local w:f64 = filmic_reinhard_curve(W)
	local q:vec = (T+1.0)*x*x
	return (q / (q + x + (vec_1*T)))/w
end

local function mul_vecmat3(v:vec,r0:vec,r1:vec,r2:vec):vec
	return
		vec(r0.x,r1.x,r2.x)*v.x +
		vec(r0.y,r1.y,r2.y)*v.y +
		vec(r0.z,r1.z,r2.z)*v.z
end

local function orient(v:vec, d:vec): (vec, vec, vec)
	v = normalize(v)
	local i:vec = normalize(cross(v, normalize(d)))
	return i, cross(i,v), v
end

local function main( fragColor: vec, fragCoord: vec, iTime: f64 ): vec
	local uv:vec = -vec_1+((vec_1*2)*fragCoord) / iRes
	uv = vec(uv.x * (iRes.x/iRes.y), uv.y)

	--// oriented direction vectors (up-vector)
	local d:vec = normalize( mul_vecmat3( vec( uv.x, uv.y, 2.0 ), orient( t, normalize( vec( 0, 1, 2 ) ) ) ) )
	--
	local tv:vec = vec_1
	local ac:vec = vec_0
	local i:vec = vec_0
	local aid:f64 = 0
	--
	local a:f64 = d.x*d.x + d.y*d.y
	local b:f64 = 2 * ( o.x*d.x + o.y*d.y )
	--// Sample iteration
	for l = 1, sampleCount do
		local off:f64 = 3 * ( 1 * ( length(tv) ^ 2 ) )
		aid += off
		local r:f64 = 32 - off
		local c:f64 = oSquareTotal - r*r
		local delta:f64 = sqrt( b*b - 4*a*c )
		local t:f64 = min( (-b-delta)/(2*a), (-b+delta)/(2*a) )
		t = if math.isnan(t) then 0 else t
		i = o + t*d;
		local i_atan:f64 = math.atan2(i.y, i.x)
		local co:vec = vec((i.z)*.2,i_atan*32/3.1416)
		tv = texture(iChannel0, co*.007)
		local c2:vec = vec((i.z)*.2,abs(i_atan*32/3.1416))
		local te:vec = texture(iChannel0, c2*.007)
		tv = vector.lerp(tv, te, smoothstep(0, .7, i_atan) + smoothstep(3.1416-.7,3.1416,-i_atan))
		ac += vec(tv.x^2.2,tv.y^2.2,tv.z^2.2)/sampleCount
	end
	ac /= ( smoothstep(0, 1, 1/( (length(o-i)*.02)^2 )) )^.5
	ac /= (aid^.5)*.1
	--// vignette
	local rf:f64 = sqrt(dot(uv, uv)) * .3
	local rf2_1:f64 = rf * rf + 1
	ac *= 1 / (rf2_1 * rf2_1)
	--// tonemap
	ac = filmic_reinhard(ac)
	ac = vec(smoothstep(-.12,1,ac.x),smoothstep(-.12,1,ac.y),smoothstep(-.12,1,ac.z))
	return ac
end

local function preprocess( iTime:f64 ): f64
	o = vec( sin(iTime*0.478)*8.0, cos(iTime*0.513)*8.0, iTime*100.0 )
	oSquareTotal = o.x*o.x + o.y*o.y
	t = vec( sin(iTime*0.509)*200.0, cos(iTime*0.437)*200.0, -1000.0 )
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Creation [Source: https://www.shadertoy.com/view/XsXXDn]
local iChannel0: Textures.Texture =	Textures.Load.Base( {}, "abstract3" )
local texture = Sampler_Linear
local reinhardBurn:vec = vector.one * .225
local c = {0,0,0}
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local uv:vec = fragCoord/iRes
	local c = c
	c[1], c[2], c[3] = 0, 0, 0
	local l:f64, z:f64 = iTime, iTime
	for i = 1, 3 do
		local p:vec = fragCoord/iRes
		p=vec(p.x,p.y)
		local cuv:vec = p
		p -= vec(.5,.5)
		p *= vec(iRes.x/iRes.y,1,1)
		z += .07
		l = length(p)
		cuv += p / l * ( sin( z ) + 1 ) * abs( sin( l * 9 - z - z ) )
		c[i] = .01 / length( cuv - ( cuv // 1 ) - vec(.5,.5) )
	end
	fragColor = ( vec(c[1],c[2],c[3]) / l )
	fragColor /= fragColor + reinhardBurn
	return fragColor
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Distortion
local texture = Sampler_Linear
local blob1:vec = vec(127.1,311.7,0)
local blob2:vec = vec(269.5,183.3,0)
local blob3:vec = vec(419.2,317.9,0)
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local vec_noPreCalc:vec = vec(.5,.5)*(iTime*2.)+vec(.0,2.)
	local p_noPreCalc:vec = vec(.5,.5)-(vec(.5,.5)*vec(cos(vec_noPreCalc.x),cos(vec_noPreCalc.y)))
	for i = 1, 3 do
		p_noPreCalc = p_noPreCalc*p_noPreCalc*(vec(3,3)-vec(2,2)*p_noPreCalc)
	end
	local k_noPreCalc:f64 = 1 + 63 * pow(1.-p_noPreCalc.y,6.)

	local u:f64 = p_noPreCalc.x
	local v:f64 = p_noPreCalc.y

	local uv:vec = fragCoord/iRes.x
	local p2:vec = uv * 24
	local i:vec = vector.floor(p2)
	local f:vec = vec(fract(p2.x),fract(p2.y))

	local a:f64 = 0
	local b:f64 = 0
	for y2 = -2, 2 do
		for x2 = -2, 2 do
			local g:vec = vec(x2,y2)
			local thing:vec = i + g
			local q:vec = vec(dot(thing,blob1),dot(thing,blob2),dot(thing,blob3))
			local o:vec = (vec(cos(q.x),cos(q.y),cos(q.z)))*43758.5453
			o = vec(fract(o.x),fract(o.y),fract(o.z))
			local o:vec = o*vec(u,u,1)
			local d:vec = g - f + vec(o.x,o.y)
			local w_noPreCalc:f64 = pow(1.-smoothstep(0.,1.414,length(d)),k_noPreCalc)
			a += o.z * w_noPreCalc
			b += w_noPreCalc
		end
	end

	local f:f64 = a/b
	local ratio:vec = f*((fragCoord-(iRes/2.))/iRes)
	ratio = vector.abs(ratio)

	return vec(sin(ratio.x),sin(ratio.y),sin(ratio.x+ratio.y))
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Fractal Pyramid [Source: https://www.shadertoy.com/view/tsXBzS]
--[[ SHADER ]]--
local reinhardBurn:vec = vector.one * .225
local up_vector:vec = vec(0, 1, 0)
local const_ro:vec = vec(0, 0, -50)
local ro_xz:vec = vec(const_ro.x, const_ro.z)
local iTime_cos:f64 = 0
local iTime_sin:f64 = 0
local pal0:vec = vec(.2, .7, .9)
local pal1:vec = vec(1, 0, 1)
local rm_Count:f64 = 51 -- Default is 63
local rm_Map:f64 = 5 -- Default is 7
local rm_Div:f64 = ( 400 / 64 ) * ( rm_Count + 1 ) -- Default is 400

-- Preprocessor - main
local xz:vec, ro:vec, cf:vec, cs:vec, cu:vec, cf3:vec
-- Preprocessor - map
local c0:f64, c1:f64, s0:f64, s1:f64, iResY_Div_R:f64, iRes_Offset:vec = 0, 0, 0, 0, 1 / ( iRes.y * 1.25 ), iRes * .5

local function vec2_mat2( p: vec, c: f64, s: f64 )
	return vec(
		c * p.x + -s * p.y,
		s * p.x + c * p.y
	)
end

local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local v2:vec = (fragCoord - iRes_Offset) * iResY_Div_R
	v2 = cf3 + v2.x*cs + v2.y*cu
	v2 = normalize(v2 - ro)
	local d:f64 = 0
	local t:f64 = 0
	local R:vec, p:vec, q:vec
	-- Perform raymarching
	for rm:f64 = 0, rm_Count do
		p = ro + v2*t
		q = p
		-- Perform mapping
		for map:f64 = 0, rm_Map do
			-- Set XZ and rotate vec2 against mat2
			R = vec(q.x, q.z)
			R = vec2_mat2( R, c0, s0 )
			-- Perform 2nd mat2 rotation
			q = vec( R.x, q.y, R.y )
			R = vec2_mat2( q, c1, s1 )
			q = vec( abs(R.x), R.y, abs(q.z) )
			q -= vec(.5, 0, .5)
		end
		d = dot(vector.sign(q), q) * .1
		fragColor += vector.lerp(pal0, pal1, length(p) * .1) / (rm_Div * d)
		t += d
	end
	fragColor *= vector.max(fragColor, vec_0)
	return fragColor / ( fragColor + reinhardBurn )
end

local function preprocess( iTime: f64 ): f64
	-- for main
	xz = vec2_mat2(ro_xz, cos(iTime), sin(iTime))
	ro = vec( xz.x, const_ro.y, xz.y )
	cf = normalize(-ro)
	cs = normalize(cross(cf, up_vector))
	cu = normalize(cross(cf, cs))
	cf3 = ro+cf*3
	-- for map
	iTime *= .2
	c0,s0 = cos(iTime), sin(iTime)
	c1,s1 = cos(iTime*1.89), sin(iTime*1.89)
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Fragments [Source: https://www.shadertoy.com/view/3cScWy]
local fEnd:f64 = 6
local iTime_z: vec
local r_xxy:vec = vec(iRes.x, iRes.y, iRes.y);
local mul_vec2:vec = vec(1, 1, 0);
local nums:vec = vec(6,1,2);

local function main( O:vec, I:vec, t:f64 ):vec
	--//Iterator, raymarch depth, turbulence frequency and time
	local z:f64=0;
	--//Raymarch sample point
	local n_r_xxy:vec = normalize( I+I - r_xxy );
	--//Clear fragColor and raymarch 30 steps
	local p:vec; --local O_Alpha:f64=0
	for i:f64 = 1, 25 do
		p=(z*n_r_xxy)-iTime_z
		local f:f64=1;
		for f = 2, fEnd do
			local rnd:vec = vector.floor((p*6.)+vec_0_5)/3.*f;
			p += vec( sin(rnd.y), sin(rnd.x), sin(rnd.z) ) / f;
		end
		local len:f64 = length(p*mul_vec2);
		local cos_p:vec = vec(cos(p.x),cos(p.y),cos(p.z));
		local sin_p_yzx:vec = vec(sin(p.y),sin(p.z),sin(p.x));
		f = .003 + abs( len - 5. + dot(cos_p,sin_p_yzx) ) * .125;
		z += f;
		local wv:vec = vec_1 * (i*.3+z+t);
		wv += nums;
		O += vec(1+sin(wv.x),1+sin(wv.y),1+sin(wv.z))/f; --O_Alpha += ( 1 + sin(wv) ) / f;
	end
	O *= .001;
	return vec(tanh(O.x), tanh(O.y), tanh(O.z)) -- * tanh(O_Alpha);
end

local function preprocess( iTime:f64 ): f64
	iTime *= .5
	iTime_z = vec(0, 0, iTime)
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - tm gyroid 2 [Source: https://www.shadertoy.com/view/tXtyW8]
local reinhardBurn:vec = vector.one * .225
local FAR = 30.
local PI = 3.1415

local function vcos(v: vec) return vector.create(math.cos(v.x), math.cos(v.y), math.cos(v.z)) end
local function vsin(v: vec) return vector.create(math.sin(v.x), math.sin(v.y), math.sin(v.z)) end
local function vpow(a: vec, b: vec) return vector.create(math.pow(a.x, b.x), math.pow(a.y, b.y), math.pow(a.z, b.z)) end

local function realstep(edge: f64, x: f64) return if x < edge then 0 else 1 end

local function xy(v: vec) return vector.create(v.x, v.y, 0) end
local function xyy(v: vec) return vector.create(v.x, v.y, v.y) end
local function yyx(v: vec) return vector.create(v.y, v.y, v.x) end
local function yxy(v: vec) return vector.create(v.y, v.x, v.y) end
local function xxx(v: vec) return vector.create(v.x, v.x, v.x) end
local function zxy(v: vec) return vector.create(v.z, v.x, v.y) end

local function reflect(i: vec, n: vec): vec
	return i - 2.0 * dot(n, i) * n;
end

local function gyroid(p: vec): f64
	return dot(vcos(p), vsin(zxy(p))) + 1.;
end

local function map(p: vec): f64
	local p2: vec = p - vec(0, 0, PI)

	local r = 1e5

	local d = gyroid(p);
	r = math.min(r, d)

	d = gyroid(p2);
	r = math.min(r, d)

	return r;
end

local function getAO(p: vec, sn: vec): f64
	local occ = 0.;
	for i=0,3 do
		local t = i*.08;
		local d = map(p + sn*t);
		occ += t-d;
	end
	return clamp(1.-occ, 0., 1.);
end

local e = vec(0.5773, -0.5773, 0) * 0.001
local xyye = xyy(e)
local yyxe = yyx(e)
local yxye = yxy(e)
local xxxe = xxx(e)

local function getNormal(p: vec): vec
	return normalize(xyye*map(p+xyye) + yyxe*map(p+yyxe) + yxye*map(p+yxye) + xxxe*map(p+xxxe));
end

local m = 0;

local function raymarch(ro: vec, rd: vec): f64
	local t = 0.;
	m = 0;
	for i=1,150 do --150
		local p: vec = ro + rd * t
		local p2: vec = p - vec(0, 0, PI)

		local d1 = gyroid(p);
		local d = math.min(1e5, d1)

		local d2 = gyroid(p2);
		d = math.min(d, d2)

		if (abs(d) < .001) then
			m = if d2 < d1 then 2 else 1
			break;
		end

		t += d;
		if (t > FAR) then break; end
	end
	return t;
end

local function trace(ro: vec, rd: vec): vec
	local C: vec = vec(0, 0, 0);
	local throughput: vec = vec(1, 1, 1);

	for bounce=0,1 do
		local d = raymarch(ro, rd);

		if (d > FAR) then break; end

		-- fog
		local fog = 1. - exp(-.008*d*d);
		C += throughput * fog * vec(0, 0, 0); throughput *= 1. - fog;

		local p: vec = ro + rd*d;
		local temp: vec = vector.abs(vcos(p*64.))
		local sn: vec = normalize(getNormal(p) + vpow(temp, vec(16, 16, 16))*.1);

		-- lighting
		local lp: vec = vec(10.,-10.,-10.+ro.z) ;
		local ld: vec = normalize(lp - p);
		local diff = max(0., .5+2.*dot(sn, ld));
		local diff2 = pow(length(vsin(sn*2.)*.5+vec(.5, .5, .5)), 2.);
		local diff3 = max(0., .5+.5*sn.z);

		local spec = max(0., dot(reflect(-ld, sn), -rd));
		local fres = 1. - max(0.,dot(-rd, sn));
		local col = vec(0, 0, 0);
		local alb = vec(0, 0, 0);

		col += vec(.4, .6, .9) * diff;
		col += vec(.5, .1, .1) * diff2;
		col += vec(.9, .1, .4) * diff3;
		col += vec(.3,.25,.25) * pow(spec,4.)*8.;

		local freck = dot(vcos(p*23.),vec(1, 1, 1));
		if (m==1) then alb = vec(.2, .1, .9);  alb *= max(.6, realstep(2.5, freck)); end
		if (m==2) then alb = vec(.6, .3, .1);  alb *= max(.8, realstep(-2.5, freck)); end
		col *= alb;

		col *= getAO(p, sn); -- ~11%
		C += throughput * col;

		-- reflection
		rd = reflect(rd, sn);
		ro = p + sn*.01;
		throughput *=  .9 * pow(fres, 1.);
	end

	return C;
end

local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec

	local uv: vec = (xy(fragCoord) - xy(iRes)*.5) / iRes.y;

	local ro: vec = vec(PI/2.,0, -iTime*.5);
	local rd: vec = normalize(vec(uv.x, uv.y, -.5));

	do
		local rotArg = sin(iTime*.2)
		local c = cos(rotArg);
		local s = sin(rotArg);
		local new_x = c * rd.x - s * rd.y;
		local new_y = s * rd.x + c * rd.y;

		rd = vector.create(new_x, new_y, rd.z)
	end

	local ta: vec = vec(cos(iTime*.4), sin(iTime*.4), 4.);

	do
		local dir = normalize(ta)
		local up: vec = vec(0.,1.,0.);
		local rt: vec = normalize(cross(dir,up));
		local c0, c1, c2 = rt, cross(rt,dir), dir;

		rd = rd.x * c0 + rd.y * c1 + rd.z * c2
	end

	local col: vec = trace(ro, rd);
	col *= smoothstep(0.,1., 1.2-length(uv*.9));
	col /= col + reinhardBurn;
	return col
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mini: Hillside [Source: https://www.shadertoy.com/view/3fySDD]
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local p:vec = (fragCoord*2.-iRes)/iRes.y
	--//Quick noise pattern
	local n:f64 = (dot(fragCoord, vec(sin(fragCoord.y),sin(fragCoord.x))))%.1
	--//RGBA wave frequencies
	local f:vec = vec(.7,1,1.3)
	--//RGBA wave offsets
	local o:vec = vec(0,1,2)
	--//Distance to circle edge (radius of 1)
	local l:f64 = 1.-length(p)
	local fragMult:f64 = .1/max(l/.1,-l)
	--//Tanh for tonemapping
	--//https://www.shadertoy.com/view/ms3BD7
	--//https://mini.gmshaders.com/p/func-tanh
	local col:vec = vec(
		tanh(.2/abs(p.y+.3*cos(iTime+n+p.x*f.x+o.x))),
		tanh(.2/abs(p.y+.3*cos(iTime+n+p.x*f.y+o.y))),
		tanh(.2/abs(p.y+.3*cos(iTime+n+p.x*f.z+o.z)))
	)
	return col*vector.lerp(col,vec_1,.5)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Shader Art Coding Introduction [Source: https://www.shadertoy.com/view/mtyGWy]
local e:f64 = 2.71828183
local a:vec,b:vec,c:vec,d:vec = vec(.5,.5,.5),vec(.5,.5,.5),vec(1,1,1),vec(.263,.416,.557)
local function palette( t:f64 ):vec
	local tau_ctd:vec = 6.28318 * (c*t+d)
	return a + b * vec(cos(tau_ctd.x),cos(tau_ctd.y),cos(tau_ctd.z))
end
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local uv:vec = ( fragCoord * 2 - iRes ) / iRes.y
	local uv0:vec = uv
	local finalColor:vec = vec_0
	for i = 0, 3 do
		uv *= 1.5
		uv -= uv // 1
		uv -= vec_0_5
		local d:f64 = length(uv) * (e^-length(uv0))
		local col:vec = palette(length(uv0) + i * .4 + iTime * .4 )
		d = sin( d * 8 + iTime ) / 8
		d = abs(d)
		d = pow( 0.01 / d, 1.2 )
		finalColor += col * d
	end
	return vector.min( finalColor, vec_1 )
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mandelbrot Pattern Decoration [Source: https://www.shadertoy.com/view/ttscWn]
-- VARIABLES
local AA:f64 = 2
local AA0:f64 = AA - 1
local AA2_Reciprocal:f64 = 1 / (AA*AA)
local iter:f64 = 128
local fog:vec = vec_0
-- preprocessables
local ttm:f64, c:f64, s:f64, zm:f64

-- FUNCTIONS
local function xzy(v: vec): vec return vec(v.x, v.z, v.y) end
local function yxz(v: vec): vec return vec(v.y, v.x, v.z) end

local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	fragColor = vec_0 -- Set to black.

	for j = 0, AA0 do
		for i = 0, AA0 do
			local p = (fragCoord + vec(i, j)/AA - iRes*.5)/iRes.y
			p = vec(c * p.x - s * p.y, s * p.x + c * p.y)
			p -= vec(cos(iTime/2.)/2., sin(iTime/3.)/5.)

			local cc = vec(-.57735 + .004, .57735) + p/zm
			local z, dz = vec_0, vec_0

			local ik:f64 = 128
			local zDot:f64
			for k = 0, iter - 1 do
				dz = vec((z.x * dz.x - z.y * dz.y) * 2. + 1., (z.y * dz.x + z.x * dz.y) * 2.)
				z = vec(z.x * z.x - z.y * z.y, 2. * z.x * z.y) + cc
				zDot = dot(z, z)
				if zDot > 1/.005 then
					ik = k
					break
				end
			end

			local ln = step(0, length(z)/15.5 - 1)
			local d = sqrt(1/max(length(dz), .0001))*log(zDot)
			d = clamp(d*50, 0, 1)

			local dir = if mod(ik, 2) < .5 then -1 else 1
			local sh = (iter-ik)/iter
			local tuv = z/320

			local tm = (-ttm*sh*sh*16)
			local c = cos(tm)
			local s = sin(tm)

			tuv = vec(c * tuv.x - s * tuv.y, s * tuv.x + c * tuv.y)
			tuv = vec(
				abs(mod(tuv.x, 1/8) - 1/16),
				abs(mod(tuv.y, 1/8) - 1/16)
			)

			local pat = smoothstep(0, 1/length(dz), length(tuv) - 1/32)
			pat = min(pat, smoothstep(0, 1/length(dz), abs(max(tuv.x, tuv.y) - 1/16) - .04/16))

			local minVal = min(d * .85, .96)
			local lCol:vec = vector.min( vec( 1.5, 1, 1 ) * minVal, vec( 1, 3, 16 ) )
			lCol = vec( pow(lCol.x, 1), pow(lCol.y, 3), pow(lCol.z, 16) )
			lCol *= 1.15

			lCol =
				if dir < 0 then
				lCol * min(pat, ln)
				else
				vec(sqrt(lCol.x) * 0.5 + 0.7, sqrt(lCol.y) * 0.5 + 0.7, sqrt(lCol.z) * 0.5 + 0.7) * max(1 - pat, 1 - ln)

			local rd = normalize(vec(p.x, p.y, 1))
			rd = rd - 2 * dot(vec(0, 0, -1), rd) * vec(0, 0, -1)

			local diff = clamp(dot(z*.5 + vec_0_5, vec(rd.x, rd.y, 0)), 0, 1)*d

			tuv = z/200
			tm = -tm/1.5 + .5
			local c = cos(tm)
			local s = sin(tm)
			tuv = vec(c * tuv.x - s * tuv.y, s * tuv.x + c * tuv.y)
			tuv = vec(
				abs(mod(tuv.x, 1/8) - 1/16),
				abs(mod(tuv.y, 1/8) - 1/16)
			)
			pat = smoothstep(0., 1./length(dz), length(tuv) - 1./32.);
			pat = min(pat, smoothstep(0., 1./length(dz), abs(max(tuv.x, tuv.y) - 1./16.) - .04/16.));

			local gloss:vec = vector.lerp(lCol, vec_1 * ln, .5)
			gloss = gloss * diff * diff * .5 * (pat * .6 + .6)
			lCol += gloss

			lCol = if mod(ik, 6) < .5 then yxz(lCol) else lCol

			local d_factor:f64 = d / 1.2
			lCol *= d_factor
			lCol += xzy(lCol) * (1 - d_factor)

			local fringe_factor = (1 - step(0, -(length(z) * .05 * ik / iter - 1))) * .95
			lCol *= 1 - fringe_factor

			local mixFactor = sh * d
			lCol *= mixFactor
			lCol += fog * ( 1 - mixFactor )

			fragColor += vector.min(lCol, vec_1)
		end
	end

	fragColor *= AA2_Reciprocal

	fragColor = ( vec_1 - vec( exp( -fragColor.x ), exp( -fragColor.y ), exp( -fragColor.z ) ) ) * 1.25

	local uv:vec = fragCoord/iRes
	fragColor *= pow( 16 * ( 1 - uv.x ) * ( 1 - uv.y ) * uv.x * uv.y, 1/8 ) * 1.15
	fragColor = vec( sqrt( fragColor.x ), sqrt( fragColor.y ), sqrt( fragColor.z ) )

	-- NaN is propagating from somewhere but it never used to!! ...Luau regression?
	fragColor = vec(
		math.isnan( fragColor.x ) and 0 or fragColor.x,
		math.isnan( fragColor.y ) and 0 or fragColor.y,
		math.isnan( fragColor.z ) and 0 or fragColor.z
	)
	return vector.clamp( fragColor, vec_0, vec_1 )
end

local function preprocess( iTime:f64 ): f64
	ttm = cos(sin(iTime/8.))*6.2831
	c = cos(ttm)
	s = sin(ttm)
	zm = (200 + sin(iTime/7)*50)
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mandelbrot
local texture = Sampler_Linear
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	local uv:vec = (vec(fragCoord.x/iRes.x,fragCoord.y/iRes.y)-vec(.5,.5))*2.
	uv *= vec(iRes.x/iRes.y,1.)
	local c:vec = uv * 0.8 + vec(-.5,0.) + vec( .2 * sin( iTime * .3 ), 0. )
	local z:vec = vec_0
	local it:f64 = 0.
	for i = 0, 39 do
		if dot(z,z) > 4 then break end
		z = vec( z.x * z.x - z.y * z.y, 2. * z.x * z.y ) + c
		it += 1.
	end
	local t:f64 = it / 40.
	return vec( t * t, t * .6, sqrt( t ) )
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mini: Mosaic [Source: https://www.shadertoy.com/view/W33GRX]
local texture = Sampler_Linear
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	--//Resolution for scaling
	local r:vec = iRes
	--//Centered and scaled to fit vertically [-3, +3]
	local p:vec = 3. * ( fragCoord * 2. - r ) / r.y
	--//Doubled coordinates
	local v:vec = p + p + ( vec(iTime,iTime) + r ) * vec(
		cos(r.y+ceil(p.y+sin(p.y*5))),
		cos(r.x+ceil(p.x+sin(p.x*5)))
	)
	--//Set brightness using distance to points
	local l:f64 = length(vec(.9+sin(v.x),.9+sin(v.y)))
	--//Colour output
	return vec(
		tanh(.1* (cos(.6*p.x+.4*sin(v.y)+0)+1.) /l),
		tanh(.1* (cos(.6*p.x+.4*sin(v.y)+1)+1.) /l),
		tanh(.1* (cos(.6*p.x+.4*sin(v.y)+2)+1.) /l)
	)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Neuron
local vec_up = vec(0, 1, 0)
local ro, lookAt, fwd, right, up = vec(0,0,0), vec(0,0,0), vec(0,0,1), vec(1,0,0), vec(0,1,0)
local a = 2.51
local b_coeff = vec(0.0762, 0.0762, 0.0762)
local c_val = 2.43
local d_coeff = vec(0.59, 0.59, 0.59)
local e_coeff = vec(0.14, 0.14, 0.14)

local function mix(a: vec, b: vec, t: f64): vec
	if type(a) == "vector" then
		return vector.lerp(a, b, t)
	end
	return a + (b - a) * t
end

local function fract(x: f64): f64
	return x - floor(x)
end

local function hash12(x: f64, y: f64): f64
	local vx = fract(x * 0.1031)
	local vy = fract(y * 0.1031)
	local dot_val = vx * (vy + 33.33) + vy * (vx + 33.33)
	return fract((vx + vy) * dot_val)
end

local function noise2D(x: f64, y: f64): f64
	local i_x = floor(x)
	local i_y = floor(y)
	local f_x = fract(x)
	local f_y = fract(y)

	local u_x = f_x * f_x * (3.0 - 2.0 * f_x)
	local u_y = f_y * f_y * (3.0 - 2.0 * f_y)

	local a = hash12(i_x, i_y)
	local b = hash12(i_x + 1.0, i_y)
	local c = hash12(i_x, i_y + 1.0)
	local d = hash12(i_x + 1.0, i_y + 1.0)

	return math.lerp(math.lerp(a, b, u_x), math.lerp(c, d, u_x), u_y)
end

local function noise3D_sim(p: vec, t: f64): f64
	local np = normalize(p)
	local npx, npy, npz = np.x, np.y, np.z
	local t_scaled = t / 20.0

	local scale = 20

	local s1 = scale
	local o1 = 0.0
	local a1 = noise2D((t_scaled + npx + o1) * s1, (t_scaled + npy + o1) * s1)
	local b1 = noise2D((t_scaled + 0.77 + npy + o1) * s1, (t_scaled + npz + o1) * s1)
	a1 = math.lerp(a1, 0.5, math.abs(npx))
	b1 = math.lerp(b1, 0.5, math.abs(npz))
	local n1 = math.lerp(a1 + b1 - 0.4, 0.5, math.abs(npy) * 0.5)

	local s2 = scale * 2.5
	local o2 = 5.2
	local a2 = noise2D((t_scaled + npx + o2) * s2, (t_scaled + npy + o2) * s2)
	local b2 = noise2D((t_scaled + 0.77 + npy + o2) * s2, (t_scaled + npz + o2) * s2)
	a2 = math.lerp(a2, 0.5, math.abs(npx))
	b2 = math.lerp(b2, 0.5, math.abs(npz))
	local n2 = math.lerp(a2 + b2 - 0.4, 0.5, math.abs(npy) * 0.5)

	return n1 * 0.7 + n2 * 0.3
end

local function rotateY(v: vec, angle: f64): vec
	local c, s = cos(angle), sin(angle)
	return vec(v.x*c + v.z*s, v.y, -v.x*s + v.z*c)
end

local function smin(a: f64, b: f64, k: f64): f64
	local h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0)
	return math.lerp(b, a, h) - k * h * (1.0 - h)
end

local function map(p: vec, iTime: f64): f64
	local n_val = noise3D_sim(p, iTime)
	local len_p = length(p)

	local d = (-1.0 * len_p + 3.0) + 1.5 * n_val
	d = min(d, (len_p - 1.5) + 1.5 * n_val)

	local m = 1.5
	local s = 0.03

	local ax = abs(p.x)
	local ay = abs(p.y)
	local az = abs(p.z)

	d = smin(d, max(ax - s, abs(p.y + p.z * 0.2) - 0.07), m)
	d = smin(d, max(az - s, abs(p.x + p.y * 0.5) - 0.07), m)
	d = smin(d, max(abs(p.z - p.y * 0.4) - s, abs(p.x - p.y * 0.2) - 0.07), m)
	d = smin(d, max(abs(p.z * 0.2 - p.y) - s, abs(p.x + p.z) - 0.07), m)
	d = smin(d, max(abs(p.z * -0.2 + p.y) - s, abs(-p.x + p.z) - 0.07), m)

	return d
end

local function main(fragColor: vec, fragCoord: vec, iTime: f64): vec

	local uv = vec(-1, -1, 0) + (vec(2, 2, 0) * fragCoord) / vec(iRes.x, iRes.y, 1)
	uv = vec(uv.x * (iRes.x / iRes.y), uv.y, 0)

	local ray = normalize(vec(uv.x, uv.y, 1.0))

	local color = vec_0
	local rayCount = 128
	local iEnd:f64 = rayCount
	local t = 0.0
	local r:f64 = 1
	local d:f64 = 1
	local p:vec
	local endLoop:boolean = false
	while not endLoop do
		p = vec(0, 0, -3.0) + ray * t

		p = rotateY(p, iTime * 0.1)
		p = rotateY(p, iTime / 3.0)

		-- float mask = max(0.,(1.-length(p/3.)))
		local mask = max(0.0, 1.0 - length(p / 3.0))

		-- p = rotateY(p,mask*sin(iTime/2.)*1.2)
		p = rotateY(p, mask * sin(iTime / 2.0) * 1.2)

		-- p.y += sin(iTime+p.x)*mask*.5
		p = vec(p.x, p.y + sin(iTime + p.x) * mask * 0.5, p.z)

		-- p *= 1.1+(sin(iTime/2.)*mask*.3)
		p *= 1.1 + (sin(iTime / 2.0) * mask * 0.3)

		d = map(p, iTime)

		t += d * .6

		r += 1;

		endLoop = if d < .02 or r == rayCount then true else false

		--r += 1;
	end

	local iter = r / rayCount
	local ao = (1.0 - iter)
	ao *= ao
	ao = 1.0 - ao

	local mask_col = max(0.0, 1.0 - length(p / 2.0))
	mask_col *= abs(sin(iTime * -1.5 + length(p) + p.x) - 0.2)

	local n_val = noise3D_sim(p, iTime)
	local noise_term = max(0.0, (n_val * 4.0 - 2.6))

	color += vec(0.2, 2.0, 1.6) * noise_term * mask_col
	color += vec(0.12, 0.45, 0.7) * ao * 6.0
	color += vec(0.27, 0.2, 0.4) * (t / 8.0)
	color *= 2.0
	color -= vec(0.15, 0.15, 0.15)

	--[[
	for r = 1, rayCount do
		local p = vec(0, 0, -3.0) + ray * t

		p = rotateY(p, iTime * 0.1)
		p = rotateY(p, iTime / 3.0)

		-- float mask = max(0.,(1.-length(p/3.)))
		local mask = max(0.0, 1.0 - length(p / 3.0))

		-- p = rotateY(p,mask*sin(iTime/2.)*1.2)
		p = rotateY(p, mask * sin(iTime / 2.0) * 1.2)

		-- p.y += sin(iTime+p.x)*mask*.5
		p = vec(p.x, p.y + sin(iTime + p.x) * mask * 0.5, p.z)

		-- p *= 1.1+(sin(iTime/2.)*mask*.3)
		p *= 1.1 + (sin(iTime / 2.0) * mask * 0.3)

		local d = map(p, iTime)

		if d < 0.01 or r == rayCount then
			local iter = r / rayCount
			local ao = (1.0 - iter)
			ao = ao * ao
			ao = 1.0 - ao

			local mask_col = max(0.0, 1.0 - length(p / 2.0))
			mask_col *= abs(sin(iTime * -1.5 + length(p) + p.x) - 0.2)

			local n_val = noise3D_sim(p, iTime)
			local noise_term = max(0.0, (n_val * 4.0 - 2.6))

			color += vec(0.2, 2.0, 1.6) * noise_term * mask_col

			color += vec(0.12, 0.45, 0.7) * ao * 6.0

			color += vec(0.27, 0.2, 0.4) * (t / 8.0)

			color *= 2.0
			color -= vec(0.15, 0.15, 0.15)
			break
		end

		t += d * 0.6
	end
	]]

	local uv_vig = vec(fragCoord.x / iRes.x, fragCoord.y / iRes.y, 0)
	uv_vig = uv_vig * (vec(1.0, 1.0, 0) - vec(uv_vig.y, uv_vig.x, 0))

	local vig = uv_vig.x * uv_vig.y * 20.0
	vig = pow(vig, 0.25)

	color *= vig
	color = vec(color.x * 1.5, color.y * 0.8, color.z)

	color *= .6
	local num = color * (color * a + b_coeff)
	local den = color * (color * c_val + d_coeff) + e_coeff
	local color_mapped = num / den
	return vector.clamp(color_mapped, vec_0, vec_1)
end

local function preprocess(iTime:f64): f64
	iTime *= .66;
	ro = vec(0.0, 1.0, 6.0)
	lookAt = vec(sin(iTime*0.62222), cos(iTime*0.7428765235), -20.0)
	fwd = normalize(lookAt - ro)
	right = normalize(cross(fwd, vec_up))
	up = normalize(cross(right, fwd))
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mini: Nova [Source: https://www.shadertoy.com/view/WfGSRD]
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	--//Horizontally centered and scaled coordinates
	local p:vec = (fragCoord*2.-(iRes.x*vec(1,1)))/iRes.y
	--//Distance to circle edge (radius of 1)
	local l:f64 = 1.-length(p)
	local fragMult:f64 = .1/max(l/.1,-l)
	fragColor = vec(
		tanh( ( 1.2 + sin( p.x + iTime ) ) * fragMult ),
		tanh( ( 1.2 + sin( p.x + iTime + 2 ) ) * fragMult ),
		tanh( ( 1.2 + sin( p.x + iTime + 4 ) ) * fragMult )
	)
	return fragColor
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Rocaille [Source: https://www.shadertoy.com/view/WXyczK]
local resLen:f64

local function main( O:vec, I:vec, iTime:f64 ):vec
	--// Vector for scaling and turbulence
	local v:vec = iRes;
	--// Centered and scaled coordinates
	local p:vec = (I+I-v)/v.y/.3;
	--// Layer & turbulence freq iter. +Color, attenuate w/ turbulent coords
	-- local O_Alpha:f64 = 0;
	for i_Layer:f64 = 1, 8 do
		local resLen:f64 = length(v);
		O += vec(
			cos( i_Layer + 0 ) + 1,
			cos( i_Layer + 1 ) + 1,
			cos( i_Layer + 2 ) + 1
		) / 6 / resLen; -- O_Alpha += ( cos( i_Layer + 3 ) + 1 ) / 6. / resLen;
		--// Turbulence loop [https://mini.gmshaders.com/p/turbulence]
		v = p;
		for f_Freq:f64 = 1, 8 do
			v += vec(
				sin(v.y * f_Freq + i_Layer + iTime ) / f_Freq,
				sin(v.x * f_Freq + i_Layer + iTime ) / f_Freq
			);
		end;
	end;
	--// Tanh tonemapping [https://www.shadertoy.com/view/ms3BD7]
	O*=O; -- O_Alpha*=O_Alpha;
	return vec(tanh(O.x),tanh(O.y),tanh(O.z));
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mini: Squircle [Source: https://www.shadertoy.com/view/WcGXRD]
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	--//Centered and scale to fit vertically
	local p:vec = (fragCoord*2.-iRes)/iRes.y
	--//Distance to squircle (squared-radius of 0.4)
	local d:f64 = length(p*p) - .4
	--//Cubed coordinates for rounded edges
	--//Rotate the light vector (11 is approximately 270 degrees)
	local l:f64 = dot(p*p*p,vec(cos(iTime),cos(iTime+11)))
	--//Tanh for soft edge filtering and divide for edge curvature
	l *= (1.-tanh(.5*d*iRes.y)) / (.51-d)
	--//Add lighting and coloring with tanh for tonemapping
	return vec(
		tanh( l + 1. + p.x ) * .5 + .5,
		tanh( l + 1. + p.y ) * .5 + .5,
		tanh( l + 1. + -p.x ) * .5 + .5
	)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Mini: Squircle 2 [Source: https://www.shadertoy.com/view/WfySDD]
local function mat2(v:vec,a:f64,b:f64,c:f64,d:f64):vec return vec(a*v.x+b*v.y,c*v.x+d*v.y)end
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	--//Centered and scale to fit vertically
	local p:vec = (fragCoord*2.-iRes)/iRes.y
	--//Shortened time variable
	local t:f64 = iTime
	--//Rotate coordinates using sine tricks
	--//33 is approximately 90 degrees and 11 is approximately 270
	local c:vec = mat2( p,
		cos(iTime / 4. - sin(iTime) / 4. + 0),
		cos(iTime / 4. - sin(iTime) / 4. + 33),
		cos(iTime / 4. - sin(iTime) / 4. + 11),
		cos(iTime / 4. - sin(iTime) / 4. + 0)
	)
	--//Distance field waves for rotated squircle
	local l:f64 = cos(sqrt(length(c*c)) / 0.1- t)
	--//Generate color waves
	local w:vec = vec(
		sin(3. * length(c) + 2. * c.y + t + 6),
		sin(3. * length(c) + 2. * c.y + t + 1),
		sin(3. * length(c) + 2. * c.y + t + 2)
	)
	--//Color calculation with sine oscillations, scaled and tonemapped
	return vec(
		tanh((0.6 + 0.5 * w.x) / max(l/ 0.2, -l)),
		tanh((0.6 + 0.5 * w.y) / max(l/ 0.2, -l)),
		tanh((0.6 + 0.5 * w.z) / max(l/ 0.2, -l))
	)
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint
--/INLINE
-- Penumbra: Fragment Shader - Traced Tunnel [Source: https://www.shadertoy.com/view/tdjfDR]
local iChannel0: Textures.Texture = Textures.Load.Base( {{Name="Squared"},{Name="VFlip"},{Name="GaussianBlur"}}, "abstract3" )
local iChannel1: Textures.Texture = Textures.Load.Base( {{Name="Squared"},{Name="VFlip"},{Name="GaussianBlur"}}, "rustymetal" )
local reinhardBurn:vec = vector.one*.225
local sampleNum:f64 =		1
local reflectionNum:f64 =	2
local DOF:f64 =				.05
local DOFDist:f64 =			3
local sc:f64 =				12
local txSc:vec =			vec(2,.5) --// Texture scale (vec2, const)
local txSc2:vec =			vec(1,.25) --// Texture scale. (vec2, const)
local e:f64 =				2.71828182 --2.718281828459045235360287471352
local texture = Sampler_Nearest_IL
local function r2( v2:vec, a:f64 ):vec --// 2D rotation.
	local cosA:f64, sinA:f64 = cos(a), sin(a)
	return vec(v2.x*cosA+v2.y*sinA,v2.x*-sinA+v2.y*cosA)
end
local function hash21( p:vec ):f64 --// Random functions: All based on IQ's originals.
	local v:f64 = sin(dot(p,vec(425.215,714.388)))*45758.5453
	return v-(v//1)
end
local function hash22( p:vec ):vec
	local v:vec = vec(
		sin(dot(p,vec(72.927,98.283))),
		sin(dot(p,vec(41.295,57.263)))
	)*vec(43758.5453,23421.6361)
	return v-(v//1)
end
local function hash23( p:vec ):vec
	local v:vec = vec(
		sin(dot(p,vec(12.989,78.233))),
		sin(dot(p,vec(51.898,56.273))),
		sin(dot(p,vec(41.898,57.263)))
	)*vec(43758.5453,23421.6361,65426.6357)
	return v-(v//1)
end
local function Tick( t:f64, d:f64 ):f64
	local m:f64 = (t/d)%1
	m = smoothstep(0,1,m)
	m = smoothstep(0,1,m)
	return (floor(t/d)+m)*d
end
local function tickTime( t:f64 ): f64
	return t*2+Tick(t,4)*.75
end
local function cam( p:vec, tm:f64, tTime:f64 ):vec
	local p_r2: vector = r2(vec(p.x,p.z),sin(tTime*.3)*.4)
	p = vec(p_r2.x,p.y,p_r2.y)
	p_r2 = r2(vec(p.x,p.y),sin(tTime*.1)*2.)
	p = vec(p_r2.x,p_r2.y,p.z)
	return p
end
local function rayPlane( ro:vec, rd:vec, n:vec, d:f64 ):f64
	local t:f64 = 1e8
	local ndotdir:f64 = dot(rd,n)
	if ndotdir < 0 then
		local dist:f64 = (-d-dot(ro,n)+9e-7)/ndotdir
		t = if dist > 0 and dist < t then dist else t
	end
	return t
end
local function udBox( p:vec, b:vec ):f64
	return length(vector.max(vector.abs(p)-b+vec(.1,.1),vec_0))-.1
end
local function uvShape( p:vec ):f64
	p = vector.abs(p) -- Polar mapping a square wall.
	return max(p.x,p.y)
end
local function main( fragColor:vec, fragCoord:vec, iTime:f64 ):vec
	--// Aspect correct screen coordinates.
	local uv:vec = ( fragCoord - iRes * .5 ) / iRes.y
	uv = vec(uv.x,uv.y)

	--// Global & tick (regular) time, has lurching tick. Motion sickness liability
	local tm:f64 = iTime
	local tickTm:f64 = tickTime(tm)

	--// Cam pos init, tickTm lurches across z position.
	local ca:vec = vec(0,0,tickTm)

	--// Initialize the scene color to zero.
	local col:vec = vec_0

	--// Number of samples. Ray spread is influenced by j.
	local j:f64 = 0

	--// Pixel offset (vec2)
	local j_off:f64 = j*74.542+35.877
	local offs:vec = hash22( uv + vec(j_off,j_off) ) - vec(.5,.5)

	local ro:vec = vec_0

	local r: vector = normalize( ( uv - offs / iRes.y ) + vector.create( 0, 0, 1 ) )

	--// Camera movement. Rotation, swivle, etc.
	ro = cam(ro,tm,tickTm)
	r = cam(r,tm,tickTm)
	ro += vector.create(0,0,ca.z)

	--// Alpha, for blending layers.
	local alpha:f64 = 1.

	--// Fog distance.
	local fogD:f64 = 1e5

	--// Reflective bounces. Three is usual, but reduce for better performance.
	--local i: number = 0
	for i:f64 = 0, 1 do
		--// Tracing the four planes, then determining the closest.
		--// You could group this into a faster, more tailored function,
		--// but this is easier to read.
		local pl_x = rayPlane(ro, r, vec( 0, 1 ), 1)	--// x, Bottom
		local pl_y = rayPlane(ro, r, vec( 0, -1 ), 1)	--// y, Top
		local pl_z = rayPlane(ro, r, vec( 1, 0 ), 1)	--// z, Left
		local pl_w = rayPlane(ro, r, vec( -1, 0 ), 1)	--// w, Right

		--// Minimum plane distance.
		local d:f64 = min(min(pl_x,pl_y),min(pl_z,pl_w))

		--// Set the fog distance on the first pass.
		fogD = if i == 0 then d else fogD

		--// Hit position.
		local p:vec = ro+r*d

		--// Determine the UV coordinates for texturing, and the normal, for lighting and other things.
		--// Set the normal and UVs to the bottom or top planes.
		local n:vec = vec( 0, if pl_x < pl_y then 1 else -1 )
		local tuv:vec = vec(p.x,p.z+n.y)

		--// If we've hit the side walls instead, change the normal and UVs accordingly.
		if min(pl_z,pl_w) < min(pl_x,pl_y) then
			n = vec( if pl_z < pl_w then 1 else -1, 0 )
			tuv = vec( p.y + n.x, p.z ) --// Left walls.
		end

		--// Texture scaling for texturing.
		tuv *= sc

		--// Sample color.
		local sampleCol:vec = vec_1

		--// Grid square ID and local coordinates.
		local id:vec = vector.floor(tuv)
		tuv -= id + vec(.5,.5)

		--// Use the UV coordinates to create a whitish colored rounded box grid.
		local patDist:f64 = udBox(tuv,vec(.4,.4))

		--// Use the square grid shape for shading.
		local sh:f64 = clamp(.5-patDist/.2,0.,1.)

		--// Subtle coloring.
		local sqCol_cos:vec = vec_1 * ( hash21( id + vec(.2,.2) ) * 2 ) * 6.2831 + vec(0,1,2)
		local sqCol:vec = (vec_1*.85) + .3 * vec(cos(sqCol_cos.x),cos(sqCol_cos.y),cos(sqCol_cos.z))
		sampleCol = vector.lerp(vec_0,sqCol*sh,1.-smoothstep(.0,.005,patDist))

		--// Perform a squarish polar mapping (of sorts), read in to some textures, then color them up, etc.
		--// Quantized squarish polar mapping.
		local ip3:vec = (vector.floor(p*sc)+vec_0)/sc --// Quantizing... as opposed to continuous values.
		local ang:f64 = math.atan2(ip3.x,ip3.y)/6.2831 --// Angle of grid cell from the tube center.
		local tnuv:vec = vec(uvShape(vec(ip3.x,ip3.y)) * ang * txSc.x, ip3.z * txSc.y ) --// Square polar UVs. (vec2)

		--// Smooth squarish polar mapping.
		local p3:vec = vector.lerp(p,(vector.floor(p*sc)+vec_0)/sc,.8) --// Slightly smooth quantized values. (vec3)
		local ang2:f64 = math.atan2(p3.x,p3.y)/6.2831 --// Angle of grid cell from the tube center.
		local tnuv2:vec = vec(uvShape(vec(p3.x,p3.y))*ang2*txSc2.x+p3.z*.075,p3.z*txSc2.y) --// Square polar UVs.

		--// Reading the texel values, and manipulating a bit. Note the squaring of the value, (tx *= tx) which is a rough sRGB to linear conversion.
		local tx_fract:vec = tnuv-vec(.5,.5)-vec(iTime/(sc)/2,.0)
		local tx:vec = texture(iChannel0,tx_fract) --tx *= tx
		tx = vector.lerp(tx,vec_1*dot(tx,vec(.299,.587,.114)),.75)
		tx = vec(smoothstep(.1,.55,tx.x),smoothstep(.1,.55,tx.y),smoothstep(.1,.55,tx.z))
		local tx2_fract:vec = tnuv2-vec(.5,.5)-vec(iTime/(sc)/2,0.)
		local tx2:vec = texture(iChannel1,tx2_fract) -- tx2 *= tx2
		tx2 = vec(smoothstep(.18,.5,tx2.x),smoothstep(.18,.5,tx2.y),smoothstep(.18,.5,tx2.z))
		sampleCol *= tx*tx2*4 --// Apply the textures to the sample color.

		--// Some fakish point lighting. Light direction vector. The light is 3 units up from the camera, which coincides with the depth of field distance.
		local ld:vec = normalize(ca+vec(0,0,3)-p)
		local dif:f64 = max(dot(ld,n),0.) --// Diffuse.
		local spe:f64 = pow(max(dot(ld-2*dot(ld,-n)*-n,-r),0.),8) --// Specular.
		local fre:f64 = pow(max(1.-abs(dot(r,n))*.5,0.),1) --// Fresnel.
		sampleCol *= (vec_1*dif+vec(1,.9,.7)*spe*4.+vec(.5,.7,1.)*fre)

		sampleCol *= 1.35/(1.+fogD*fogD*.05) --// Applying some fog.
		col += sampleCol*alpha*fre --// Add the sample color to overall accumulated scene color.
		alpha *= .9 --// Reduce the alpha factor by a bit and mix in the Fresnel factor as well.

		--// Calculate the reflection vector for the next pass.
		r -= 2 * vector.dot( r, n ) * n --// Pure reflection.

		--// Advance the position to the new hit point. Also be sure to bump the ray off the surface to avoid self collision... If I had a dollar for every time I've forgotten to do this... :)
		ro = p+n*.0011
	end

	--// Divide by the total number of samples.
	--col /= sampleNum

	col /= col + reinhardBurn
	return col
end

local function preprocess( iTime:f64 ): f64
	return iTime
end


--!nocheck
--!nolint

-- Multi-shader wrapper module
-- Calls multiple different modules based on os.clock()

-- Shaders
local ShaderList = {
	require( "./frag/cardgame" ),
	require( "./frag/cave" ),
	require( "./frag/cloudytunnel" ),
	require( "./frag/rocaille" ),
	require( "./frag/fractalpyramid" ),
	require( "./frag/fragments" ),
	require( "./frag/gyroid" ),
	require( "./frag/mandelbrot_standard" ),
	require( "./frag/neuron" ),
	require( "./frag/squircle2" ),
	require( "./frag/tracedtunnel" )
}

-- WRAPPER --
local ShaderDuration: number = 10
return function( iTime: number, ...)
	ShaderList[ ( ( iTime // ShaderDuration ) % #ShaderList ) + 1 ]( iTime, ... )
end


-- Pipeline : Types --
local Textures = require(`../textures`)

export type Shader = {
	-- Shader needs its own write buffer due to temporal accumulation.
	read FB_Slice_Write: buffer,
	read Run: ShaderModule
}

export type ShaderModule = (
	T: number,
	Sync_FB_Slice_Read: buffer,
	FB_Slice_Write: buffer, --Out: buffer,
	IL: boolean,
	FB_Full: Textures.Texture
) -> ()

export type Swapchain = {
	Bool: boolean,
	Swap: ( Swapchain, buffer? ) -> ( buffer, buffer ),
	Buffers: { [boolean]: buffer }
}

export type Program = {
	Name: string,
	Execute: ( Program, buffer? ) -> (),
	Shaders: { Shader }
}

return {}


local Types = require("./Types")

export type IHDR = {
	width: number,
	height: number,
	bitDepth: number,
	colorType: number,
	interlaced: boolean,
}

export type PLTE = {
	colors: { Types.Color },
}

export type tRNS = {
	gray: number,
	red: number,
	green: number,
	blue: number,
}

return {}


--!strict
--!native

local function new(lengths: { number })
	local blCount = {}
	local maxBits = 0
	for _, len in lengths do
		if len > 0 then
			blCount[len] = (blCount[len] or 0) + 1
			if len > maxBits then
				maxBits = len
			end
		end
	end

	local thisCode = 1
	local nextCode = {}
	for bits = 1, maxBits do
		thisCode = 2 * (thisCode + (blCount[bits - 1] or 0))
		nextCode[bits] = thisCode
	end

	local look = {}
	for n, len in lengths do
		if len > 0 then
			look[nextCode[len]] = n - 1
			nextCode[len] += 1
		end
	end

	local function read(readBit: () -> number)
		local code = 1
		repeat
			code = 2 * code + readBit()
		until look[code]
		return look[code]
	end

	return {
		read = read,
	}
end

return {
	new = new,
}


export type PNG = {
	Width: number,
	Height: number,
	Pixels: buffer,
	--ReadPixel: (x: number, y: number) -> Color,
}

export type Chunk = {
	type: string,
	offset: number,
	length: number,
}

export type Color = {
	r: number,
	g: number,
	b: number,
	a: number,
}

return {}


--!strict
--!native

local ChunkTypes = require("../ChunkTypes")
local Types = require("../Types")

local COLOR_TYPE_BIT_DEPTH = {
	[0] = { 1, 2, 4, 8, 16 },
	[2] = { 8, 16 },
	[3] = { 1, 2, 4, 8 },
	[4] = { 8, 16 },
	[6] = { 8, 16 },
}

local function read(buf: buffer, chunk: Types.Chunk): ChunkTypes.IHDR
	assert(chunk.length == 13, "IHDR data must be 13 bytes")

	local offset = chunk.offset

	local width = bit32.byteswap(buffer.readu32(buf, offset))
	local height = bit32.byteswap(buffer.readu32(buf, offset + 4))
	local bitDepth = buffer.readu8(buf, offset + 8)
	local colorType = buffer.readu8(buf, offset + 9)
	local compression = buffer.readu8(buf, offset + 10)
	local filter = buffer.readu8(buf, offset + 11)
	local interlace = buffer.readu8(buf, offset + 12)

	assert(width > 0 and width <= 2 ^ 31 and height > 0 and height <= 2 ^ 31, "invalid dimensions")
	assert(compression == 0, "invalid compression method")
	assert(filter == 0, "invalid filter method")
	assert(interlace == 0 or interlace == 1, "invalid interlace method")

	local allowedBitDepth = COLOR_TYPE_BIT_DEPTH[colorType]
	assert(allowedBitDepth ~= nil, "invalid color type")
	assert(table.find(allowedBitDepth, bitDepth) ~= nil, "invalid bit depth")

	return {
		width = width,
		height = height,
		bitDepth = bitDepth,
		colorType = colorType,
		interlaced = interlace == 1,
	}
end

return read


--!strict
--!native

local ChunkTypes = require("../ChunkTypes")
local Types = require("../Types")

local function read(buf: buffer, chunk: Types.Chunk, header: ChunkTypes.IHDR): ChunkTypes.PLTE
	assert(chunk.length % 3 == 0, "malformed PLTE chunk")

	local count = chunk.length / 3
	assert(count > 0, "no entries in PLTE")
	assert(count <= 256, "too many entries in PLTE")
	assert(count <= 2 ^ header.bitDepth, "too many entries in PLTE for bit depth")

	local colors = table.create(count)
	local offset = chunk.offset

	for i = 1, count do
		colors[i] = {
			r = buffer.readu8(buf, offset),
			g = buffer.readu8(buf, offset + 1),
			b = buffer.readu8(buf, offset + 2),
			a = 255,
		}
		offset += 3
	end

	return {
		colors = colors,
	}
end

return read


--!strict
--!native

return {
	IHDR = require("@self/IHDR"),
	PLTE = require("@self/PLTE"),
	tRNS = require("@self/tRNS"),
}


--!strict
--!native

local ChunkTypes = require("../ChunkTypes")
local Types = require("../Types")

local function readU16(buf: buffer, offset: number, depth: number)
	return bit32.extract(
		bit32.bor(bit32.lshift(buffer.readu8(buf, offset), 8), buffer.readu8(buf, offset + 1)),
		0,
		depth
	)
end

local function read(
	buf: buffer,
	chunk: Types.Chunk,
	header: ChunkTypes.IHDR,
	palette: ChunkTypes.PLTE?
): ChunkTypes.tRNS
	local gray = -1
	local red = -1
	local green = -1
	local blue = -1

	if header.colorType == 0 then
		assert(chunk.length == 2, "invalid tRNS length for color type")
		gray = readU16(buf, chunk.offset, header.bitDepth)
	elseif header.colorType == 2 then
		assert(chunk.length == 6, "invalid tRNS length for color type")
		red = readU16(buf, chunk.offset, header.bitDepth)
		green = readU16(buf, chunk.offset + 2, header.bitDepth)
		blue = readU16(buf, chunk.offset + 4, header.bitDepth)
	else
		local count = chunk.length -- 1 byte per transparency entry
		assert(palette, "tRNS requires PLTE for color type")
		assert(count <= #palette.colors, "tRNS specified too many PLTE alphas")
		for i = 1, count do
			palette.colors[i].a = buffer.readu8(buf, chunk.offset + i - 1)
		end
	end

	return {
		gray = gray,
		red = red,
		green = green,
		blue = blue,
	}
end

return read


--!strict
--!native

local HuffmanTable = require("./HuffmanTable")

-- stylua: ignore
local LIT_LEN = {
	3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
	35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258,
}

-- stylua: ignore
local LIT_EXTRA = {
	0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
	2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0,
}

-- stylua: ignore
local DIST_OFF = {
	1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513,
	769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
}

-- stylua: ignore
local DIST_EXTRA = {
	0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
	7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
}

-- stylua: ignore
local LEN_ORDER = {
	16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
}

local FIXED_LIT = {}
for n = 0, 287 do
	FIXED_LIT[n + 1] = if n < 144 or n > 279 then 8 elseif n < 256 then 9 else 7
end

local FIXED_DIST = {}
for n = 0, 31 do
	FIXED_DIST[n + 1] = 5
end

local readFixedLit = HuffmanTable.new(FIXED_LIT).read
local readFixedDist = HuffmanTable.new(FIXED_DIST).read

local function inflate(input: buffer, output: buffer)
	local byteIdx = 0
	local bitIdx = 0

	local function readBit()
		local byte = buffer.readu8(input, byteIdx)
		local bit = bit32.extract(byte, bitIdx)
		bitIdx += 1
		if bitIdx == 8 then
			bitIdx = 0
			byteIdx += 1
		end
		return bit
	end

	local function readBits(count: number)
		local n = 0
		local byte = buffer.readu8(input, byteIdx)
		for p = 0, count - 1 do
			local bit = bit32.extract(byte, bitIdx)
			n += bit32.lshift(bit, p)
			bitIdx += 1
			if bitIdx == 8 then
				bitIdx = 0
				byteIdx += 1
				byte = buffer.readu8(input, byteIdx)
			end
		end
		return n
	end

	local method = readBits(4)
	assert(method == 8, "invalid zlib compression method")

	local info = readBits(4)
	assert(info <= 7, "invalid zlib window size")

	local fcheck = readBits(5)
	local fdict = readBit()
	assert(fdict == 0, "preset dictionary is not allowed")

	local flevel = readBits(2)
	local sum = bit32.bor(
		bit32.lshift(info, 12),
		bit32.lshift(method, 8),
		bit32.lshift(flevel, 6),
		bit32.lshift(fdict, 5),
		fcheck
	)
	assert(sum % 31 == 0, "invalid zlib header sum")

	local window = buffer.create(0x8000)
	local windowIdx = 0
	local outputIdx = 0

	repeat
		local bfinal = readBit()
		local btype = readBits(2)
		assert(btype ~= 3, "reserved btype")
		assert(btype == 0 or btype == 1 or btype == 2, "unknown btype")

		if btype == 0 then
			if bitIdx > 0 then
				byteIdx += 1
				bitIdx = 0
			end

			local len = bit32.bor(buffer.readu8(input, byteIdx), bit32.lshift(buffer.readu8(input, byteIdx + 1), 8))
			local nlen =
				bit32.bor(buffer.readu8(input, byteIdx + 2), bit32.lshift(buffer.readu8(input, byteIdx + 3), 8))
			assert(len + nlen == 0xFFFF, "invalid len/nlen")
			byteIdx += 4

			for _ = 1, len do
				local b = buffer.readu8(input, byteIdx)
				buffer.writeu8(window, windowIdx, b)
				buffer.writeu8(output, outputIdx, b)
				byteIdx += 1
				windowIdx = bit32.band(windowIdx + 1, 0x7FFF)
				outputIdx += 1
			end
		elseif btype == 1 or btype == 2 then
			local readLit = readFixedLit
			local readDist = readFixedDist

			if btype == 2 then
				local litsCount = readBits(5) + 257
				local distsCount = readBits(5) + 1
				local codesCount = readBits(4) + 4

				local codeLengths = table.create(19, 0)
				local readLength
				for i = 1, codesCount do
					codeLengths[LEN_ORDER[i] + 1] = readBits(3)
				end

				local function getReader(numCodes)
					local lengths = {}
					local index = 1
					local length
					while index <= numCodes do
						local code = readLength(readBit)
						local repeatCount = 1
						if code <= 15 then
							length = code
						elseif code == 16 then
							repeatCount = readBits(2) + 3
						elseif code == 17 then
							length = 0
							repeatCount = readBits(3) + 3
						elseif code == 18 then
							length = 0
							repeatCount = readBits(7) + 11
						end
						for _ = 1, repeatCount do
							lengths[index] = length
							index += 1
						end
					end
					return HuffmanTable.new(lengths).read
				end

				readLength = HuffmanTable.new(codeLengths).read
				readLit = getReader(litsCount)
				readDist = getReader(distsCount)
			end

			while true do
				local v = readLit(readBit)
				if v == 0x100 then
					break
				elseif v < 0x100 then
					buffer.writeu8(window, windowIdx, v)
					buffer.writeu8(output, outputIdx, v)
					windowIdx = bit32.band(windowIdx + 1, 0x7FFF)
					outputIdx += 1
				else
					local len = LIT_LEN[v - 0x100]
					local lenExtra = LIT_EXTRA[v - 0x100]
					if lenExtra > 0 then
						len += readBits(lenExtra)
					end
					local d = readDist(readBit)
					local dist = DIST_OFF[d + 1]
					local distExtra = DIST_EXTRA[d + 1]
					if distExtra > 0 then
						dist += readBits(distExtra)
					end
					for _ = 1, len do
						local b = buffer.readu8(window, bit32.band(windowIdx - dist, 0x7FFF))
						buffer.writeu8(window, windowIdx, b)
						buffer.writeu8(output, outputIdx, b)
						windowIdx = bit32.band(windowIdx + 1, 0x7FFF)
						outputIdx += 1
					end
				end
			end
		end
	until bfinal == 1

	return outputIdx
end

return inflate


--!strict
--!native
local B64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIElEQVQokWP4z8Dw//9/BuJJ0lRDwKgNozYMBRv+MwAA53OAjjUvXLcAAAAASUVORK5CYII="
return buffer.fromstring(B64)


--!strict
--!native

--[[
	Penumbra: textures.luau

	This module handles loading and processing a texture via any
	of the given load parameters by the calling module.

	Assets are stored via textures/assets, additional ones can be
	added by providing PNG files as base64-encoded strings so that
	no filesystem dependency is required.
]]

-- Requires
local Settings = 	require( `../` )()
local PNG =			require( `./png` )
local Base64 =		require( `./base64` )

-- [[ TYPES ]] --
local Types = require( `@self/types` )
export type Texture = Types.Texture
export type Cubemap = Types.Cubemap
export type LoadTexture = Types.LoadTexture

-- RGBAu8/vec3 buffer read/write functions
local u32B: number = 4
local buffer_vec3 = function(b: buffer ,i: number ): vector
	local n:number = buffer.readu32(b,i)
	return vector.create(bit32.band(n,0x000000FF),bit32.rshift(bit32.band(n,0x0000FF00),8),bit32.rshift(bit32.band(n,0x00FF0000),16))*.00392156862745098
end
local vec3_buffer = function(b: buffer, i: number, v: vector)
	v*=0xFF
	buffer.writeu32(b,i,v.x+bit32.lshift(v.y,8)+bit32.lshift(v.z,16)+0xFF000000)
end

-- Sampling function pulled from shader_builder for pre-processing textures
local function Sampler_Nearest(t: Types.Texture, UV: vector ): vector
	UV *= vector.create( t.Width, t.Height )		-- Expand resolution
	UV //= 1										-- Set top-left UV integer
	local x:number	= UV.x % t.Width				-- Wrap U to texture width
	local y:number	= ( UV.y % t.Height ) * t.Width	-- Wrap V to texture height
	local i:number	= (x + y) * u32B				-- Set index value
	return buffer_vec3(t.Buffer, i)					-- Return read value
end

-- Locals
local vec_0: vector = vector.zero
local HalfPi: number = math.pi * 0.5
local Tau: number = math.pi * 2
local vec = vector.create

-- FALLBACK: TEXTURE --
local FallbackTexture: Texture = (function(): Texture
	local Width: number = 128
	local Height: number = 128
	local x: number, y: number = 0, 0
	local CreatedTexture = {
		Buffer = buffer.create(1),
		Dimensions = vector.create(Width, Height),
		Width = Width,
		Height = Height,
		Width0 = Width-1,
		Height0 = Height-1,
		FD = vector.create(1/Width,1/Height,1/Height),
		Mult = vector.one
	}
	-- Generate colour for every pixel
	for write: number = 0, Width * Height do
		local xRate: number = y / Width
		local yRate: number = x / Height
		local zRate: number = 1 - math.min( ( 1 - xRate ) + ( 1 - yRate ), 1 )
		xRate -= zRate
		yRate -= zRate
		CreatedTexture[ write ] = vector.create( xRate, zRate, yRate )
		x += 1
		y = x == Width and y + 1 or y
		x = x == Width and 0 or x
	end
	return table.freeze( CreatedTexture ) :: Texture
end)()

-- LOADED TEXTURES --
-- This module can return already-loaded textures once they are initially created and stored here.
-- Be mindful of this is you choose to load the same texture in the same VM multiple times, as pre-processing is fixed.
local Textures = table.freeze( {
	Base = {} :: { [ string ]: Texture },		--All Textures
	Cubemaps = {} :: { [ string ]: Cubemap },	--Cubemaps [Contains Textures]
	Load = {}									-- Loading functions are stored here.
} )

local vX: vector = vector.create( 1, 0, 0 )
local vY: vector = vector.create( 0, 1, 0 )
local vZ: vector = vector.create( 0, 0, 1 )

-- Specific functions which can be run on the texture.
local ProcessFunctions = {
	-- Per-pixel processing modes
	Fragment = {
		From_BT709 = function( In: vector ): vector
			local R, G, B = In.x, In.y, In.z
			return vector.create(
				if R >= 0.081 then ( ( R + .099 ) / 1.099 ) ^ 2.22222222 else R / 4.5,
				if G >= 0.081 then ( ( G + .099 ) / 1.099 ) ^ 2.22222222 else G / 4.5,
				if B >= 0.081 then ( ( B + .099 ) / 1.099 ) ^ 2.22222222 else B / 4.5
			)
		end,
		To_sRGB = function( In: vector ): vector
			-- Gamma Reciprocal: 1 / 2.4 = 0.4166666666666667
			local R, G, B = In.x, In.y, In.z
			return vector.create(
				R > 0.0031308 and 1.055 * ( R ^ 0.4166666666666667 ) - 0.055 or R * 12.92,
				G > 0.0031308 and 1.055 * ( G ^ 0.4166666666666667 ) - 0.055 or G * 12.92,
				B > 0.0031308 and 1.055 * ( B ^ 0.4166666666666667 ) - 0.055 or B * 12.92
			)
		end,
		To_lRGB = function( In: vector ): vector
			local R, G, B = In.x, In.y, In.z
			return vector.create(
				if R<0.04045 then R/12.92 else ((R+0.055)/1.055)^2.4,
				if G<0.04045 then G/12.92 else ((G+0.055)/1.055)^2.4,
				if B<0.04045 then B/12.92 else ((B+0.055)/1.055)^2.4
			)
		end,
		Squared = function( In: vector ): vector
			return In*In
		end,
		Mult = function( In: vector, Config: {vector} ): vector
			return In * Config[1]
		end,
	},

	-- Processing modes which require access to the full texture
	Full = {
		-- Vertically flip the texture in pre-processing to save compute
		-- Use this when loading a texture to match OpenGL's behaviour.
		VFlip = function( In: Texture, Config: any ): Texture
			local HalfY: number = In.Height0 // 2
			for Y = 0, HalfY do
				local i_Y0: number = Y * In.Width
				local i_Y1: number = ( In.Height0 - Y ) * In.Width
				for X = 0, In.Width0 do
					local S0: vector = buffer_vec3( In.Buffer, (i_Y0 + X)*4 )
					local S1: vector = buffer_vec3( In.Buffer, (i_Y1 + X)*4 )
					-- Swap pixels with above & below row.
					vec3_buffer( In.Buffer, (i_Y0 + X)*4, S0 )
					vec3_buffer( In.Buffer, (i_Y1 + X)*4, S1 )
				end
			end
			return In
		end,

		BoxBlur = function( In: Texture, Config: any ): Texture
			local WriteBuffer = buffer.create(buffer.len(In.Buffer))
			local c:number = 0
			for y = 0, In.Height0 do
				for x = 0, In.Width0 do
					local accumulation: vector = vector.zero
					for yB = -1, 1 do
						for xB = -1, 1 do
							accumulation += Sampler_Nearest( In, vector.create( (x + xB) / In.Width, (y + yB) / In.Height ) )
						end
					end
					vec3_buffer( WriteBuffer, c, vector.clamp( accumulation * 0.1111111111111111, vector.zero, vector.one ) )
					c += 4
				end
			end
			buffer.copy( In.Buffer, 0, WriteBuffer )
			WriteBuffer = nil
			return In
		end,

		GaussianBlur = function( In: Texture, Config: any ): Texture
			local WriteBuffer = buffer.create(buffer.len(In.Buffer))
			local c:number = 0
			for y = 0, In.Height0 do
				for x = 0, In.Width0 do
					local accumulation: vector = vector.zero

					-- Corners: X0Y0, X2Y0, X0Y2, X2Y2
					accumulation += Sampler_Nearest( In, vector.create(x-1,y-1) / In.Dimensions ) * .0625
					accumulation += Sampler_Nearest( In, vector.create(x+1,y-1) / In.Dimensions ) * .0625
					accumulation += Sampler_Nearest( In, vector.create(x-1,y+1) / In.Dimensions ) * .0625
					accumulation += Sampler_Nearest( In, vector.create(x+1,y+1) / In.Dimensions ) * .0625

					-- Adjacents: X1Y0, X0Y1, X2Y1, X1Y2
					accumulation += Sampler_Nearest( In, vector.create(x,y-1) / In.Dimensions ) * .1250
					accumulation += Sampler_Nearest( In, vector.create(x-1,y) / In.Dimensions ) * .1250
					accumulation += Sampler_Nearest( In, vector.create(x+1,y) / In.Dimensions ) * .1250
					accumulation += Sampler_Nearest( In, vector.create(x,y+1) / In.Dimensions ) * .1250

					-- Center: X1Y1
					accumulation += Sampler_Nearest( In, vector.create(x,y) / In.Dimensions ) * .25
					vec3_buffer( WriteBuffer, c, vector.clamp( accumulation, vector.zero, vector.one ) )
					c += 4
				end
			end
			buffer.copy( In.Buffer, 0, WriteBuffer )
			WriteBuffer = nil
			return In
		end,

		-- Removes alpha channel from texture
		RemoveAlpha = function( In: Texture, Config: any ): Texture
			for y = 0, In.Height0 do
				for x = 0, In.Width0 do
					local idx: number = ( ( x % In.Width ) + y * In.Width ) * 4
					local pixel: vector = buffer_vec3( In.Buffer, idx )
					vec3_buffer( In.Buffer, idx, pixel )
				end
			end
			return In
		end,
	},
}

-- Main loader function
function Textures.Load.Base( cfg: LoadTexture?, ...: string ): ...Texture
	local cfg = cfg or {} :: LoadTexture
	local LoadedTextures: { Texture } = {}
	for _, TexturePath: string in { ... } do

		-- Add texture to list if it's already loaded
		if Textures.Base[ TexturePath ] then
			table.insert( LoadedTextures, Textures.Base[ TexturePath ] )
			continue
		end

		-- [Errors if texture isn't present]
		local TextureData = require( `@self/assets/{ TexturePath }` ) :: any

		-- [Redundant?] Replace texture with fallback if data doesn't exist
		if not TextureData then
			print(`Texture '{ TexturePath }' doesn't exist: Providing fallback`)
			table.insert( LoadedTextures, FallbackTexture )
			continue
		end

		-- Are we a buffer or a table?
		local ImageFile: PNG.PNG
		if type(TextureData) == "buffer" then
			ImageFile = PNG.decode( Base64.Decode( TextureData :: buffer ) :: buffer )
		elseif type(TextureData) == "table" then
			ImageFile = TextureData
		else
			error("Lol")
		end

		local Width_O: number = ImageFile.Width
		local Width_Mip: number = Width_O
		local Height_O: number = ImageFile.Height
		local Height_Mip: number = Height_O

		-- Unloaded data exists, parse and add to list.
		local TxSize_I: number = math.sqrt( Width_O * Width_O )
		local TxSize_O: number = TxSize_I
		local Mults={ _ft = vec(1,Width_O,0), _rt = vec(0,Width_O,1), _up = vec(1,0,Width_O), _bk = vec(1,Width_O,0), _lf = vec(0,Width_O,1), _dn = vec(1,0,Width_O) }
		local Mult_O = Mults[ string.sub( TexturePath, #TexturePath - 2, #TexturePath ) ] or Mults._ft

		-- Create original texture
		local CreatedTexture: Texture = {
			Buffer = buffer.create(Width_O * Height_O * 4),
			Dimensions = vector.create(Width_O, Height_O),
			Width = Width_O,
			Height = Height_O,
			Width0 = Width_O-1,
			Height0 = Height_O-1,
			FD = vec(
				1 / Width_O,
				1 / Height_O,
				1 / TxSize_O
			),
			Mult = Mult_O or 1
		}

		-- Initial processing of texture
		local TextureBuffer = ImageFile.Pixels
		local Buffer_i: number = 0
		local RGB_i: number = 0
		for RGB_i = 0, ( Width_O * Height_O ) - 1 do
			buffer.writeu8( CreatedTexture.Buffer, Buffer_i, buffer.readu8( TextureBuffer, Buffer_i ) )
			Buffer_i += 1
			buffer.writeu8( CreatedTexture.Buffer, Buffer_i, buffer.readu8( TextureBuffer, Buffer_i ) )
			Buffer_i += 1
			buffer.writeu8( CreatedTexture.Buffer, Buffer_i, buffer.readu8( TextureBuffer, Buffer_i ) )
			Buffer_i += 2
		end

		-- Iterate through given list of process functions and relevant configs
		for i: number, Mode: {[string]: any} in cfg do
			if ProcessFunctions.Fragment[ Mode.Name ] then
				-- Fragment modes
				for RGB_i = 0, ( Width_O * Height_O ) - 1 do
					vec3_buffer(
						CreatedTexture.Buffer,
						RGB_i*4,
						ProcessFunctions.Fragment[ Mode.Name ](
							buffer_vec3( CreatedTexture.Buffer, RGB_i*4 ),
							Mode.Config
						)
					)
				end
			elseif ProcessFunctions.Full[ Mode.Name ] then
				-- Full modes
				ProcessFunctions.Full[ Mode.Name ](
					CreatedTexture,
					Mode.Config
				)
			end
		end

		-- Texture loaded & processed - Frozen for fast reads - Mutability not expected.
		table.freeze( CreatedTexture )

		-- Stores texture in global dictionary and own array for returning
		Textures.Base[ TexturePath ] = CreatedTexture :: Texture
		table.insert( LoadedTextures, CreatedTexture )
	end

	-- Returns each texture as requested.
	return table.unpack( LoadedTextures )
end

-- Make a texture with a given input buffer and NOT store it in the textures module
function Textures.Load.FromBuffer( name: string, input: buffer, dimensions: vector ): Texture
	local TxSize_I: number = math.sqrt( dimensions.x * dimensions.y )
	local TxSize_O: number = TxSize_I
	-- Create original texture
	local CreatedTexture: Texture = {
		Buffer = input,
		Dimensions = vector.create(dimensions.x,dimensions.y,0),
		Width = dimensions.x,
		Height = dimensions.y,
		Width0 = dimensions.x-1,
		Height0 = dimensions.y-1,
		FD = vec(
			1 / dimensions.x,
			1 / dimensions.y,
			1 / TxSize_O
		),
		Mult = 1 -- Not relevant for a 2D image
	}
	table.freeze( CreatedTexture )
	return CreatedTexture
end

-- Generates Cubemap
function Textures.Load.Cubemap( cfg: LoadTexture, Name: string ): Cubemap
	-- Early exit if cubemap with a given name is already loaded/available for returning.
	if Textures.Cubemaps[ Name ] then
		return Textures.Cubemaps[ Name ]
	end
	local Front = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_ft`)

	-- Create Immutable & Error-Free
	local CreatedCubemap: Cubemap = setmetatable(
		{
			[ vec( 0, 0,-1 ) ] = Front,													--Front
			[ vec( 1, 0, 0 ) ] = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_rt`),	--Right
			[ vec( 0, 1, 0 ) ] = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_up`),	--Up
			[ vec( 0, 0, 1 ) ] = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_bk`),	--Back
			[ vec(-1, 0, 0 ) ] = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_lf`),	--Left
			[ vec( 0,-1, 0 ) ] = Textures.Load.Base(cfg,`Cubemaps/{Name}/{Name}_dn`)	--Down
		},
		{ __index = Front }
	)
	table.freeze( CreatedCubemap )
	Textures.Cubemaps[ Name ] = CreatedCubemap
	return CreatedCubemap
end

return Textures


-- Types : Textures --

-- Represents what constitutes as a texture.
export type Texture = {
	[number]: 			vector,
	read Buffer:		buffer,
	read Dimensions:	vector,
	read Width:			number,
	read Height:		number,
	read Width0:		number,
	read Height0:		number,
	read FD:			vector,
	read Mult:			vector | number
}

-- A superset of textures used in a 3d context.
export type Cubemap = typeof(
	setmetatable(
		{} :: { [ vector ]: Texture },
		{} :: { __index: Texture }
	)
)

-- Required input when asking to load a texture.
export type LoadTexture = {
	[number]: {
		Name: string,
		Config: {any}?
	}
}

return {}


--[[
MIT License

Copyright (c) 2025 daily3014

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]


--[[
The MIT License (MIT)

Copyright (c) 2026 Crazyblox

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]


--[[
The MIT License (MIT)

Copyright (c) 2024 sircfenner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]


local PyLua = require("../src/PyLua")

-- Create a Python runtime
local python = PyLua.new()

-- Execute multi-line Python code (statements)
python:execute([[
x = 10
y = 32
print("sum:", x + y)
]])

-- Evaluate a Python expression and get the result in Luau
local value = python:eval("1 + 2 * 3")
print("eval result:", value) -- 7

-- Share values via the globals table
python:execute("z = 5")
local globals = python:globals()
print("z from python globals:", globals.z) -- 5


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
print("PyLua Version:", __pylua_version__)

string = "Hello, PyLua!"

print("len of string:", len(string))
print("type of 123:", type(123))

if string[0] == "H":
	print("First character is H")

if isinstance(123, int):
	print("123 is an integer")

# range usage (iterate rather than casting to list)
for i in range(0, 5):
	print("range:", i)
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
s = "hello"
b = b"hello"
print("str:", s)
print("bytes:", b)
print("len(bytes):", len(b))
print("b[1] (first byte):", b[0])
print("concat bytes:", b + b" world")
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

person = Person("Alice", 30)
print(person.greet())

class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)
        self.student_id = student_id

    def study(self, subject):
        return f"{self.name} is studying {subject}."

student = Student("Bob", 20, "S12345")
print(student.greet())
print(student.study("Mathematics"))
]])

python:execute([[
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

    def scale(self, factor):
        self.width *= factor
        self.height *= factor

    def __str__(self):
        return f"Rectangle(width={self.width}, height={self.height})"

rect = Rectangle(4, 5)
print("Area:", rect.area())
print("Perimeter:", rect.perimeter())
rect.scale(2)
print("Scaled Rectangle:", rect)
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
# Lists, tuples, dicts, sets
nums = [1, 2, 3]
pair = ("a", 42)
conf = {"host": "localhost", "port": 8080}
letters = {"a", "b", "c"}

# Unpacking into new dict
conf2 = {**conf, "debug": True}

print("nums:", nums)
print("pair:", pair)
print("conf:", conf)
print("conf2:", conf2)
print("letters:", letters)

# Looping
for n in nums:
    print("num:", n)

# Builtins interop
print("len(nums) =", len(nums))
print("min(nums) =", min(nums))
print("max(nums) =", max(nums))
print("sum(nums) =", sum(nums))

# Methods
nums.append(4)
print("after append:", nums)
print("popped:", nums.pop())
print("after pop:", nums)
print("contains 2?", 2 in nums)

# List comprehensions
squares = [x * x for x in nums]
print("squares:", squares)
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
x = 7
if x < 5:
    print("small")
elif x < 10:
    print("medium")
else:
    print("large")

# chained comparisons combine checks without repeating the middle operand
value = 8
if 5 < value < 10:
    print("value is between 5 and 10")

print("strictly increasing chain:", 1 < 2 < 3)

# failing a link short-circuits further comparisons
if not (0 < value < 5):
    print("value is not less than 5")

# while + break/continue
i = 0
while i < 5:
    i = i + 1
    if i == 2:
        continue
    if i == 4:
        break
    print("i:", i)

# for over range and list
for n in range(3):
    print("range:", n)

for v in ["a", "b", "c"]:
    print("list:", v)

# Iterate over list of tuples
for x, y in [(1, 2), (3, 4), (5, 6)]:
    print(f"x={x}, y={y}, sum={x + y}")

# Parenthesized tuple targets work too
for (a, b) in [(10, 20), (30, 40)]:
    print(f"a={a}, b={b}, product={a * b}")

# Triple tuple destructuring
coordinates = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
for x, y, z in coordinates:
    print(f"point: ({x}, {y}, {z})")

# Nested tuple destructuring
data = [((1, 2), 100), ((3, 4), 200)]
for (a, b), value in data:
    print(f"pair ({a}, {b}) has value {value}")
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
def divide(a, b):
    return a / b

try:
    print("divide(10, 2) =", divide(10, 2))
    print("divide(5, 0) =", divide(5, 0))
except ZeroDivisionError as e:
    print("Error:", e)

try:
    array = [1, 2, 3]
    print("Accessing array[5]:", array[5])
except IndexError as e:
    print("Error:", e)

try:
    print("Undefined variable x:", x)
except NameError as e:
    print("Error:", e)

try:
    value = int("not_a_number")
except:
    print("Error: Could not convert string to int")
finally:
    print("Execution completed.")

try:
    # This might raise ValueError or TypeError
    result = int("abc")
    # result = "string" + 123 # This would raise TypeError
except (ValueError, TypeError) as e:
    print("Caught multiple types error:", e)

# Unhandled exception example
print("This will raise an unhandled exception:")
print(1 / 0)
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

python:execute([[
def add(a, b):
    return a + b

def greet(name):
    return "Hello, " + name

print("add(2, 3) =", add(2, 3))
print(greet("World"))

# Simple function with one default argument
def greet(name, greeting="Hello"):
    print(greeting + ", " + name)

greet("Alice", "Hi")
greet("Bob")

# Function with multiple default arguments
def create_user(username, role="user", active=True):
    print(username + ", " + role + ", " + str(active))

create_user("admin", "administrator", False)
create_user("john", "moderator")
create_user("jane")

# Lambda function
square = lambda x: x * x
print("square(5) =", square(5))

# lambda with default argument
increment = lambda x, inc=1: x + inc
print("increment(10) =", increment(10))
print("increment(10, 5) =", increment(10, 5))

# Function calling another function
def helper(x):
    return x + 1

def main(x):
    return helper(x) * 2

print("main(5) =", main(5))

# Generator function
def countdown(n):
    while n > 0:
        yield n
        n -= 1

print("Countdown:")
for value in countdown(3):
    print("  ", value)
]])

-- You can also evaluate expressions and read back results
local result = python:eval("add(10, 32)")
print("add(10,32) ->", result)


local PyLua = require("../src/PyLua")

local python = PyLua.new()

python:execute([[
print("Hello world from python!")
]])


local PyLua = require("../../src/PyLua")
local FileSystem = require("../../src/Packages/FileSystem/Lune")

-- Create a Python runtime
local python = PyLua.new({
    fileSystem = FileSystem,
    searchPath = {"examples/imports/libs"},
    enableFilesystem = true,
})

python:execute([[
import my_lib
my_lib.pretty_print("Hello from the lune runtime!")
]])


local PyLua = require("../../src/PyLua")
local FileSystem = require("../../src/Packages/FileSystem/Lute")

-- Create a Python runtime
local python = PyLua.new({
    fileSystem = FileSystem,
    searchPath = {"examples/imports/libs"},
    enableFilesystem = true,
})

python:execute([[
import my_lib
my_lib.pretty_print("Hello from the lute runtime!")
]])


local PyLua = require("../../src/PyLua")
local FileSystem = require("../../src/Packages/FileSystem/Virtual")

-- 1. Define a native module (Luau table)
local myMathModule = {
    add = function(a:number, b:number) return a + b end,
    PI = 3.14159
}

-- 2. Define a virtual filesystem (mocking real files)
local files = {
    ["/lib/utils.py"] = [[
def greet(name):
    return "Hello, " + name + "!"
]],
    ["/lib/pkg/__init__.py"] = "version = '1.0.0'"
}

-- 3. Initialize PyLua with these options
local py = PyLua.new({
    nativeModules = {
        ["my_math"] = myMathModule
    },
    fileSystem = FileSystem.new(files),
    searchPath = {"/lib"}
})

-- 4. Use them in Python
py:execute([[
import my_math
import utils
import pkg

print("Native module:", my_math.add(10, 20))
print("FS module:", utils.greet("User"))
print("Package version:", pkg.version)
]])


local PyLua = require("../src/PyLua")
local python = PyLua.new()

-- Minimal interop: expose a Luau value/function via globals
local globals = python:globals()

-- Expose a simple Luau function that multiplies numbers
globals.lua_mul = function(a: number, b: number): number
    return a * b
end

python:execute([[
print("calling into Luau from Python:", lua_mul(6, 7))
result = lua_mul(3, 9)
]])

print("result captured in python globals:", globals.result)


--!strict
-- Lute FileSystem Implementation
-- https://lute.luau.org/reference/lute/fs.html

local fs = require("@lute/fs")

local LuteFS = {}

function LuteFS.readFile(path: string): string?
	if fs.exists(path) and fs.type(path) == "file" then
		return fs.readfiletostring(path)
	end
	return nil
end

function LuteFS.exists(path: string): boolean
	return fs.exists(path)
end

function LuteFS.isDir(path: string): boolean
	return fs.exists(path) and fs.type(path) == "dir"
end

return LuteFS


--!strict
-- Virtual FileSystem (Memory-based)

local VirtualFS = {}

function VirtualFS.new(files: { [string]: string })
	return {
		readFile = function(path: string): string?
			return files[path]
		end,
		exists = function(path: string): boolean
			-- Check exact match
			if files[path] then
				return true
			end
			-- Check if it's a directory (prefix match)
			for k in pairs(files) do
				if string.sub(k, 1, #path) == path and string.sub(k, #path + 1, #path + 1) == "/" then
					return true
				end
			end
			return false
		end,
		isDir = function(path: string): boolean
			if files[path] then
				return false
			end
			for k in pairs(files) do
				if string.sub(k, 1, #path) == path and string.sub(k, #path + 1, #path + 1) == "/" then
					return true
				end
			end
			return false
		end,
	}
end

return VirtualFS