34ac1bc58c129b15e525f36e08f1321ae37f6ae9
[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 -depth 8 -alpha set -alpha extract "$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" ] && [ x"$offsetmapping_match8" != x"firststagedone" ]; then
93                         if [ -f "../${2}_norm.tga" ] || [ -f "../${2}_norm.png" ] || [ -f "../${2}_norm.jpg" ]; 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 "(WW) shader $1 uses broken normalmap ${2}_norm.tga (add dpoffsetmapping none)"
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 "(WW) shader $1 uses broken normalmap ${2}_norm.tga, maybe use dpoffsetmapping none?"
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 "(WW) 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_alphagen=none
276         ss_map=
277         while read L A1 Aother; do
278                 case "`echo "$L" | tr A-Z a-z`" in
279                         blendfunc)
280                                 ss_blendfunc=`echo $A1 $Aother | tr A-Z a-z`
281                                 ;;
282                         alphafunc)
283                                 ss_alphafunc=`echo $A1 | tr A-Z a-z`
284                                 ;;
285                         alphagen)
286                                 ss_alphagen=`echo $A1 | tr A-Z a-z`
287                                 ;;
288                         map|clampmap)
289                                 case "$A1" in
290                                         '$lightmap')
291                                                 ;;
292                                         *)
293                                                 use_texture "$parsing_shader" "`normalize "$A1"`" map
294                                                 ss_map="`normalize "$A1"`"
295                                                 offsetmapping_match8=firststagedone
296                                                 ;;
297                                 esac
298                                 ;;
299                         animmap)
300                                 for X in $Aother; do
301                                         use_texture "$parsing_shader" "`normalize "$X"`" animmap
302                                 done
303                                 for X in $Aother; do
304                                         ss_map="`normalize "$X"`"
305                                         break
306                                 done
307                                 ;;
308                         '{')
309                                 echo "(EE) brace nesting error in $parsing_shader"; seterror
310                                 ;;
311                         '}')
312                                 break
313                                 ;;
314                         *)
315                                 ;;
316                 esac
317         done
318
319         if [ -n "$ss_map" ]; then
320                 if [ -z "$maintexture" ]; then
321                         maintexture=$ss_map
322                         mainblendfunc=$ss_blendfunc
323                         mainalphafunc=$ss_alphafunc
324                         mainalphagen=$ss_alphagen
325                 elif [ x"$ss_alphagen" = x"vertex" ] && ! $textureblending; then
326                         case "$mainblendfunc:$mainalphafunc:$ss_blendfunc:$ss_alphafunc" in
327                                 none:none:"gl_src_alpha gl_one_minus_src_alpha":none) textureblending=true ;;
328                                 none:none:filter:none) textureblending=true ;;
329                                 none:none:none:g*) textureblending=true ;;
330                                 "gl_one gl_zero":none:filter:none) textureblending=true ;;
331                                 "gl_one gl_zero":none:none:g*) textureblending=true ;;
332                                 *)
333                                         echo "(EE) texture blending requires first stage to have no blendfunc/alphatest, and requires second stage to be blendfunc filter"; seterror
334                                         ;;
335                         esac
336                 else
337                         echo "(EE) multistage shader without alphagen vertex, or using more than 2 stages, is not supported by DarkPlaces"; seterror
338                 fi
339         fi
340 }
341
342 parse_shader()
343 {
344         use_texture "$parsing_shader" "$parsing_shader" shader
345         offsetmapping_match8=
346         textureblending=false
347         maintexture=
348         while read L A1 Aother; do
349                 case "`echo "$L" | tr A-Z a-z`" in
350                         dpoffsetmapping)
351                                 set -- $Aother
352                                 if [ x"$A1" = x"none" ]; then
353                                         offsetmapping_match8=none
354                                 elif [ x"$A1" = x"off" ]; then
355                                         offsetmapping_match8=none
356                                 elif [ x"$A1" = x"disabled" ]; then
357                                         offsetmapping_match8=none
358                                 elif [ x"$2" = x"match8" ]; then
359                                         offsetmapping_match8=`echo "($3 + 0.5) / 1" | bc`
360                                 elif [ x"$2" = x"match16" ]; then
361                                         offsetmapping_match8=`echo "($3 / 257 + 0.5) / 1" | bc`
362                                 elif [ x"$2" = x"match" ]; then
363                                         offsetmapping_match8=`echo "($3 * 255 + 0.5) / 1" | bc`
364                                 elif [ x"$2" = x"bias" ]; then
365                                         offsetmapping_match8=`echo "((1 - $3) * 255 + 0.5) / 1" | bc`
366                                 else
367                                         offsetmapping_match8=default
368                                 fi
369                                 ;;
370                         qer_editorimage)
371                                 use_texture "$parsing_shader" "`normalize "$A1"`" editorimage
372                                 ;;
373                         skyparms)
374                                 use_texture "$parsing_shader" "${A1}_lf" sky
375                                 use_texture "$parsing_shader" "${A1}_rt" sky
376                                 use_texture "$parsing_shader" "${A1}_up" sky
377                                 use_texture "$parsing_shader" "${A1}_dn" sky
378                                 use_texture "$parsing_shader" "${A1}_ft" sky
379                                 use_texture "$parsing_shader" "${A1}_bk" sky
380                                 ;;
381                         '{')
382                                 parse_shaderstage
383                                 ;;
384                         '}')
385                                 break
386                                 ;;
387                         *)
388                                 ;;
389                 esac
390         done
391         if [ -n "$AUDIT_ALPHACHANNELS" ] && [ -n "$maintexture" ] && ! $textureblending; then
392                 getstats "../$maintexture.tga" || getstats "../$maintexture.png" || getstats "../$maintexture.jpg"
393                 case "$mainblendfunc" in
394                         *src_alpha*|*blend*)
395                                 # texture must have alpha
396                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
397                                         echo "(EE) $parsing_shader uses alpha-less texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"; seterror
398                                 fi
399                                 ;;
400                         add|"gl_one gl_one")
401                                 # texture must not have alpha (engine bug)
402                                 if [ x"$mainalphagen" != x"none" -o $min -lt 255 ]; then
403                                         echo "(EE) $parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"; seterror
404                                 fi
405                                 ;;
406                         *)
407                                 case "$mainalphafunc" in
408                                         g*)
409                                                 # texture must have alpha
410                                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
411                                                         echo "(EE) $parsing_shader uses alpha-less texture $maintexture with alphafunc $mainalphafunc and alphagen $mainalphagen"; seterror
412                                                 fi
413                                                 ;;
414                                         *)
415                                                 # texture should not have alpha (no bug if not)
416                                                 case "$mainalphagen" in
417                                                         none)
418                                                                 if [ $min -lt 255 ]; then
419                                                                         echo "(WW) $parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"
420                                                                 fi
421                                                                 ;;
422                                                         *)
423                                                                 # alphagen is set, but blendfunc has no use for it
424                                                                 echo "(EE) $parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"; seterror
425                                                                 ;;
426                                                 esac
427                                                 ;;
428                                 esac
429                                 ;;
430                 esac
431         fi
432 }
433
434 parse_shaderfile()
435 {
436         case "$1" in
437                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
438                 map_*)
439                         allowed_prefixes="textures/map_`echo "$1" | cut -d _ -f 2`_ textures/map_`echo "$1" | cut -d _ -f 2`/"
440                         forbidden_prefixes=
441                         ;;
442                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
443                 skies_*)
444                         allowed_prefixes="textures/skies/`echo "$1" | cut -d _ -f 2`: textures/skies/`echo "$1" | cut -d _ -f 2`_"
445                         forbidden_prefixes=
446                         ;;
447                 ## RULE: model_*.shader may define models/*
448                 model_*)
449                         allowed_prefixes="models/"
450                         forbidden_prefixes=
451                         ;;
452                 ## RULE: any other FOO.shader may define textures/FOO/*
453                 *)
454                         allowed_prefixes="textures/$1/"
455                         forbidden_prefixes="textures/skies/ textures/map_ models/"
456                         ;;
457         esac
458         while read L; do
459                 case "$L" in
460                         */*)
461                                 parsing_shader="`normalize "$L"`"
462                                 if [ x"$L" != x"$parsing_shader" ]; then
463                                         echo "(WW) normalized shader name $L to $parsing_shader"
464                                 fi
465                                 ;;
466                         '{')
467                                 parse_shader
468                                 ;;
469                         *)
470                                 ;;
471                 esac
472         done
473 }
474
475 strip_comments()
476 {
477         sed 's,//.*,,g; s,\r, ,g; s,\t, ,g; s,  *, ,g; s, $,,; s,^ ,,; /^$/ d'
478 }
479
480 t=`mktemp || echo ".temp"`
481 for X in *.shader; do
482         strip_comments < "$X" > "$t"
483         parse_shaderfile "${X%.shader}" < "$t"
484 done
485 rm -f "$t"
486
487 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`
488 textures_used=`echo "${textures_used#$LF}" | sort -u`
489
490 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
491         case "$L" in
492                 textures/radiant/*)
493                         ;;
494                 textures/map_*/*)
495                         ;;
496                 *)
497                         echo "(EE) texture $L is not referenced by any shader"; seterror
498                         ;;
499         esac
500 done
501
502 $status