Began migration of code from rayquake for sdf parsing and glsl generation

This commit is contained in:
Hopeless Tyromancy 2026-04-20 05:23:39 -04:00
parent e210e0d7f2
commit 6859a56ad4
5 changed files with 546 additions and 45 deletions

View File

@ -4,7 +4,7 @@ SHADER_DIR := shaders
BUILD_DIR := build
CXX := g++
CXXFLAGS := -g -std=c++20 -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-unused-function
CXXFLAGS := -g -std=c++23 -Wall -Wextra -Wpedantic -Wno-missing-field-initializers -Wno-unused-function
LDFLAGS := -lSDL3
GLSLC := glslc

View File

@ -39,11 +39,51 @@ float f(mat3 kernel) {
}
float brightness(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}
float dither() {
if(length(texture(screen, uv).xyz) == 0.0)
return 0.0;
vec2 coord = uv * textureSize(screen, 0).xy;
int x = int(mod(coord.x, 4.0));
int y = int(mod(coord.y, 4.0));
int index = x + y * 4;
float threshold[16] = float[](
0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0,
12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0,
3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0,
15.0/16.0,7.0/16.0, 13.0/16.0, 5.0/16.0
);
vec3 color = texture(screen, uv).rgb;
return brightness(color) < threshold[index] ? 0.0 : 1.0;
}
void main() {
const mat3 sobelx = mat3(-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0);
const mat3 sobely = mat3(-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0);
out_color = texture(screen, uv) + 0.1 * ((t(uv.x, uv.y) > 0.01)?
vec4(abs(f(sobelx) + f(sobely)), 0.0, 0.0, 1.0) : vec4(0.0));
}
int time = int(fract(time_padded.x/25.0) * 30.0)/10;
switch(time) {
case 0:
out_color = texture(screen, uv);
break;
case 1:
out_color = vec4(brightness(texture(screen, uv).xyz) < 0.5? 0.0 : 1.0);
break;
case 2:
out_color = vec4(dither());
break;
}
out_color = out_color * 0.9 +
0.1*((t(uv.x, uv.y) > 0.01)?
vec4(abs(f(sobelx) + f(sobely)), 0.0, 0.0, 1.0) : vec4(0.0));
}

View File

