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