4c318dc42456bd1e489055a42e10351b4e1c5775
[xonotic/xonotic-maps.pk3dir.git] / scripts / shader-audit.sh
1 #!/bin/sh
2
3 case "$0" in
4         */*)
5                 cd "${0%/*}"
6                 ;;
7 esac
8
9 pid=$$
10 status=true
11 trap 'status=false' USR1
12 seterror()
13 {
14         kill -USR1 "$pid"
15 }
16
17 LF="
18 "
19
20 normalize()
21 {
22         echo "$1" | sed 's/\.\(tga\|jpg\|png\)$//'
23 }
24
25 allowed_prefixes=
26 forbidden_prefixes=
27
28 getstats_e()
29 {
30         identify -verbose -alpha extract -depth 8 "$1" | {
31                 pix=0
32                 while read -r L V R; do
33                         case "$L" in
34                                 Geometry:)
35                                         V=${V%%[-+]*}
36                                         pix=$(( (${V%%x*} * ${V#*x}) / 2 ))
37                                         ;;
38                                 min:)
39                                         min=$V
40                                         ;;
41                                 max:)
42                                         max=$V
43                                         ;;
44                                 [0-9]*:)
45                                         pix=$(( $pix - ${L%:} ))
46                                         if [ $pix -le 0 ]; then
47                                                 median=`echo "$V $R" | cut -d , -f 1 | tr -cd 0-9`
48                                                 break
49                                         fi
50                         esac
51                 done
52                 cat >/dev/null
53                 echo "min=$min"
54                 echo "max=$max"
55                 echo "median=$median"
56         }
57 }
58 getstats()
59 {
60         eval `getstats_e "$1"`
61 }
62
63 textures_used=
64 # $1 = shader
65 # $2 = texture
66 # $3 = shader | map | animmap | editorimage | sky
67 use_texture()
68 {
69         # does texture exist?
70         if \
71                 [ -f "../$2.tga" ] || \
72                 [ -f "../$2.jpg" ] || \
73                 [ -f "../$2.png" ]; then
74                 :
75         else
76                 if [ "$3" = "shader" ]; then
77                         return
78                 else
79                         echo "(EE) shader $1 uses non-existing texture $2"; seterror
80                 fi
81         fi
82         textures_used="$textures_used$LF$2"
83
84         if [ x"$3" = x"map" ]; then
85                 lasttex=$2
86                 if [ -n "$AUDIT_OFFSETMAPPING" ]; then
87                         if [ -f "../${2}_norm.tga" ]; then
88                                 case "$offsetmapping_match8" in
89                                         '') # no dpoffsetmapping keyword
90                                                 getstats "../${2}_norm.tga"
91                                                 if [ "$min" -eq "$max" ]; then
92                                                         echo "(EE) shader $1 uses broken normalmap ${2}_norm.tga (add dpoffsetmapping none)"; seterror
93                                                 else
94                                                         echo "(EE) shader $1 uses ${2}_norm.tga but lacks median (add dpoffsetmapping - 1 match8 $median)"; seterror
95                                                 fi
96                                                 ;;
97                                         none) # offsetmapping turned off explicitly
98                                                 ;;
99                                         default) # offsetmapping keyword without bias
100                                                 getstats "../${2}_norm.tga"
101                                                 if [ "$min" -eq "$max" ]; then
102                                                         echo "(EE) shader $1 uses broken normalmap ${2}_norm.tga, maybe use dpoffsetmapping none?"; seterror
103                                                 else
104                                                         echo "(EE) shader $1 uses ${2}_norm.tga but lacks median (add to dpoffsetmapping: match8 $median)"; seterror
105                                                 fi
106                                                 ;;
107                                         *) # offsetmapping keyword with bias
108                                                 ;;
109                                 esac
110                         else
111                                 if [ -n "$offsetmapping_match8" ]; then
112                                         echo "(EE) shader $1 specifies offsetmapping, but texture $2 does not have a normalmap"
113                                 fi
114                         fi
115                 fi
116         fi
117
118         if [ -n "$allowed_prefixes" ]; then
119                 ok=false
120                 for p in $allowed_prefixes; do
121                         case "$1:" in
122                                 "$p"*)
123                                         ok=true
124                                         ;;
125                         esac
126                 done
127         else
128                 ok=true
129         fi
130         for p in $forbidden_prefixes; do
131                 case "$1:" in
132                         "$p"*)
133                                 ok=false
134                                 ;;
135                 esac
136         done
137         if ! $ok; then
138                 echo "(EE) shader $1 is not allowed in this shader file (allowed: $allowed_prefixes, forbidden: $forbidden_prefixes)"; seterror
139         fi
140
141         case "$3" in
142                 ## RULE: skyboxes must lie in env/
143                 sky)
144                         case "$2" in
145                                 env/*)
146                                         ;;
147                                 *)
148                                         echo "(EE) texture $2 of shader $1 is out of place, $3 textures must be in env/"; seterror
149                                         ;;
150                         esac
151                         ;;
152                 ## RULE: non-skyboxes must not lie in env/
153                 *)
154                         case "$2" in
155                                 env/*)
156                                         echo "(EE) texture $2 of shader $1 is out of place, $3 textures must not be in env/"; seterror
157                                         ;;
158                                 *)
159                                         ;;
160                         esac
161                         ;;
162         esac
163
164         # verify shader -> texture name
165         case "$1" in
166                 ## RULE: textures/FOOx/BAR-BAZ must use textures/FOOx/*/*, recommended textures/FOOx/BAR/BAZ
167                 textures/*x/*-*)
168                         pre=${1%%x/*}x
169                         suf=${1#*x/}
170                         suf="`echo "$suf" | sed 's,-,/,g'`"
171                         case "$2" in
172                                 "$pre"/*/*)
173                                         ;;
174                                 *)
175                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/$suf"; seterror
176                                         ;;
177                         esac
178                         ;;
179                 ## RULE: textures/FOOx/BAR must use textures/FOOx/*/*, recommended textures/FOOx/base/BAR
180                 textures/*x/*)
181                         pre=${1%%x/*}x
182                         suf=${1#*x/}
183                         case "$2" in
184                                 "$pre"/*/*)
185                                         ;;
186                                 *)
187                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/base/$suf"; seterror
188                                         ;;
189                         esac
190                         ;;
191                 ## RULE: textures/map_FOO[_/]* must use textures/map_FOO[_/]*
192                 textures/map_*/*)
193                         pre=${1%%/map_*}
194                         suf=${1#*/map_}
195                         map=${suf%%[_/]*}
196                         case "$2" in
197                                 "$pre"/map_$map[/_]*)
198                                         ;;
199                                 textures/map_*)
200                                         # protect one map's textures from the evil of other maps :P
201                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/map_$map/*"; seterror
202                                         ;;
203                                 *)
204                                         # using outside stuff is permitted
205                                         ;;
206                         esac
207                         ;;
208                 ## RULE: textures/common/FOO must use textures/common/FOO or textures/common/*/*
209                 textures/common/*)
210                         case "$2" in
211                                 "$1")
212                                         ;;
213                                 textures/common/*/*)
214                                         ;;
215                                 *)
216                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1 or textures/common/*/*"; seterror
217                                         ;;
218                         esac
219                         ;;
220                 ## RULE: textures/FOO/* must use textures/FOO/*, for FOO in decals, liquids_water, liquids_slime, liquids_lava
221                 textures/decals/*|textures/liquids_*/*|textures/effects_*/*|textures/screens/*|textures/logos/*)
222                         pre=`echo "$1" | cut -d / -f 1-2`
223                         case "$2" in
224                                 "$pre"/*)
225                                         # I _suppose_ this is fine, as tZork committed this pack
226                                         ;;
227                                 *)
228                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1"; seterror
229                                         ;;
230                         esac
231                         ;;
232                 ## RULE: textures/skies/FOO or textures/skies/FOO_BAR must use textures/skies/FOO respective textures/skies/FOO_BAR as preview image, and env/FOO[_/]* as skybox
233                 textures/skies/*)
234                         sky=${1#textures/skies/}
235                         sky=${sky%%_*}
236                         case "$2" in
237                                 textures/skies/$sky|textures/skies/$sky[_]*)
238                                         # typical place for preview image
239                                         ;;
240                                 env/$sky[/_]*)
241                                         # typical place for skybox
242                                         ;;
243                                 *)
244                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1"; seterror
245                                         ;;
246                         esac
247                         ;;
248                 ## RULE: models/* must use models/*
249                 models/*)
250                         case "$2" in
251                                 models/*)
252                                         ;;
253                                 *)
254                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1 or models/*"; seterror
255                                         ;;
256                         esac
257                         ;;
258                 *)
259                         echo "(EE) no shader name pattern for $1"; seterror
260                         ;;
261         esac
262 }
263
264 parsing_shader=
265 parse_shaderstage()
266 {
267         while read L A1 Aother; do
268                 case "`echo "$L" | tr A-Z a-z`" in
269                         map|clampmap)
270                                 case "$A1" in
271                                         '$lightmap')
272                                                 ;;
273                                         *)
274                                                 use_texture "$parsing_shader" "`normalize "$A1"`" map
275                                                 ;;
276                                 esac
277                                 ;;
278                         animmap)
279                                 for X in $Aother; do
280                                         use_texture "$parsing_shader" "`normalize "$X"`" animmap
281                                 done
282                                 ;;
283                         '{')
284                                 echo "(EE) brace nesting error in $parsing_shader"; seterror
285                                 ;;
286                         '}')
287                                 break
288                                 ;;
289                         *)
290                                 ;;
291                 esac
292         done
293 }
294
295 parse_shader()
296 {
297         use_texture "$parsing_shader" "$parsing_shader" shader
298         offsetmapping_match8=
299         while read L A1 Aother; do
300                 case "`echo "$L" | tr A-Z a-z`" in
301                         dpoffsetmapping)
302                                 set -- $Aother
303                                 if [ x"$A1" = x"none" ]; then
304                                         offsetmapping_match8=none
305                                 elif [ x"$A1" = x"off" ]; then
306                                         offsetmapping_match8=none
307                                 elif [ x"$A1" = x"disabled" ]; then
308                                         offsetmapping_match8=none
309                                 elif [ x"$2" = x"match8" ]; then
310                                         offsetmapping_match8=`echo "($3 + 0.5) / 1" | bc`
311                                 elif [ x"$2" = x"match16" ]; then
312                                         offsetmapping_match8=`echo "($3 / 257 + 0.5) / 1" | bc`
313                                 elif [ x"$2" = x"match" ]; then
314                                         offsetmapping_match8=`echo "($3 * 255 + 0.5) / 1" | bc`
315                                 elif [ x"$2" = x"bias" ]; then
316                                         offsetmapping_match8=`echo "((1 - $3) * 255 + 0.5) / 1" | bc`
317                                 else
318                                         offsetmapping_match8=default
319                                 fi
320                                 ;;
321                         qer_editorimage)
322                                 use_texture "$parsing_shader" "`normalize "$A1"`" editorimage
323                                 ;;
324                         skyparms)
325                                 use_texture "$parsing_shader" "${A1}_lf" sky
326                                 use_texture "$parsing_shader" "${A1}_rt" sky
327                                 use_texture "$parsing_shader" "${A1}_up" sky
328                                 use_texture "$parsing_shader" "${A1}_dn" sky
329                                 use_texture "$parsing_shader" "${A1}_ft" sky
330                                 use_texture "$parsing_shader" "${A1}_bk" sky
331                                 ;;
332                         '{')
333                                 parse_shaderstage
334                                 ;;
335                         '}')
336                                 break
337                                 ;;
338                         *)
339                                 ;;
340                 esac
341         done
342 }
343
344 parse_shaderfile()
345 {
346         case "$1" in
347                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
348                 map_*)
349                         allowed_prefixes="textures/map_`echo "$1" | cut -d _ -f 2`_ textures/map_`echo "$1" | cut -d _ -f 2`/"
350                         forbidden_prefixes=
351                         ;;
352                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
353                 skies_*)
354                         allowed_prefixes="textures/skies/`echo "$1" | cut -d _ -f 2`: textures/skies/`echo "$1" | cut -d _ -f 2`_"
355                         forbidden_prefixes=
356                         ;;
357                 ## RULE: model_*.shader may define models/*
358                 model_*)
359                         allowed_prefixes="models/"
360                         forbidden_prefixes=
361                         ;;
362                 ## RULE: any other FOO.shader may define textures/FOO/*
363                 *)
364                         allowed_prefixes="textures/$1/"
365                         forbidden_prefixes="textures/skies/ textures/map_ models/"
366                         ;;
367         esac
368         while read L; do
369                 case "$L" in
370                         */*)
371                                 parsing_shader="`normalize "$L"`"
372                                 if [ x"$L" != x"$parsing_shader" ]; then
373                                         echo "(WW) normalized shader name $L to $parsing_shader"
374                                 fi
375                                 ;;
376                         '{')
377                                 parse_shader
378                                 ;;
379                         *)
380                                 ;;
381                 esac
382         done
383 }
384
385 strip_comments()
386 {
387         sed 's,//.*,,g; s,\r, ,g; s,\t, ,g; s,  *, ,g; s, $,,; s,^ ,,; /^$/ d'
388 }
389
390 t=`mktemp || echo ".temp"`
391 for X in *.shader; do
392         strip_comments < "$X" > "$t"
393         parse_shaderfile "${X%.shader}" < "$t"
394 done
395 rm -f "$t"
396
397 textures_avail=`( cd ..; find textures/ -type f -not -name '*_norm.*' -not -name '*_glow.*' -not -name '*_gloss.*' -not -name '*_reflect.*' -not -name '*.xcf' ) | while IFS= read -r T; do normalize "$T"; done | sort -u`
398 textures_used=`echo "${textures_used#$LF}" | sort -u`
399
400 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
401         case "$L" in
402                 textures/radiant/*)
403                         ;;
404                 textures/map_*/*)
405                         ;;
406                 *)
407                         echo "(EE) texture $L is not referenced by any shader"; seterror
408                         ;;
409         esac
410 done
411
412 $status