@ -69,8 +69,6 @@ vec4 kernel(float max_depth) {
return march(cam_pos.xyz, cam_ray, max_depth);
}
float t(float x, float y) { return length(texture(screen, vec2(x, y)).xyz); }
float linearize_depth(vec2 uv) {
float d = texture(depth_tex, uv).r;
if (d >= 1.0) return 1000.0;
@ -80,31 +78,6 @@ float linearize_depth(vec2 uv) {
return -view_pos.z; // view space, positive distance
}
float brightness(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}
float dither() {
if(length(texture(screen, uv).xyz) == 0.0)
return 0.0;
vec2 coord = uv * textureSize(screen, 0).xy;
int x = int(mod(coord.x, 4.0));
int y = int(mod(coord.y, 4.0));
int index = x + y * 4;
float threshold[16] = float[](
0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0,
12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0,
3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0,
15.0/16.0,7.0/16.0, 13.0/16.0, 5.0/16.0
);
vec3 color = texture(screen, uv).rgb;
return brightness(color) < threshold[index] ? 0.0 : 1.0;
}
void main() {
float max_depth = linearize_depth(uv);
@ -112,18 +85,7 @@ void main() {
if (hit.w > 0.0) {
out_color = vec4(getnormal(hit.xyz), 1.0);
} else {
int t = int(fract(time_padded.x/5.0) * 30.0)/10;
switch(t) {
case 0:
out_color = texture(screen, uv);
break;
case 1:
out_color = vec4(brightness(texture(screen, uv).xyz) < 0.5? 0.0 : 1.0);
break;
case 2:
out_color = vec4(dither());
break;
}
out_color = texture(screen, uv);
}
}

View File

@ -248,7 +248,6 @@ void Renderer::add_gol_layer() {
}
}
gol(graph, frame);
size_t src = ((frame >> 8) & 1) ^ 1;
@ -259,7 +258,7 @@ void Renderer::add_gol_layer() {
if (graph[src][y * 256 + x]) {
float s = 0.01f;
vec3 p = vec3(x + s * sinf((float)frame), height, y + cosf((float)frame)) * s * 3.0f;
vec3 p = vec3(-x - s * sinf((float)frame), height, -y - cosf((float)frame)) * s * 3.0f;
vec3 v0 = p + vec3(-s, -s, 0);
vec3 v1 = p + vec3( s, -s, 0);
@ -279,6 +278,7 @@ void Renderer::add_gol_layer() {
}
}
}
if (vert_buff) SDL_ReleaseGPUBuffer(dev, vert_buff);
SDL_GPUBufferCreateInfo info {

499
src/sdf.cpp Normal file
View File

@ -0,0 +1,499 @@
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <memory>
#include <cassert>
#include <cmath>
#include <variant>
#include <cctype>
/* converted form of the rayquake sdf parsing code I wrote with Caz, which was in Zig */
struct Expr;
struct BinArgs { std::unique_ptr<Expr> lhs, rhs; };
struct SqrtArg { std::unique_ptr<Expr> arg; };
struct Constant { float val; };
struct Input { unsigned idx; };
using ExprVariant = std::variant<
Constant, Input,
struct Add, struct Sub, struct Mul, struct Div,
struct Min, struct Max, struct Sqrt
>;
struct Add { std::unique_ptr<Expr> lhs, rhs; };
struct Sub { std::unique_ptr<Expr> lhs, rhs; };
struct Mul { std::unique_ptr<Expr> lhs, rhs; };
struct Div { std::unique_ptr<Expr> lhs, rhs; };
struct Min { std::unique_ptr<Expr> lhs, rhs; };
struct Max { std::unique_ptr<Expr> lhs, rhs; };
struct Sqrt { std::unique_ptr<Expr> arg; };
struct Expr {
std::variant<Constant, Input, Add, Sub, Mul, Div, Min, Max, Sqrt> v;
template<typename T>
Expr(T &&t) : v(std::forward<T>(t)) {}
Expr(Expr &&) = default;
Expr &operator=(Expr &&) = default;
Expr(const Expr &) = delete;
Expr &operator=(const Expr &) = delete;
};
static std::unique_ptr<Expr> box(Expr e) {
return std::make_unique<Expr>(std::move(e));
}
Expr make_constant(float f) { return Expr{ Constant{ f } }; }
Expr make_input(unsigned i) { return Expr{ Input{ i } }; }
Expr expr_add(Expr l, Expr r) { return Expr{ Add{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_sub(Expr l, Expr r) { return Expr{ Sub{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_mul(Expr l, Expr r) { return Expr{ Mul{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_div(Expr l, Expr r) { return Expr{ Div{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_min(Expr l, Expr r) { return Expr{ Min{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_max(Expr l, Expr r) { return Expr{ Max{ box(std::move(l)), box(std::move(r)) } }; }
Expr expr_sqrt(Expr a) { return Expr{ Sqrt{ box(std::move(a)) } }; }
/* deep copy */
Expr clone(const Expr &e);
template<typename T>
std::unique_ptr<Expr> clone_box(const std::unique_ptr<T> &p) {
return box(clone(*p));
}
Expr clone(const Expr &e) {
return std::visit([](const auto &node) -> Expr {
using T = std::decay_t<decltype(node)>;
/* filter 0-ary */
if constexpr (std::is_same_v<T, Constant> || std::is_same_v<T, Input>) {
return Expr{ node };
} else if constexpr (std::is_same_v<T, Sqrt>) {
/* only unary */
return expr_sqrt(clone(*node.arg));
} else {
/* all binary nodes have lhs and rhs */
using R = T;
return Expr{ R{ box(clone(*node.lhs)), box(clone(*node.rhs)) } };
}
}, e.v);
}
float expr_eval(const Expr &e, const float *vals) {
return std::visit([&](const auto &node) -> float {
using T = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<T, Constant>) return node.val;
if constexpr (std::is_same_v<T, Input>) return vals[node.idx];
if constexpr (std::is_same_v<T, Add>) return expr_eval(*node.lhs, vals) + expr_eval(*node.rhs, vals);
if constexpr (std::is_same_v<T, Sub>) return expr_eval(*node.lhs, vals) - expr_eval(*node.rhs, vals);
if constexpr (std::is_same_v<T, Mul>) return expr_eval(*node.lhs, vals) * expr_eval(*node.rhs, vals);
if constexpr (std::is_same_v<T, Div>) return expr_eval(*node.lhs, vals) / expr_eval(*node.rhs, vals);
if constexpr (std::is_same_v<T, Min>) return std::min(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals));
if constexpr (std::is_same_v<T, Max>) return std::max(expr_eval(*node.lhs, vals), expr_eval(*node.rhs, vals));
if constexpr (std::is_same_v<T, Sqrt>) return std::sqrt(expr_eval(*node.arg, vals));
return 0.f;
}, e.v);
}
template<class... Ts>
struct overloads : Ts... { using Ts::operator()...; };
// symbolic differentiation
Expr expr_diff(const Expr &e, unsigned i) {
return std::visit([&](const auto &node) -> Expr {
using T = std::decay_t<decltype(node)>;
/* Rules for diff:
* - Variable/Input: same? 1 : 0
* - Constant: 0
* - Addition/Subtraction: a' +/- b'
* - Mul: f'g + g'f
* - Div: (fg' - gf') / g^2
* - Sqrt: (1/2)f' / sqrt(f)
* - Min/Max: Identity
*/
if constexpr (std::is_same_v<T, Input>)
return make_constant(node.idx == i ? 1.f : 0.f);
if constexpr (std::is_same_v<T, Constant>)
return make_constant(0.f);
if constexpr (std::is_same_v<T, Add>)
return expr_add(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i));
if constexpr (std::is_same_v<T, Sub>)
return expr_sub(expr_diff(*node.lhs, i), expr_diff(*node.rhs, i));
if constexpr (std::is_same_v<T, Mul>)
return expr_add(
expr_mul(expr_diff(*node.lhs, i), clone(*node.rhs)),
expr_mul(expr_diff(*node.rhs, i), clone(*node.lhs))
);
if constexpr (std::is_same_v<T, Div>)
return expr_div(
expr_add(
expr_mul(clone(*node.lhs), expr_diff(*node.rhs, i)),
expr_mul(expr_mul(clone(*node.rhs), expr_diff(*node.lhs, i)),
make_constant(-1.f))
),
expr_mul(clone(*node.rhs), clone(*node.rhs))
);
if constexpr (std::is_same_v<T, Sqrt>)
return expr_mul(
expr_diff(*node.arg, i),
expr_mul(make_constant(0.5f),
expr_div(make_constant(1.f), expr_sqrt(clone(*node.arg))))
);
if constexpr (std::is_same_v<T, Min> || std::is_same_v<T, Max>)
return clone(e);
return make_constant(0.f);
}, e.v);
}
struct Dual { float val, d; };
/* Autodiff */
Dual expr_auto_diff(const Expr &e, const Dual *pos) {
return std::visit([&](const auto &node) -> Dual {
using T = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<T, Input>) return pos[node.idx];
if constexpr (std::is_same_v<T, Constant>) return { node.val, 0.f };
if constexpr (std::is_same_v<T, Add>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return { l.val + r.val, l.d + r.d };
}
if constexpr (std::is_same_v<T, Sub>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return { l.val - r.val, l.d - r.d };
}
if constexpr (std::is_same_v<T, Mul>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return { l.val * r.val, l.val * r.d + l.d * r.val };
}
if constexpr (std::is_same_v<T, Div>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return { l.val / r.val, (l.d * r.val - l.val * r.d) / (r.val * r.val) };
}
if constexpr (std::is_same_v<T, Sqrt>) {
auto v = expr_auto_diff(*node.arg, pos);
float sv = std::sqrt(v.val);
return { sv, v.d / (2.f * sv) };
}
if constexpr (std::is_same_v<T, Min>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return l.val < r.val ? l : r;
}
if constexpr (std::is_same_v<T, Max>) {
auto l = expr_auto_diff(*node.lhs, pos), r = expr_auto_diff(*node.rhs, pos);
return l.val > r.val ? l : r;
}
return { 0.f, 0.f };
}, e.v);
}
void expr_ndiff(const Expr &e, const float *vals, float eps, float out[3]) {
for (int k = 0; k < 3; k++) {
float vp[3] = { vals[0], vals[1], vals[2] };
float vm[3] = { vals[0], vals[1], vals[2] };
vp[k] += eps; vm[k] -= eps;
out[k] = (expr_eval(e, vp) - expr_eval(e, vm)) / (2.f * eps);
}
}
void expr_print(const Expr &e, std::ostream &os = std::cout) {
const auto visitor = overloads {
[&](const Input& node) { os << "x" << node.idx; },
[&](const Constant& node) { os << node.val; },
[&](const Sqrt& node) { os << "(sqrt "; expr_print(*node.arg, os); os << ")"; },
[&](const auto& node) {
using T = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<T, Input>) { os << "x" << node.idx; return; }
if constexpr (std::is_same_v<T, Constant>) { os << node.val; return; }
if constexpr (std::is_same_v<T, Sqrt>) {
os << "(sqrt "; expr_print(*node.arg, os); os << ")"; return;
}
const char *op =
std::is_same_v<T, Add> ? "+" :
std::is_same_v<T, Sub> ? "-" :
std::is_same_v<T, Mul> ? "*" :
std::is_same_v<T, Div> ? "/" :
std::is_same_v<T, Min> ? "min" : "max";
os << "(" << op << " ";
expr_print(*node.lhs, os);
os << " ";
expr_print(*node.rhs, os);
os << ")";
}
};
std::visit(visitor, e.v);
}
static size_t gen_glsl_step(
const Expr &e,
std::unordered_map<const Expr *, size_t> &visited,
std::ostringstream &ss)
{
auto it = visited.find(&e);
if (it != visited.end()) return it->second;
size_t idx = visited.size();
visited[&e] = idx;
std::ostringstream tmp;
const auto visitor = overloads {
[&](const Constant& node) -> std::string {
tmp << "vec2(" << node.val << ", 0.0)";
return tmp.str();
},
[&](const Input& node)-> std::string {
switch (node.idx) {
case 0: return "vec2(v.x, vp.x)";
case 1: return "vec2(v.y, vp.y)";
case 2: return "vec2(v.z, vp.z)";
default: return "vec2(0.0, 0.0)";
}
},
[&](const Sqrt& node) -> std::string {
size_t a = gen_glsl_step(*node.arg, visited, ss);
tmp << "nsqrt(_" << a << ")";
return tmp.str();
},
[&](const auto& node) -> std::string {
/* binops */
size_t l = gen_glsl_step(*node.lhs, visited, ss);
size_t r = gen_glsl_step(*node.rhs, visited, ss);
using T = std::decay_t<decltype(node)>;
const char *fn =
std::is_same_v<T, Add> ? nullptr :
std::is_same_v<T, Sub> ? nullptr :
std::is_same_v<T, Mul> ? "nmul" :
std::is_same_v<T, Div> ? "ndiv" :
std::is_same_v<T, Min> ? "nmin" : "nmax";
if constexpr (std::is_same_v<T, Add>)
tmp << "(_" << l << " + _" << r << ")";
else if constexpr (std::is_same_v<T, Sub>)
tmp << "(_" << l << " - _" << r << ")";
else
tmp << fn << "(_" << l << ", _" << r << ")";
return tmp.str();
}
};
std::string rhs = std::visit(visitor, e.v);
ss << "vec2 _" << idx << " = " << rhs << ";\n";
return idx;
}
std::string gen_glsl(const Expr &e) {
std::unordered_map<const Expr *, size_t> visited;
std::ostringstream ss;
size_t last = gen_glsl_step(e, visited, ss);
ss << "return _" << last << ";";
return ss.str();
}
static void skip_ws(const std::string &s, size_t &i) {
while (i < s.size() && std::isspace((unsigned char)s[i])) i++;
}
static bool is_op_char(char c) {
return std::isalpha((unsigned char)c) || std::string("+-/*").find(c) != std::string::npos;
}
static std::string parse_op_str(const std::string &s, size_t &i) {
size_t start = i;
while (i < s.size() && is_op_char(s[i])) i++;
return s.substr(start, i - start);
}
static Expr parse_arg(const std::string &s, size_t &i,
std::unordered_map<std::string, unsigned> &vm);
static Expr parse_iden(const std::string &s, size_t &i,
std::unordered_map<std::string, unsigned> &vm) {
size_t start = i;
while (i < s.size() && std::isalnum((unsigned char)s[i])) i++;
std::string name = s.substr(start, i - start);
auto [it, inserted] = vm.emplace(name, (unsigned)vm.size());
return make_input(it->second);
}
static Expr parse_num(const std::string &s, size_t &i) {
bool neg = false;
if (i < s.size() && s[i] == '-') { neg = true; ++i; }
float num = 0.f;
int frac_digits = 0;
size_t start = i;
bool saw_digit = false;
while (i < s.size() && std::isdigit((unsigned char)s[i])) {
saw_digit = true;
num = num * 10.f + (s[i] - '0');
++i;
}
if (i < s.size() && s[i] == '.') {
++i;
while (i < s.size() && std::isdigit((unsigned char)s[i])) {
saw_digit = true;
num = num * 10.f + (s[i] - '0');
++i;
++frac_digits;
}
}
if (!saw_digit) {
return make_constant(0.0f);
}
if (frac_digits > 0) {
num /= std::pow(10.f, (float)frac_digits);
}
if (neg) num = -num;
return make_constant(num);
}
static Expr parse_expr(const std::string &s, size_t &i,
std::unordered_map<std::string, unsigned> &vm) {
skip_ws(s, i);
assert(s[i] == '('); i++;
skip_ws(s, i);
std::string op = parse_op_str(s, i);
Expr e = [&]() -> Expr {
if (op == "sqrt") return expr_sqrt(parse_arg(s, i, vm));
Expr lhs = parse_arg(s, i, vm);
Expr rhs = parse_arg(s, i, vm);
if (op == "+") return expr_add(std::move(lhs), std::move(rhs));
if (op == "-") return expr_sub(std::move(lhs), std::move(rhs));
if (op == "*") return expr_mul(std::move(lhs), std::move(rhs));
if (op == "/") return expr_div(std::move(lhs), std::move(rhs));
if (op == "min") return expr_min(std::move(lhs), std::move(rhs));
if (op == "max") return expr_max(std::move(lhs), std::move(rhs));
std::cerr << "Unknown op: " << op << "\n"; std::exit(1);
}();
skip_ws(s, i);
assert(s[i] == ')'); i++;
return e;
}
static Expr parse_arg(const std::string &s, size_t &i,
std::unordered_map<std::string, unsigned> &vm) {
skip_ws(s, i);
char c = s[i];
if (c == '(') return parse_expr(s, i, vm);
if (std::isalpha((unsigned char)c)) return parse_iden(s, i, vm);
if (std::isdigit((unsigned char)c) || c == '-' || c == '.')
return parse_num(s, i);
std::cerr << "Unexpected char '" << c << "'\n"; std::exit(1);
}
Expr expr_parse(const std::string &src) {
std::unordered_map<std::string, unsigned> vm;
size_t i = 0;
skip_ws(src, i);
return parse_expr(src, i, vm);
}
Expr sdf_length(Expr pos[3]) {
return expr_sqrt(
expr_add(
expr_mul(clone(pos[0]), clone(pos[0])),
expr_add(
expr_mul(clone(pos[1]), clone(pos[1])),
expr_mul(clone(pos[2]), clone(pos[2]))
)
)
);
}
Expr sdf_sphere(Expr pos[3], float radius) {
return expr_sub(sdf_length(pos), make_constant(radius));
}
Expr sdf_box(Expr p[3], float b[3]) {
Expr q[3] = {
expr_sub(expr_sqrt(expr_mul(clone(p[0]), clone(p[0]))), make_constant(b[0])),
expr_sub(expr_sqrt(expr_mul(clone(p[1]), clone(p[1]))), make_constant(b[1])),
expr_sub(expr_sqrt(expr_mul(clone(p[2]), clone(p[2]))), make_constant(b[2])),
};
Expr inner[3] = {
expr_max(clone(q[0]), make_constant(0.f)),
expr_max(clone(q[1]), make_constant(0.f)),
expr_max(clone(q[2]), make_constant(0.f)),
};
return expr_add(
sdf_length(inner),
expr_min(
expr_max(clone(q[0]), expr_max(clone(q[1]), clone(q[2]))),
make_constant(0.f)
)
);
}
/*
int main() {
Expr x = make_input(0), y = make_input(1), z = make_input(2);
Expr x2 = expr_mul(clone(x), clone(x));
Expr y2 = expr_mul(clone(y), clone(y));
Expr z2 = expr_mul(clone(z), clone(z));
Expr l = expr_sqrt(expr_add(expr_add(std::move(x2), std::move(y2)), std::move(z2)));
Expr dl = expr_diff(l, 0);
expr_print(l); std::cout << "\n";
expr_print(dl); std::cout << "\n";
float v[3] = { 0.5f, 0.3f, 0.2f };
std::cout << expr_eval(l, v) << "\n";
std::string src =
"(min"
" (- y 1)"
" (max"
" (max x (+ -9 (* -1 x)))"
" (* -1 (- (sqrt (+ (+ (* x x) (* y y)) (* z z))) 15))"
" ))";
Expr parsed = expr_parse(src);
expr_print(parsed);
std::cout << "\n";
std::cout << "\nGLSL:\n" << gen_glsl(l) << "\n";
return 0;
}
*/