]> de.git.xonotic.org Git - xonotic/xonotic-maps.pk3dir.git/blob - scripts/shader-audit.sh
fix another too weak offsetmapped texture
[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" ]; 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 "(EE) shader $1 uses broken normalmap ${2}_norm.tga, maybe use dpoffsetmapping none?"; seterror
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 "(EE) 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                                                 ;;
296                                 esac
297                                 ;;
298                         animmap)
299                                 for X in $Aother; do
300                                         use_texture "$parsing_shader" "`normalize "$X"`" animmap
301                                 done
302                                 for X in $Aother; do
303                                         ss_map="`normalize "$X"`"
304                                         break
305                                 done
306                                 ;;
307                         '{')
308                                 echo "(EE) brace nesting error in $parsing_shader"; seterror
309                                 ;;
310                         '}')
311                                 break
312                                 ;;
313                         *)
314                                 ;;
315                 esac
316         done
317
318         if [ -n "$AUDIT_ALPHACHANNELS" ] && [ -n "$ss_map" ]; then
319                 getstats "../$ss_map.tga" || getstats "../$ss_map.png" || getstats "../$ss_map.jpg"
320                 case "$ss_blendfunc" in
321                         *src_alpha*|*blend*)
322                                 # texture must have alpha
323                                 if [ x"$ss_alphagen" = x"none" -a $min -eq 255 ]; then
324                                         echo "(EE) $parsing_shader uses alpha-less texture $ss_map with blendfunc $ss_blendfunc and alphagen $ss_alphagen"; seterror
325                                 fi
326                                 ;;
327                         add|"gl_one gl_one")
328                                 # texture must not have alpha (engine bug)
329                                 if [ x"$ss_alphagen" != x"none" -o $min -lt 255 ]; then
330                                         echo "(EE) $parsing_shader uses alpha-using texture $ss_map with blendfunc $ss_blendfunc and alphagen $ss_alphagen"; seterror
331                                 fi
332                                 ;;
333                         *)
334                                 case "$ss_alphafunc" in
335                                         g*)
336                                                 # texture must have alpha
337                                                 if [ x"$ss_alphagen" = x"none" -a $min -eq 255 ]; then
338                                                         echo "(EE) $parsing_shader uses alpha-less texture $ss_map with alphafunc $ss_alphafunc and alphagen $ss_alphagen"; seterror
339                                                 fi
340                                                 ;;
341                                         *)
342                                                 # texture should not have alpha (no bug if not)
343                                                 case "$ss_alphagen" in
344                                                         none)
345                                                                 if [ $min -lt 255 ]; then
346                                                                         echo "(WW) $parsing_shader uses alpha-using texture $ss_map with blendfunc $ss_blendfunc and alphafunc $ss_alphafunc and alphagen $ss_alphagen"
347                                                                 fi
348                                                                 ;;
349                                                         *)
350                                                                 # alphagen is set, but blendfunc has no use for it
351                                                                 echo "(EE) $parsing_shader uses alpha-using texture $ss_map with blendfunc $ss_blendfunc and alphafunc $ss_alphafunc and alphagen $ss_alphagen"; seterror
352                                                                 ;;
353                                                 esac
354                                                 ;;
355                                 esac
356                                 ;;
357                 esac
358         fi
359 }
360
361 parse_shader()
362 {
363         use_texture "$parsing_shader" "$parsing_shader" shader
364         offsetmapping_match8=
365         while read L A1 Aother; do
366                 case "`echo "$L" | tr A-Z a-z`" in
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 }
409
410 parse_shaderfile()
411 {
412         case "$1" in
413                 ## RULE: map_FOO.shader may define tetxures/map_FOO_* and textures/map_FOO/*
414                 map_*)
415                         allowed_prefixes="textures/map_`echo "$1" | cut -d _ -f 2`_ textures/map_`echo "$1" | cut -d _ -f 2`/"
416                         forbidden_prefixes=
417                         ;;
418                 ## RULE: skies_FOO.shader may define tetxures/skies/FOO and textures/skies/FOO_*
419                 skies_*)
420                         allowed_prefixes="textures/skies/`echo "$1" | cut -d _ -f 2`: textures/skies/`echo "$1" | cut -d _ -f 2`_"
421                         forbidden_prefixes=
422                         ;;
423                 ## RULE: model_*.shader may define models/*
424                 model_*)
425                         allowed_prefixes="models/"
426                         forbidden_prefixes=
427                         ;;
428                 ## RULE: any other FOO.shader may define textures/FOO/*
429                 *)
430                         allowed_prefixes="textures/$1/"
431                         forbidden_prefixes="textures/skies/ textures/map_ models/"
432                         ;;
433         esac
434         while read L; do
435                 case "$L" in
436                         */*)
437                                 parsing_shader="`normalize "$L"`"
438                                 if [ x"$L" != x"$parsing_shader" ]; then
439                                         echo "(WW) normalized shader name $L to $parsing_shader"
440                                 fi
441                                 ;;
442                         '{')
443                                 parse_shader
444                                 ;;
445                         *)
446                                 ;;
447                 esac
448         done
449 }
450
451 strip_comments()
452 {
453         sed 's,//.*,,g; s,\r, ,g; s,\t, ,g; s,  *, ,g; s, $,,; s,^ ,,; /^$/ d'
454 }
455
456 t=`mktemp || echo ".temp"`
457 for X in *.shader; do
458         strip_comments < "$X" > "$t"
459         parse_shaderfile "${X%.shader}" < "$t"
460 done
461 rm -f "$t"
462
463 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`
464 textures_used=`echo "${textures_used#$LF}" | sort -u`
465
466 echo "$textures_used$LF$textures_used$LF$textures_avail" | sort | uniq -u | while IFS= read -r L; do
467         case "$L" in
468                 textures/radiant/*)
469                         ;;
470                 textures/map_*/*)
471                         ;;
472                 *)
473                         echo "(EE) texture $L is not referenced by any shader"; seterror
474                         ;;
475         esac
476 done
477
478 $status