summaryrefslogtreecommitdiffhomepage
path: root/mrbgems/raylib/tools/gen_smaa_tex.rb
blob: 8ca52f2fbe777c3b8d9c27a30933fa29e93c5a52 (plain)
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)"