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