]> de.git.xonotic.org Git - xonotic/xonotic-maps.pk3dir.git/blob - scripts/shader-audit.sh
fix some shader naming bugs
[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_*/*|models/map_*/*)
210                         pre=${1%%/map_*}
211                         suf=${1#*/map_}
212                         map=${suf%%[_/]*}
213                         case "$2" in
214                                 textures/map_$map[/_]*)
215                                         ;;
216                                 models/map_$map[/_]*)
217                                         ;;
218                                 textures/map_*|models/map_*)
219                                         # protect one map's textures from the evil of other maps :P
220                                         err "texture $2 of shader $1 is out of place, recommended file name is $pre/map_$map/*"
221                                         ;;
222                                 *)
223                                         # using outside stuff is permitted
224                                         ;;
225                         esac
226                         ;;
227                 ## RULE: textures/common/FOO must use textures/common/FOO or textures/common/*/*
228                 textures/common/*)
229                         case "$2" in
230                                 "$1")
231                                         ;;
232                                 textures/common/*/*)
233                                         ;;
234                                 *)
235                                         err "texture $2 of shader $1 is out of place, recommended file name is $1 or textures/common/*/*"
236                                         ;;
237                         esac
238                         ;;
239                 ## RULE: textures/FOO/* must use textures/FOO/*, for FOO in decals, liquids_water, liquids_slime, liquids_lava, alphamod
240                 textures/decals/*|textures/liquids_*/*|textures/effects_*/*|textures/screens/*|textures/logos/*|textures/alphamod/*)
241                         pre=`echo "$1" | cut -d / -f 1-2`
242                         case "$2" in
243                                 "$pre"/*)
244                                         # I _suppose_ this is fine, as tZork committed this pack
245                                         ;;
246                                 *)
247                                         err "texture $2 of shader $1 is out of place, recommended file name is $1"
248                                         ;;
249                         esac
250                         ;;
251                 ## 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
252                 textures/skies/*)
253                         sky=${1#textures/skies/}
254                         sky=${sky%%_*}
255                         case "$2" in
256                                 textures/skies/$sky|textures/skies/$sky[_]*)
257                                         # typical place for preview image
258                                         ;;
259                                 env/$sky[/_]*)
260                                         # typical place for skybox
261                                         ;;
262                                 *)
263                                         err "texture $2 of shader $1 is out of place, recommended file name is $1"
264                                         ;;
265                         esac
266                         ;;
267                 ## RULE: models/* must use models/*
268                 models/*)
269                         case "$2" in
270                                 models/*)
271                                         ;;
272                                 *)
273                                         err "texture $2 of shader $1 is out of place, recommended file name is $1 or models/*"
274                                         ;;
275                         esac
276                         ;;
277                 *)
278                         err "no shader name pattern for $1"
279                         ;;
280         esac
281 }
282
283 parsing_shader=
284 parse_shaderstage()
285 {
286         ss_blendfunc=none
287         ss_alphafunc=none
288         ss_alphagen=none
289         ss_map=
290         while read L A1 Aother; do
291                 case "`echo "$L" | tr A-Z a-z`" in
292                         blendfunc)
293                                 ss_blendfunc=`echo $A1 $Aother | tr A-Z a-z`
294                                 ;;
295                         alphafunc)
296                                 ss_alphafunc=`echo $A1 | tr A-Z a-z`
297                                 ;;
298                         alphagen)
299                                 ss_alphagen=`echo $A1 | tr A-Z a-z`
300                                 ;;
301                         map|clampmap)
302                                 case "$A1" in
303                                         '$lightmap')
304                                                 ;;
305                                         *)
306                                                 use_texture "$parsing_shader" "`normalize "$A1"`" map
307                                                 ss_map="`normalize "$A1"`"
308                                                 offsetmapping_match8=firststagedone
309                                                 ;;
310                                 esac
311                                 ;;
312                         animmap)
313                                 for X in $Aother; do
314                                         use_texture "$parsing_shader" "`normalize "$X"`" animmap
315                                 done
316                                 for X in $Aother; do
317                                         ss_map="`normalize "$X"`"
318                                         break
319                                 done
320                                 ;;
321                         '{')
322                                 err "brace nesting error in $parsing_shader"
323                                 ;;
324                         '}')
325                                 break
326                                 ;;
327                         *)
328                                 ;;
329                 esac
330         done
331
332         if [ -n "$ss_map" ]; then
333                 if [ -z "$maintexture" ]; then
334                         maintexture=$ss_map
335                         mainblendfunc=$ss_blendfunc
336                         mainalphafunc=$ss_alphafunc
337                         mainalphagen=$ss_alphagen
338                 elif [ x"$ss_alphagen" = x"vertex" ] && ! $textureblending; then
339                         case "$mainblendfunc:$mainalphafunc:$ss_blendfunc:$ss_alphafunc" in
340                                 none:none:"gl_src_alpha gl_one_minus_src_alpha":none) textureblending=true ;;
341                                 none:none:filter:none) textureblending=true ;;
342                                 none:none:none:g*) textureblending=true ;;
343                                 "gl_one gl_zero":none:filter:none) textureblending=true ;;
344                                 "gl_one gl_zero":none:none:g*) textureblending=true ;;
345                                 *)
346                                         err "texture blending requires first stage to have no blendfunc/alphatest, and requires second stage to be blendfunc filter"
347                                         ;;
348                         esac
349                 else
350                         err "multistage shader without alphagen vertex, or using more than 2 stages, is not supported by DarkPlaces"
351                 fi
352         fi
353 }
354
355 parse_shader()
356 {
357         use_texture "$parsing_shader" "$parsing_shader" shader
358         offsetmapping_match8=
359         textureblending=false
360         maintexture=
361         nowarn=false
362         while read L A1 Aother; do
363                 case "`echo "$L" | tr A-Z a-z`" in
364                         xon_nowarn)
365                                 nowarn=true
366                                 ;;
367                         dpoffsetmapping)
368                                 set -- $Aother
369                                 if [ x"$A1" = x"none" ]; then
370                                         offsetmapping_match8=none
371                                 elif [ x"$A1" = x"off" ]; then
372                                         offsetmapping_match8=none
373                                 elif [ x"$A1" = x"disabled" ]; then
374                                         offsetmapping_match8=none
375                                 elif [ x"$2" = x"match8" ]; then
376                                         offsetmapping_match8=`echo "($3 + 0.5) / 1" | bc`
377                                 elif [ x"$2" = x"match16" ]; then
378                                         offsetmapping_match8=`echo "($3 / 257 + 0.5) / 1" | bc`
379                                 elif [ x"$2" = x"match" ]; then
380                                         offsetmapping_match8=`echo "($3 * 255 + 0.5) / 1" | bc`
381                                 elif [ x"$2" = x"bias" ]; then
382                                         offsetmapping_match8=`echo "((1 - $3) * 255 + 0.5) / 1" | bc`
383                                 else
384                                         offsetmapping_match8=default
385                                 fi
386                                 ;;
387                         qer_editorimage)
388                                 use_texture "$parsing_shader" "`normalize "$A1"`" editorimage
389                                 ;;
390                         skyparms)
391                                 use_texture "$parsing_shader" "${A1}_lf" sky
392                                 use_texture "$parsing_shader" "${A1}_rt" sky
393                                 use_texture "$parsing_shader" "${A1}_up" sky
394                                 use_texture "$parsing_shader" "${A1}_dn" sky
395                                 use_texture "$parsing_shader" "${A1}_ft" sky
396                                 use_texture "$parsing_shader" "${A1}_bk" sky
397                                 ;;
398                         '{')
399                                 parse_shaderstage
400                                 ;;
401                         '}')
402                                 break
403                                 ;;
404                         *)
405                                 ;;
406                 esac
407         done
408         if [ -n "$AUDIT_ALPHACHANNELS" ] && [ -n "$maintexture" ] && ! $textureblending; then
409                 getstats "../$maintexture.tga" || getstats "../$maintexture.png" || getstats "../$maintexture.jpg"
410                 case "$mainblendfunc" in
411                         *src_alpha*|*blend*)
412                                 # texture must have alpha
413                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
414                                         err "$parsing_shader uses alpha-less texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"
415                                 fi
416                                 ;;
417                         add|"gl_one gl_one")
418                                 # texture must not have alpha (engine bug)
419                                 if [ x"$mainalphagen" != x"none" -o $min -lt 255 ]; then
420                                         err "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphagen $mainalphagen"
421                                 fi
422                                 ;;
423                         *)
424                                 case "$mainalphafunc" in
425                                         g*)
426                                                 # texture must have alpha
427                                                 if [ x"$mainalphagen" = x"none" -a $min -eq 255 ]; then
428                                                         err "$parsing_shader uses alpha-less texture $maintexture with alphafunc $mainalphafunc and alphagen $mainalphagen"
429                                                 fi
430                                                 ;;
431                                         *)
432                                                 # texture should not have alpha (no bug if not)
433                                                 case "$mainalphagen" in
434                                                         none)
435                                                                 if [ $min -lt 255 ]; then
436                                                                         warn "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"
437                                                                 fi
438                                                                 ;;
439                                                         *)
440                                                                 # alphagen is set, but blendfunc has no use for it
441                                                                 err "$parsing_shader uses alpha-using texture $maintexture with blendfunc $mainblendfunc and alphafunc $mainalphafunc and alphagen $mainalphagen"
442                                                                 ;;
443                                                 esac
444                                                 ;;
445                                 esac
446                                 ;;
447                 esac
448         fi
449 }
450
451 parse_shaderfile()
452 {
453         case "$1" in
454                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
455                 map_*)
456                         allowed_prefixes="textures/map_`echo "$1" | cut -d _ -f 2`_ textures/map_`echo "$1" | cut -d _ -f 2`/ models/map_`echo "$1" | cut -d _ -f 2`_ models/map_`echo "$1" | cut -d _ -f 2`/"
457                         forbidden_prefixes=
458                         ;;
459                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
460                 skies_*)
461                         allowed_prefixes="textures/skies/`echo "$1" | cut -d _ -f 2`: textures/skies/`echo "$1" | cut -d _ -f 2`_"
462                         forbidden_prefixes=
463                         ;;
464                 ## RULE: model_*.shader may define models/*
465                 model_*)
466                         allowed_prefixes="models/"
467                         forbidden_prefixes="models/map_"
468                         ;;
469                 ## RULE: any other FOO.shader may define textures/FOO/*
470                 *)
471                         allowed_prefixes="textures/$1/"
472                         forbidden_prefixes="textures/skies/ textures/map_ models/"
473                         ;;
474         esac
475         while read L; do
476                 case "$L" in
477                         */*)
478                                 parsing_shader="`normalize "$L"`"
479                                 if [ x"$L" != x"$parsing_shader" ]; then
480                                         warn "normalized shader name $L to $parsing_shader"
481                                 fi
482                                 ;;
483                         '{')
484                                 parse_shader
485                                 ;;
486                         *)
487                                 ;;
488                 esac
489         done
490 }
491
492 strip_comments()
493 {
494         sed 's,//.*,,g; s,\r, ,g; s,\t, ,g; s,  *, ,g; s, $,,; s,^ ,,; /^$/ d'
495 }
496
497 t=`mktemp || echo ".temp"`
498 for X in *.shader; do
499         strip_comments < "$X" > "$t"
500         parse_shaderfile "${X%.shader}" < "$t"
501 done
502 rm -f "$t"
503
504 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 normalize "$T"; done | sort -u`
505 textures_used=`echo "${textures_used#$LF}" | sort -u`
506
507 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
508         case "$L" in
509                 textures/radiant/*)
510                         ;;
511                 models/map_*/*)
512                         ;;
513                 textures/map_*/*)
514                         ;;
515                 *)
516                         err "texture $L is not referenced by any shader"
517                         ;;
518         esac
519 done
520
521 $status