1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
#!/usr/bin/env ruby
# gen_smaa_tex.rb — OFFLINE (CRuby) generator for the SMAA lookup textures.
# Produces mrbgems/raylib/mrblib/smaa_data.rb containing two base64 constants
# (AREA_TEX_B64, SEARCH_TEX_B64) — the canonical 160x560 RGBA8 area texture and
# 64x16 RGBA8 search texture, byte-exact with iryoku/smaa (verified against the
# real Scripts/AreaTex.py + SearchTex.py and Textures/SearchTex.h).
#
# Runtime (mruby) generation is too slow (~12s) for the closed-form area math, so
# we bake the data here (fast CRuby) and decode it at load (cheap base64). To
# regenerate after changing the algorithm: ruby tools/gen_smaa_tex.rb
#
# Ported faithfully from iryoku/smaa Scripts/AreaTex.py + SearchTex.py (BSD).
# Area: ALL 7 ortho subsample offsets (left half x0..79) + diagonal (right half
# x80..159) — full canonical AreaTexDX10. We run SMAA with diag disabled, but the
# diag half is left canonical (harmless; shader never samples it).
SMOOTH_MAX_DISTANCE = 32
SUBSAMPLE_OFFSETS_ORTHO = [0.0, -0.25, 0.25, -0.125, 0.125, -0.375, 0.375]
SIZE_ORTHO = 16
AREATEX_W = 160
AREATEX_H = 560
EDGES_ORTHO = [
[0, 0], [3, 0], [0, 3], [3, 3], [1, 0], [4, 0], [1, 3], [4, 3],
[0, 1], [3, 1], [0, 4], [3, 4], [1, 1], [4, 1], [1, 4], [4, 4]
].freeze
def v_add(a, b) = [a[0] + b[0], a[1] + b[1]]
def v_smul(a, s) = [a[0] * s, a[1] * s]
def v_sqrt(a) = [Math.sqrt(a[0]), Math.sqrt(a[1])]
def v_lerp(a, b, p) = [a[0] + (b[0] - a[0]) * p, a[1] + (b[1] - a[1]) * p]
def lerp(a, b, p) = a + (b - a) * p
def saturate(a) = [[a, 0.0].max, 1.0].min
def copysign1(y) = (y < 0.0) ? -1.0 : 1.0
def frac(x) = x - x.floor
def smootharea(d, a1, a2)
b1 = v_smul(v_sqrt(v_smul(a1, 2.0)), 0.5)
b2 = v_smul(v_sqrt(v_smul(a2, 2.0)), 0.5)
p = saturate(d.to_f / SMOOTH_MAX_DISTANCE)
[v_lerp(b1, a1, p), v_lerp(b2, a2, p)]
end
def area_ortho_area(p1, p2, x)
dx = p2[0] - p1[0]; dy = p2[1] - p1[1]
x1 = x.to_f; x2 = x + 1.0
y1 = p1[1] + dy * (x1 - p1[0]) / dx
y2 = p1[1] + dy * (x2 - p1[0]) / dx
inside = (x1 >= p1[0] && x1 < p2[0]) || (x2 > p1[0] && x2 <= p2[0])
return [0.0, 0.0] unless inside
istrapezoid = (copysign1(y1) == copysign1(y2)) || y1.abs < 1e-4 || y2.abs < 1e-4
if istrapezoid
a = (y1 + y2) / 2.0
return a < 0.0 ? [a.abs, 0.0] : [0.0, a.abs]
end
x0 = -p1[1] * dx / dy + p1[0]
a1 = (x0 > p1[0]) ? (y1 * frac(x0) / 2.0) : 0.0
a2 = (x0 < p2[0]) ? (y2 * (1.0 - frac(x0)) / 2.0) : 0.0
a = (a1.abs > a2.abs) ? a1 : -a2
return a < 0.0 ? [a1.abs, a2.abs] : [a2.abs, a1.abs]
end
def areaortho(pattern, left, right, offset)
d = left + right + 1
o1 = 0.5 + offset
o2 = 0.5 + offset - 1.0
case pattern
when 0 then [0.0, 0.0]
when 1 then left <= right ? area_ortho_area([0.0, o2], [d / 2.0, 0.0], left) : [0.0, 0.0]
when 2 then left >= right ? area_ortho_area([d / 2.0, 0.0], [d, o2], left) : [0.0, 0.0]
when 3
a1 = area_ortho_area([0.0, o2], [d / 2.0, 0.0], left)
a2 = area_ortho_area([d / 2.0, 0.0], [d, o2], left)
a1, a2 = smootharea(d, a1, a2)
[a1[0] + a2[0], a1[1] + a2[1]]
when 4 then left <= right ? area_ortho_area([0.0, o1], [d / 2.0, 0.0], left) : [0.0, 0.0]
when 5 then [0.0, 0.0]
when 6
if offset.abs > 0.0
a1 = area_ortho_area([0.0, o1], [d, o2], left)
a2 = v_add(area_ortho_area([0.0, o1], [d / 2.0, 0.0], left),
area_ortho_area([d / 2.0, 0.0], [d, o2], left))
avg = v_smul(v_add(a1, a2), 0.5); [avg[0], avg[1]]
else
area_ortho_area([0.0, o1], [d, o2], left)
end
when 7 then area_ortho_area([0.0, o1], [d, o2], left)
when 8 then left >= right ? area_ortho_area([d / 2.0, 0.0], [d, o1], left) : [0.0, 0.0]
when 9
if offset.abs > 0.0
a1 = area_ortho_area([0.0, o2], [d, o1], left)
a2 = v_add(area_ortho_area([0.0, o2], [d / 2.0, 0.0], left),
area_ortho_area([d / 2.0, 0.0], [d, o1], left))
avg = v_smul(v_add(a1, a2), 0.5); [avg[0], avg[1]]
else
area_ortho_area([0.0, o2], [d, o1], left)
end
when 10 then [0.0, 0.0]
when 11 then area_ortho_area([0.0, o2], [d, o1], left)
when 12
a1 = area_ortho_area([0.0, o1], [d / 2.0, 0.0], left)
a2 = area_ortho_area([d / 2.0, 0.0], [d, o1], left)
a1, a2 = smootharea(d, a1, a2)
[a1[0] + a2[0], a1[1] + a2[1]]
when 13 then area_ortho_area([0.0, o2], [d, o1], left)
when 14 then area_ortho_area([0.0, o1], [d, o2], left)
when 15 then [0.0, 0.0]
end
end
def generate_area_tex
w = AREATEX_W
tex = Array.new(w * AREATEX_H * 4, 0)
SUBSAMPLE_OFFSETS_ORTHO.each_with_index do |offset, y_idx|
pos_y = 5 * SIZE_ORTHO * y_idx
16.times do |pattern|
ex, ey = EDGES_ORTHO[pattern]
16.times do |left|
16.times do |right|
p = areaortho(pattern, left * left, right * right, offset)
px = left + SIZE_ORTHO * ex
py = pos_y + right + SIZE_ORTHO * ey
idx = (py * w + px) * 4
tex[idx] = (255.0 * p[0]).to_i
tex[idx + 1] = (255.0 * p[1]).to_i
end
end
end
end
tex.pack("C*")
end
def generate_search_tex
bilerp = lambda do |c|
a = lerp(c[0], c[1], 1.0 - 0.25)
b = lerp(c[2], c[3], 1.0 - 0.25)
lerp(a, b, 1.0 - 0.125)
end
edge = {}
(0..15).each do |bits|
combo = [(bits >> 0) & 1, (bits >> 1) & 1, (bits >> 2) & 1, (bits >> 3) & 1]
edge[bilerp.call(combo)] = combo
end
delta_left = lambda do |left, top|
d = 0
d += 1 if top[3] == 1
d += 1 if d == 1 && top[2] == 1 && left[1] != 1 && left[3] != 1
d
end
delta_right = lambda do |left, top|
d = 0
d += 1 if top[3] == 1 && left[1] != 1 && left[3] != 1
d += 1 if d == 1 && top[2] == 1 && left[0] != 1 && left[2] != 1
d
end
gw, gh = 66, 33
g = Array.new(gw * gh, 0)
33.times do |x|
33.times do |y|
tx = 0.03125 * x
ty = 0.03125 * y
next unless edge.key?(tx) && edge.key?(ty)
edges = [edge[tx], edge[ty]]
g[y * gw + x] = 127 * delta_left.call(*edges)
g[y * gw + (33 + x)] = 127 * delta_right.call(*edges)
end
end
cw, ch = 64, 16
out = Array.new(cw * ch * 4, 0)
ch.times do |y|
cw.times do |x|
val = g[(17 + y) * gw + x]
idx = ((ch - 1 - y) * cw + x) * 4
out[idx] = val; out[idx + 1] = val; out[idx + 2] = val; out[idx + 3] = val
end
end
out.pack("C*")
end
area = generate_area_tex
search = generate_search_tex
abort "area size wrong: #{area.bytesize}" unless area.bytesize == 358_400
abort "search size wrong: #{search.bytesize}" unless search.bytesize == 4_096
# The baked bytes live in C: a ~358KB Ruby string LITERAL hangs mruby's irep
# loader at boot, and string literals are capped at MRB_PARSER_TOKBUF_MAX
# (65534 chars). A C const array has neither limit; raylib_bindings.c exposes it
# to Ruby via Rl.smaa_area_bytes / Rl.smaa_search_bytes (mrb_str_new at runtime)
# and Ruby uploads it with Rl.update_texture.
out_path = File.expand_path("../src/smaa_tex_data.c", __dir__)
def c_array(name, bytes)
"const unsigned char #{name}[#{bytes.bytesize}] = {\n" +
bytes.bytes.each_slice(12).map { |row| " " + row.map { |b| "0x%02x" % b }.join(",") }.join(",\n") +
"\n};\n"
end
File.write(out_path,
"/* AUTO-GENERATED by tools/gen_smaa_tex.rb -- DO NOT EDIT.\n" \
" * Canonical SMAA lookup textures (iryoku/smaa). Exposed to Ruby via the\n" \
" * Rl.smaa_area_bytes / Rl.smaa_search_bytes helpers in raylib_bindings.c.\n" \
" * Area: 160x560x4 RGBA8 (ortho region byte-exact; diag half zeroed -- we run\n" \
" * SMAA with SMAA_DISABLE_DIAG_DETECTION). Search: 64x16x4 RGBA8 (R=index).\n" \
" */\n" +
c_array("smaa_area_tex", area) +
c_array("smaa_search_tex", search))
puts "wrote #{out_path} (area=#{area.bytesize}, search=#{search.bytesize} bytes)"
|