// The raw shading layer: compileShader compiles one stage from complete GLSL
// ES (nothing injected - the source declares its own #version, precision,
// varyings and uniforms), linkProgram links a vertex and a fragment stage
// into a program, createRenderPipeline pairs the program with draw state
// (none here: attributeless triangles are the defaults), and
// createShaderTarget builds a render target over the pipeline. One program
// can back many pipelines, one pipeline many targets, and only the compile
// step compiles, so swapping precompiled programs is free of compilation.
// Stages can be destroyed right after linking; the program keeps its own
// compiled copies.
//
// A raw program carries its own vertex stage, so a fullscreen pass is a
// covering triangle from gl_VertexID with vertexCount: 3. Compare
// gpu-shader.tsx, where the fused createShaderTexture does all of this in
// one call with an injected preamble.
import { render, onFrame, createSignal } from "@solidrt/core"
import {
  compileShader,
  createRenderPipeline,
  createShaderTarget,
  destroyShader,
  glsl,
  linkProgram,
} from "@solidrt/core/gpu"

let VERTEX = glsl`#version 300 es
  precision highp float;
  out vec2 vUV;
  void main() {
    vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
    vUV = p;
    gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
  }
`

// Two fragment stages linked against the same vertex stage: two programs,
// one shared compile of the vertex half.
let WAVES = glsl`#version 300 es
  precision highp float;
  in vec2 vUV;
  out vec4 fragColor;
  uniform float uTime;
  void main() {
    float t = uTime * 2.0;
    float a = 0.5 + 0.5 * sin(vUV.x * 10.0 + t);
    float b = 0.5 + 0.5 * sin(vUV.y * 10.0 - t * 1.3);
    fragColor = vec4(a, b, 1.0 - a * b, 1.0);
  }
`

// The standard header ({ header: true }) declares #version, precision,
// iResolution and fragColor, so this source only adds its own inputs -
// including the app-driven time uniform.
let RINGS = glsl`
  in vec2 vUV;
  uniform float uTime;
  void main() {
    float d = length(vUV - 0.5);
    float r = 0.5 + 0.5 * sin(d * 40.0 - uTime * 3.0);
    fragColor = vec4(r, r * 0.6, 1.0 - r, 1.0);
  }
`

function App() {
  let vs = compileShader("vertex", VERTEX)
  let wavesFs = compileShader("fragment", WAVES)
  let ringsFs = compileShader("fragment", RINGS, { header: true })
  // Labels name each object in the dev tooling's GPU inventory, which is
  // what keeps a chain of programs, pipelines, and targets readable. They
  // are free-form and need not be unique - one name per chain works, since
  // the inventory already groups by kind.
  let waves = linkProgram(vs, wavesFs, { label: "waves" })
  let rings = linkProgram(vs, ringsFs, { label: "rings" })
  destroyShader(vs)
  destroyShader(wavesFs)
  destroyShader(ringsFs)

  let wavesPipeline = createRenderPipeline(waves, { label: "waves" })
  let ringsPipeline = createRenderPipeline(rings, { label: "rings" })

  let wavesId = createShaderTarget(wavesPipeline, 512, 512, { uTime: 0 }, { vertexCount: 3, label: "waves" })
  let ringsId = createShaderTarget(ringsPipeline, 512, 512, { uTime: 0 }, { vertexCount: 3, label: "rings" })

  let [time, setTime] = createSignal(0)
  onFrame(tick => setTime(tick / 1000))

  return (
    <window flexDirection="row" gap={16} alignItems="center" justifyContent="center">
      <texture src={wavesId} params={{ uTime: time() }} width={360} height={360} />
      <texture src={ringsId} params={{ uTime: time() }} width={360} height={360} />
    </window>
  )
}

render(() => <App />)
