LCOV - code coverage report
Current view: top level - engine/render - texture.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 19.7 % 1500 296
Test Date: 2025-02-21 06:59:27 Functions: 37.8 % 119 45

            Line data    Source code
       1              : // texture.cpp: texture slot management
       2              : 
       3              : #include "../libprimis-headers/cube.h"
       4              : #include "../../shared/geomexts.h"
       5              : #include "../../shared/glexts.h"
       6              : #include "../../shared/stream.h"
       7              : 
       8              : #include "SDL_image.h"
       9              : 
      10              : #include "imagedata.h"
      11              : #include "octarender.h"
      12              : #include "renderwindow.h"
      13              : #include "shader.h"
      14              : #include "shaderparam.h"
      15              : #include "texture.h"
      16              : 
      17              : #include "world/light.h"
      18              : #include "world/material.h"
      19              : #include "world/octaedit.h"
      20              : #include "world/octaworld.h"
      21              : #include "world/world.h"
      22              : 
      23              : #include "interface/console.h"
      24              : #include "interface/control.h"
      25              : 
      26              : extern const texrotation texrotations[8] =
      27              : {
      28              :     { false, false, false }, // 0: default
      29              :     { false,  true,  true }, // 1: 90 degrees
      30              :     {  true,  true, false }, // 2: 180 degrees
      31              :     {  true, false,  true }, // 3: 270 degrees
      32              :     {  true, false, false }, // 4: flip X
      33              :     { false,  true, false }, // 5: flip Y
      34              :     { false, false,  true }, // 6: transpose
      35              :     {  true,  true,  true }, // 7: flipped transpose
      36              : };
      37              : 
      38              : //copies every other pixel into a destination buffer
      39              : //sw,sh are source width/height
      40              : template<int BPP>
      41            3 : static void halvetexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst)
      42              : {
      43           10 :     for(const uchar *yend = &src[sh*stride]; src < yend;)
      44              :     {
      45           28 :         for(const uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += 2*BPP, dst += BPP)
      46              :         {
      47           42 :             for(int i = 0; i < BPP; ++i)
      48              :             {
      49           21 :                 dst[i] = (static_cast<uint>(xsrc[i]) + static_cast<uint>(xsrc[i+BPP]) + static_cast<uint>(xsrc[stride+i]) + static_cast<uint>(xsrc[stride+i+BPP]))>>2;
      50              :             }
      51              :         }
      52            7 :         src += 2*stride;
      53              :     }
      54            3 : }
      55              : 
      56              : template<int BPP>
      57            0 : static void shifttexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh)
      58              : {
      59            0 :     uint wfrac = sw/dw,
      60            0 :          hfrac = sh/dh,
      61            0 :          wshift = 0,
      62            0 :          hshift = 0;
      63            0 :     while(dw<<wshift < sw)
      64              :     {
      65            0 :         wshift++;
      66              :     }
      67            0 :     while(dh<<hshift < sh)
      68              :     {
      69            0 :         hshift++;
      70              :     }
      71            0 :     uint tshift = wshift + hshift;
      72            0 :     for(const uchar *yend = &src[sh*stride]; src < yend;)
      73              :     {
      74            0 :         for(const uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += wfrac*BPP, dst += BPP)
      75              :         {
      76              : 
      77            0 :             uint t[BPP] = {0};
      78            0 :             for(const uchar *ycur = xsrc, *xend = &ycur[wfrac*BPP], *yend = &src[hfrac*stride];
      79            0 :                 ycur < yend;
      80            0 :                 ycur += stride, xend += stride)
      81              :             {
      82              :                 //going to (xend - 1) seems to be necessary to avoid buffer overrun
      83            0 :                 for(const uchar *xcur = ycur; xcur < xend -1; xcur += BPP)
      84              :                 {
      85            0 :                     for(int i = 0; i < BPP; ++i)
      86              :                     {
      87            0 :                         t[i] += xcur[i];
      88              :                     }
      89              :                 }
      90              :             }
      91            0 :             for(int i = 0; i < BPP; ++i)
      92              :             {
      93            0 :                 dst[i] = t[i] >> tshift;
      94              :             }
      95              :         }
      96            0 :         src += hfrac*stride;
      97              :     }
      98            0 : }
      99              : 
     100              : template<int BPP>
     101            0 : static void scaletexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh)
     102              : {
     103            0 :     uint wfrac = (sw<<12)/dw,
     104            0 :          hfrac = (sh<<12)/dh,
     105            0 :          darea = dw*dh,
     106            0 :          sarea = sw*sh;
     107              :     int over, under;
     108              :     //the for loops here are merely to increment over & under vars which are used later
     109            0 :     for(over = 0; (darea>>over) > sarea; over++)
     110              :     {
     111              :         //(empty body)
     112              :     }
     113            0 :     for(under = 0; (darea<<under) < sarea; under++)
     114              :     {
     115              :         //(empty body)
     116              :     }
     117            0 :     uint cscale = std::clamp(under, over - 12, 12),
     118            0 :          ascale = std::clamp(12 + under - over, 0, 24),
     119            0 :          dscale = ascale + 12 - cscale,
     120            0 :          area = (static_cast<ullong>(darea)<<ascale)/sarea;
     121            0 :     dw *= wfrac;
     122            0 :     dh *= hfrac;
     123            0 :     for(uint y = 0; y < dh; y += hfrac)
     124              :     {
     125            0 :         const uint yn = y + hfrac - 1,
     126            0 :                    yi = y>>12, h = (yn>>12) - yi,
     127            0 :                    ylow = ((yn|(-static_cast<int>(h)>>24))&0xFFFU) + 1 - (y&0xFFFU),
     128            0 :                    yhigh = (yn&0xFFFU) + 1;
     129            0 :         const uchar *ysrc = &src[yi*stride];
     130            0 :         for(uint x = 0; x < dw; x += wfrac, dst += BPP)
     131              :         {
     132            0 :             const uint xn = x + wfrac - 1,
     133            0 :                        xi = x>>12,
     134            0 :                        w = (xn>>12) - xi,
     135            0 :                        xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU),
     136            0 :                        xhigh = (xn&0xFFFU) + 1;
     137            0 :             const uchar *xsrc = &ysrc[xi*BPP],
     138            0 :                         *xend = &xsrc[w*BPP];
     139            0 :             uint t[BPP] = {0};
     140            0 :             for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     141              :             {
     142            0 :                 for(int i = 0; i < BPP; ++i)
     143              :                 {
     144            0 :                     t[i] += xcur[i];
     145              :                 }
     146              :             }
     147            0 :             for(int i = 0; i < BPP; ++i)
     148              :             {
     149            0 :                 t[i] = (ylow*(t[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale;
     150              :             }
     151            0 :             if(h)
     152              :             {
     153            0 :                 xsrc += stride;
     154            0 :                 xend += stride;
     155            0 :                 for(uint hcur = h; --hcur; xsrc += stride, xend += stride)
     156              :                 {
     157            0 :                     uint c[BPP] = {0};
     158            0 :                     for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     159            0 :                         for(int i = 0; i < BPP; ++i)
     160              :                         {
     161            0 :                             c[i] += xcur[i];
     162              :                         }
     163            0 :                     for(int i = 0; i < BPP; ++i)
     164              :                     {
     165            0 :                         t[i] += ((c[i]<<12) + xsrc[i]*xlow + xend[i]*xhigh)>>cscale;
     166              :                     }
     167              :                 }
     168            0 :                 uint c[BPP] = {0};
     169            0 :                 for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     170            0 :                     for(int i = 0; i < BPP; ++i)
     171              :                     {
     172            0 :                         c[i] += xcur[i];
     173              :                     }
     174            0 :                 for(int i = 0; i < BPP; ++i)
     175              :                 {
     176            0 :                     t[i] += (yhigh*(c[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale;
     177              :                 }
     178              :             }
     179            0 :             for(int i = 0; i < BPP; ++i)
     180              :             {
     181            0 :                 dst[i] = (t[i] * area)>>dscale;
     182              :             }
     183              :         }
     184              :     }
     185            0 : }
     186              : 
     187            3 : void scaletexture(const uchar * RESTRICT src, uint sw, uint sh, uint bpp, uint pitch, uchar * RESTRICT dst, uint dw, uint dh)
     188              : {
     189            3 :     if(sw == dw*2 && sh == dh*2)
     190              :     {
     191            3 :         switch(bpp)
     192              :         {
     193            3 :             case 1: return halvetexture<1>(src, sw, sh, pitch, dst);
     194            0 :             case 2: return halvetexture<2>(src, sw, sh, pitch, dst);
     195            0 :             case 3: return halvetexture<3>(src, sw, sh, pitch, dst);
     196            0 :             case 4: return halvetexture<4>(src, sw, sh, pitch, dst);
     197              :         }
     198              :     }
     199            0 :     else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1) || dw&(dw-1) || dh&(dh-1))
     200              :     {
     201            0 :         switch(bpp)
     202              :         {
     203            0 :             case 1: return scaletexture<1>(src, sw, sh, pitch, dst, dw, dh);
     204            0 :             case 2: return scaletexture<2>(src, sw, sh, pitch, dst, dw, dh);
     205            0 :             case 3: return scaletexture<3>(src, sw, sh, pitch, dst, dw, dh);
     206            0 :             case 4: return scaletexture<4>(src, sw, sh, pitch, dst, dw, dh);
     207              :         }
     208              :     }
     209              :     else
     210              :     {
     211            0 :         switch(bpp)
     212              :         {
     213            0 :             case 1: return shifttexture<1>(src, sw, sh, pitch, dst, dw, dh);
     214            0 :             case 2: return shifttexture<2>(src, sw, sh, pitch, dst, dw, dh);
     215            0 :             case 3: return shifttexture<3>(src, sw, sh, pitch, dst, dw, dh);
     216            0 :             case 4: return shifttexture<4>(src, sw, sh, pitch, dst, dw, dh);
     217              :         }
     218              :     }
     219              : }
     220              : 
     221              : template<int BPP>
     222            0 : static void reorienttexture(const uchar * RESTRICT src, int sw, int sh, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy)
     223              : {
     224            0 :     int stridex = BPP,
     225            0 :         stridey = BPP;
     226            0 :     if(swapxy)
     227              :     {
     228            0 :         stridex *= sh;
     229              :     }
     230              :     else
     231              :     {
     232            0 :         stridey *= sw;
     233              :     }
     234            0 :     if(flipx)
     235              :     {
     236            0 :         dst += (sw-1)*stridex;
     237            0 :         stridex = -stridex;
     238              :     }
     239            0 :     if(flipy)
     240              :     {
     241            0 :         dst += (sh-1)*stridey;
     242            0 :         stridey = -stridey;
     243              :     }
     244            0 :     const uchar *srcrow = src;
     245            0 :     for(int i = 0; i < sh; ++i)
     246              :     {
     247              :         uchar* curdst;
     248              :         const uchar* src;
     249              :         const uchar* end;
     250            0 :         for(curdst = dst, src = srcrow, end = &srcrow[sw*BPP]; src < end;)
     251              :         {
     252            0 :             for(int k = 0; k < BPP; ++k)
     253              :             {
     254            0 :                 curdst[k] = *src++;
     255              :             }
     256            0 :             curdst += stridex;
     257              :         }
     258            0 :         srcrow += stride;
     259            0 :         dst += stridey;
     260              :     }
     261            0 : }
     262              : 
     263            0 : void reorienttexture(const uchar * RESTRICT src, int sw, int sh, int bpp, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy)
     264              : {
     265            0 :     switch(bpp)
     266              :     {
     267            0 :         case 1: return reorienttexture<1>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     268            0 :         case 2: return reorienttexture<2>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     269            0 :         case 3: return reorienttexture<3>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     270            0 :         case 4: return reorienttexture<4>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     271              :     }
     272              : }
     273              : /*  var             min  default max  */
     274              : VAR(hwtexsize,      1,   0,      0);
     275              : VAR(hwcubetexsize,  1,   0,      0);
     276              : VAR(hwmaxaniso,     1,   0,      0);
     277              : VAR(hwtexunits,     1,   0,      0);
     278              : VAR(hwvtexunits,    1,   0,      0);
     279            0 : VARFP(maxtexsize,   0,   0,      1<<12, initwarning("texture quality",   Init_Load));
     280            0 : VARFP(reducefilter, 0,   1,      1,     initwarning("texture quality",   Init_Load));
     281            0 : VARF(trilinear,     0,   1,      1,     initwarning("texture filtering", Init_Load));
     282            0 : VARF(bilinear,      0,   1,      1,     initwarning("texture filtering", Init_Load));
     283            0 : VARFP(aniso,        0,   0,      16,    initwarning("texture filtering", Init_Load));
     284              : 
     285            1 : static int formatsize(GLenum format)
     286              : {
     287            1 :     switch(format)
     288              :     {
     289            1 :         case GL_RED:
     290              :         {
     291            1 :             return 1;
     292              :         }
     293            0 :         case GL_RG:
     294              :         {
     295            0 :             return 2;
     296              :         }
     297            0 :         case GL_RGB:
     298              :         {
     299            0 :             return 3;
     300              :         }
     301            0 :         case GL_RGBA:
     302              :         {
     303            0 :             return 4;
     304              :         }
     305            0 :         default:
     306              :         {
     307            0 :             return 4;
     308              :         }
     309              :     }
     310              : }
     311              : 
     312            1 : static void resizetexture(int w, int h, bool mipmap, GLenum target, int compress, int &tw, int &th)
     313              : {
     314            1 :     int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize,
     315            1 :         sizelimit = mipmap && maxtexsize ? std::min(maxtexsize, hwlimit) : hwlimit;
     316            1 :     if(compress > 0)
     317              :     {
     318            0 :         w = std::max(w/compress, 1);
     319            0 :         h = std::max(h/compress, 1);
     320              :     }
     321            1 :     w = std::min(w, sizelimit);
     322            1 :     h = std::min(h, sizelimit);
     323            1 :     tw = w;
     324            1 :     th = h;
     325            1 : }
     326              : 
     327            5 : static int texalign(int w, int bpp)
     328              : {
     329            5 :     int stride = w*bpp;
     330            5 :     if(stride&1)
     331              :     {
     332            1 :         return 1;
     333              :     }
     334            4 :     if(stride&2)
     335              :     {
     336            1 :         return 2;
     337              :     }
     338            3 :     return 4;
     339              : }
     340              : 
     341            1 : static void uploadtexture(GLenum target, GLenum internal, int tw, int th, GLenum format, GLenum type, const void *pixels, int pw, int ph, int pitch, bool mipmap)
     342              : {
     343            1 :     int bpp = formatsize(format),
     344            1 :         row = 0,
     345            1 :         rowalign = 0;
     346            1 :     if(!pitch)
     347              :     {
     348            0 :         pitch = pw*bpp;
     349              :     }
     350            1 :     uchar *buf = nullptr;
     351            1 :     if(pw!=tw || ph!=th)
     352              :     {
     353            0 :         buf = new uchar[tw*th*bpp];
     354            0 :         scaletexture(static_cast<uchar *>(const_cast<void *>(pixels)), pw, ph, bpp, pitch, buf, tw, th);
     355              :     }
     356            1 :     else if(tw*bpp != pitch)
     357              :     {
     358            0 :         row = pitch/bpp;
     359            0 :         rowalign = texalign(pitch, 1);
     360            0 :         while(rowalign > 0 && ((row*bpp + rowalign - 1)/rowalign)*rowalign != pitch)
     361              :         {
     362            0 :             rowalign >>= 1;
     363              :         }
     364            0 :         if(!rowalign)
     365              :         {
     366            0 :             row = 0;
     367            0 :             buf = new uchar[tw*th*bpp];
     368            0 :             for(int i = 0; i < th; ++i)
     369              :             {
     370            0 :                 std::memcpy(&buf[i*tw*bpp], &(const_cast<uchar *>(reinterpret_cast<const uchar *>(pixels)))[i*pitch], tw*bpp);
     371              :             }
     372              :         }
     373              :     }
     374            1 :     for(int level = 0, align = 0;; level++)
     375              :     {
     376            4 :         uchar *src = buf ? buf : const_cast<uchar *>(reinterpret_cast<const uchar *>(pixels));
     377            4 :         if(buf)
     378              :         {
     379            3 :             pitch = tw*bpp;
     380              :         }
     381            4 :         int srcalign = row > 0 ? rowalign : texalign(pitch, 1);
     382            4 :         if(align != srcalign)
     383              :         {
     384            3 :             glPixelStorei(GL_UNPACK_ALIGNMENT, align = srcalign);
     385              :         }
     386            4 :         if(row > 0)
     387              :         {
     388            0 :             glPixelStorei(GL_UNPACK_ROW_LENGTH, row);
     389              :         }
     390            4 :         if(target==GL_TEXTURE_1D)
     391              :         {
     392            0 :             glTexImage1D(target, level, internal, tw, 0, format, type, src);
     393              :         }
     394              :         else
     395              :         {
     396            4 :             glTexImage2D(target, level, internal, tw, th, 0, format, type, src);
     397              :         }
     398            4 :         if(row > 0)
     399              :         {
     400            0 :             glPixelStorei(GL_UNPACK_ROW_LENGTH, row = 0);
     401              :         }
     402            4 :         if(!mipmap || std::max(tw, th) <= 1)
     403              :         {
     404            1 :             break;
     405              :         }
     406            3 :         int srcw = tw,
     407            3 :             srch = th;
     408            3 :         if(tw > 1)
     409              :         {
     410            3 :             tw /= 2;
     411              :         }
     412            3 :         if(th > 1)
     413              :         {
     414            3 :             th /= 2;
     415              :         }
     416            3 :         if(src)
     417              :         {
     418            3 :             if(!buf)
     419              :             {
     420            1 :                 buf = new uchar[tw*th*bpp];
     421              :             }
     422            3 :             scaletexture(src, srcw, srch, bpp, pitch, buf, tw, th);
     423              :         }
     424            3 :     }
     425            1 :     if(buf)
     426              :     {
     427            1 :         delete[] buf;
     428              :     }
     429            1 : }
     430              : 
     431            0 : static void uploadcompressedtexture(GLenum target, GLenum subtarget, GLenum format, int w, int h, const uchar *data, int align, int blocksize, int levels, bool mipmap)
     432              : {
     433            0 :     int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize,
     434            0 :         sizelimit = levels > 1 && maxtexsize ? std::min(maxtexsize, hwlimit) : hwlimit;
     435            0 :     int level = 0;
     436            0 :     for(int i = 0; i < levels; ++i)
     437              :     {
     438            0 :         int size = ((w + align-1)/align) * ((h + align-1)/align) * blocksize;
     439            0 :         if(w <= sizelimit && h <= sizelimit)
     440              :         {
     441            0 :             if(target==GL_TEXTURE_1D)
     442              :             {
     443            0 :                 glCompressedTexImage1D(subtarget, level, format, w, 0, size, data);
     444              :             }
     445              :             else
     446              :             {
     447            0 :                 glCompressedTexImage2D(subtarget, level, format, w, h, 0, size, data);
     448              :             }
     449            0 :             level++;
     450            0 :             if(!mipmap)
     451              :             {
     452            0 :                 break;
     453              :             }
     454              :         }
     455            0 :         if(std::max(w, h) <= 1)
     456              :         {
     457            0 :             break;
     458              :         }
     459            0 :         if(w > 1)
     460              :         {
     461            0 :             w /= 2;
     462              :         }
     463            0 :         if(h > 1)
     464              :         {
     465            0 :             h /= 2;
     466              :         }
     467            0 :         data += size;
     468              :     }
     469            0 : }
     470              : 
     471            1 : static GLenum textarget(GLenum subtarget)
     472              : {
     473            1 :     switch(subtarget)
     474              :     {
     475            0 :         case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
     476              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
     477              :         case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
     478              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
     479              :         case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
     480              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
     481              :         {
     482            0 :             return GL_TEXTURE_CUBE_MAP;
     483              :         }
     484              :     }
     485            1 :     return subtarget;
     486              : }
     487              : 
     488            1 : const GLint *swizzlemask(GLenum format)
     489              : {
     490              :     static const GLint luminance[4] = { GL_RED, GL_RED, GL_RED, GL_ONE },
     491              :                        luminancealpha[4] = { GL_RED, GL_RED, GL_RED, GL_GREEN };
     492            1 :     switch(format)
     493              :     {
     494            1 :         case GL_RED:
     495              :         {
     496            1 :             return luminance;
     497              :         }
     498            0 :         case GL_RG:
     499              :         {
     500            0 :             return luminancealpha;
     501              :         }
     502              :     }
     503            0 :     return nullptr;
     504              : }
     505              : 
     506            1 : static void setuptexparameters(int tnum, int clamp, int filter, GLenum format, GLenum target, bool swizzle)
     507              : {
     508            1 :     glBindTexture(target, tnum);
     509            1 :     glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : (clamp&0x100 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     510            1 :     if(target!=GL_TEXTURE_1D)
     511              :     {
     512            1 :         glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : (clamp&0x200 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     513              :     }
     514            1 :     if(target==GL_TEXTURE_3D)
     515              :     {
     516            0 :         glTexParameteri(target, GL_TEXTURE_WRAP_R, clamp&4 ? GL_CLAMP_TO_EDGE : (clamp&0x400 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     517              :     }
     518            1 :     if(target==GL_TEXTURE_2D && std::min(aniso, hwmaxaniso) > 0 && filter > 1)
     519              :     {
     520            0 :         glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, std::min(aniso, hwmaxaniso));
     521              :     }
     522            1 :     glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST);
     523            2 :     glTexParameteri(target, GL_TEXTURE_MIN_FILTER,
     524              :         filter > 1 ?
     525            2 :             (trilinear ?
     526            1 :                 (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) :
     527            0 :                 (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) :
     528            0 :             (filter && bilinear ? GL_LINEAR : GL_NEAREST));
     529            1 :     if(swizzle)
     530              :     {
     531            1 :         const GLint *mask = swizzlemask(format);
     532            1 :         if(mask)
     533              :         {
     534            1 :             glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, mask);
     535              :         }
     536              :     }
     537            1 : }
     538              : 
     539            1 : static GLenum textype(GLenum &component, GLenum &format)
     540              : {
     541            1 :     GLenum type = GL_UNSIGNED_BYTE;
     542            1 :     switch(component)
     543              :     {
     544            0 :         case GL_R16F:
     545              :         case GL_R32F:
     546              :         {
     547            0 :             if(!format)
     548              :             {
     549            0 :                 format = GL_RED;
     550              :             }
     551            0 :             type = GL_FLOAT;
     552            0 :             break;
     553              :         }
     554            0 :         case GL_RG16F:
     555              :         case GL_RG32F:
     556              :         {
     557            0 :             if(!format)
     558              :             {
     559            0 :                 format = GL_RG;
     560              :             }
     561            0 :             type = GL_FLOAT;
     562            0 :             break;
     563              :         }
     564            0 :         case GL_RGB16F:
     565              :         case GL_RGB32F:
     566              :         case GL_R11F_G11F_B10F:
     567              :         {
     568            0 :             if(!format)
     569              :             {
     570            0 :                 format = GL_RGB;
     571              :             }
     572            0 :             type = GL_FLOAT;
     573            0 :             break;
     574              :         }
     575            0 :         case GL_RGBA16F:
     576              :         case GL_RGBA32F:
     577              :         {
     578            0 :             if(!format)
     579              :             {
     580            0 :                 format = GL_RGBA;
     581              :             }
     582            0 :             type = GL_FLOAT;
     583            0 :             break;
     584              :         }
     585            0 :         case GL_DEPTH_COMPONENT16:
     586              :         case GL_DEPTH_COMPONENT24:
     587              :         case GL_DEPTH_COMPONENT32:
     588              :         {
     589            0 :             if(!format)
     590              :             {
     591            0 :                 format = GL_DEPTH_COMPONENT;
     592              :             }
     593            0 :             break;
     594              :         }
     595            0 :         case GL_DEPTH_STENCIL:
     596              :         case GL_DEPTH24_STENCIL8:
     597              :         {
     598            0 :             if(!format)
     599              :             {
     600            0 :                 format = GL_DEPTH_STENCIL;
     601              :             }
     602            0 :             type = GL_UNSIGNED_INT_24_8;
     603            0 :             break;
     604              :         }
     605            0 :         case GL_R8:
     606              :         case GL_R16:
     607              :         {
     608            0 :             if(!format)
     609              :             {
     610            0 :                 format = GL_RED;
     611              :             }
     612            0 :             break;
     613              :         }
     614            0 :         case GL_RG8:
     615              :         case GL_RG16:
     616              :         {
     617            0 :             if(!format)
     618              :             {
     619            0 :                 format = GL_RG;
     620              :             }
     621            0 :             break;
     622              :         }
     623            0 :         case GL_RGB5:
     624              :         case GL_RGB8:
     625              :         case GL_RGB16:
     626              :         case GL_RGB10:
     627              :         {
     628            0 :             if(!format)
     629              :             {
     630            0 :                 format = GL_RGB;
     631              :             }
     632            0 :             break;
     633              :         }
     634            0 :         case GL_RGB5_A1:
     635              :         case GL_RGBA8:
     636              :         case GL_RGBA16:
     637              :         case GL_RGB10_A2:
     638              :         {
     639            0 :             if(!format)
     640              :             {
     641            0 :                 format = GL_RGBA;
     642              :             }
     643            0 :             break;
     644              :         }
     645            0 :         case GL_RGB8UI:
     646              :         case GL_RGB16UI:
     647              :         case GL_RGB32UI:
     648              :         case GL_RGB8I:
     649              :         case GL_RGB16I:
     650              :         case GL_RGB32I:
     651              :         {
     652            0 :             if(!format)
     653              :             {
     654            0 :                 format = GL_RGB_INTEGER;
     655              :             }
     656            0 :             break;
     657              :         }
     658            0 :         case GL_RGBA8UI:
     659              :         case GL_RGBA16UI:
     660              :         case GL_RGBA32UI:
     661              :         case GL_RGBA8I:
     662              :         case GL_RGBA16I:
     663              :         case GL_RGBA32I:
     664              :         {
     665            0 :             if(!format)
     666              :             {
     667            0 :                 format = GL_RGBA_INTEGER;
     668              :             }
     669            0 :             break;
     670              :         }
     671            0 :         case GL_R8UI:
     672              :         case GL_R16UI:
     673              :         case GL_R32UI:
     674              :         case GL_R8I:
     675              :         case GL_R16I:
     676              :         case GL_R32I:
     677              :         {
     678            0 :             if(!format)
     679              :             {
     680            0 :                 format = GL_RED_INTEGER;
     681              :             }
     682            0 :             break;
     683              :         }
     684            0 :         case GL_RG8UI:
     685              :         case GL_RG16UI:
     686              :         case GL_RG32UI:
     687              :         case GL_RG8I:
     688              :         case GL_RG16I:
     689              :         case GL_RG32I:
     690              :         {
     691            0 :             if(!format)
     692              :             {
     693            0 :                 format = GL_RG_INTEGER;
     694              :             }
     695            0 :             break;
     696              :         }
     697              :     }
     698            1 :     if(!format)
     699              :     {
     700            0 :         format = component;
     701              :     }
     702            1 :     return type;
     703              : }
     704              : 
     705            1 : void createtexture(int tnum, int w, int h, const void *pixels, int clamp, int filter, GLenum component, GLenum subtarget, int pw, int ph, int pitch, bool resize, GLenum format, bool swizzle)
     706              : {
     707            1 :     GLenum target = textarget(subtarget),
     708            1 :            type = textype(component, format);
     709            1 :     if(tnum)
     710              :     {
     711            1 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     712              :     }
     713            1 :     if(!pw)
     714              :     {
     715            0 :         pw = w;
     716              :     }
     717            1 :     if(!ph)
     718              :     {
     719            0 :         ph = h;
     720              :     }
     721            1 :     int tw = w,
     722            1 :         th = h;
     723            1 :     bool mipmap = filter > 1;
     724            1 :     if(resize && pixels)
     725              :     {
     726            0 :         resizetexture(w, h, mipmap, false, target, tw, th);
     727              :     }
     728            1 :     uploadtexture(subtarget, component, tw, th, format, type, pixels, pw, ph, pitch, mipmap);
     729            1 : }
     730              : 
     731            0 : static void createcompressedtexture(int tnum, int w, int h, const uchar *data, int align, int blocksize, int levels, int clamp, int filter, GLenum format, GLenum subtarget, bool swizzle = false)
     732              : {
     733            0 :     GLenum target = textarget(subtarget);
     734            0 :     if(tnum)
     735              :     {
     736            0 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     737              :     }
     738            0 :     uploadcompressedtexture(target, subtarget, format, w, h, data, align, blocksize, levels, filter > 1);
     739            0 : }
     740              : 
     741            0 : void create3dtexture(int tnum, int w, int h, int d, const void *pixels, int clamp, int filter, GLenum component, GLenum target, bool swizzle)
     742              : {
     743            0 :     GLenum format = GL_FALSE, type = textype(component, format);
     744            0 :     if(tnum)
     745              :     {
     746            0 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     747              :     }
     748            0 :     glTexImage3D(target, 0, component, w, h, d, 0, format, type, pixels);
     749            0 : }
     750              : 
     751              : std::unordered_map<std::string, Texture> textures;
     752              : 
     753              : Texture *notexture = nullptr; // used as default, ensured to be loaded
     754              : 
     755            2 : GLenum texformat(int bpp)
     756              : {
     757            2 :     switch(bpp)
     758              :     {
     759            2 :         case 1:
     760              :         {
     761            2 :             return GL_RED;
     762              :         }
     763            0 :         case 2:
     764              :         {
     765            0 :             return GL_RG;
     766              :         }
     767            0 :         case 3:
     768              :         {
     769            0 :             return GL_RGB;
     770              :         }
     771            0 :         case 4:
     772              :         {
     773            0 :             return GL_RGBA;
     774              :         }
     775            0 :         default:
     776              :         {
     777            0 :             return 0;
     778              :         }
     779              :     }
     780              : }
     781              : 
     782            1 : static bool alphaformat(GLenum format)
     783              : {
     784            1 :     switch(format)
     785              :     {
     786            0 :         case GL_RG:
     787              :         case GL_RGBA:
     788              :         {
     789            0 :             return true;
     790              :         }
     791            1 :         default:
     792              :         {
     793            1 :             return false;
     794              :         }
     795              :     }
     796              : }
     797              : 
     798            0 : bool floatformat(GLenum format)
     799              : {
     800            0 :     switch(format)
     801              :     {
     802            0 :         case GL_R16F:
     803              :         case GL_R32F:
     804              :         case GL_RG16F:
     805              :         case GL_RG32F:
     806              :         case GL_RGB16F:
     807              :         case GL_RGB32F:
     808              :         case GL_R11F_G11F_B10F:
     809              :         case GL_RGBA16F:
     810              :         case GL_RGBA32F:
     811              :         {
     812            0 :             return true;
     813              :         }
     814            0 :         default:
     815              :         {
     816            0 :             return false;
     817              :         }
     818              :     }
     819              : }
     820              : 
     821            1 : static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clamp = 0, bool mipit = true, bool canreduce = false, bool transient = false, int compress = 0)
     822              : {
     823            1 :     if(!t)
     824              :     {
     825            1 :         char *key = newstring(rname);
     826            2 :         auto itr = textures.insert_or_assign(key, Texture()).first;
     827            1 :         t = &(*itr).second;
     828            1 :         t->name = key;
     829              :     }
     830              : 
     831            1 :     t->clamp = clamp;
     832            1 :     t->mipmap = mipit;
     833            1 :     t->type = Texture::IMAGE;
     834            1 :     if(transient)
     835              :     {
     836            0 :         t->type |= Texture::TRANSIENT;
     837              :     }
     838            1 :     if(clamp&0x300)
     839              :     {
     840            0 :         t->type |= Texture::MIRROR;
     841              :     }
     842            1 :     if(!s.data)
     843              :     {
     844            0 :         t->type |= Texture::STUB;
     845            0 :         t->w = t->h = t->xs = t->ys = t->bpp = 0;
     846            0 :         return t;
     847              :     }
     848              : 
     849            1 :     bool swizzle = !(clamp&0x10000);
     850              :     GLenum format;
     851            1 :     format = texformat(s.bpp);
     852            1 :     t->bpp = s.bpp;
     853            1 :     if(alphaformat(format))
     854              :     {
     855            0 :         t->type |= Texture::ALPHA;
     856              :     }
     857            1 :     t->w = t->xs = s.w;
     858            1 :     t->h = t->ys = s.h;
     859            1 :     int filter = !canreduce || reducefilter ? (mipit ? 2 : 1) : 0;
     860            1 :     glGenTextures(1, &t->id);
     861            1 :     if(s.compressed)
     862              :     {
     863            0 :         uchar *data = s.data;
     864            0 :         int levels = s.levels, level = 0;
     865            0 :         int sizelimit = mipit && maxtexsize ? std::min(maxtexsize, hwtexsize) : hwtexsize;
     866            0 :         while(t->w > sizelimit || t->h > sizelimit)
     867              :         {
     868            0 :             data += s.calclevelsize(level++);
     869            0 :             levels--;
     870            0 :             if(t->w > 1)
     871              :             {
     872            0 :                 t->w /= 2;
     873              :             }
     874            0 :             if(t->h > 1)
     875              :             {
     876            0 :                 t->h /= 2;
     877              :             }
     878              :         }
     879            0 :         createcompressedtexture(t->id, t->w, t->h, data, s.align, s.bpp, levels, clamp, filter, s.compressed, GL_TEXTURE_2D, swizzle);
     880              :     }
     881              :     else
     882              :     {
     883            1 :         resizetexture(t->w, t->h, mipit, GL_TEXTURE_2D, compress, t->w, t->h);
     884            1 :         createtexture(t->id, t->w, t->h, s.data, clamp, filter, format, GL_TEXTURE_2D, t->xs, t->ys, s.pitch, false, format, swizzle);
     885              :     }
     886            1 :     return t;
     887              : }
     888              : 
     889            0 : static SDL_Surface *creatergbsurface(SDL_Surface *os)
     890              : {
     891            0 :     SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 24, 0x0000ff, 0x00ff00, 0xff0000, 0);
     892            0 :     if(ns)
     893              :     {
     894            0 :         SDL_BlitSurface(os, nullptr, ns, nullptr);
     895              :     }
     896            0 :     SDL_FreeSurface(os);
     897            0 :     return ns;
     898              : }
     899              : 
     900            0 : static SDL_Surface *creatergbasurface(SDL_Surface *os)
     901              : {
     902            0 :     SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
     903            0 :     if(ns)
     904              :     {
     905            0 :         SDL_SetSurfaceBlendMode(os, SDL_BLENDMODE_NONE);
     906            0 :         SDL_BlitSurface(os, nullptr, ns, nullptr);
     907              :     }
     908            0 :     SDL_FreeSurface(os);
     909            0 :     return ns;
     910              : }
     911              : 
     912            1 : static bool checkgrayscale(SDL_Surface *s)
     913              : {
     914              :     // gray scale images have 256 levels, no colorkey, and the palette is a ramp
     915            1 :     if(s->format->palette)
     916              :     {
     917            1 :         if(s->format->palette->ncolors != 256 || SDL_GetColorKey(s, nullptr) >= 0)
     918              :         {
     919            0 :             return false;
     920              :         }
     921            1 :         const SDL_Color *colors = s->format->palette->colors;
     922          257 :         for(int i = 0; i < 256; ++i)
     923              :         {
     924          256 :             if(colors[i].r != i || colors[i].g != i || colors[i].b != i)
     925              :             {
     926            0 :                 return false;
     927              :             }
     928              :         }
     929              :     }
     930            1 :     return true;
     931              : }
     932              : 
     933           10 : static SDL_Surface *fixsurfaceformat(SDL_Surface *s)
     934              : {
     935           10 :     if(!s)
     936              :     {
     937            9 :         return nullptr;
     938              :     }
     939            1 :     if(!s->pixels || std::min(s->w, s->h) <= 0 || s->format->BytesPerPixel <= 0)
     940              :     {
     941            0 :         SDL_FreeSurface(s);
     942            0 :         return nullptr;
     943              :     }
     944              :     static const uint rgbmasks[]  = { 0x0000ff, 0x00ff00, 0xff0000, 0 },
     945              :                       rgbamasks[] = { 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 };
     946            1 :     switch(s->format->BytesPerPixel)
     947              :     {
     948            1 :         case 1:
     949            1 :             if(!checkgrayscale(s))
     950              :             {
     951            0 :                 return SDL_GetColorKey(s, nullptr) >= 0 ? creatergbasurface(s) : creatergbsurface(s);
     952              :             }
     953            1 :             break;
     954            0 :         case 3:
     955            0 :             if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2])
     956              :             {
     957            0 :                 return creatergbsurface(s);
     958              :             }
     959            0 :             break;
     960            0 :         case 4:
     961            0 :             if(s->format->Rmask != rgbamasks[0] || s->format->Gmask != rgbamasks[1] || s->format->Bmask != rgbamasks[2] || s->format->Amask != rgbamasks[3])
     962              :             {
     963            0 :                 return s->format->Amask ? creatergbasurface(s) : creatergbsurface(s);
     964              :             }
     965            0 :             break;
     966              :     }
     967            1 :     return s;
     968              : }
     969              : 
     970           10 : SDL_Surface *loadsurface(const char *name)
     971              : {
     972           10 :     SDL_Surface *s = nullptr;
     973           10 :     stream *z = openzipfile(name, "rb");
     974           10 :     if(z)
     975              :     {
     976            0 :         SDL_RWops *rw = z->rwops();
     977            0 :         if(rw)
     978              :         {
     979            0 :             const char *ext = std::strrchr(name, '.');
     980            0 :             if(ext)
     981              :             {
     982            0 :                 ++ext;
     983              :             }
     984            0 :             s = IMG_LoadTyped_RW(rw, 0, ext);
     985            0 :             SDL_FreeRW(rw);
     986              :         }
     987            0 :         delete z;
     988              :     }
     989           10 :     if(!s)
     990              :     {
     991           10 :         s = IMG_Load(findfile(name, "rb"));
     992              :     }
     993           10 :     return fixsurfaceformat(s);
     994              : }
     995              : 
     996            2 : const uchar * Texture::loadalphamask()
     997              : {
     998            2 :     if(alphamask)
     999              :     {
    1000            0 :         return alphamask;
    1001              :     }
    1002            2 :     if(!(type&Texture::ALPHA))
    1003              :     {
    1004            2 :         return nullptr;
    1005              :     }
    1006            0 :     ImageData s;
    1007            0 :     if(!s.texturedata(name, false) || !s.data || s.compressed)
    1008              :     {
    1009            0 :         return nullptr;
    1010              :     }
    1011            0 :     alphamask = new uchar[s.h * ((s.w+7)/8)];
    1012            0 :     uchar *srcrow = s.data,
    1013            0 :           *dst = alphamask-1;
    1014            0 :     for(int y = 0; y < s.h; ++y)
    1015              :     {
    1016            0 :         uchar *src = srcrow+s.bpp-1;
    1017            0 :         for(int x = 0; x < s.w; ++x)
    1018              :         {
    1019            0 :             int offset = x%8;
    1020            0 :             if(!offset)
    1021              :             {
    1022            0 :                 *++dst = 0;
    1023              :             }
    1024            0 :             if(*src)
    1025              :             {
    1026            0 :                 *dst |= 1<<offset;
    1027              :             }
    1028            0 :             src += s.bpp;
    1029              :         }
    1030            0 :         srcrow += s.pitch;
    1031              :     }
    1032            0 :     return alphamask;
    1033            0 : }
    1034              : 
    1035            0 : float Texture::ratio() const
    1036              : {
    1037            0 :     return (w / static_cast<float>(h));
    1038              : }
    1039              : 
    1040           17 : Texture *textureload(const char *name, int clamp, bool mipit, bool msg)
    1041              : {
    1042           17 :     std::string tname(name);
    1043           17 :     auto itr = textures.find(path(tname));
    1044           17 :     if(itr != textures.end())
    1045              :     {
    1046            7 :         return &(*itr).second;
    1047              :     }
    1048           10 :     int compress = 0;
    1049           10 :     ImageData s;
    1050           10 :     if(s.texturedata(tname.c_str(), msg, &compress, &clamp))
    1051              :     {
    1052            1 :         return newtexture(nullptr, tname.c_str(), s, clamp, mipit, false, false, compress);
    1053              :     }
    1054            9 :     return notexture;
    1055           17 : }
    1056              : 
    1057            0 : bool settexture(const char *name, int clamp)
    1058              : {
    1059            0 :     Texture *t = textureload(name, clamp, true, false);
    1060            0 :     glBindTexture(GL_TEXTURE_2D, t->id);
    1061            0 :     return t != notexture;
    1062              : }
    1063              : 
    1064              : std::vector<VSlot *> vslots;
    1065              : std::vector<Slot *> slots;
    1066              : MatSlot materialslots[(MatFlag_Volume|MatFlag_Index)+1];
    1067              : Slot dummyslot;
    1068              : VSlot dummyvslot(&dummyslot);
    1069              : static std::vector<DecalSlot *> decalslots;
    1070              : DecalSlot dummydecalslot;
    1071              : Slot *defslot = nullptr;
    1072              : 
    1073            2 : const char *Slot::name() const
    1074              : {
    1075            2 :     return tempformatstring("slot %d", index);
    1076              : }
    1077              : 
    1078           32 : MatSlot::MatSlot() : Slot(static_cast<int>(this - materialslots)), VSlot(this) {}
    1079            0 : const char *MatSlot::name() const
    1080              : {
    1081            0 :     return tempformatstring("material slot %s", findmaterialname(Slot::index));
    1082              : }
    1083              : 
    1084            2 : const char *DecalSlot::name() const
    1085              : {
    1086            2 :     return tempformatstring("decal slot %d", Slot::index);
    1087              : }
    1088              : 
    1089            1 : void texturereset(int *n)
    1090              : {
    1091            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1092              :     {
    1093            1 :         return;
    1094              :     }
    1095            0 :     defslot = nullptr;
    1096            0 :     resetslotshader();
    1097            0 :     int limit = std::clamp(*n, 0, static_cast<int>(slots.size()));
    1098            0 :     for(size_t i = limit; i < slots.size(); i++)
    1099              :     {
    1100            0 :         Slot *s = slots[i];
    1101            0 :         for(VSlot *vs = s->variants; vs; vs = vs->next)
    1102              :         {
    1103            0 :             vs->slot = &dummyslot;
    1104              :         }
    1105            0 :         delete s;
    1106              :     }
    1107            0 :     slots.resize(limit);
    1108            0 :     while(vslots.size())
    1109              :     {
    1110            0 :         VSlot *vs = vslots.back();
    1111            0 :         if(vs->slot != &dummyslot || vs->changed)
    1112              :         {
    1113              :             break;
    1114              :         }
    1115            0 :         delete vslots.back();
    1116            0 :         vslots.pop_back();
    1117              :     }
    1118              : }
    1119              : 
    1120            1 : void materialreset()
    1121              : {
    1122            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1123              :     {
    1124            1 :         return;
    1125              :     }
    1126            0 :     defslot = nullptr;
    1127            0 :     for(int i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    1128              :     {
    1129            0 :         materialslots[i].reset();
    1130              :     }
    1131              : }
    1132              : 
    1133            1 : void decalreset(const int *n)
    1134              : {
    1135            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1136              :     {
    1137            1 :         return;
    1138              :     }
    1139            0 :     defslot = nullptr;
    1140            0 :     resetslotshader();
    1141            0 :     for(uint i = *n; i < decalslots.size(); ++i)
    1142              :     {
    1143            0 :         delete decalslots.at(i);
    1144              :     }
    1145            0 :     decalslots.resize(*n);
    1146              : }
    1147              : 
    1148              : static int compactedvslots = 0,
    1149              :            compactvslotsprogress = 0,
    1150              :            clonedvslots = 0;
    1151              : static bool markingvslots = false;
    1152              : 
    1153            0 : void clearslots()
    1154              : {
    1155            0 :     defslot = nullptr;
    1156            0 :     resetslotshader();
    1157            0 :     for(Slot * i : slots)
    1158              :     {
    1159            0 :         delete i;
    1160              :     }
    1161            0 :     slots.clear();
    1162            0 :     for(VSlot * i : vslots)
    1163              :     {
    1164            0 :         delete i;
    1165              :     }
    1166            0 :     vslots.clear();
    1167            0 :     for(int i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    1168              :     {
    1169            0 :         materialslots[i].reset();
    1170              :     }
    1171            0 :     decalslots.clear();
    1172            0 :     clonedvslots = 0;
    1173            0 : }
    1174              : 
    1175            0 : static void assignvslot(VSlot &vs)
    1176              : {
    1177            0 :     vs.index = compactedvslots++;
    1178            0 : }
    1179              : 
    1180            0 : void compactvslot(int &index)
    1181              : {
    1182            0 :     if(static_cast<long>(vslots.size()) > index)
    1183              :     {
    1184            0 :         VSlot &vs = *vslots[index];
    1185            0 :         if(vs.index < 0)
    1186              :         {
    1187            0 :             assignvslot(vs);
    1188              :         }
    1189            0 :         if(!markingvslots)
    1190              :         {
    1191            0 :             index = vs.index;
    1192              :         }
    1193              :     }
    1194            0 : }
    1195              : 
    1196            0 : void compactvslot(VSlot &vs)
    1197              : {
    1198            0 :     if(vs.index < 0)
    1199              :     {
    1200            0 :         assignvslot(vs);
    1201              :     }
    1202            0 : }
    1203              : 
    1204              : //n will be capped at 8
    1205            0 : void compactvslots(cube * const c, int n)
    1206              : {
    1207            0 :     if((compactvslotsprogress++&0xFFF)==0)
    1208              :     {
    1209            0 :         renderprogress(std::min(static_cast<float>(compactvslotsprogress)/allocnodes, 1.0f), markingvslots ? "marking slots..." : "compacting slots...");
    1210              :     }
    1211            0 :     for(int i = 0; i < std::min(n, 8); ++i)
    1212              :     {
    1213            0 :         if(c[i].children)
    1214              :         {
    1215            0 :             compactvslots(c[i].children->data());
    1216              :         }
    1217              :         else
    1218              :         {
    1219            0 :             for(int j = 0; j < 6; ++j)
    1220              :             {
    1221            0 :                 if(vslots.size() > c[i].texture[j])
    1222              :                 {
    1223            0 :                     VSlot &vs = *vslots[c[i].texture[j]];
    1224            0 :                     if(vs.index < 0)
    1225              :                     {
    1226            0 :                         assignvslot(vs);
    1227              :                     }
    1228            0 :                     if(!markingvslots)
    1229              :                     {
    1230            0 :                         c[i].texture[j] = vs.index;
    1231              :                     }
    1232              :                 }
    1233              :             }
    1234              :         }
    1235              :     }
    1236            0 : }
    1237              : 
    1238            1 : int cubeworld::compactvslots(bool cull)
    1239              : {
    1240            1 :     if(!worldroot)
    1241              :     {
    1242            1 :         conoutf(Console_Error, "no cube to compact");
    1243            1 :         return 0;
    1244              :     }
    1245            0 :     defslot = nullptr;
    1246            0 :     clonedvslots = 0;
    1247            0 :     markingvslots = cull;
    1248            0 :     compactedvslots = 0;
    1249            0 :     compactvslotsprogress = 0;
    1250            0 :     for(uint i = 0; i < vslots.size(); i++)
    1251              :     {
    1252            0 :         vslots[i]->index = -1;
    1253              :     }
    1254            0 :     if(cull)
    1255              :     {
    1256            0 :         uint numdefaults = std::min(static_cast<uint>(Default_NumDefaults), static_cast<uint>(slots.size()));
    1257            0 :         for(uint i = 0; i < numdefaults; ++i)
    1258              :         {
    1259            0 :             slots[i]->variants->index = compactedvslots++;
    1260              :         }
    1261              :     }
    1262              :     else
    1263              :     {
    1264            0 :         for(const Slot *i : slots)
    1265              :         {
    1266            0 :             i->variants->index = compactedvslots++;
    1267              :         }
    1268            0 :         for(const VSlot *i : vslots)
    1269              :         {
    1270            0 :             if(!i->changed && i->index < 0)
    1271              :             {
    1272            0 :                 markingvslots = true;
    1273            0 :                 break;
    1274              :             }
    1275              :         }
    1276              :     }
    1277            0 :     ::compactvslots(worldroot->data());
    1278            0 :     int total = compactedvslots;
    1279            0 :     compacteditvslots();
    1280            0 :     for(VSlot *i : vslots)
    1281              :     {
    1282            0 :         if(i->changed)
    1283              :         {
    1284            0 :             continue;
    1285              :         }
    1286            0 :         while(i->next)
    1287              :         {
    1288            0 :             if(i->next->index < 0)
    1289              :             {
    1290            0 :                 i->next = i->next->next;
    1291              :             }
    1292              :             else
    1293              :             {
    1294            0 :                 i = i->next;
    1295              :             }
    1296              :         }
    1297              :     }
    1298            0 :     if(markingvslots)
    1299              :     {
    1300            0 :         markingvslots = false;
    1301            0 :         compactedvslots = 0;
    1302            0 :         compactvslotsprogress = 0;
    1303            0 :         int lastdiscard = 0;
    1304            0 :         for(uint i = 0; i < vslots.size(); i++)
    1305              :         {
    1306            0 :             VSlot &vs = *vslots[i];
    1307            0 :             if(vs.changed || (vs.index < 0 && !vs.next))
    1308              :             {
    1309            0 :                 vs.index = -1;
    1310              :             }
    1311              :             else
    1312              :             {
    1313            0 :                 if(!cull)
    1314              :                 {
    1315            0 :                     while(lastdiscard < static_cast<int>(i))
    1316              :                     {
    1317            0 :                         VSlot &ds = *vslots[lastdiscard++];
    1318            0 :                         if(!ds.changed && ds.index < 0)
    1319              :                         {
    1320            0 :                             ds.index = compactedvslots++;
    1321              :                         }
    1322              :                     }
    1323              :                 }
    1324            0 :                 vs.index = compactedvslots++;
    1325              :             }
    1326              :         }
    1327            0 :         ::compactvslots(worldroot->data());
    1328            0 :         total = compactedvslots;
    1329            0 :         compacteditvslots();
    1330              :     }
    1331            0 :     compactmruvslots();
    1332            0 :     if(cull)
    1333              :     {
    1334            0 :         for(int i = static_cast<int>(slots.size()); --i >=0;) //note reverse iteration
    1335              :         {
    1336            0 :             if(slots[i]->variants->index < 0)
    1337              :             {
    1338            0 :                 delete slots.at(i);
    1339            0 :                 slots.erase(slots.begin() + i);
    1340              :             }
    1341              :         }
    1342            0 :         for(uint i = 0; i < slots.size(); i++)
    1343              :         {
    1344            0 :             slots[i]->index = i;
    1345              :         }
    1346              :     }
    1347            0 :     for(uint i = 0; i < vslots.size(); i++)
    1348              :     {
    1349            0 :         while(vslots[i]->index >= 0 && vslots[i]->index != static_cast<int>(i))
    1350              :         {
    1351            0 :             std::swap(vslots[i], vslots[vslots[i]->index]);
    1352              :         }
    1353              :     }
    1354            0 :     for(uint i = compactedvslots; i < vslots.size(); i++)
    1355              :     {
    1356            0 :         delete vslots[i];
    1357              :     }
    1358            0 :     vslots.resize(compactedvslots);
    1359            0 :     return total;
    1360              : }
    1361              : 
    1362            0 : static void clampvslotoffset(VSlot &dst, Slot *slot = nullptr)
    1363              : {
    1364            0 :     if(!slot)
    1365              :     {
    1366            0 :         slot = dst.slot;
    1367              :     }
    1368            0 :     if(slot && slot->sts.size())
    1369              :     {
    1370            0 :         if(!slot->loaded)
    1371              :         {
    1372            0 :             slot->load();
    1373              :         }
    1374            0 :         Texture *t = slot->sts[0].t;
    1375            0 :         int xs = t->xs,
    1376            0 :             ys = t->ys;
    1377            0 :         if(t->type & Texture::MIRROR)
    1378              :         {
    1379            0 :             xs *= 2;
    1380            0 :             ys *= 2;
    1381              :         }
    1382            0 :         if(texrotations[dst.rotation].swapxy)
    1383              :         {
    1384            0 :             std::swap(xs, ys);
    1385              :         }
    1386            0 :         dst.offset.x() %= xs;
    1387            0 :         if(dst.offset.x() < 0)
    1388              :         {
    1389            0 :             dst.offset.x() += xs;
    1390              :         }
    1391            0 :         dst.offset.y() %= ys;
    1392            0 :         if(dst.offset.y() < 0)
    1393              :         {
    1394            0 :             dst.offset.y() += ys;
    1395              :         }
    1396              :     }
    1397              :     else
    1398              :     {
    1399            0 :         dst.offset.max(0);
    1400              :     }
    1401            0 : }
    1402              : 
    1403            0 : static void propagatevslot(VSlot &dst, const VSlot &src, int diff, bool edit = false)
    1404              : {
    1405            0 :     if(diff & (1 << VSlot_ShParam))
    1406              :     {
    1407            0 :         for(uint i = 0; i < src.params.size(); i++)
    1408              :         {
    1409            0 :             dst.params.push_back(src.params[i]);
    1410              :         }
    1411              :     }
    1412            0 :     if(diff & (1 << VSlot_Scale))
    1413              :     {
    1414            0 :         dst.scale = src.scale;
    1415              :     }
    1416            0 :     if(diff & (1 << VSlot_Rotation))
    1417              :     {
    1418            0 :         dst.rotation = src.rotation;
    1419            0 :         if(edit && !dst.offset.iszero())
    1420              :         {
    1421            0 :             clampvslotoffset(dst);
    1422              :         }
    1423              :     }
    1424            0 :     if(diff & (1 << VSlot_Angle))
    1425              :     {
    1426            0 :         dst.angle = src.angle;
    1427              :     }
    1428            0 :     if(diff & (1 << VSlot_Offset))
    1429              :     {
    1430            0 :         dst.offset = src.offset;
    1431            0 :         if(edit)
    1432              :         {
    1433            0 :             clampvslotoffset(dst);
    1434              :         }
    1435              :     }
    1436            0 :     if(diff & (1 << VSlot_Scroll))
    1437              :     {
    1438            0 :         dst.scroll = src.scroll;
    1439              :     }
    1440            0 :     if(diff & (1 << VSlot_Alpha))
    1441              :     {
    1442            0 :         dst.alphafront = src.alphafront;
    1443            0 :         dst.alphaback = src.alphaback;
    1444              :     }
    1445            0 :     if(diff & (1 << VSlot_Color))
    1446              :     {
    1447            0 :         dst.colorscale = src.colorscale;
    1448              :     }
    1449            0 :     if(diff & (1 << VSlot_Refract))
    1450              :     {
    1451            0 :         dst.refractscale = src.refractscale;
    1452            0 :         dst.refractcolor = src.refractcolor;
    1453              :     }
    1454            0 : }
    1455              : 
    1456            0 : static void propagatevslot(const VSlot *root, int changed)
    1457              : {
    1458            0 :     for(VSlot *vs = root->next; vs; vs = vs->next)
    1459              :     {
    1460            0 :         int diff = changed & ~vs->changed;
    1461            0 :         if(diff)
    1462              :         {
    1463            0 :             propagatevslot(*vs, *root, diff);
    1464              :         }
    1465              :     }
    1466            0 : }
    1467              : 
    1468            0 : static void mergevslot(VSlot &dst, const VSlot &src, int diff, Slot *slot = nullptr)
    1469              : {
    1470            0 :     if(diff & (1 << VSlot_ShParam))
    1471              :     {
    1472            0 :         for(uint i = 0; i < src.params.size(); i++)
    1473              :         {
    1474            0 :             const SlotShaderParam &sp = src.params[i];
    1475            0 :             for(uint j = 0; j < dst.params.size(); j++)
    1476              :             {
    1477            0 :                 SlotShaderParam &dp = dst.params[j];
    1478            0 :                 if(sp.name == dp.name)
    1479              :                 {
    1480            0 :                     std::memcpy(dp.val, sp.val, sizeof(dp.val));
    1481            0 :                     goto nextparam; //bail out of loop
    1482              :                 }
    1483              :             }
    1484            0 :             dst.params.push_back(sp);
    1485            0 :         nextparam:;
    1486              :         }
    1487              :     }
    1488            0 :     if(diff & (1 << VSlot_Scale))
    1489              :     {
    1490            0 :         dst.scale = std::clamp(dst.scale*src.scale, 1/8.0f, 8.0f);
    1491              :     }
    1492            0 :     if(diff & (1 << VSlot_Rotation))
    1493              :     {
    1494            0 :         dst.rotation = std::clamp(dst.rotation + src.rotation, 0, 7);
    1495            0 :         if(!dst.offset.iszero())
    1496              :         {
    1497            0 :             clampvslotoffset(dst, slot);
    1498              :         }
    1499              :     }
    1500            0 :     if(diff & (1 << VSlot_Angle))
    1501              :     {
    1502            0 :         dst.angle.add(src.angle);
    1503              :     }
    1504            0 :     if(diff & (1 << VSlot_Offset))
    1505              :     {
    1506            0 :         dst.offset.add(src.offset);
    1507            0 :         clampvslotoffset(dst, slot);
    1508              :     }
    1509            0 :     if(diff & (1 << VSlot_Scroll))
    1510              :     {
    1511            0 :         dst.scroll.add(src.scroll);
    1512              :     }
    1513            0 :     if(diff & (1 << VSlot_Alpha))
    1514              :     {
    1515            0 :         dst.alphafront = src.alphafront;
    1516            0 :         dst.alphaback = src.alphaback;
    1517              :     }
    1518            0 :     if(diff & (1 << VSlot_Color))
    1519              :     {
    1520            0 :         dst.colorscale.mul(src.colorscale);
    1521              :     }
    1522            0 :     if(diff & (1 << VSlot_Refract))
    1523              :     {
    1524            0 :         dst.refractscale *= src.refractscale;
    1525            0 :         dst.refractcolor.mul(src.refractcolor);
    1526              :     }
    1527            0 : }
    1528              : 
    1529            0 : void mergevslot(VSlot &dst, const VSlot &src, const VSlot &delta)
    1530              : {
    1531            0 :     dst.changed = src.changed | delta.changed;
    1532            0 :     propagatevslot(dst, src, (1 << VSlot_Num) - 1);
    1533            0 :     mergevslot(dst, delta, delta.changed, src.slot);
    1534            0 : }
    1535              : 
    1536            0 : static VSlot *reassignvslot(Slot &owner, VSlot *vs)
    1537              : {
    1538            0 :     vs->reset();
    1539            0 :     owner.variants = vs;
    1540            0 :     while(vs)
    1541              :     {
    1542            0 :         vs->slot = &owner;
    1543            0 :         vs->linked = false;
    1544            0 :         vs = vs->next;
    1545              :     }
    1546            0 :     return owner.variants;
    1547              : }
    1548              : 
    1549            0 : static VSlot *emptyvslot(Slot &owner)
    1550              : {
    1551            0 :     int offset = 0;
    1552            0 :     for(int i = static_cast<int>(slots.size()); --i >=0;) //note reverse iteration
    1553              :     {
    1554            0 :         if(slots[i]->variants)
    1555              :         {
    1556            0 :             offset = slots[i]->variants->index + 1;
    1557            0 :             break;
    1558              :         }
    1559              :     }
    1560            0 :     for(uint i = offset; i < vslots.size(); i++)
    1561              :     {
    1562            0 :         if(!vslots[i]->changed)
    1563              :         {
    1564            0 :             return reassignvslot(owner, vslots[i]);
    1565              :         }
    1566              :     }
    1567            0 :     vslots.push_back(new VSlot(&owner, vslots.size()));
    1568            0 :     return vslots.back();
    1569              : }
    1570              : 
    1571            0 : static bool comparevslot(const VSlot &dst, const VSlot &src, int diff)
    1572              : {
    1573            0 :     if(diff & (1 << VSlot_ShParam))
    1574              :     {
    1575            0 :         if(src.params.size() != dst.params.size())
    1576              :         {
    1577            0 :             return false;
    1578              :         }
    1579            0 :         for(uint i = 0; i < src.params.size(); i++)
    1580              :         {
    1581            0 :             const SlotShaderParam &sp = src.params[i], &dp = dst.params[i];
    1582            0 :             if(sp.name != dp.name || std::memcmp(sp.val, dp.val, sizeof(sp.val)))
    1583              :             {
    1584            0 :                 return false;
    1585              :             }
    1586              :         }
    1587              :     }
    1588            0 :     if(diff & (1 << VSlot_Scale)   && dst.scale != src.scale)       return false;
    1589            0 :     if(diff & (1 << VSlot_Rotation)&& dst.rotation != src.rotation) return false;
    1590            0 :     if(diff & (1 << VSlot_Angle)   && dst.angle != src.angle)       return false;
    1591            0 :     if(diff & (1 << VSlot_Offset)  && dst.offset != src.offset)     return false;
    1592            0 :     if(diff & (1 << VSlot_Scroll)  && dst.scroll != src.scroll)     return false;
    1593            0 :     if(diff & (1 << VSlot_Alpha)   && (dst.alphafront != src.alphafront || dst.alphaback != src.alphaback)) return false;
    1594            0 :     if(diff & (1 << VSlot_Color)   && dst.colorscale != src.colorscale) return false;
    1595            0 :     if(diff & (1 << VSlot_Refract) && (dst.refractscale != src.refractscale || dst.refractcolor != src.refractcolor)) return false;
    1596            0 :     return true;
    1597              : }
    1598              : 
    1599            0 : void packvslot(std::vector<uchar> &buf, const VSlot &src)
    1600              : {
    1601            0 :     if(src.changed & (1 << VSlot_ShParam))
    1602              :     {
    1603            0 :         for(uint i = 0; i < src.params.size(); i++)
    1604              :         {
    1605            0 :             const SlotShaderParam &p = src.params[i];
    1606            0 :             buf.push_back(VSlot_ShParam);
    1607            0 :             sendstring(p.name, buf);
    1608            0 :             for(int j = 0; j < 4; ++j)
    1609              :             {
    1610            0 :                 putfloat(buf, p.val[j]);
    1611              :             }
    1612              :         }
    1613              :     }
    1614            0 :     if(src.changed & (1 << VSlot_Scale))
    1615              :     {
    1616            0 :         buf.push_back(VSlot_Scale);
    1617            0 :         putfloat(buf, src.scale);
    1618              :     }
    1619            0 :     if(src.changed & (1 << VSlot_Rotation))
    1620              :     {
    1621            0 :         buf.push_back(VSlot_Rotation);
    1622            0 :         putint(buf, src.rotation);
    1623              :     }
    1624            0 :     if(src.changed & (1 << VSlot_Angle))
    1625              :     {
    1626            0 :         buf.push_back(VSlot_Angle);
    1627            0 :         putfloat(buf, src.angle.x);
    1628            0 :         putfloat(buf, src.angle.y);
    1629            0 :         putfloat(buf, src.angle.z);
    1630              :     }
    1631            0 :     if(src.changed & (1 << VSlot_Offset))
    1632              :     {
    1633            0 :         buf.push_back(VSlot_Offset);
    1634            0 :         putint(buf, src.offset.x());
    1635            0 :         putint(buf, src.offset.y());
    1636              :     }
    1637            0 :     if(src.changed & (1 << VSlot_Scroll))
    1638              :     {
    1639            0 :         buf.push_back(VSlot_Scroll);
    1640            0 :         putfloat(buf, src.scroll.x);
    1641            0 :         putfloat(buf, src.scroll.y);
    1642              :     }
    1643            0 :     if(src.changed & (1 << VSlot_Alpha))
    1644              :     {
    1645            0 :         buf.push_back(VSlot_Alpha);
    1646            0 :         putfloat(buf, src.alphafront);
    1647            0 :         putfloat(buf, src.alphaback);
    1648              :     }
    1649            0 :     if(src.changed & (1 << VSlot_Color))
    1650              :     {
    1651            0 :         buf.push_back(VSlot_Color);
    1652            0 :         putfloat(buf, src.colorscale.r());
    1653            0 :         putfloat(buf, src.colorscale.g());
    1654            0 :         putfloat(buf, src.colorscale.b());
    1655              :     }
    1656            0 :     if(src.changed & (1 << VSlot_Refract))
    1657              :     {
    1658            0 :         buf.push_back(VSlot_Refract);
    1659            0 :         putfloat(buf, src.refractscale);
    1660            0 :         putfloat(buf, src.refractcolor.r());
    1661            0 :         putfloat(buf, src.refractcolor.g());
    1662            0 :         putfloat(buf, src.refractcolor.b());
    1663              :     }
    1664            0 :     buf.push_back(0xFF);
    1665            0 : }
    1666              : 
    1667              : //used in iengine.h
    1668            0 : void packvslot(std::vector<uchar> &buf, int index)
    1669              : {
    1670            0 :     if(static_cast<long>(vslots.size()) > index)
    1671              :     {
    1672            0 :         packvslot(buf, *vslots[index]);
    1673              :     }
    1674              :     else
    1675              :     {
    1676            0 :         buf.push_back(0xFF);
    1677              :     }
    1678            0 : }
    1679              : 
    1680              : //used in iengine.h
    1681            0 : void packvslot(std::vector<uchar> &buf, const VSlot *vs)
    1682              : {
    1683            0 :     if(vs)
    1684              :     {
    1685            0 :         packvslot(buf, *vs);
    1686              :     }
    1687              :     else
    1688              :     {
    1689            0 :         buf.push_back(0xFF);
    1690              :     }
    1691            0 : }
    1692              : 
    1693            0 : bool unpackvslot(ucharbuf &buf, VSlot &dst, bool delta)
    1694              : {
    1695            0 :     while(buf.remaining())
    1696              :     {
    1697            0 :         int changed = buf.get();
    1698            0 :         if(changed >= 0x80)
    1699              :         {
    1700            0 :             break;
    1701              :         }
    1702            0 :         switch(changed)
    1703              :         {
    1704            0 :             case VSlot_ShParam:
    1705              :             {
    1706              :                 string name;
    1707            0 :                 getstring(name, buf);
    1708            0 :                 SlotShaderParam p = { name[0] ? getshaderparamname(name) : nullptr, SIZE_MAX, 0, { 0, 0, 0, 0 } };
    1709            0 :                 for(int i = 0; i < 4; ++i)
    1710              :                 {
    1711            0 :                     p.val[i] = getfloat(buf);
    1712              :                 }
    1713            0 :                 if(p.name)
    1714              :                 {
    1715            0 :                     dst.params.push_back(p);
    1716              :                 }
    1717            0 :                 break;
    1718              :             }
    1719            0 :             case VSlot_Scale:
    1720              :             {
    1721            0 :                 dst.scale = getfloat(buf);
    1722            0 :                 if(dst.scale <= 0)
    1723              :                 {
    1724            0 :                     dst.scale = 1;
    1725              :                 }
    1726            0 :                 else if(!delta)
    1727              :                 {
    1728            0 :                     dst.scale = std::clamp(dst.scale, 1/8.0f, 8.0f);
    1729              :                 }
    1730            0 :                 break;
    1731              :             }
    1732            0 :             case VSlot_Rotation:
    1733            0 :                 dst.rotation = getint(buf);
    1734            0 :                 if(!delta)
    1735              :                 {
    1736            0 :                     dst.rotation = std::clamp(dst.rotation, 0, 7);
    1737              :                 }
    1738            0 :                 break;
    1739            0 :             case VSlot_Angle:
    1740              :             {
    1741            0 :                 dst.angle.x = getfloat(buf);
    1742            0 :                 dst.angle.y = getfloat(buf);
    1743            0 :                 dst.angle.z = getfloat(buf);
    1744            0 :                 break;
    1745              :             }
    1746            0 :             case VSlot_Offset:
    1747              :             {
    1748            0 :                 dst.offset.x() = getint(buf);
    1749            0 :                 dst.offset.y() = getint(buf);
    1750            0 :                 if(!delta)
    1751              :                 {
    1752            0 :                     dst.offset.max(0);
    1753              :                 }
    1754            0 :                 break;
    1755              :             }
    1756            0 :             case VSlot_Scroll:
    1757              :             {
    1758            0 :                 dst.scroll.x = getfloat(buf);
    1759            0 :                 dst.scroll.y = getfloat(buf);
    1760            0 :                 break;
    1761              :             }
    1762            0 :             case VSlot_Alpha:
    1763              :             {
    1764            0 :                 dst.alphafront = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1765            0 :                 dst.alphaback = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1766            0 :                 break;
    1767              :             }
    1768            0 :             case VSlot_Color:
    1769              :             {
    1770            0 :                 dst.colorscale.r() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1771            0 :                 dst.colorscale.g() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1772            0 :                 dst.colorscale.b() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1773            0 :                 break;
    1774              :             }
    1775            0 :             case VSlot_Refract:
    1776              :             {
    1777            0 :                 dst.refractscale = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1778            0 :                 dst.refractcolor.r() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1779            0 :                 dst.refractcolor.g() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1780            0 :                 dst.refractcolor.b() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1781            0 :                 break;
    1782              :             }
    1783            0 :             default:
    1784              :             {
    1785            0 :                 return false;
    1786              :             }
    1787              :         }
    1788            0 :         dst.changed |= 1<<changed;
    1789              :     }
    1790            0 :     if(buf.overread())
    1791              :     {
    1792            0 :         return false;
    1793              :     }
    1794            0 :     return true;
    1795              : }
    1796              : 
    1797            0 : VSlot *findvslot(const Slot &slot, const VSlot &src, const VSlot &delta)
    1798              : {
    1799            0 :     for(VSlot *dst = slot.variants; dst; dst = dst->next)
    1800              :     {
    1801            0 :         if((!dst->changed || dst->changed == (src.changed | delta.changed)) &&
    1802            0 :            comparevslot(*dst, src, src.changed & ~delta.changed) &&
    1803            0 :            comparevslot(*dst, delta, delta.changed))
    1804              :         {
    1805            0 :             return dst;
    1806              :         }
    1807              :     }
    1808            0 :     return nullptr;
    1809              : }
    1810              : 
    1811            0 : static VSlot *clonevslot(const VSlot &src, const VSlot &delta)
    1812              : {
    1813            0 :     vslots.push_back(new VSlot(src.slot, vslots.size()));
    1814            0 :     VSlot *dst = vslots.back();
    1815            0 :     dst->changed = src.changed | delta.changed;
    1816            0 :     propagatevslot(*dst, src, ((1 << VSlot_Num) - 1) & ~delta.changed);
    1817            0 :     propagatevslot(*dst, delta, delta.changed, true);
    1818            0 :     return dst;
    1819              : }
    1820              : 
    1821              : VARP(autocompactvslots, 0, 256, 0x10000);
    1822              : 
    1823            0 : VSlot *editvslot(const VSlot &src, const VSlot &delta)
    1824              : {
    1825            0 :     VSlot *exists = findvslot(*src.slot, src, delta);
    1826            0 :     if(exists)
    1827              :     {
    1828            0 :         return exists;
    1829              :     }
    1830            0 :     if(vslots.size()>=0x10000)
    1831              :     {
    1832            0 :         ::rootworld.compactvslots();
    1833            0 :         rootworld.allchanged();
    1834            0 :         if(vslots.size()>=0x10000)
    1835              :         {
    1836            0 :             return nullptr;
    1837              :         }
    1838              :     }
    1839            0 :     if(autocompactvslots && ++clonedvslots >= autocompactvslots)
    1840              :     {
    1841            0 :         ::rootworld.compactvslots();
    1842            0 :         rootworld.allchanged();
    1843              :     }
    1844            0 :     return clonevslot(src, delta);
    1845              : }
    1846              : 
    1847            0 : static void fixinsidefaces(std::array<cube, 8> &c, const ivec &o, int size, int tex)
    1848              : {
    1849            0 :     for(int i = 0; i < 8; ++i)
    1850              :     {
    1851            0 :         ivec co(i, o, size);
    1852            0 :         if(c[i].children)
    1853              :         {
    1854            0 :             fixinsidefaces(*(c[i].children), co, size>>1, tex);
    1855              :         }
    1856              :         else
    1857              :         {
    1858            0 :             for(int j = 0; j < 6; ++j)
    1859              :             {
    1860            0 :                 if(!visibletris(c[i], j, co, size))
    1861              :                 {
    1862            0 :                     c[i].texture[j] = tex;
    1863              :                 }
    1864              :             }
    1865              :         }
    1866              :     }
    1867            0 : }
    1868              : 
    1869            1 : int findslottex(const char *name)
    1870              : {
    1871              : 
    1872              :     const struct slottex
    1873              :     {
    1874              :         const char *name;
    1875              :         int id;
    1876            1 :     } slottexs[] =
    1877              :     {
    1878              :         {"0", Tex_Diffuse},
    1879              :         {"1", Tex_Unknown},
    1880              : 
    1881              :         {"c", Tex_Diffuse},
    1882              :         {"u", Tex_Unknown},
    1883              :         {"n", Tex_Normal},
    1884              :         {"g", Tex_Glow},
    1885              :         {"s", Tex_Spec},
    1886              :         {"z", Tex_Depth},
    1887              :         {"a", Tex_Alpha}
    1888              :     };
    1889              : 
    1890           10 :     for(int i = 0; i < static_cast<int>(sizeof(slottexs)/sizeof(slottex)); ++i)
    1891              :     {
    1892            9 :         if(!std::strcmp(slottexs[i].name, name))
    1893              :         {
    1894            0 :             return slottexs[i].id;
    1895              :         }
    1896              :     }
    1897            1 :     return -1;
    1898              : }
    1899              : 
    1900            1 : static void texture(char *type, char *name, int *rot, int *xoffset, int *yoffset, float *scale)
    1901              : {
    1902            1 :     int tnum = findslottex(type), matslot;
    1903            1 :     if(tnum == Tex_Diffuse)
    1904              :     {
    1905            0 :         if(slots.size() >= 0x10000)
    1906              :         {
    1907            0 :             return;
    1908              :         }
    1909            0 :         slots.push_back(new Slot(slots.size()));
    1910            0 :         defslot = slots.back();
    1911              :     }
    1912            1 :     else if(!std::strcmp(type, "decal"))
    1913              :     {
    1914            0 :         if(decalslots.size() >= 0x10000)
    1915              :         {
    1916            0 :             return;
    1917              :         }
    1918            0 :         tnum = Tex_Diffuse;
    1919            0 :         decalslots.push_back(new DecalSlot(decalslots.size()));
    1920            0 :         defslot = decalslots.back();
    1921              :     }
    1922            1 :     else if((matslot = findmaterial(type)) >= 0)
    1923              :     {
    1924            0 :         tnum = Tex_Diffuse;
    1925            0 :         defslot = &materialslots[matslot];
    1926            0 :         defslot->reset();
    1927              :     }
    1928            1 :     else if(!defslot)
    1929              :     {
    1930            1 :         return;
    1931              :     }
    1932            0 :     else if(tnum < 0)
    1933              :     {
    1934            0 :         tnum = Tex_Unknown;
    1935              :     }
    1936            0 :     Slot &s = *defslot;
    1937            0 :     s.loaded = false;
    1938            0 :     s.texmask |= 1<<tnum;
    1939            0 :     if(s.sts.size()>=8)
    1940              :     {
    1941            0 :         conoutf(Console_Warn, "warning: too many textures in %s", s.name());
    1942              :     }
    1943            0 :     s.sts.emplace_back();
    1944            0 :     Slot::Tex &st = s.sts.back();
    1945            0 :     st.type = tnum;
    1946            0 :     copystring(st.name, name);
    1947            0 :     path(st.name);
    1948            0 :     if(tnum == Tex_Diffuse)
    1949              :     {
    1950            0 :         setslotshader(s);
    1951            0 :         VSlot &vs = s.emptyvslot();
    1952            0 :         vs.rotation = std::clamp(*rot, 0, 7);
    1953            0 :         vs.offset = ivec2(*xoffset, *yoffset).max(0);
    1954            0 :         vs.scale = *scale <= 0 ? 1 : *scale;
    1955            0 :         propagatevslot(&vs, (1 << VSlot_Num) - 1);
    1956              :     }
    1957              : }
    1958              : 
    1959            1 : void texgrass(char *name)
    1960              : {
    1961            1 :     if(!defslot)
    1962              :     {
    1963            1 :         return;
    1964              :     }
    1965            0 :     Slot &s = *defslot;
    1966            0 :     delete[] s.grass;
    1967            0 :     s.grass = name[0] ? newstring(makerelpath("media/texture", name)) : nullptr;
    1968              : }
    1969              : 
    1970            1 : void texscroll(float *scrollS, float *scrollT)
    1971              : {
    1972            1 :     if(!defslot)
    1973              :     {
    1974            1 :         return;
    1975              :     }
    1976            0 :     Slot &s = *defslot;
    1977            0 :     s.variants->scroll = vec2(*scrollS/1000.0f, *scrollT/1000.0f);
    1978            0 :     propagatevslot(s.variants, 1 << VSlot_Scroll);
    1979              : }
    1980              : 
    1981            1 : void texoffset_(int *xoffset, int *yoffset)
    1982              : {
    1983            1 :     if(!defslot)
    1984              :     {
    1985            1 :         return;
    1986              :     }
    1987            0 :     Slot &s = *defslot;
    1988            0 :     s.variants->offset = ivec2(*xoffset, *yoffset).max(0);
    1989            0 :     propagatevslot(s.variants, 1 << VSlot_Offset);
    1990              : }
    1991              : 
    1992            1 : void texrotate_(int *rot)
    1993              : {
    1994            1 :     if(!defslot)
    1995              :     {
    1996            1 :         return;
    1997              :     }
    1998            0 :     Slot &s = *defslot;
    1999            0 :     s.variants->rotation = std::clamp(*rot, 0, 7);
    2000            0 :     propagatevslot(s.variants, 1 << VSlot_Rotation);
    2001              : }
    2002              : 
    2003            1 : void texangle_(float *a)
    2004              : {
    2005            1 :     if(!defslot)
    2006              :     {
    2007            1 :         return;
    2008              :     }
    2009            0 :     Slot &s = *defslot;
    2010            0 :     s.variants->angle = vec(*a, std::sin(*a/RAD), std::cos(*a/RAD));
    2011            0 :     propagatevslot(s.variants, 1 << VSlot_Angle);
    2012              : }
    2013              : 
    2014            1 : void texscale(float *scale)
    2015              : {
    2016            1 :     if(!defslot)
    2017              :     {
    2018            1 :         return;
    2019              :     }
    2020            0 :     Slot &s = *defslot;
    2021            0 :     s.variants->scale = *scale <= 0 ? 1 : *scale;
    2022            0 :     propagatevslot(s.variants, 1 << VSlot_Scale);
    2023              : }
    2024              : 
    2025            1 : void texalpha(float *front, float *back)
    2026              : {
    2027            1 :     if(!defslot)
    2028              :     {
    2029            1 :         return;
    2030              :     }
    2031            0 :     Slot &s = *defslot;
    2032            0 :     s.variants->alphafront = std::clamp(*front, 0.0f, 1.0f);
    2033            0 :     s.variants->alphaback = std::clamp(*back, 0.0f, 1.0f);
    2034            0 :     propagatevslot(s.variants, 1 << VSlot_Alpha);
    2035              : }
    2036              : 
    2037            1 : void texcolor(float *r, float *g, float *b)
    2038              : {
    2039            1 :     if(!defslot)
    2040              :     {
    2041            1 :         return;
    2042              :     }
    2043            0 :     Slot &s = *defslot;
    2044            0 :     s.variants->colorscale = vec(std::clamp(*r, 0.0f, 2.0f), std::clamp(*g, 0.0f, 2.0f), std::clamp(*b, 0.0f, 2.0f));
    2045            0 :     propagatevslot(s.variants, 1 << VSlot_Color);
    2046              : }
    2047              : 
    2048            1 : void texrefract(float *k, float *r, float *g, float *b)
    2049              : {
    2050            1 :     if(!defslot)
    2051              :     {
    2052            1 :         return;
    2053              :     }
    2054            0 :     Slot &s = *defslot;
    2055            0 :     s.variants->refractscale = std::clamp(*k, 0.0f, 1.0f);
    2056            0 :     if(s.variants->refractscale > 0 && (*r > 0 || *g > 0 || *b > 0))
    2057              :     {
    2058            0 :         s.variants->refractcolor = vec(std::clamp(*r, 0.0f, 1.0f), std::clamp(*g, 0.0f, 1.0f), std::clamp(*b, 0.0f, 1.0f));
    2059              :     }
    2060              :     else
    2061              :     {
    2062            0 :         s.variants->refractcolor = vec(1, 1, 1);
    2063              :     }
    2064            0 :     propagatevslot(s.variants, 1 << VSlot_Refract);
    2065              : }
    2066              : 
    2067            1 : void texsmooth(int *id, int *angle)
    2068              : {
    2069            1 :     if(!defslot)
    2070              :     {
    2071            1 :         return;
    2072              :     }
    2073            0 :     Slot &s = *defslot;
    2074            0 :     s.smooth = smoothangle(*id, *angle);
    2075              : }
    2076              : 
    2077            1 : void decaldepth(float *depth, float *fade)
    2078              : {
    2079            1 :     if(!defslot || defslot->type() != Slot::SlotType_Decal)
    2080              :     {
    2081            1 :         return;
    2082              :     }
    2083            0 :     DecalSlot &s = *static_cast<DecalSlot *>(defslot);
    2084            0 :     s.depth = std::clamp(*depth, 1e-3f, 1e3f);
    2085            0 :     s.fade = std::clamp(*fade, 0.0f, s.depth);
    2086              : }
    2087              : 
    2088            0 : int DecalSlot::cancombine(int type) const
    2089              : {
    2090            0 :     switch(type)
    2091              :     {
    2092            0 :         case Tex_Glow:
    2093              :         {
    2094            0 :             return Tex_Spec;
    2095              :         }
    2096            0 :         case Tex_Normal:
    2097              :         {
    2098            0 :             return texmask&(1 << Tex_Depth) ? Tex_Depth : (texmask & (1 << Tex_Glow) ? -1 : Tex_Spec);
    2099              :         }
    2100            0 :         default:
    2101              :         {
    2102            0 :             return -1;
    2103              :         }
    2104              :     }
    2105              : }
    2106              : 
    2107            0 : bool DecalSlot::shouldpremul(int type) const
    2108              : {
    2109            0 :     switch(type)
    2110              :     {
    2111            0 :         case Tex_Diffuse:
    2112              :         {
    2113            0 :             return true;
    2114              :         }
    2115            0 :         default:
    2116              :         {
    2117            0 :             return false;
    2118              :         }
    2119              :     }
    2120              : }
    2121              : 
    2122            0 : static void addname(std::vector<char> &key, Slot &slot, Slot::Tex &t, bool combined = false, const char *prefix = nullptr)
    2123              : {
    2124            0 :     if(combined)
    2125              :     {
    2126            0 :         key.push_back('&');
    2127              :     }
    2128            0 :     if(prefix)
    2129              :     {
    2130            0 :         while(*prefix)
    2131              :         {
    2132            0 :             key.push_back(*prefix++);
    2133              :         }
    2134              :     }
    2135            0 :     DEF_FORMAT_STRING(tname, "%s/%s", slot.texturedir(), t.name);
    2136            0 :     for(const char *s = path(tname); *s; key.push_back(*s++))
    2137              :     {
    2138              :         //(empty body)
    2139              :     }
    2140            0 : }
    2141              : 
    2142              : // Slot object
    2143              : 
    2144            0 : VSlot &Slot::emptyvslot()
    2145              : {
    2146            0 :     return *::emptyvslot(*this);
    2147              : }
    2148              : 
    2149            0 : int Slot::findtextype(int type, int last) const
    2150              : {
    2151            0 :     for(uint i = last+1; i<sts.size(); i++)
    2152              :     {
    2153            0 :         if((type&(1<<sts[i].type)) && sts[i].combined<0)
    2154              :         {
    2155            0 :             return i;
    2156              :         }
    2157              :     }
    2158            0 :     return -1;
    2159              : }
    2160              : 
    2161            0 : int Slot::cancombine(int type) const
    2162              : {
    2163            0 :     switch(type)
    2164              :     {
    2165            0 :         case Tex_Diffuse:
    2166              :         {
    2167            0 :             return texmask&((1 << Tex_Spec) | (1 << Tex_Normal)) ? Tex_Spec : Tex_Alpha;
    2168              :         }
    2169            0 :         case Tex_Normal:
    2170              :         {
    2171            0 :             return texmask&(1 << Tex_Depth) ? Tex_Depth : Tex_Alpha;
    2172              :         }
    2173            0 :         default:
    2174              :         {
    2175            0 :             return -1;
    2176              :         }
    2177              :     }
    2178              : }
    2179              : 
    2180            0 : void Slot::load(int index, Slot::Tex &t)
    2181              : {
    2182            0 :     std::vector<char> key;
    2183            0 :     addname(key, *this, t, false, shouldpremul(t.type) ? "<premul>" : nullptr);
    2184            0 :     Slot::Tex *combine = nullptr;
    2185            0 :     for(uint i = 0; i < sts.size(); i++)
    2186              :     {
    2187            0 :         Slot::Tex &c = sts[i];
    2188            0 :         if(c.combined == index)
    2189              :         {
    2190            0 :             combine = &c;
    2191            0 :             addname(key, *this, c, true);
    2192            0 :             break;
    2193              :         }
    2194              :     }
    2195            0 :     key.push_back('\0');
    2196            0 :     auto itr = textures.find(key.data());
    2197            0 :     if(itr != textures.end())
    2198              :     {
    2199            0 :         t.t = &(*itr).second;
    2200            0 :         return;
    2201              :     }
    2202            0 :     t.t = nullptr;
    2203            0 :     int compress = 0,
    2204            0 :         wrap = 0;
    2205            0 :     ImageData ts;
    2206            0 :     if(!ts.texturedata(*this, t, true, &compress, &wrap))
    2207              :     {
    2208            0 :         t.t = notexture;
    2209            0 :         return;
    2210              :     }
    2211            0 :     if(!ts.compressed)
    2212              :     {
    2213            0 :         switch(t.type)
    2214              :         {
    2215            0 :             case Tex_Spec:
    2216              :             {
    2217            0 :                 if(ts.bpp > 1)
    2218              :                 {
    2219            0 :                     ts.collapsespec();
    2220              :                 }
    2221            0 :                 break;
    2222              :             }
    2223            0 :             case Tex_Glow:
    2224              :             case Tex_Diffuse:
    2225              :             case Tex_Normal:
    2226            0 :                 if(combine)
    2227              :                 {
    2228            0 :                     ImageData cs;
    2229            0 :                     if(cs.texturedata(*this, *combine))
    2230              :                     {
    2231            0 :                         if(cs.w!=ts.w || cs.h!=ts.h)
    2232              :                         {
    2233            0 :                             cs.scaleimage(ts.w, ts.h);
    2234              :                         }
    2235            0 :                         switch(combine->type)
    2236              :                         {
    2237            0 :                             case Tex_Spec:
    2238              :                             {
    2239            0 :                                 ts.mergespec(cs);
    2240            0 :                                 break;
    2241              :                             }
    2242            0 :                             case Tex_Depth:
    2243              :                             {
    2244            0 :                                 ts.mergedepth(cs);
    2245            0 :                                 break;
    2246              :                             }
    2247            0 :                             case Tex_Alpha:
    2248              :                             {
    2249            0 :                                 ts.mergealpha(cs);
    2250            0 :                                 break;
    2251              :                             }
    2252              :                         }
    2253              :                     }
    2254            0 :                 }
    2255            0 :                 if(ts.bpp < 3)
    2256              :                 {
    2257            0 :                     ts.swizzleimage();
    2258              :                 }
    2259            0 :                 break;
    2260              :         }
    2261              :     }
    2262            0 :     if(!ts.compressed && shouldpremul(t.type))
    2263              :     {
    2264            0 :         ts.texpremul();
    2265              :     }
    2266            0 :     t.t = newtexture(nullptr, key.data(), ts, wrap, true, true, true, compress);
    2267            0 : }
    2268              : 
    2269            0 : void Slot::load()
    2270              : {
    2271            0 :     linkslotshader(*this);
    2272            0 :     for(uint i = 0; i < sts.size(); i++)
    2273              :     {
    2274            0 :         Slot::Tex &t = sts[i];
    2275            0 :         if(t.combined >= 0)
    2276              :         {
    2277            0 :             continue;
    2278              :         }
    2279            0 :         int combine = cancombine(t.type);
    2280            0 :         if(combine >= 0 && (combine = findtextype(1<<combine)) >= 0)
    2281              :         {
    2282            0 :             Slot::Tex &c = sts[combine];
    2283            0 :             c.combined = i;
    2284              :         }
    2285              :     }
    2286            0 :     for(uint i = 0; i < sts.size(); i++)
    2287              :     {
    2288            0 :         Slot::Tex &t = sts[i];
    2289            0 :         if(t.combined >= 0)
    2290              :         {
    2291            0 :             continue;
    2292              :         }
    2293            0 :         switch(t.type)
    2294              :         {
    2295              :             default:
    2296              :             {
    2297            0 :                 load(i, t);
    2298            0 :                 break;
    2299              :             }
    2300              :         }
    2301              :     }
    2302            0 :     loaded = true;
    2303            0 : }
    2304              : 
    2305              : // VSlot
    2306              : 
    2307           40 : void VSlot::addvariant(Slot *slot)
    2308              : {
    2309           40 :     if(!slot->variants)
    2310              :     {
    2311           40 :         slot->variants = this;
    2312              :     }
    2313              :     else
    2314              :     {
    2315            0 :         VSlot *prev = slot->variants;
    2316            0 :         while(prev->next)
    2317              :         {
    2318            0 :             prev = prev->next;
    2319              :         }
    2320            0 :         prev->next = this;
    2321              :     }
    2322           40 : }
    2323              : 
    2324            0 : bool VSlot::isdynamic() const
    2325              : {
    2326            0 :     return !scroll.iszero() || slot->shader->isdynamic();
    2327              : }
    2328              : 
    2329              : // end of Slot/VSlot
    2330            0 : MatSlot &lookupmaterialslot(int index, bool load)
    2331              : {
    2332            0 :     MatSlot &s = materialslots[index];
    2333            0 :     if(load && !s.linked)
    2334              :     {
    2335            0 :         if(!s.loaded)
    2336              :         {
    2337            0 :             s.load();
    2338              :         }
    2339            0 :         linkvslotshader(s);
    2340            0 :         s.linked = true;
    2341              :     }
    2342            0 :     return s;
    2343              : }
    2344              : 
    2345            0 : Slot &lookupslot(int index, bool load)
    2346              : {
    2347            0 :     Slot &s = (static_cast<long>(slots.size()) > index) ? *slots[index] : ((slots.size() > Default_Geom) ? *slots[Default_Geom] : dummyslot);
    2348            0 :     if(!s.loaded && load)
    2349              :     {
    2350            0 :         s.load();
    2351              :     }
    2352            0 :     return s;
    2353              : }
    2354              : 
    2355            0 : VSlot &lookupvslot(int index, bool load)
    2356              : {
    2357            0 :     VSlot &s = (static_cast<long>(vslots.size()) > index) && vslots[index]->slot ? *vslots[index] : ((slots.size() > Default_Geom) && slots[Default_Geom]->variants ? *slots[Default_Geom]->variants : dummyvslot);
    2358            0 :     if(load && !s.linked)
    2359              :     {
    2360            0 :         if(!s.slot->loaded)
    2361              :         {
    2362            0 :             s.slot->load();
    2363              :         }
    2364            0 :         linkvslotshader(s);
    2365            0 :         s.linked = true;
    2366              :     }
    2367            0 :     return s;
    2368              : }
    2369              : 
    2370            0 : DecalSlot &lookupdecalslot(int index, bool load)
    2371              : {
    2372            0 :     DecalSlot &s = (static_cast<int>(decalslots.size()) > index) ? *decalslots[index] : dummydecalslot;
    2373            0 :     if(load && !s.linked)
    2374              :     {
    2375            0 :         if(!s.loaded)
    2376              :         {
    2377            0 :             s.load();
    2378              :         }
    2379            0 :         linkvslotshader(s);
    2380            0 :         s.linked = true;
    2381              :     }
    2382            0 :     return s;
    2383              : }
    2384              : 
    2385            0 : void linkslotshaders()
    2386              : {
    2387            0 :     for(Slot * const &i : slots)
    2388              :     {
    2389            0 :         if(i->loaded)
    2390              :         {
    2391            0 :             linkslotshader(*i);
    2392              :         }
    2393              :     }
    2394            0 :     for(VSlot * const &i : vslots)
    2395              :     {
    2396            0 :         if(i->linked)
    2397              :         {
    2398            0 :             linkvslotshader(*i);
    2399              :         }
    2400              :     }
    2401            0 :     for(uint i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    2402              :     {
    2403            0 :         if(materialslots[i].loaded)
    2404              :         {
    2405            0 :             linkslotshader(materialslots[i]);
    2406            0 :             linkvslotshader(materialslots[i]);
    2407              :         }
    2408              :     }
    2409            0 :     for(DecalSlot * const &i : decalslots)
    2410              :     {
    2411            0 :         if(i->loaded)
    2412              :         {
    2413            0 :             linkslotshader(*i);
    2414            0 :             linkvslotshader(*i);
    2415              :         }
    2416              :     }
    2417            0 : }
    2418              : 
    2419            0 : static void blitthumbnail(ImageData &d, ImageData &s, int x, int y)
    2420              : {
    2421            0 :     d.forcergbimage();
    2422            0 :     s.forcergbimage();
    2423            0 :     uchar *dstrow = &d.data[d.pitch*y + d.bpp*x],
    2424            0 :           *srcrow = s.data;
    2425            0 :     for(int y = 0; y < s.h; ++y)
    2426              :     {
    2427            0 :         for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; dst += d.bpp, src += s.bpp)
    2428              :         {
    2429            0 :             for(int k = 0; k < 3; ++k)
    2430              :             {
    2431            0 :                 dst[k] = src[k];
    2432              :             }
    2433              :         }
    2434            0 :         dstrow += d.pitch;
    2435            0 :         srcrow += s.pitch;
    2436              :     }
    2437            0 : }
    2438              : 
    2439            0 : Texture *Slot::loadthumbnail()
    2440              : {
    2441            0 :     if(thumbnail)
    2442              :     {
    2443            0 :         return thumbnail;
    2444              :     }
    2445            0 :     if(!variants)
    2446              :     {
    2447            0 :         thumbnail = notexture;
    2448            0 :         return thumbnail;
    2449              :     }
    2450            0 :     VSlot &vslot = *variants;
    2451            0 :     linkslotshader(*this, false);
    2452            0 :     linkvslotshader(vslot, false);
    2453            0 :     std::vector<char> name;
    2454            0 :     if(vslot.colorscale == vec(1, 1, 1))
    2455              :     {
    2456            0 :         addname(name, *this, sts[0], false, "<thumbnail>");
    2457              :     }
    2458              :     else
    2459              :     {
    2460            0 :         DEF_FORMAT_STRING(prefix, "<thumbnail:%.2f/%.2f/%.2f>", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z);
    2461            0 :         addname(name, *this, sts[0], false, prefix);
    2462              :     }
    2463            0 :     int glow = -1;
    2464            0 :     if(texmask&(1 << Tex_Glow))
    2465              :     {
    2466            0 :         for(uint j = 0; j < sts.size(); j++)
    2467              :         {
    2468            0 :             if(sts[j].type == Tex_Glow)
    2469              :             {
    2470            0 :                 glow = j;
    2471            0 :                 break;
    2472              :             }
    2473              :         }
    2474            0 :         if(glow >= 0)
    2475              :         {
    2476            0 :             DEF_FORMAT_STRING(prefix, "<glow:%.2f/%.2f/%.2f>", vslot.glowcolor.x, vslot.glowcolor.y, vslot.glowcolor.z);
    2477            0 :             addname(name, *this, sts[glow], true, prefix);
    2478              :         }
    2479              :     }
    2480            0 :     name.push_back('\0');
    2481            0 :     auto itr = textures.find(path(name.data()));
    2482            0 :     if(itr != textures.end())
    2483              :     {
    2484            0 :         thumbnail = &(*itr).second;
    2485            0 :         return &(*itr).second;
    2486              :     }
    2487              :     else
    2488              :     {
    2489            0 :         auto insert = textures.insert( { std::string(name.data()), Texture() } ).first;
    2490            0 :         Texture *t = &(*insert).second;
    2491            0 :         ImageData s, g, l, d;
    2492            0 :         s.texturedata(*this, sts[0], false);
    2493            0 :         if(glow >= 0)
    2494              :         {
    2495            0 :             g.texturedata(*this, sts[glow], false);
    2496              :         }
    2497            0 :         if(!s.data)
    2498              :         {
    2499            0 :             t = thumbnail = notexture;
    2500              :         }
    2501              :         else
    2502              :         {
    2503            0 :             if(vslot.colorscale != vec(1, 1, 1))
    2504              :             {
    2505            0 :                 s.texmad(vslot.colorscale, vec(0, 0, 0));
    2506              :             }
    2507            0 :             int xs = s.w, ys = s.h;
    2508            0 :             if(s.w > 128 || s.h > 128)
    2509              :             {
    2510            0 :                 s.scaleimage(std::min(s.w, 128), std::min(s.h, 128));
    2511              :             }
    2512            0 :             if(g.data)
    2513              :             {
    2514            0 :                 if(g.w != s.w || g.h != s.h) g.scaleimage(s.w, s.h);
    2515            0 :                 s.addglow(g, vslot.glowcolor);
    2516              :             }
    2517            0 :             if(l.data)
    2518              :             {
    2519            0 :                 if(l.w != s.w/2 || l.h != s.h/2)
    2520              :                 {
    2521            0 :                     l.scaleimage(s.w/2, s.h/2);
    2522              :                 }
    2523            0 :                 blitthumbnail(s, l, s.w-l.w, s.h-l.h);
    2524              :             }
    2525            0 :             if(d.data)
    2526              :             {
    2527            0 :                 if(vslot.colorscale != vec(1, 1, 1))
    2528              :                 {
    2529            0 :                     d.texmad(vslot.colorscale, vec(0, 0, 0));
    2530              :                 }
    2531            0 :                 if(d.w != s.w/2 || d.h != s.h/2)
    2532              :                 {
    2533            0 :                     d.scaleimage(s.w/2, s.h/2);
    2534              :                 }
    2535            0 :                 blitthumbnail(s, d, 0, 0);
    2536              :             }
    2537            0 :             if(s.bpp < 3)
    2538              :             {
    2539            0 :                 s.forcergbimage();
    2540              :             }
    2541            0 :             t = newtexture(nullptr, name.data(), s, 0, false, false, true);
    2542            0 :             t->xs = xs;
    2543            0 :             t->ys = ys;
    2544            0 :             thumbnail = t;
    2545              :         }
    2546            0 :         return t;
    2547            0 :     }
    2548            0 : }
    2549              : 
    2550              : // environment mapped reflections
    2551              : 
    2552            0 : void cleanuptextures()
    2553              : {
    2554            0 :     for(Slot * const &i : slots)
    2555              :     {
    2556            0 :         i->cleanup();
    2557              :     }
    2558            0 :     for(VSlot * const &i : vslots)
    2559              :     {
    2560            0 :         i->cleanup();
    2561              :     }
    2562            0 :     for(uint i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    2563              :     {
    2564            0 :         materialslots[i].cleanup();
    2565              :     }
    2566            0 :     for(DecalSlot * const &i : decalslots)
    2567              :     {
    2568            0 :         i->cleanup();
    2569              :     }
    2570            0 :     for(auto itr = textures.begin(); itr != textures.end(); ++itr)
    2571              :     {
    2572            0 :         Texture &t = (*itr).second;
    2573            0 :         delete[] t.alphamask;
    2574            0 :         t.alphamask = nullptr;
    2575            0 :         if(t.id)
    2576              :         {
    2577            0 :             glDeleteTextures(1, &t.id);
    2578            0 :             t.id = 0;
    2579              :         }
    2580            0 :         if(t.type&Texture::TRANSIENT)
    2581              :         {
    2582            0 :             itr = textures.erase(itr);
    2583              :         }
    2584              :     }
    2585            0 : }
    2586              : 
    2587            0 : bool reloadtexture(const char *name)
    2588              : {
    2589            0 :     auto itr = textures.find(path(std::string(name)));
    2590            0 :     if(itr != textures.end())
    2591              :     {
    2592            0 :         return (*itr).second.reload();
    2593              :     }
    2594            0 :     return true;
    2595              : }
    2596              : 
    2597            0 : bool Texture::reload()
    2598              : {
    2599            0 :     if(id)
    2600              :     {
    2601            0 :         return true;
    2602              :     }
    2603            0 :     switch(type&TYPE)
    2604              :     {
    2605            0 :         case IMAGE:
    2606              :         {
    2607            0 :             int compress = 0;
    2608            0 :             ImageData s;
    2609            0 :             if(!s.texturedata(name, true, &compress) || !newtexture(this, nullptr, s, clamp, mipmap, false, false, compress))
    2610              :             {
    2611            0 :                 return false;
    2612              :             }
    2613            0 :             break;
    2614            0 :         }
    2615              :     }
    2616            0 :     return true;
    2617              : }
    2618              : 
    2619            1 : void reloadtex(char *name)
    2620              : {
    2621            1 :     auto itr = textures.find(path(std::string(name)));
    2622            1 :     if(itr == textures.end())
    2623              :     {
    2624            1 :         conoutf(Console_Error, "texture %s is not loaded", name);
    2625            1 :         return;
    2626              :     }
    2627            0 :     Texture *t = &(*itr).second;
    2628            0 :     if(t->type&Texture::TRANSIENT)
    2629              :     {
    2630            0 :         conoutf(Console_Error, "can't reload transient texture %s", name);
    2631            0 :         return;
    2632              :     }
    2633            0 :     delete[] t->alphamask;
    2634            0 :     t->alphamask = nullptr;
    2635            0 :     Texture oldtex = *t;
    2636            0 :     t->id = 0;
    2637            0 :     if(!t->reload())
    2638              :     {
    2639            0 :         if(t->id)
    2640              :         {
    2641            0 :             glDeleteTextures(1, &t->id);
    2642              :         }
    2643            0 :         *t = oldtex;
    2644            0 :         conoutf(Console_Error, "failed to reload texture %s", name);
    2645              :     }
    2646              : }
    2647              : 
    2648            0 : void reloadtextures()
    2649              : {
    2650            0 :     int reloaded = 0;
    2651            0 :     for(auto &[k, t] : textures)
    2652              :     {
    2653            0 :         loadprogress = static_cast<float>(++reloaded)/textures.size();
    2654            0 :         t.reload();
    2655              :     }
    2656            0 :     loadprogress = 0;
    2657            0 : }
    2658              : 
    2659            0 : static void writepngchunk(stream *f, const char *type, const uchar *data = nullptr, uint len = 0)
    2660              : {
    2661            0 :     f->putbig<uint>(len);
    2662            0 :     f->write(type, 4);
    2663            0 :     f->write(data, len);
    2664              : 
    2665            0 :     uint crc = crc32(0, Z_NULL, 0);
    2666            0 :     crc = crc32(crc, reinterpret_cast<const Bytef *>(type), 4);
    2667            0 :     if(data)
    2668              :     {
    2669            0 :         crc = crc32(crc, data, len);
    2670              :     }
    2671            0 :     f->putbig<uint>(crc);
    2672            0 : }
    2673              : 
    2674              : VARP(compresspng, 0, 9, 9);
    2675              : 
    2676            0 : static void flushzip(z_stream &z, uchar *buf, const uint &buflen, uint &len, stream *f, uint &crc)
    2677              : {
    2678            0 :     int flush = buflen- z.avail_out;
    2679            0 :     crc = crc32(crc, buf, flush);
    2680            0 :     len += flush;
    2681            0 :     f->write(buf, flush);
    2682            0 :     z.next_out = static_cast<Bytef *>(buf);
    2683            0 :     z.avail_out = buflen;
    2684            0 : }
    2685              : 
    2686            1 : static void savepng(const char *filename, const ImageData &image, bool flip)
    2687              : {
    2688            1 :     if(!image.h || !image.w)
    2689              :     {
    2690            1 :         conoutf(Console_Error, "cannot save 0-size png");
    2691            1 :         return;
    2692              :     }
    2693            0 :     uchar ctype = 0;
    2694            0 :     switch(image.bpp)
    2695              :     {
    2696            0 :         case 1:
    2697              :         {
    2698            0 :             ctype = 0;
    2699            0 :             break;
    2700              :         }
    2701            0 :         case 2:
    2702              :         {
    2703            0 :             ctype = 4;
    2704            0 :             break;
    2705              :         }
    2706            0 :         case 3:
    2707              :         {
    2708            0 :             ctype = 2;
    2709            0 :             break;
    2710              :         }
    2711            0 :         case 4:
    2712              :         {
    2713            0 :             ctype = 6;
    2714            0 :             break;
    2715              :         }
    2716            0 :         default:
    2717              :         {
    2718            0 :             conoutf(Console_Error, "failed saving png to %s", filename);
    2719            0 :             return;
    2720              :         }
    2721              :     }
    2722            0 :     stream *f = openfile(filename, "wb");
    2723            0 :     if(!f)
    2724              :     {
    2725            0 :         conoutf(Console_Error, "could not write to %s", filename);
    2726            0 :         return;
    2727              :     }
    2728            0 :     uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
    2729            0 :     f->write(signature, sizeof(signature));
    2730              :     struct pngihdr
    2731              :     {
    2732              :         uint width,
    2733              :              height;
    2734              :         uchar bitdepth,
    2735              :               colortype,
    2736              :               compress,
    2737              :               filter,
    2738              :               interlace;
    2739              :     };
    2740            0 :     pngihdr ihdr =
    2741              :     {
    2742            0 :         static_cast<uint>(endianswap(image.w)),
    2743            0 :         static_cast<uint>(endianswap(image.h)),
    2744              :         8,
    2745              :         ctype,
    2746              :         0,
    2747              :         0,
    2748              :         0
    2749            0 :     };
    2750            0 :     writepngchunk(f, "IHDR", reinterpret_cast<uchar *>(&ihdr), 13);
    2751            0 :     stream::offset idat = f->tell();
    2752            0 :     uint len = 0;
    2753            0 :     f->write("\0\0\0\0IDAT", 8);
    2754            0 :     uint crc = crc32(0, Z_NULL, 0);
    2755            0 :     crc = crc32(crc, reinterpret_cast<const Bytef *>("IDAT"), 4);
    2756              :     z_stream z;
    2757            0 :     z.zalloc = nullptr;
    2758            0 :     z.zfree = nullptr;
    2759            0 :     z.opaque = nullptr;
    2760            0 :     if(deflateInit(&z, compresspng) != Z_OK)
    2761              :     {
    2762            0 :         goto error; //goto is beneath FLUSHZ macro
    2763              :     }
    2764              :     uchar buf[1<<12];
    2765            0 :     z.next_out = static_cast<Bytef *>(buf);
    2766            0 :     z.avail_out = sizeof(buf);
    2767            0 :     for(int i = 0; i < image.h; ++i)
    2768              :     {
    2769            0 :         uchar filter = 0;
    2770            0 :         for(int j = 0; j < 2; ++j)
    2771              :         {
    2772            0 :             z.next_in = j ? static_cast<Bytef *>(image.data) + (flip ? image.h-i-1 : i)*image.pitch : static_cast<Bytef *>(&filter);
    2773            0 :             z.avail_in = j ? image.w*image.bpp : 1;
    2774            0 :             while(z.avail_in > 0)
    2775              :             {
    2776            0 :                 if(deflate(&z, Z_NO_FLUSH) != Z_OK)
    2777              :                 {
    2778            0 :                     goto cleanuperror; //goto is beneath FLUSHZ macro
    2779              :                 }
    2780            0 :                 flushzip(z, buf, sizeof(buf), len, f, crc);
    2781              :             }
    2782              :         }
    2783              :     }
    2784              : 
    2785              :     for(;;)
    2786              :     {
    2787            0 :         int err = deflate(&z, Z_FINISH);
    2788            0 :         if(err != Z_OK && err != Z_STREAM_END)
    2789              :         {
    2790            0 :             goto cleanuperror;
    2791              :         }
    2792            0 :         flushzip(z, buf, sizeof(buf), len, f, crc);
    2793            0 :         if(err == Z_STREAM_END)
    2794              :         {
    2795            0 :             break;
    2796              :         }
    2797            0 :     }
    2798            0 :     deflateEnd(&z);
    2799              : 
    2800            0 :     f->seek(idat, SEEK_SET);
    2801            0 :     f->putbig<uint>(len);
    2802            0 :     f->seek(0, SEEK_END);
    2803            0 :     f->putbig<uint>(crc);
    2804            0 :     writepngchunk(f, "IEND");
    2805            0 :     delete f;
    2806            0 :     return;
    2807              : 
    2808            0 : cleanuperror:
    2809            0 :     deflateEnd(&z);
    2810              : 
    2811            0 : error:
    2812            0 :     delete f;
    2813            0 :     conoutf(Console_Error, "failed saving png to %s", filename);
    2814              : }
    2815              : 
    2816              : SVARP(screenshotdir, "screenshot");
    2817              : 
    2818            1 : void screenshot(char *filename)
    2819              : {
    2820              :     static string buf;
    2821            1 :     int dirlen = 0;
    2822            1 :     copystring(buf, screenshotdir);
    2823            1 :     if(screenshotdir[0])
    2824              :     {
    2825            1 :         dirlen = std::strlen(buf);
    2826            1 :         if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < static_cast<int>(sizeof(buf)))
    2827              :         {
    2828            1 :             buf[dirlen++] = '/';
    2829            1 :             buf[dirlen] = '\0';
    2830              :         }
    2831            1 :         const char *dir = findfile(buf, "w");
    2832            1 :         if(!fileexists(dir, "w"))
    2833              :         {
    2834            1 :             createdir(dir);
    2835              :         }
    2836              :     }
    2837            1 :     if(filename[0])
    2838              :     {
    2839            0 :         concatstring(buf, filename);
    2840              :     }
    2841              :     else
    2842              :     {
    2843              :         string sstime;
    2844            1 :         time_t t = std::time(nullptr);
    2845            1 :         size_t len = std::strftime(sstime, sizeof(sstime), "%Y-%m-%d_%H.%M.%S.png", std::localtime(&t));
    2846            1 :         sstime[std::min(len, sizeof(sstime)-1)] = '\0';
    2847            1 :         concatstring(buf, sstime);
    2848           24 :         for(char *s = &buf[dirlen]; *s; s++)
    2849              :         {
    2850           23 :             if(iscubespace(*s) || *s == '/' || *s == '\\')
    2851              :             {
    2852            0 :                 *s = '-';
    2853              :             }
    2854              :         }
    2855              :     }
    2856              : 
    2857            1 :     ImageData image(screenw, screenh, 3);
    2858            1 :     glPixelStorei(GL_PACK_ALIGNMENT, texalign(screenw, 3));
    2859            1 :     glReadPixels(0, 0, screenw, screenh, GL_RGB, GL_UNSIGNED_BYTE, image.data);
    2860            1 :     savepng(path(buf), image, true);
    2861            1 : }
    2862              : 
    2863              : //used in libprimis api, avoids having to provide entire Shader iface
    2864            0 : void setldrnotexture()
    2865              : {
    2866            0 :     ldrnotextureshader->set();
    2867            0 : }
    2868              : 
    2869            1 : void inittexturecmds()
    2870              : {
    2871            1 :     addcommand("texturereset", reinterpret_cast<identfun>(texturereset), "i", Id_Command);
    2872            1 :     addcommand("materialreset", reinterpret_cast<identfun>(materialreset), "", Id_Command);
    2873            1 :     addcommand("decalreset", reinterpret_cast<identfun>(decalreset), "i", Id_Command);
    2874            2 :     addcommand("compactvslots", reinterpret_cast<identfun>(+[](int *cull){multiplayerwarn();rootworld.compactvslots(*cull!=0);rootworld.allchanged();}), "i", Id_Command);
    2875            1 :     addcommand("texture", reinterpret_cast<identfun>(texture), "ssiiif", Id_Command);
    2876            1 :     addcommand("texgrass", reinterpret_cast<identfun>(texgrass), "s", Id_Command);
    2877            1 :     addcommand("texscroll", reinterpret_cast<identfun>(texscroll), "ff", Id_Command);
    2878            1 :     addcommand("texoffset", reinterpret_cast<identfun>(texoffset_), "ii", Id_Command);
    2879            1 :     addcommand("texrotate", reinterpret_cast<identfun>(texrotate_), "i", Id_Command);
    2880            1 :     addcommand("texangle", reinterpret_cast<identfun>(texangle_), "f", Id_Command);
    2881            1 :     addcommand("texscale", reinterpret_cast<identfun>(texscale), "f", Id_Command);
    2882            1 :     addcommand("texalpha", reinterpret_cast<identfun>(texalpha), "ff", Id_Command);
    2883            1 :     addcommand("texcolor", reinterpret_cast<identfun>(texcolor), "fff", Id_Command);
    2884            1 :     addcommand("texrefract", reinterpret_cast<identfun>(texrefract), "ffff", Id_Command);
    2885            1 :     addcommand("texsmooth", reinterpret_cast<identfun>(texsmooth), "ib", Id_Command);
    2886            1 :     addcommand("decaldepth", reinterpret_cast<identfun>(decaldepth), "ff", Id_Command);
    2887            1 :     addcommand("reloadtex", reinterpret_cast<identfun>(reloadtex), "s", Id_Command);
    2888            1 :     addcommand("screenshot", reinterpret_cast<identfun>(screenshot), "s", Id_Command);
    2889            1 : }
    2890              : 
        

Generated by: LCOV version 2.0-1