fp64 Julia set

fp64 Julia set

같은 배정밀도 기법을 줄리아 집합으로 보인 예제.

// ═══ typeshade example — fp64 deep-zoom Julia set ═══
//
// The Julia twin of fp64-mandelbrot.ts: the SEED is fixed (c = −0.8 + 0.156i)
// and the PIXEL becomes z₀, so the precision-critical value is the per-pixel
// starting point itself — exactly the extended-precision `center + offset` add
// that df64 exists for. The camera parks on a Julia-set point BESIDE the
// repelling fixed point z* = (1 + √(1−4c))/2 (repelling fixed points lie ON
// the set, and its neighbourhood keeps escape times low and self-similar to
// any depth), bisected along the horizontal line y = toF32(Im z*) so that the
// center's y survives f32 narrowing EXACTLY: the plain-f32 left half then
// collapses to horizontal escape BANDS (the x axis dies first — same
// signature as fp64-mandelbrot's thin-line left half) instead of vanishing
// into a solid interior fill.
//
// The `_fp64` guard uniform is auto-injected by the lowering; the render
// harnesses bind it to 1.0f by probing the program for the Fp64Guard block.
import {
fn,
module,
vec3,
vec4,
f32,
pow,
cos,
log2,
max,
mix,
step,
toF32,
toF64,
f32T,
vec2fT,
vec2f64T,
If,
Loop,
Break,
Var,
Let,
u32,
uniformStruct,
} from '../src/index.js';
import { VsOut, vs } from './_fullscreen.js';
import type { ShaderExample } from './_shared.js';
// Seed of the Julia set; both components are exactly f32-representable, so the
// f32 half degrades ONLY through the pixel coordinate — the cleanest A/B.
const C_RE = -0.8;
const C_IM = 0.156;
// On the Julia set beside the repelling fixed point (|2z*| ≈ 3.06 > 1):
// y is EXACTLY f32-representable (toF32(Im z*)), x is CPU-bisected onto the
// escape boundary along that line — verified to keep 44+ distinct escape
// bands per 40² window down to a 1e-12 span, with ~7 surviving row bands on
// the narrowed f32 side.
const CENTER_X = 1.5255044073468653;
const CENTER_Y = -0.07591217756271362;
const ITER = 128;
const U = uniformStruct(
'Uniforms',
{ group: 0, binding: 0, as: 'u' },
{
center: vec2f64T, // one DF64Vec2 slot — host packs [hi.x, hi.y, lo.x, lo.y]
resolution: vec2fT,
zoom_exp: f32T, // view span = 10^-zoom_exp complex units
fp64: f32T, // toggle: 1 = split-screen f32 | f64 (canonical), 0 = all-f32
},
);
const fsJulia = fn(
'fs_julia',
{ vo: VsOut },
(p) => {
const span = Let(pow(f32(10.0), U.field.zoom_exp.neg()));
// Each half maps its own 0..1 sub-range onto the SAME complex window
// (pan lives on the HOST in full double precision — see fp64-mandelbrot.ts).
const half = Let(p.vo.uv.x.mul(2.0));
const sx = Let(half.sub(p.vo.uv.x.lt(0.5).select(0.0, 1.0)));
const dx = Let(sx.sub(0.5).mul(span));
const dy = Let(
p.vo.uv.y.sub(0.5).mul(span).mul(U.field.resolution.y.div(U.field.resolution.x).mul(2.0)),
);
// |z|² of the last z the loop reached, CARRIED beside z: set from z₀ before the loop,
// refreshed after every step, read by the escape test, and after the loop already the
// |z|² the smooth colouring wants. The test is one compare. It used to recompute |z|² every
// trip, escaped or not, and on the right half in df64: two squares, a df64_add, a df64_le
// and three compares, a trip.
//
// The loop LEAVES at the first escaped z — `If(m2 > 16) Break()` at the top of the trip —
// instead of running to ITER and skipping its body. The natural spelling is the loop
// condition, `j < ITER && m2 <= 16`, and this file could write it; the twin cannot. The
// source language's `for` is counted (surface §17, Rule 7.5 of docs/language-design.md),
// its condition is ONE comparison of the counter against a constant, and the conjunction
// is TS8006. A `break` is the same program in the counted form, so both files spell that.
// It makes everything after the loop non-uniform in WGSL's analysis, and nothing after it
// needs uniform control flow (no sample, derivative or barrier), so Tint takes it.
//
// What it saves is bounded by the wave: a wave runs until its LAST lane leaves, so only a
// tile whose every lane has escaped drops its remaining trips. At 640×480, 831 of the right
// half's 2400 8×8 pixel tiles have every lane escape before trip 128 at the default zoom
// (1272 at a 1e-10 span), and each drops trips that cost a u32 increment, two compares and
// a branch. Measured on the GPU-like evaluator (the fp64-lowered module at f32 precision)
// over the full 640×480 frame, the exit gives the same colour bit for bit on both halves.
const it = Var(f32(0));
const m2 = Var(f32(0));
If(p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5)), () => {
// f32 twin — z₀ built from the narrowed center: at deep zoom the pixel
// coordinate quantizes to f32 ulps and whole columns collapse.
//
// The squares are carried as well, beside m2. The step needs zx² and zy², and the m2
// refresh at the end of the trip before squared that same z. Squaring it again in the
// step puts the two on opposite sides of the loop's back edge, where neither dominates
// the other and no CSE can share them: 12 f32 operations a trip against 10, counted on
// the emitted WGSL. The shape before m2 was carried squared z twice in one trip as
// well, but there the test dominates the step and a dominator-based CSE shares the
// squares. `fp64-julia.test.ts` holds every loop here to one square of an operand a
// trip.
const zx = Var(toF32(U.field.center.x).add(dx));
const zy = Var(toF32(U.field.center.y).add(dy));
const x2 = Var(zx.mul(zx));
const y2 = Var(zy.mul(zy));
m2.assign(x2.add(y2));
Loop(
u32(0),
(j) => j.lt(u32(ITER)),
() => {
If(m2.gt(16.0), () => {
Break();
});
const nzx = Let(x2.sub(y2).add(C_RE));
zy.assign(zx.mul(zy).mul(2.0).add(C_IM));
zx.assign(nzx);
it.assign(it.add(1.0));
x2.assign(zx.mul(zx));
y2.assign(zy.mul(zy));
m2.assign(x2.add(y2));
},
);
}).else(() => {
// f64 — the same loop, z₀ keeps its extended-precision position, and m2 is taken
// differently. The escape test asks only which side of 16 |z|² lies on, and 48 bits
// change that answer only within an f32 rounding of the threshold, so m2 is squared in
// f32 from the narrowed words instead of in df64: two df64_narrow, two multiplies and an
// add a trip, in place of two df64 squares, a df64_add, a df64_le and three compares.
// `toF32(zx)` rounds hi + lo, which is the high word itself up to a half-ulp tie; the
// high word alone is not an author's to name (the split is compiler-internal, Rule 2.2
// of docs/language-design.md).
//
// So this half has no squares to carry: the step squares z in df64 and the refresh
// squares its narrowed words in f32, two different values, neither computed twice a
// trip. Carrying the df64 squares in variables, with m2 narrowed from them, costs more
// than it saves: a df64 value read from a variable is renormalized (a df64_add with 0,
// `renormForCancel` in fp64-lower.ts) before it feeds the step's cancelling subtraction,
// and the two df64_add that adds a trip outweigh the two f32 multiplies it removes, 62
// f32 operations a trip more on the emitted WGSL.
//
// Near |z|² = 16 a pixel can escape one step earlier or later than the df64 test had
// it, and the smooth colouring absorbs the step: `sn` subtracts log₂ log₂ |z|², which
// rises by about one as |z|² squares past the threshold and cancels the extra count.
// Measured on the GPU-like evaluator over 256×256 samples a half at spans of 1e-4,
// 1e-7, 1e-10 and 1e-13, no pixel's count moved, `sn` moved by 7.6e-6 at most on an
// escaped pixel, and the colour by 1.2e-4 of an 8-bit step; the closest any test came
// to 16 was 6.3e-6 relative, about 50 f32 ulps. Bisecting 560 count boundaries at 1e-4
// down to adjacent f32 uv values finds the case: 3 of 10,080 samples there escape one
// step later, with `sn` moved by 0.027 and the colour by 0.11 of an 8-bit step. The
// double does not side with either test there: it counts with this one at the first
// (`fp64-twins.test.ts` samples it) and with the df64 test at the other two.
const zx = Var(U.field.center.x.add(toF64(dx)));
const zy = Var(U.field.center.y.add(toF64(dy)));
const hx0 = Let(toF32(zx));
const hy0 = Let(toF32(zy));
m2.assign(hx0.mul(hx0).add(hy0.mul(hy0)));
Loop(
u32(0),
(j) => j.lt(u32(ITER)),
() => {
If(m2.gt(16.0), () => {
Break();
});
const nzx = Let(zx.mul(zx).sub(zy.mul(zy)).add(C_RE));
zy.assign(zx.mul(zy).mul(2.0).add(C_IM));
zx.assign(nzx);
it.assign(it.add(1.0));
const hx = Let(toF32(zx));
const hy = Let(toF32(zy));
m2.assign(hx.mul(hx).add(hy.mul(hy)));
},
);
});
// Smooth escape time (same log₂ log₂ treatment as fp64-mandelbrot.ts)
// through a cool cosine palette; interior stays black.
const sn = Let(it.sub(log2(max(log2(max(m2, 1.0001)), 0.0001))).add(1.0));
const inside = Let(step(f32(ITER).sub(0.5), it));
const s = Let(sn.div(ITER));
const ph = vec3(0.0, 0.25, 0.6);
const rgb = vec3(0.5)
.add(cos(ph.add(s.mul(5.5)).add(2.2)).mul(0.5))
.mul(mix(f32(0.35), f32(1.0), s))
.mul(f32(1).sub(inside));
return vec4(rgb, f32(1));
},
{ stage: 'fragment', retAttr: '@location(0)' },
);
// `_fp64` guard lands at (group 0, binding 1) automatically.
const fp64JuliaModule = module({
funcs: [vs, fsJulia],
uses: [U, VsOut],
});
export const fp64Julia: ShaderExample = {
id: 'fp64-julia',
title: 'fp64 Julia set',
blurb:
'The Julia-set face of the double-float technique: the seed c is fixed and the PIXEL becomes z₀, so precision lives entirely in the starting coordinate. The camera parks on a repelling fixed point — a point that is ON the Julia set at every scale — and dives: the plain-f32 left half collapses flat past a ~1e-7 span while the emulated-double right half keeps spiralling to the df64 floor. Drag to pan, wheel to zoom, flip the fp64 toggle to collapse the right half in place.',
category: 'generic',
file: 'fp64-julia.ts',
module: fp64JuliaModule,
renderable: true,
splitLabels: ['f32', 'f64 (emulated)'],
controls: {
center: {
kind: 'pan2d',
value: [CENTER_X, CENTER_Y],
zoomExpField: 'zoom_exp',
unitsPerWidth: 2,
},
resolution: { kind: 'resolution' },
zoom_exp: {
kind: 'slider',
label: 'Zoom 10^-x',
min: 0,
max: 16,
step: 0.05,
value: 4,
wheel: true,
},
fp64: { kind: 'toggle', label: 'fp64 emulation', value: true },
},
};
struct Uniforms {
@align(16) center: DF64Vec2,
resolution: vec2<f32>,
zoom_exp: f32,
fp64: f32,
}
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
}
struct DF64Vec2 {
hi: vec2<f32>,
lo: vec2<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var _fp64: texture_2d<f32>;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0);
let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0);
return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));
}
@fragment
fn fs_julia(vo: VsOut) -> @location(0) vec4<f32> {
let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x;
let _licm0 = vec2<f32>(-0.800000011920929, 1.1920929132713809e-8);
let _licm1 = vec2<f32>(0.15600000321865082, -3.218650901359865e-9);
let _v0 = pow(10.0, (-u.zoom_exp));
let _v1 = (vo.uv.x * 2.0);
let _cse0 = (vo.uv.x < 0.5);
let _v2 = (_v1 - select(1.0, 0.0, _cse0));
let _v3 = ((_v2 - 0.5) * _v0);
let _v4 = (((vo.uv.y - 0.5) * _v0) * ((u.resolution.y / u.resolution.x) * 2.0));
var _v5: f32 = 0.0;
var _v6: f32 = 0.0;
let _cse1 = vec2<f32>(u.center.hi.x, u.center.lo.x);
let _cse2 = vec2<f32>(u.center.hi.y, u.center.lo.y);
if ((_cse0 || (u.fp64 < 0.5))) {
var _v7: f32 = (df64_narrow(_cse1) + _v3);
var _v8: f32 = (df64_narrow(_cse2) + _v4);
var _v9: f32 = (_v7 * _v7);
var _v10: f32 = (_v8 * _v8);
_v6 = (_v9 + _v10);
for (var _v11: u32 = 0u; (_v11 < 128u); _v11 = (_v11 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
let _v12 = ((_v9 - _v10) + -0.8);
_v8 = (((_v7 * _v8) * 2.0) + 0.156);
_v7 = _v12;
_v5 = (_v5 + 1.0);
_v9 = (_v7 * _v7);
_v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
}
} else {
var _v13: vec2<f32> = df64_add(_cse1, vec2<f32>(_v3, 0.0), _fp64_g);
var _v14: vec2<f32> = df64_add(_cse2, vec2<f32>(_v4, 0.0), _fp64_g);
let _v15 = df64_narrow(_v13);
let _v16 = df64_narrow(_v14);
_v6 = ((_v15 * _v15) + (_v16 * _v16));
for (var _v17: u32 = 0u; (_v17 < 128u); _v17 = (_v17 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
let _v18 = df64_add(df64_sub(df64_sqr(_v13, _fp64_g), df64_sqr(_v14, _fp64_g), _fp64_g), _licm0, _fp64_g);
_v14 = df64_add((df64_mul(_v13, _v14, _fp64_g) * 2.0), _licm1, _fp64_g);
_v13 = _v18;
_v5 = (_v5 + 1.0);
let _v19 = df64_narrow(_v13);
let _v20 = df64_narrow(_v14);
_v6 = ((_v19 * _v19) + (_v20 * _v20));
}
}
let _v21 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0);
let _v22 = step(127.5, _v5);
let _v23 = (_v21 * 0.0078125);
return vec4<f32>((((vec3<f32>(0.5) + (cos(((vec3<f32>(0.0, 0.25, 0.6) + (_v23 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v23)) * (1.0 - _v22)), 1.0);
}
fn df64_twoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a + b);
let _v1 = (((_v0 * _fp64_g) - a) * _fp64_g);
let _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1));
return vec2<f32>(_v0, _v2);
}
fn df64_quickTwoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = ((a + b) * _fp64_g);
let _v1 = (b - ((_v0 - a) * _fp64_g));
return vec2<f32>(_v0, _v1);
}
fn df64_split(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * (_fp64_g * 4097.0));
let _v1 = ((_v0 * _fp64_g) - (_v0 - a));
let _v2 = ((a * _fp64_g) - _v1);
return vec2<f32>(_v1, _v2);
}
fn df64_twoProd(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * b);
let _v1 = df64_split(a, _fp64_g);
let _v2 = df64_split(b, _fp64_g);
let _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y));
return vec2<f32>(_v0, _v3);
}
fn df64_twoSqr(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * a);
let _v1 = df64_split(a, _fp64_g);
let _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2<f32>(_v0, _v2);
}
fn df64_add(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoSum(a.x, b.x, _fp64_g);
let _v1 = df64_twoSum(a.y, b.y, _fp64_g);
_v0.y = (_v0.y + _v1.x);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + _v1.y);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
return _v0;
}
fn df64_sub(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
return df64_add(a, (-b), _fp64_g);
}
fn df64_mul(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
fn df64_sqr(a: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
fn df64_narrow(a: vec2<f32>) -> f32 {
return (a.x + a.y);
}
#version 300 es
precision highp float;
precision highp int;
out vec2 uv;
void main() {
uint vi = uint(gl_VertexID);
float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0);
float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0);
gl_Position = vec4(_cse0, _cse1, 0.0, 1.0);
uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));
}
#version 300 es
precision highp float;
precision highp int;
struct DF64Vec2 {
vec2 hi;
vec2 lo;
};
layout(std140) uniform Uniforms {
DF64Vec2 center;
vec2 resolution;
float zoom_exp;
float fp64;
} u;
uniform highp sampler2D _fp64;
vec2 df64_twoSum(float a, float b, float _fp64_g) {
float _v0 = (a + b);
float _v1 = (((_v0 * _fp64_g) - a) * _fp64_g);
float _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1));
return vec2(_v0, _v2);
}
vec2 df64_quickTwoSum(float a, float b, float _fp64_g) {
float _v0 = ((a + b) * _fp64_g);
float _v1 = (b - ((_v0 - a) * _fp64_g));
return vec2(_v0, _v1);
}
vec2 df64_split(float a, float _fp64_g) {
float _v0 = (a * (_fp64_g * 4097.0));
float _v1 = ((_v0 * _fp64_g) - (_v0 - a));
float _v2 = ((a * _fp64_g) - _v1);
return vec2(_v1, _v2);
}
vec2 df64_twoProd(float a, float b, float _fp64_g) {
float _v0 = (a * b);
vec2 _v1 = df64_split(a, _fp64_g);
vec2 _v2 = df64_split(b, _fp64_g);
float _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y));
return vec2(_v0, _v3);
}
vec2 df64_twoSqr(float a, float _fp64_g) {
float _v0 = (a * a);
vec2 _v1 = df64_split(a, _fp64_g);
float _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2(_v0, _v2);
}
vec2 df64_add(vec2 a, vec2 b, float _fp64_g) {
vec2 _v0 = df64_twoSum(a.x, b.x, _fp64_g);
vec2 _v1 = df64_twoSum(a.y, b.y, _fp64_g);
_v0.y = (_v0.y + _v1.x);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + _v1.y);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
return _v0;
}
vec2 df64_sub(vec2 a, vec2 b, float _fp64_g) {
return df64_add(a, (-b), _fp64_g);
}
vec2 df64_mul(vec2 a, vec2 b, float _fp64_g) {
vec2 _v0 = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
vec2 df64_sqr(vec2 a, float _fp64_g) {
vec2 _v0 = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
float df64_narrow(vec2 a) {
return (a.x + a.y);
}
in vec2 uv;
layout(location = 0) out vec4 _ret;
void main() {
float _fp64_g = texelFetch(_fp64, ivec2(0, 0), 0).x;
vec2 _licm0 = vec2(-0.800000011920929, 1.1920929132713809e-8);
vec2 _licm1 = vec2(0.15600000321865082, -3.218650901359865e-9);
float _v0 = pow(10.0, (-u.zoom_exp));
float _v1 = (uv.x * 2.0);
bool _cse0 = (uv.x < 0.5);
float _v2 = (_v1 - (_cse0 ? 0.0 : 1.0));
float _v3 = ((_v2 - 0.5) * _v0);
float _v4 = (((uv.y - 0.5) * _v0) * ((u.resolution.y / u.resolution.x) * 2.0));
float _v5 = 0.0;
float _v6 = 0.0;
vec2 _cse1 = vec2(u.center.hi.x, u.center.lo.x);
vec2 _cse2 = vec2(u.center.hi.y, u.center.lo.y);
if ((_cse0 || (u.fp64 < 0.5))) {
float _v7 = (df64_narrow(_cse1) + _v3);
float _v8 = (df64_narrow(_cse2) + _v4);
float _v9 = (_v7 * _v7);
float _v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
for (uint _v11 = 0u; (_v11 < 128u); _v11 = (_v11 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
float _v12 = ((_v9 - _v10) + -0.8);
_v8 = (((_v7 * _v8) * 2.0) + 0.156);
_v7 = _v12;
_v5 = (_v5 + 1.0);
_v9 = (_v7 * _v7);
_v10 = (_v8 * _v8);
_v6 = (_v9 + _v10);
}
} else {
vec2 _v13 = df64_add(_cse1, vec2(_v3, 0.0), _fp64_g);
vec2 _v14 = df64_add(_cse2, vec2(_v4, 0.0), _fp64_g);
float _v15 = df64_narrow(_v13);
float _v16 = df64_narrow(_v14);
_v6 = ((_v15 * _v15) + (_v16 * _v16));
for (uint _v17 = 0u; (_v17 < 128u); _v17 = (_v17 + 1u)) {
if ((_v6 > 16.0)) {
break;
}
vec2 _v18 = df64_add(df64_sub(df64_sqr(_v13, _fp64_g), df64_sqr(_v14, _fp64_g), _fp64_g), _licm0, _fp64_g);
_v14 = df64_add((df64_mul(_v13, _v14, _fp64_g) * 2.0), _licm1, _fp64_g);
_v13 = _v18;
_v5 = (_v5 + 1.0);
float _v19 = df64_narrow(_v13);
float _v20 = df64_narrow(_v14);
_v6 = ((_v19 * _v19) + (_v20 * _v20));
}
}
float _v21 = ((_v5 - log2(max(log2(max(_v6, 1.0001)), 0.0001))) + 1.0);
float _v22 = step(127.5, _v5);
float _v23 = (_v21 * 0.0078125);
_ret = vec4((((vec3(0.5) + (cos(((vec3(0.0, 0.25, 0.6) + (_v23 * 5.5)) + 2.2)) * 0.5)) * mix(0.35, 1.0, _v23)) * (1.0 - _v22)), 1.0);
}

이 예제를 움직이는 컨트롤에 페이지가 넣을 값이 없어서, 이 페이지에는 그림이 없습니다.

WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).

이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.

GitHub의 파일

이 페이지 편집 문제 보고