Fix most of solarpanel.
[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, solarpanel
237                 textures/decals/*|textures/liquids_*/*|textures/effects_*/*|textures/screens/*|textures/logos/*|textures/alphamod/*|textures/solarpanel/*)
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, blend
346                                 none:none:blend:none) textureblending=true ;;
347                                 none:none:"gl_src_alpha gl_one_minus_src_alpha":none) textureblending=true ;;
348                                 "gl_one gl_zero":none:blend:none) textureblending=true ;;
349                                 "gl_one zl_zero":none:"gl_src_alpha gl_one_minus_src_alpha":none) textureblending=true ;;
350                                 # none, alphafunc
351                                 none:none:none:g*) textureblending=true ;;
352                                 none:none:"gl_one gl_zero":g*) textureblending=true ;;
353                                 "gl_one gl_zero":none:none:g*) textureblending=true ;;
354                                 "gl_one gl_zero":none:"gl_one gl_zero":g*) textureblending=true ;;
355                                 *)
356                                         err "$parsing_shader uses texture blending, but that requires first stage to have no blendfunc/alphatest, and requires second stage to be blendfunc filter"
357                                         ;;
358                         esac
359                 else
360                         err "$parsing_shader, a multistage shader without alphagen vertex, or using more than 2 stages, is not supported by DarkPlaces"
361                 fi
362         fi
363 }
364
365 parse_shader_pre()
366 {
367         use_texture "$parsing_shader" "$parsing_shader" shader
368         offsetmapping_match8=
369         textureblending=false
370         maintexture=
371         nowarn=false
372 }
373
374 parse_shader_line()
375 {
376         L=$1
377         A1=$2
378         Aother=$3
379         case "`echo "$L" | tr A-Z a-z`" in
380                 xon_nowarn)
381                         nowarn=true
382                         ;;
383                 dpoffsetmapping)
384                         set -- $Aother
385                         if [ x"$A1" = x"none" ]; then
386                                 offsetmapping_match8=none
387                         elif [ x"$A1" = x"off" ]; then
388                                 offsetmapping_match8=none
389                         elif [ x"$A1" = x"disabled" ]; then
390                                 offsetmapping_match8=none
391                         elif [ x"$2" = x"match8" ]; then
392                                 offsetmapping_match8=`echo "($3 + 0.5) / 1" | bc`
393                         elif [ x"$2" = x"match16" ]; then
394                                 offsetmapping_match8=`echo "($3 / 257 + 0.5) / 1" | bc`
395                         elif [ x"$2" = x"match" ]; then
396                                 offsetmapping_match8=`echo "($3 * 255 + 0.5) / 1" | bc`
397                         elif [ x"$2" = x"bias" ]; then
398                                 offsetmapping_match8=`echo "((1 - $3) * 255 + 0.5) / 1" | bc`
399                         else
400                                 offsetmapping_match8=default
401                         fi
402                         ;;
403                 qer_editorimage)
404                         use_texture "$parsing_shader" "`shader_normalize "$A1"`" editorimage
405                         ;;
406                 skyparms)
407                         use_texture "$parsing_shader" "${A1}_lf" sky
408                         use_texture "$parsing_shader" "${A1}_rt" sky
409                         use_texture "$parsing_shader" "${A1}_up" sky
410                         use_texture "$parsing_shader" "${A1}_dn" sky
411                         use_texture "$parsing_shader" "${A1}_ft" sky
412                         use_texture "$parsing_shader" "${A1}_bk" sky
413                         ;;
414                 *)
415                         ;;
416         esac
417 }
418
419 parse_shader_post()
420 {
421         if [ -n "$AUDIT_ALPHACHANNELS" ] && [ -n "$maintexture" ] && ! $textureblending; then
422                 getstats "../$maintexture.tga" || getstats "../$maintexture.png" || getstats "../$maintexture.jpg"
423                 case "$mainblendfunc" in
424                         *src_alpha*|*blend*)
425                                 # texture must have alpha
426                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
427                                         err "$parsing_shader uses alpha-less texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"
428                                 fi
429                                 ;;
430                         add|"gl_one gl_one")
431                                 # texture must not have alpha (engine bug)
432                                 if [ x"$mainalphagen" != x"none" -o $min -lt 255 ]; then
433                                         err "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"
434                                 fi
435                                 ;;
436                         *)
437                                 case "$mainalphafunc" in
438                                         g*)
439                                                 # texture must have alpha
440                                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
441                                                         err "$parsing_shader uses alpha-less texture $maintexture with alphafunc $mainalphafunc and alphagen $mainalphagen"
442                                                 fi
443                                                 ;;
444                                         *)
445                                                 # texture should not have alpha (no bug if not)
446                                                 case "$mainalphagen" in
447                                                         none)
448                                                                 if [ $min -lt 255 ]; then
449                                                                         warn "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"
450                                                                 fi
451                                                                 ;;
452                                                         *)
453                                                                 # alphagen is set, but blendfunc has no use for it
454                                                                 err "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"
455                                                                 ;;
456                                                 esac
457                                                 ;;
458                                 esac
459                                 ;;
460                 esac
461         fi
462 }
463
464 parse_shaderfile_pre()
465 {
466         s="${parsing_shaderfile%.shader}"
467         case "$s" in
468                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
469                 map_*)
470                         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`/"
471                         forbidden_prefixes=
472                         ;;
473                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
474                 skies_*)
475                         allowed_prefixes="textures/skies/`echo "$s" | cut -d _ -f 2`: textures/skies/`echo "$s" | cut -d _ -f 2`_"
476                         forbidden_prefixes=
477                         ;;
478                 ## RULE: model_*.shader may define models/*
479                 model_*)
480                         allowed_prefixes="models/"
481                         forbidden_prefixes="models/map_"
482                         ;;
483                 ## RULE: any other FOO.shader may define textures/FOO/*
484                 *)
485                         allowed_prefixes="textures/$s/"
486                         forbidden_prefixes="textures/skies/ textures/map_ models/"
487                         ;;
488         esac
489 }
490
491 parse_shaders *.shader
492
493 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`
494 textures_used=`echo "${textures_used#$LF}" | sort -u`
495
496 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
497         case "$L" in
498                 textures/radiant/*)
499                         ;;
500                 models/map_*/*)
501                         ;;
502                 textures/map_*/*)
503                         ;;
504                 *)
505                         err "texture $L is not referenced by any shader"
506                         ;;
507         esac
508 done
509
510 $status