Merge branch 'master' of git://de.git.xonotic.org/xonotic/xonotic-maps.pk3dir
[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                 echo "error=false"
57         }
58 }
59 getstats()
60 {
61         min=255
62         max=255
63         median=255
64         error=true
65         [ -f "$1" ] || return 1
66         eval `getstats_e "$1"`
67 }
68
69 textures_used=
70 # $1 = shader
71 # $2 = texture
72 # $3 = shader | map | animmap | editorimage | sky
73 use_texture()
74 {
75         # does texture exist?
76         if \
77                 [ -f "../$2.tga" ] || \
78                 [ -f "../$2.jpg" ] || \
79                 [ -f "../$2.png" ]; then
80                 :
81         else
82                 if [ "$3" = "shader" ]; then
83                         return
84                 else
85                         echo "(EE) shader $1 uses non-existing texture $2"; seterror
86                 fi
87         fi
88         textures_used="$textures_used$LF$2"
89
90         if [ x"$3" = x"map" ]; then
91                 lasttex=$2
92                 if [ -n "$AUDIT_ALPHACHANNELS" ]; then
93                         if [ -f "../${2}_norm.tga" ]; then
94                                 case "$offsetmapping_match8" in
95                                         '') # no dpoffsetmapping keyword
96                                                 getstats "../${2}_norm.tga" || getstats "../${2}_norm.png" || getstats "../${2}_norm.jpg"
97                                                 if [ "$min" -eq "$max" ]; then
98                                                         echo "(EE) shader $1 uses broken normalmap ${2}_norm.tga (add dpoffsetmapping none)"; seterror
99                                                 else
100                                                         echo "(EE) shader $1 uses ${2}_norm.tga but lacks median (add dpoffsetmapping - 1 match8 $median)"; seterror
101                                                 fi
102                                                 ;;
103                                         none) # offsetmapping turned off explicitly
104                                                 ;;
105                                         default) # offsetmapping keyword without bias
106                                                 getstats "../${2}_norm.tga"
107                                                 if [ "$min" -eq "$max" ]; then
108                                                         echo "(EE) shader $1 uses broken normalmap ${2}_norm.tga, maybe use dpoffsetmapping none?"; seterror
109                                                 else
110                                                         echo "(EE) shader $1 uses ${2}_norm.tga but lacks median (add to dpoffsetmapping: match8 $median)"; seterror
111                                                 fi
112                                                 ;;
113                                         *) # offsetmapping keyword with bias
114                                                 ;;
115                                 esac
116                         else
117                                 if [ -n "$offsetmapping_match8" ]; then
118                                         echo "(EE) shader $1 specifies offsetmapping, but texture $2 does not have a normalmap"
119                                 fi
120                         fi
121                 fi
122         fi
123
124         if [ -n "$allowed_prefixes" ]; then
125                 ok=false
126                 for p in $allowed_prefixes; do
127                         case "$1:" in
128                                 "$p"*)
129                                         ok=true
130                                         ;;
131                         esac
132                 done
133         else
134                 ok=true
135         fi
136         for p in $forbidden_prefixes; do
137                 case "$1:" in
138                         "$p"*)
139                                 ok=false
140                                 ;;
141                 esac
142         done
143         if ! $ok; then
144                 echo "(EE) shader $1 is not allowed in this shader file (allowed: $allowed_prefixes, forbidden: $forbidden_prefixes)"; seterror
145         fi
146
147         case "$3" in
148                 ## RULE: skyboxes must lie in env/
149                 sky)
150                         case "$2" in
151                                 env/*)
152                                         ;;
153                                 *)
154                                         echo "(EE) texture $2 of shader $1 is out of place, $3 textures must be in env/"; seterror
155                                         ;;
156                         esac
157                         ;;
158                 ## RULE: non-skyboxes must not lie in env/
159                 *)
160                         case "$2" in
161                                 env/*)
162                                         echo "(EE) texture $2 of shader $1 is out of place, $3 textures must not be in env/"; seterror
163                                         ;;
164                                 *)
165                                         ;;
166                         esac
167                         ;;
168         esac
169
170         # verify shader -> texture name
171         case "$1" in
172                 ## RULE: textures/FOOx/BAR-BAZ must use textures/FOOx/*/*, recommended textures/FOOx/BAR/BAZ
173                 textures/*x/*-*)
174                         pre=${1%%x/*}x
175                         suf=${1#*x/}
176                         suf="`echo "$suf" | sed 's,-,/,g'`"
177                         case "$2" in
178                                 "$pre"/*/*)
179                                         ;;
180                                 *)
181                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/$suf"; seterror
182                                         ;;
183                         esac
184                         ;;
185                 ## RULE: textures/FOOx/BAR must use textures/FOOx/*/*, recommended textures/FOOx/base/BAR
186                 textures/*x/*)
187                         pre=${1%%x/*}x
188                         suf=${1#*x/}
189                         case "$2" in
190                                 "$pre"/*/*)
191                                         ;;
192                                 *)
193                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/base/$suf"; seterror
194                                         ;;
195                         esac
196                         ;;
197                 ## RULE: textures/map_FOO[_/]* must use textures/map_FOO[_/]*
198                 textures/map_*/*)
199                         pre=${1%%/map_*}
200                         suf=${1#*/map_}
201                         map=${suf%%[_/]*}
202                         case "$2" in
203                                 "$pre"/map_$map[/_]*)
204                                         ;;
205                                 textures/map_*)
206                                         # protect one map's textures from the evil of other maps :P
207                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $pre/map_$map/*"; seterror
208                                         ;;
209                                 *)
210                                         # using outside stuff is permitted
211                                         ;;
212                         esac
213                         ;;
214                 ## RULE: textures/common/FOO must use textures/common/FOO or textures/common/*/*
215                 textures/common/*)
216                         case "$2" in
217                                 "$1")
218                                         ;;
219                                 textures/common/*/*)
220                                         ;;
221                                 *)
222                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1 or textures/common/*/*"; seterror
223                                         ;;
224                         esac
225                         ;;
226                 ## RULE: textures/FOO/* must use textures/FOO/*, for FOO in decals, liquids_water, liquids_slime, liquids_lava
227                 textures/decals/*|textures/liquids_*/*|textures/effects_*/*|textures/screens/*|textures/logos/*)
228                         pre=`echo "$1" | cut -d / -f 1-2`
229                         case "$2" in
230                                 "$pre"/*)
231                                         # I _suppose_ this is fine, as tZork committed this pack
232                                         ;;
233                                 *)
234                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1"; seterror
235                                         ;;
236                         esac
237                         ;;
238                 ## 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
239                 textures/skies/*)
240                         sky=${1#textures/skies/}
241                         sky=${sky%%_*}
242                         case "$2" in
243                                 textures/skies/$sky|textures/skies/$sky[_]*)
244                                         # typical place for preview image
245                                         ;;
246                                 env/$sky[/_]*)
247                                         # typical place for skybox
248                                         ;;
249                                 *)
250                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1"; seterror
251                                         ;;
252                         esac
253                         ;;
254                 ## RULE: models/* must use models/*
255                 models/*)
256                         case "$2" in
257                                 models/*)
258                                         ;;
259                                 *)
260                                         echo "(EE) texture $2 of shader $1 is out of place, recommended file name is $1 or models/*"; seterror
261                                         ;;
262                         esac
263                         ;;
264                 *)
265                         echo "(EE) no shader name pattern for $1"; seterror
266                         ;;
267         esac
268 }
269
270 parsing_shader=
271 parse_shaderstage()
272 {
273         ss_blendfunc=none
274         ss_alphafunc=none
275         ss_map=
276         while read L A1 Aother; do
277                 case "`echo "$L" | tr A-Z a-z`" in
278                         blendfunc)
279                                 ss_blendfunc=`echo $A1 $Aother | tr A-Z a-z`
280                                 ;;
281                         alphafunc)
282                                 ss_alphafunc=`echo $A1 | tr A-Z a-z`
283                                 ;;
284                         map|clampmap)
285                                 case "$A1" in
286                                         '$lightmap')
287                                                 ;;
288                                         *)
289                                                 use_texture "$parsing_shader" "`normalize "$A1"`" map
290                                                 ss_map="`normalize "$A1"`"
291                                                 ;;
292                                 esac
293                                 ;;
294                         animmap)
295                                 for X in $Aother; do
296                                         use_texture "$parsing_shader" "`normalize "$X"`" animmap
297                                 done
298                                 for X in $Aother; do
299                                         ss_map="`normalize "$X"`"
300                                         break
301                                 done
302                                 ;;
303                         '{')
304                                 echo "(EE) brace nesting error in $parsing_shader"; seterror
305                                 ;;
306                         '}')
307                                 break
308                                 ;;
309                         *)
310                                 ;;
311                 esac
312         done
313
314         if [ -n "$AUDIT_ALPHACHANNELS" ] && [ -n "$ss_map" ]; then
315                 getstats "../$ss_map.tga" || getstats "../$ss_map.png" || getstats "../$ss_map.jpg"
316                 case "$ss_blendfunc" in
317                         *src_alpha*|*blend*)
318                                 # texture must have alpha
319                                 if [ $min -eq 255 ]; then
320                                         echo "(EE) $parsing_shader uses alpha-less texture $ss_map with blendfunc $ss_blendfunc"; seterror
321                                 fi
322                                 ;;
323                         add|"gl_one gl_one")
324                                 # texture must not have alpha (engine bug)
325                                 if [ $min -lt 255 ]; then
326                                         echo "(EE) $parsing_shader uses alpha-using texture $ss_map with blendfunc $ss_blendfunc"; seterror
327                                 fi
328                                 ;;
329                         *)
330                                 case "$ss_alphafunc" in
331                                         g*)
332                                                 # texture must have alpha
333                                                 if [ $min -eq 255 ]; then
334                                                         echo "(EE) $parsing_shader uses alpha-less texture $ss_map with alphafunc $ss_alphafunc"; seterror
335                                                 fi
336                                                 ;;
337                                         *)
338                                                 # texture should not have alpha (no bug if not)
339                                                 if [ $min -lt 255 ]; then
340                                                         echo "(WW) $parsing_shader uses alpha-using texture $ss_map with blendfunc $ss_blendfunc and alphafunc $ss_alphafunc"
341                                                 fi
342                                                 ;;
343                                 esac
344                                 ;;
345                 esac
346         fi
347 }
348
349 parse_shader()
350 {
351         use_texture "$parsing_shader" "$parsing_shader" shader
352         offsetmapping_match8=
353         while read L A1 Aother; do
354                 case "`echo "$L" | tr A-Z a-z`" in
355                         dpoffsetmapping)
356                                 set -- $Aother
357                                 if [ x"$A1" = x"none" ]; then
358                                         offsetmapping_match8=none
359                                 elif [ x"$A1" = x"off" ]; then
360                                         offsetmapping_match8=none
361                                 elif [ x"$A1" = x"disabled" ]; then
362                                         offsetmapping_match8=none
363                                 elif [ x"$2" = x"match8" ]; then
364                                         offsetmapping_match8=`echo "($3 + 0.5) / 1" | bc`
365                                 elif [ x"$2" = x"match16" ]; then
366                                         offsetmapping_match8=`echo "($3 / 257 + 0.5) / 1" | bc`
367                                 elif [ x"$2" = x"match" ]; then
368                                         offsetmapping_match8=`echo "($3 * 255 + 0.5) / 1" | bc`
369                                 elif [ x"$2" = x"bias" ]; then
370                                         offsetmapping_match8=`echo "((1 - $3) * 255 + 0.5) / 1" | bc`
371                                 else
372                                         offsetmapping_match8=default
373                                 fi
374                                 ;;
375                         qer_editorimage)
376                                 use_texture "$parsing_shader" "`normalize "$A1"`" editorimage
377                                 ;;
378                         skyparms)
379                                 use_texture "$parsing_shader" "${A1}_lf" sky
380                                 use_texture "$parsing_shader" "${A1}_rt" sky
381                                 use_texture "$parsing_shader" "${A1}_up" sky
382                                 use_texture "$parsing_shader" "${A1}_dn" sky
383                                 use_texture "$parsing_shader" "${A1}_ft" sky
384                                 use_texture "$parsing_shader" "${A1}_bk" sky
385                                 ;;
386                         '{')
387                                 parse_shaderstage
388                                 ;;
389                         '}')
390                                 break
391                                 ;;
392                         *)
393                                 ;;
394                 esac
395         done
396 }
397
398 parse_shaderfile()
399 {
400         case "$1" in
401                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
402                 map_*)
403                         allowed_prefixes="textures/map_`echo "$1" | cut -d _ -f 2`_ textures/map_`echo "$1" | cut -d _ -f 2`/"
404                         forbidden_prefixes=
405                         ;;
406                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
407                 skies_*)
408                         allowed_prefixes="textures/skies/`echo "$1" | cut -d _ -f 2`: textures/skies/`echo "$1" | cut -d _ -f 2`_"
409                         forbidden_prefixes=
410                         ;;
411                 ## RULE: model_*.shader may define models/*
412                 model_*)
413                         allowed_prefixes="models/"
414                         forbidden_prefixes=
415                         ;;
416                 ## RULE: any other FOO.shader may define textures/FOO/*
417                 *)
418                         allowed_prefixes="textures/$1/"
419                         forbidden_prefixes="textures/skies/ textures/map_ models/"
420                         ;;
421         esac
422         while read L; do
423                 case "$L" in
424                         */*)
425                                 parsing_shader="`normalize "$L"`"
426                                 if [ x"$L" != x"$parsing_shader" ]; then
427                                         echo "(WW) normalized shader name $L to $parsing_shader"
428                                 fi
429                                 ;;
430                         '{')
431                                 parse_shader
432                                 ;;
433                         *)
434                                 ;;
435                 esac
436         done
437 }
438
439 strip_comments()
440 {
441         sed 's,//.*,,g; s,\r, ,g; s,\t, ,g; s,  *, ,g; s, $,,; s,^ ,,; /^$/ d'
442 }
443
444 t=`mktemp || echo ".temp"`
445 for X in *.shader; do
446         strip_comments < "$X" > "$t"
447         parse_shaderfile "${X%.shader}" < "$t"
448 done
449 rm -f "$t"
450
451 textures_avail=`( cd ..; find textures/ -type f -not -name '*.sh' -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`
452 textures_used=`echo "${textures_used#$LF}" | sort -u`
453
454 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
455         case "$L" in
456                 textures/radiant/*)
457                         ;;
458                 textures/map_*/*)
459                         ;;
460                 *)
461                         echo "(EE) texture $L is not referenced by any shader"; seterror
462                         ;;
463         esac
464 done
465
466 $status