From bcbc5632e38a51a2dc9e31b87746640754d33c2e Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 28 Jun 2024 19:13:28 +0200 Subject: [PATCH 1/8] cmake: disable LTO by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dba76ea1..9995f4af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ option(BUILD_EXAMPLES "Build examples" OFF) # Enable extra optimization flags, like using -O3 even in RelWithDebInfo build. option(USE_EXTRA_OPTIMIZATION "Enable extra optimization" ON) # Enable link time optimization, slows down the build but produce faster and smaller binaries. -option(USE_LTO "Enable link-time optimization" ON) +option(USE_LTO "Enable link-time optimization" OFF) # Enabling fast math makes generated images less likely to be reproducible. # See https://github.com/DaemonEngine/crunch/issues/29 option(USE_FAST_MATH "Enable fast math (generated images are less likely to be reproducible)" ON) From 7bf66f04a3b6a3af3cbb98bd30a88edc38eb8ea2 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Tue, 2 Jul 2024 23:45:52 +0200 Subject: [PATCH 2/8] cmake: bump required cmake version to silence deprecation warning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9995f4af..f5eb7032 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.5) set(CMAKE_CXX_STANDARD 11) From 85a2f770ca05a4a2da95340cc2ed2d472ac9f7f2 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 3 Jul 2024 00:47:43 +0200 Subject: [PATCH 3/8] cmake: this is a C++-only project --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5eb7032..e8a0304d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CRUNCH_PROJECT_NAME crunch) set(CRUNCH_LIBRARY_NAME crn) set(CRUNCH_EXE_NAME crunch) -project(${CRUNCH_PROJECT_NAME}) +project(${CRUNCH_PROJECT_NAME} LANGUAGES CXX) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) From 30e4a49cfa8e4d0059db565b19a9c9d0de7a8f99 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 3 Jul 2024 01:08:13 +0200 Subject: [PATCH 4/8] emscripten: add build scripts --- .gitignore | 4 +++- emscripten/CMakeLists.txt | 28 ++++++++++++++++++++++++++++ emscripten/Makefile | 17 +++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 emscripten/CMakeLists.txt create mode 100644 emscripten/Makefile diff --git a/.gitignore b/.gitignore index 7d6843b3..62df287e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.o +*.js +*.wasm *.2010.vcxproj.user *.2010.suo /crnlib/crunch @@ -15,4 +17,4 @@ /lib /bin/* !bin/crunch_x64.exe -/build +build* diff --git a/emscripten/CMakeLists.txt b/emscripten/CMakeLists.txt new file mode 100644 index 00000000..1344bbf1 --- /dev/null +++ b/emscripten/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 11) + +set(CRUNCHJS_EXE_NAME crunch) + +if (NOT CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER emcc) +endif() + +project(${CRUNCHJS_EXE_NAME}.js LANGUAGES CXX) + +if (NOT CMAKE_TOOLCHAIN_FILE) + set(CMAKE_EXECUTABLE_SUFFIX .js) +endif() + +set(CMAKE_CXX_FLAGS "-O3 ${CMAKE_CXX_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS=\"['_malloc', '_free', '_crn_get_width', '_crn_get_height', '_crn_get_levels', '_crn_get_dxt_format', '_crn_get_bytes_per_block', '_crn_get_uncompressed_size', '_crn_decompress']\" -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s ELIMINATE_DUPLICATE_FUNCTIONS=1 -s ALLOW_MEMORY_GROWTH=1 --memory-init-file 0") + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../inc +) + +set(CRUNCHJS_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/crunch_lib.cpp +) + +add_executable(${CRUNCHJS_EXE_NAME} ${CRUNCHJS_SRCS}) diff --git a/emscripten/Makefile b/emscripten/Makefile new file mode 100644 index 00000000..598a9ffb --- /dev/null +++ b/emscripten/Makefile @@ -0,0 +1,17 @@ +.DEFAULT_GOAL := all +.PHONY: all clean + +CXX = emcc + +COMPILE_OPTIMIZATION_OPTIONS = -O3 +COMPILE_OPTIONS = -std=c++11 ${COMPILE_OPTIMIZATION_OPTIONS} + +LINKER_OPTIONS = -s EXPORTED_FUNCTIONS="['_malloc', '_free', '_crn_get_width', '_crn_get_height', '_crn_get_levels', '_crn_get_dxt_format', '_crn_get_bytes_per_block', '_crn_get_uncompressed_size', '_crn_decompress']" -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s ELIMINATE_DUPLICATE_FUNCTIONS=1 -s ALLOW_MEMORY_GROWTH=1 --memory-init-file 0 + +all: crunch.js + +crunch.js: crunch_lib.cpp + $(CXX) $< -o $@ -I../inc $(COMPILE_OPTIONS) $(LINKER_OPTIONS) + +clean: + rm crunch.js crunch.wasm From c5abbd0bda9f2967d6584cb121a75ad425eaa93e Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 3 Jul 2024 17:51:01 +0200 Subject: [PATCH 5/8] test: add test images and scripts Except black.jpg and unvanquished_64.png, the test images come from: - https://github.com/UnvanquishedAssets/UnvanquishedTestAssets/tree/master/pkg/map-test-pngalpha_src.dpkdir/textures/test-pngalpha_custom_src - https://gitlab.com/illwieckz/investigating-tga-orientation-in-imagemagick/-/tree/master/samples-default - https://gitlab.com/illwieckz/investigating-tga-orientation-in-imagemagick/-/tree/master/samples I'm the author of all of them and I place them under CC0 1.0 (public domain). --- test/black.jpg | Bin 0 -> 172 bytes test/raw-bottom-left.tga | Bin 0 -> 4156 bytes test/raw-bottom-right.tga | Bin 0 -> 4157 bytes test/raw-top-left.tga | Bin 0 -> 4153 bytes test/raw-top-right.tga | Bin 0 -> 4154 bytes test/sample-default.bmp | Bin 0 -> 4234 bytes test/sample-vertical-flip.bmp | Bin 0 -> 4234 bytes test/test-colormap1-alpha1.png | Bin 0 -> 432 bytes test/test-colormap2-alpha1.png | Bin 0 -> 454 bytes test/test-colormap4-alpha1.png | Bin 0 -> 486 bytes test/test-colormap8-alpha1.png | Bin 0 -> 4726 bytes test/test-grayscale1-alpha1.png | Bin 0 -> 415 bytes test/test-grayscale1-alpha8.png | Bin 0 -> 5720 bytes test/test-grayscale8-alpha1.png | Bin 0 -> 2833 bytes test/test-rgb8-alpha8.png | Bin 0 -> 10798 bytes test/test.py | 101 ++++++++++++++++++++++++++++++++ test/unvanquished_64.png | Bin 0 -> 7471 bytes 17 files changed, 101 insertions(+) create mode 100644 test/black.jpg create mode 100644 test/raw-bottom-left.tga create mode 100644 test/raw-bottom-right.tga create mode 100644 test/raw-top-left.tga create mode 100644 test/raw-top-right.tga create mode 100644 test/sample-default.bmp create mode 100644 test/sample-vertical-flip.bmp create mode 100644 test/test-colormap1-alpha1.png create mode 100644 test/test-colormap2-alpha1.png create mode 100644 test/test-colormap4-alpha1.png create mode 100644 test/test-colormap8-alpha1.png create mode 100644 test/test-grayscale1-alpha1.png create mode 100644 test/test-grayscale1-alpha8.png create mode 100644 test/test-grayscale8-alpha1.png create mode 100644 test/test-rgb8-alpha8.png create mode 100755 test/test.py create mode 100644 test/unvanquished_64.png diff --git a/test/black.jpg b/test/black.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5b62d451c141275cd23e63b47797139fa1aa3704 GIT binary patch literal 172 zcmex=}uh3P6f++aIt%9zUf)rf#XgOL;iVud;q{9?k=%lo7`X=*U zGF>z?Gcg!*U009ne!bPk>Cg^n2ebp)0quZx;O{!%PX{h`hh|u7UDGr! z&-2eb=X`~Kv`2^0=ZBa#mmmAV`h0>W<*5hdLp*kL^`J@l;6dMM{A>PnKiKED_YYqV z()CJWjz?;a#FN%QJ8+1BeH?N?swIi34&s0uZ_JbUq6VJSkH*Ny5IRzXE2h*jyB+)Op*WVia?;LfENs8{Gpq41&LKiuj@zl8`=aM`2vXfY8UjE_b$ahq0|yvfV& zmD#jo840eb_uMfP$p0&P$p0&P$p0&V4r|E@xO5UuL=LT z;p)0}UDvt3?_adgi~a3!?Rd92~j$<$}S;mUce z;n%F3H8@;#p5_ku^uBe?Jgc_mvEQ1Mor=B&B{mdTH{0v$%&#H&=hI*U{ zk2R0|;G|SM18O+)>y?tLJ;*~3I=6xot1&M8vHjFOIJq@7C+7Q<>(9Ji%3t2i9wD+_ S@89U5Deb^2ajIrFZ5KQAPTOoI}A_b5Aw0>He4Gt_TX+xqH*%dRp*_|Cb z9&~CZ1~D`4HGiI8MRR`?x68FL)=qMK-`a^CuAJh&vki@eh4$9Jr#SUrmQh1@mKn! z6Z09qr7xc=w*EJL{ygwt=zHMvDLFJaNOhPG?*E66{q|5D>s`HB7pG;tsurdP D-^Xfl literal 0 HcmV?d00001 diff --git a/test/raw-top-right.tga b/test/raw-top-right.tga new file mode 100644 index 0000000000000000000000000000000000000000..e45bc155492b3cd3d25795ce685f741d691afb53 GIT binary patch literal 4154 zcmeHJ%?iRW3@$!^&)^Hpv$vfl3`D^{yn0ek$`Cy6(e`MyseBN^>Pm{c*pBr}(&YQn zT(+s17_6Cb&!RcMis$`NG`Gw3X>8(h+&1(&YC5Eiz~_}^Y1_87UDqWlf67DJvGLR< zhIB&ws0*FP#=#Sd;6*-sErKToNrUo$uzf(e#ppr@xnt{dy}n+QSNCPOQwXK@*!rQq z5Kp;#-`&6)i@(z6y>SiM7eCY3k`F%Drp^?Xc0U>&Vv{sE$+kS z`5GHMTGVKYn!#`Ex84L-cJl=C1o8y(1o8y(1pZ(G?%%+#{q|5D>s`HB7pG;ts^;bm DB6@1} literal 0 HcmV?d00001 diff --git a/test/sample-default.bmp b/test/sample-default.bmp new file mode 100644 index 0000000000000000000000000000000000000000..068d10c22eff814edb36d507525dd2ab1d9f55fd GIT binary patch literal 4234 zcmeH|!41MN3`K)jfDyV#F5EdY0~aPh%DqcAm!^7agldEv+ZDU-E^y)mz_N&TZw?`b5X-MVHTOE9FW(RyOKm%nZHPT-onoBcm1z vr{<65<2`Cj%pb{bW%E6b=4(BC*HWzeTlXc+mRu{Z)c@%}cI`eTy_VbvV&sad literal 0 HcmV?d00001 diff --git a/test/sample-vertical-flip.bmp b/test/sample-vertical-flip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e5f8a9bf0f3bb1e31bdf06d76ae7e655db972926 GIT binary patch literal 4234 zcmeH}%?-jZ426SOfDyV#FWfmZ0~aPh>b*;K6*e((;8QItfyzA+tLc03&wEuBsXbrj zQz|j%`y;O*ucwF{GCO9TK6RyQNJ{TZQ!?D{*R>XAGLEB6(m zO`l`Ws;h3#?(x=HqNPK*&a+Dk) zfFLrvT^`%S3q$StYW@IzH1p_US(as4|IsFrmq|{(ixY5MpiY4N6lO2D%Cj%bzacXJ z#tM)K*Z~Rw9-t9$3ZgTj$W}m^y%81mM%3sV5#135wj)aH2$cB|{EXu_sO(DA=#_|G z$?tS`5ZMeUu^CWiGoZp|KvfU?9W=TVqC26W1+E8?4T+K#Y(2gNGJf@Z+L`@jo--Y*SMF-V8fo(^>u z~)xoVij>?pt*xOrjUqTd!DGk<9aZ@aHKSIYHpol4n4 z?Qp~WRgUq84R|2P{3)m3|33^72j2@bGhBK3mm!^+0-ul|IytkGeVRa5YBNac)(WU}&cn9*=bXhV_Xl?i;( zLS{=qjH#weiXF>Xihx+lnn_#S#eD&Ye@}aZV{BBxjfA63Z;Th{>sH))c!??cN;}K` z`~QFZobcd=@EXBA!5?l2f8lU5XcFvsAlCFVdw0`=^|96lj0_x!5CO+1UA7L-ug&GSS5y5xFTddb0bY*k=4gd?3wLln*6X|N@J@N6Agj}b lLtMrBy=N_oKF{d@17>$oHimo&-m{?S@O1TaS?83{1OQ`}wut}$ literal 0 HcmV?d00001 diff --git a/test/test-colormap4-alpha1.png b/test/test-colormap4-alpha1.png new file mode 100644 index 0000000000000000000000000000000000000000..2761d0362566f5af93c5bd2a4c53f6aefae95a36 GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58CaNs)Vi3hp+Jg1z$e5NNdE@{5Cy_IPTypJ zG-FASUoeBivm0qZcB`k0V@SoVH~pp=lOz~y8?8(et{q``!8wKTg769(?FVl^pWDo| zuC>9+@BjmInyJEUMmC8L|4N_FXW}scitrdbc>McZRs$nj%xcE<#X!LYX3U>1pOd}H zz;Hx?=j(eZN#2vIn^M1*H-BUPvEt4DF5V0AK0FT!Zr<pJoeNF!DsoF23r`JB;l7Dv^Q0#i`_dWUdzyE8zcObyl zDgR-H`U_v3o!!f}eY|3~=+=o4>y=5`;qO#epUwH6X9bK3FlcCCytkTrLZ#-$k08FM LtDnm{r-UW|rSZtY literal 0 HcmV?d00001 diff --git a/test/test-colormap8-alpha1.png b/test/test-colormap8-alpha1.png new file mode 100644 index 0000000000000000000000000000000000000000..fbdd685d06f1eb5e8f486805c095b19e75ac8118 GIT binary patch literal 4726 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 ZH+UC(JJ0ag5654|?j8HKzjR>4{s-}iP4WN$ literal 0 HcmV?d00001 diff --git a/test/test-grayscale1-alpha1.png b/test/test-grayscale1-alpha1.png new file mode 100644 index 0000000000000000000000000000000000000000..665cc9b2e02917e140d21c46dee77543452c1c49 GIT binary patch literal 415 zcmV;Q0bu@#P)BiqBEk% zRzR7(5f%1E)aV-#-4O-0BTDQDl=%_-jN><`>`K(=m55%+?{s$%*$gPL8Bk_3pu%QA zRS*0fG`bU_JE5Qjt_P6~iINs>Q{|#upiEHdhx5nb(aq4i%u9SiO!IUdLd8wEq=D7d>002ov JPDHLkV1la6wqO7N literal 0 HcmV?d00001 diff --git a/test/test-grayscale1-alpha8.png b/test/test-grayscale1-alpha8.png new file mode 100644 index 0000000000000000000000000000000000000000..3b6b4184c800ac865a9d49698f5a3e42f97f7165 GIT binary patch literal 5720 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 K;djZ0m-RmqXYKI- literal 0 HcmV?d00001 diff --git a/test/test-grayscale8-alpha1.png b/test/test-grayscale8-alpha1.png new file mode 100644 index 0000000000000000000000000000000000000000..d1705c0f44a4c6685be5ede5600544c818f906b1 GIT binary patch literal 2833 zcmXw5X;hQf7X7B=%LEC5U>Fmq2qH?9B7;IcCS_1S0i_isI6M$3Sc`%*WE4SzB(0)2 z)Wm8PTdFByu?{3bROE?(ih`mBQ31s^V3Z(`y!h7YzH6Oxf1I<|*>_lL?@3NvYD01+ z0RUhV7aP4206^;&1PIm$>~tExY)w$%%ES}^*s;Go7yw9ZanTW}#g9iigTU(;4TyM; z4p5Tc{j#!qnj(FQes230efq#f!C|3Z=ATBCoI|VEupXE?YL}-U%zUhgyGono_nZ8c z##4}PR6Q&o6Vk&QP?xa{Q!(Fs-W}<00x0^xN`pd$9t&G{5y;+Sc zY9(E+?C5oQrXRSo>m({uBOg^)NN-HXN~zEE`>WHVlc4VnGD+e-Za>wItG~S{Ffec} z(X6+4h)KKap+l;afuQi=vP|uM{_exeyI98LSOpZSmdvYW%b%Mfl1vhQetv!veQHIq z0$D`B{e`$W3vUL6>EoJ2eyf^=Lm+*GJ!dGMc!%|l@+@J1RDX;>t5)1RJH0I-uEpxnlD%0A2h^Ki`Dl0f`S4r zPVNAb6&P9vuF=zzb;%#Be446`-U7RILnw-t&n`ZIkMCJeeM5Sj&o+r!Q+Y!DQFlW$ zH#BrfqN_PP^k;mmTAHj4Bf!a>@$jpOAnv)$5OZZxN%Q_GqtWPxlN&K&p@MP1OFOqg zT>Z}REf5IA8NO571&N=Bh!ZF11ND>YGPjCx;X2E?>NHQc!O2bjs z6Zz=(pP1pp#H5c`u3VXPSbPui>I8H3jw<95^7}&C)U;HplOM{gNFnq!{V~ntwiWR2lc$G=heIUjW;HxV>-m`~i?Bz!;M`CkP4H0TD%?|0azf+r z4@np&bvr<6qi?mz#g94V+y^OU#@Krk%W=Re&VJnx%Pu?)a@{5Q87b$xb!y|8Ib9y7 zM0~)W`$+lWk*UnTq**q7!SO2<5~6^rMO(B{3d+lo~D&RNUPdCQbis^M) zFY9u4b$dv8(1E=tYhl$4mFb%0YvDspxy6I8v#gWNjMXho0v}=JSdkWQT=3EzK@sP${WycSYKCYk7`Q5Y zEgyBsG+G{dqO0`uUEsRNW1zEvZjy4+RJ~9ORJR#y5mrxx3HpzkHSr^u^_S=A8DrO z&XhEWnXNGmEusXr<9xbLv7Hap3tR)50Vm*zNo=`kOZwBPl}WHKr1Qu$X`pR|-7=`} zvKyWaP`C!StI8BTg$8Fyo;1danJt0tOi#lU4%xa0QP|W1ty8cNwfw$zDM;B!_r=$f zqoBqgv}NxD=U2cL*m;1cwc+T4j4%qr`B0oz#$K;(hJ!@}Ba#fQ2d;Mvq93ljGKmS5 z;4+n~(SeO0#l-N}$UGrj$LJ(tCZ#0Ppvxb0&}viWcgFGNN?ysq^)za#m7>y)>4HHO zNDGi)b`dP)ZsSq>){2OpsF%10nvd>@7{?;eOYkyaKGfHo_Kt|B-{ zi`j$fNUcom2{lMU(K;6`!qF=O^z`&bY!LkB#1R?~*Fd=RGoiIe<%%Kk0F$?uh3A0u zWJ()6F)Njjv4`f94n46`-{pmf2+>waT))P4fX>Fh0^9pNkTyam$v|keC7_E-TJT#z zX31S9hp5K6hmoiosp*6UFFlfaNA)LEA>yM|wgA^KjmP7uFOMo{ISDErDMG6dyMu)Q zRMXFL!XTrlT&s|5LXHukRxC!FXosPBBtYMH8W{r1w22-V2@huPB_tCuQMSsH_Zl!v zwlD6C)TYp&#ILEVC|U8UcD5V{m zZ>?`)$qrFa1WVdPnD8CBrBGfs(?$=~aAQ^rWdqHvxOw~j3Vt(o38o+YVnOiC?a~d~ zY~%B*+Fq!QjH>@8;b(Ccd{=(rtggou@9m#8FI3h)yr!~4&hX)t-3(k-UCY~dH;lrV zomIUG{!iP3Saiu7V=S4>@b(Riqb8)Gx!dFH-`r?BCF_3tYTNdEyK}FF4@a#~V~)vx ztk_?tu<_Zvqm`5zov;xl{W`ZE8LSVi$KARdR*yf^98-^$w=J(HWZz4xCoX-www~nu zVpBbtzq%mmbpHkZPj%NFLcK0Y>bmq>ND%ji$hp+E!au*{V z0X=s0#qW+YG+fJ19R(-pMoM)0YRAGIo5)Q{ZuNN)5+-%?VAOXr+$YJ;J8lgsYcSaE z#}jWO%gkNeCy)GFL( mLyDhRLtS6+;j_E_7qp2=mrX~b7Fb;^0K~;4MxTr1N&W}GHMNHT literal 0 HcmV?d00001 diff --git a/test/test-rgb8-alpha8.png b/test/test-rgb8-alpha8.png new file mode 100644 index 0000000000000000000000000000000000000000..0881b896fe212b5c1c91522a25bd385dae592e43 GIT binary patch literal 10798 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 literal 0 HcmV?d00001 From 4a5c8d22bbc3cddd9591d6c6d444ee98a404c28e Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 24 Jun 2024 12:01:32 +0200 Subject: [PATCH 6/8] ci/azure: test building Linux, MinGW and macOS --- .azure-pipelines.yml | 127 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .azure-pipelines.yml diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml new file mode 100644 index 00000000..a1579f15 --- /dev/null +++ b/.azure-pipelines.yml @@ -0,0 +1,127 @@ +# Documentation: https://wiki.unvanquished.net/wiki/Continuous_integration + +trigger: + branches: + include: + - master + +pr: + branches: + include: + - '*' + paths: + include: + - .azure-pipelines.yml + # The rest of this list should stay in sync with .appveyor.yml + - crunch/ + - crnlib/ + - inc/ + - example1/ + - example2/ + - example3/ + - test/ + - cmake/ + - CMakeLists.txt + +strategy: + matrix: + Linux amd64 GCC: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-8 + CXX_COMPILER: g++-8 + RUN_TESTS: true + Linux i686 GCC: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-i686-linux-gnu + CXX_COMPILER: i686-linux-gnu-g++ + RUN_TESTS: true + Linux arm64 GCC: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-aarch64-linux-gnu + CXX_COMPILER: aarch64-linux-gnu-g++ + Linux armhf GCC: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-arm-linux-gnueabihf + CXX_COMPILER: arm-linux-gnueabihf-g++ + Linux amd64 Clang: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build + CXX_COMPILER: clang++ + RUN_TESTS: true + Windows amd64 MinGW: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-mingw-w64-x86-64 mingw-w64-x86-64-dev + SETUP_COMMANDS: sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + TOOLCHAIN_FILE: cmake/cross-toolchain-mingw64.cmake + BUILD_EXAMPLES: ON + EXE_EXTENSIONS: .exe + Windows i686 MinGW: + VM_IMAGE: 'ubuntu-20.04' + APT_PACKAGES: ninja-build g++-mingw-w64-i686 mingw-w64-i686-dev + SETUP_COMMANDS: sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix + TOOLCHAIN_FILE: cmake/cross-toolchain-mingw32.cmake + BUILD_EXAMPLES: ON + EXE_EXTENSIONS: .exe + macOS amd64 AppleClang: + VM_IMAGE: 'macOS-12' + CMAKE_GENERATOR: Unix Makefiles + NPROC_COMMAND: sysctl -n hw.logicalcpu + RUN_TESTS: true + macOS arm64 AppleClang: + VM_IMAGE: 'macOS-12' + CMAKE_GENERATOR: Unix Makefiles + COMPILER_FLAGS: -target arm64-apple-macos11 -Wno-overriding-t-option + NPROC_COMMAND: sysctl -n hw.logicalcpu + Web Asm.js Emscripten: + VM_IMAGE: 'ubuntu-22.04' + APT_PACKAGES: ninja-build emscripten + TOOLCHAIN_FILE: /usr/share/emscripten/cmake/Modules/Platform/Emscripten.cmake + SOURCE_DIR: emscripten + EXE_EXTENSIONS: .js .wasm + +pool: + vmImage: $(VM_IMAGE) + +steps: +- bash: | + set -xue + if [ -n "${APT_PACKAGES:-}" ]; then + sudo apt-get update && sudo apt-get -y -q --no-install-recommends install ${APT_PACKAGES} + fi + if [ -n "${SETUP_COMMANDS:-}" ]; then + $(SETUP_COMMANDS) + fi + displayName: 'Setup' +- bash: | + set -xue + export CMAKE_BUILD_PARALLEL_LEVEL="$(${NPROC_COMMAND:-nproc})" + echo "${CMAKE_BUILD_PARALLEL_LEVEL}" + cmake_args=(-G"${CMAKE_GENERATOR:-Ninja}") + if [ -n "${TOOLCHAIN_FILE:-}" ]; then + cmake_args+=(-DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}") + fi + if [ -n "${CXX_COMPILER:-}" ]; then + cmake_args+=(-DCMAKE_CXX_COMPILER="${CXX_COMPILER}") + fi + if [ -n "${COMPILER_FLAGS:-}" ]; then + cmake_args+=(-DCMAKE_CXX_FLAGS="${COMPILER_FLAGS}") + fi + if [ -z "${SOURCE_DIR:-}" ]; then + cmake_args+=(-DBUILD_CRUNCH=ON -DBUILD_SHARED_LIBCRN=ON -DBUILD_EXAMPLES="${BUILD_EXAMPLES:-OFF}") + fi + cmake -S"${SOURCE_DIR:-.}" -Bbuild "${cmake_args[@]}" + cmake --build build --config Release + displayName: 'Build' +- bash: | + set -xue + if [ -z "${EXE_EXTENSIONS:-}" ]; then + file 'build/crunch' + else + for ext in ${EXE_EXTENSIONS}; do + file "build/crunch${ext}" + done + fi + if "${RUN_TESTS:-false}"; then + test/test.py + fi + displayName: 'Test' From 115021dea3d14d4bf0c2e69fdccbf4075b792c5f Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Fri, 28 Jun 2024 20:02:28 +0200 Subject: [PATCH 7/8] ci/appveyor: run test --- .appveyor.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 78b2e0bb..283e75ff 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,13 +5,14 @@ skip_branch_with_pr: true only_commits: files: - .appveyor.yml - - example1/ - - example2/ - - example3/ # The rest of this list should stay in sync with azure-pipelines.yml - crunch/ - crnlib/ + - example1/ + - example2/ + - example3/ - inc/ + - test/ - cmake/ - CMakeLists.txt @@ -52,3 +53,5 @@ build_script: -DBUILD_CRUNCH=ON -DBUILD_SHARED_LIBCRN=ON -DBUILD_EXAMPLES=ON cmake --build build --config Release + + python test\test.py From 668fff3f2ee0b8fb3d323b50a8fd782225d196a3 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 3 Jul 2024 17:38:10 +0200 Subject: [PATCH 8/8] readme: update --- README.md | 203 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 123 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 0ffa5b52..4d225776 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,29 @@ - Upstream for the Dæmon branch: https://github.com/DaemonEngine/crunch - Bug tracker for the Dæmon branch: https://github.com/DaemonEngine/crunch/issues +Target & compiler|Build status|Target & compiler|Build status +-|-|-|- +Linux amd64 GCC|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Linux%20amd64%20Clang)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|Windows amd64 MSVC|[![Build status](https://ci.appveyor.com/api/projects/status/github/DaemonEngine/crunch?banch=master&svg=true)](https://ci.appveyor.com/project/DolceTriade/crunch/history) +Linux amd64 Clang|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Linux%20amd64%20Clang)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|Windows i686 MSVC|[![Build status](https://ci.appveyor.com/api/projects/status/github/DaemonEngine/crunch?banch=master&svg=true)](https://ci.appveyor.com/project/DolceTriade/crunch/history) +Linux i686 GCC|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Linux%20i686%20GCC)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|Windows amd64 MinGW|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Windows%20amd64%20MinGW)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master) +Linux arm64 GCC|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Linux%20arm64%20GCC)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|Windows i686 MinGW|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Windows%20i686%20MinGW)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master) +Linux armhf GCC|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Linux%20armhf%20GCC)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|macOS amd64 AppleClang|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20macOS%20amd64%20AppleClang)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master) +Web Asm.js Emscripten|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20Web%20Asm.js%20Emscripten)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master)|macOS arm64 AppleClang|[![Build Status](https://dev.azure.com/UnvanquishedDevelopment/crunch/_apis/build/status%2FDaemonEngine.crunch?branchName=master&jobName=Job&configuration=Job%20macOS%20arm64%20AppleClang)](https://dev.azure.com/UnvanquishedDevelopment/crunch/_build/latest?definitionId=3&branchName=master) + ## Dæmon crunch tool -ℹ️ The [Dæmon engine](https://github.com/DaemonEngine/Daemon) uses the [`master`](https://github.com/DaemonEngine/crunch/tree/master) branch, which currently tracks the [Unity fork](https://github.com/Unity-Technologies/crunch/tree/unity), with a few additional minor fixes. The Dæmon engine is the open source game engine powering the [Unvanquished game](https://unvanquished.net). The produced CRN files are openable by the Unity game engine. +ℹ️ The [Dæmon engine](https://github.com/DaemonEngine/Daemon) uses the [`master`](https://github.com/DaemonEngine/crunch/tree/master) branch, which tracks the [Unity fork](https://github.com/Unity-Technologies/crunch/tree/unity), with additional fixes and features. The Dæmon engine is the open source game engine powering the [Unvanquished game](https://unvanquished.net). The produced `.crn` files are compatible with both the Daemon game engine and the Unity game engine. -Crunch is brought to you by: +Dæmon crunch is brought to you by: -- **2014-2023**: Dæmon Developers and contributors +- **2014-2024**: Dæmon Developers and contributors https://github.com/DaemonEngine/crunch - **2017-2018**: Alexander Suvorov and Unity Software Inc. https://github.com/Unity-Technologies/crunch/tree/unity - **2010-2017**: Richard Geldreich, Jr. and Binomial LLC and contributors https://github.com/BinomialLLC/crunch -The Dæmon branch is known to be used by: +The Dæmon crunch is known to be used by: - The [Dæmon game engine](https://github.com/DaemonEngine/Daemon), - The [Urcheon game data build automation tool](https://github.com/DaemonEngine/Urcheon), @@ -27,6 +36,17 @@ The Dæmon branch is known to be used by: - The [Xonotic game](https://xonotic.org), - Some games using the Unity game engine. +This branch provides many improvements over the original crunch: + +- ✅️ Unity crunch format (runs many time faster and produces smaller files), +- ✅️ Unity crunch metadata (the header is compatible with Unity), +- ✅️ Improved image compatibility (1-bit PNG images are now supported), +- ✅️ Added features and command line options (top mip renormalization and more), +- ✅️ Network file system compatibility, +- ✅️ Optional header-only checksumming, +- ✅️ Multisystem and multiplatform (runs almost everywhere), +- ✅️ CMake toolchain (with many useful build options). + ## Unity crunch format This repository merged improvements done by Unity Technologies, it produces @@ -51,7 +71,11 @@ Unlike the Unity `crunch` tool built from their public repository, this tool pro Since [February 11th 2023](faf5127b8c69dfd1ae554f4f3bf8168048b48d9f), this branch sets the CRN `m_userdata0` field to `1` to make CRN files loadable by the Unity engine which now prevents to load files with this value set to `0`. -Files with this value set to `1` are expected to use the new format. Files with this value set to `0` may or may not use the new format. When Unity updated the tool and modified the format in an incompatible way, no bit was modified to detect if a CRN file was using the old or the new format. Unity has not updated that field in their public repository of Crunch so there may be files using new format with this value set to `0` in the wild. +Files with this value set to `1` are expected to use the new format. Files with this value set to `0` may or may not use the new format. When Unity updated the tool and modified the format in an incompatible way, no bit was modified to detect if a CRN file was using the old or the new format. Unity has not updated that field in their public repository of Crunch so there may be files in the wild using new format with this value set to `0`. + +## Improved image compatibility + +The `stb_image` library was updated from version 1.18 to version 2.30, increasing the amount of image format variants that can be converted, like 1-bit PNG formats. ## Added features and command line options @@ -61,28 +85,40 @@ In addition to the original `crunch` features and command line options this bran - `-noNormalDetection`: do not attempt to detect normal map to avoid selecting formats thought for normal maps when it's known an image is not a normal map. It may prevent the tool to use heavier and less-supported `DXT5_AGBR` format when `DXT1` is good enough. - `-h` or `--help`: print the command line built-in help. +## Network file system compatibility + +The original `crunch` tool used I/O functions not working on some network file systems like NFS or SSHFS. The limitations is now gone and `crunch` can now process image files stored over the network. + +## Optional header-only checksumming + +For applications distributing their files in containers already providing a checksum mechanism for the whole contained file, it's now possible to only validate the CRN header checksum instead of the whole file checksum to not checksum the whole file twice. + ## Multisystem and multiplatform portability Unlike upstream branches from Binomial LLC and Unity Software Inc. this branch focuses on keeping the code buildable outside of Windows and Visual Studio, and adds a CMake build option alongside the legacy Makefile. This `crunch` tool and the related `crnlib` library are known to build with and and run on: -- Compilers: GCC, MSYS2/Mingw, MSVC, Clang, Apple Clang, ICC, ICX. +- Compilers: GCC, MinGW, MSVC, Clang, Apple Clang, and more. - Systems: Linux, Windows, macOS, FreeBSD. - Architectures: amd64, arm64, i686, armhf. +## CMake toolchain + +CMake is now preferred to the old Makefile. The supplied CMake configuration provides many useful build options. Toolchain files for MinGW cross-compilation are also supplied. + ## How to build ```sh git clone https://github.com/DaemonEngine/crunch.git cd crunch -cmake -H. -Bbuild +cmake -S. -Bbuild cmake --build build --parallel $(nproc) ``` Where `$(nproc)` is the amount of cores of your computer. -You'll then find a `crunch` binary in `build/` folder. +You'll then find a `crunch` binary in the `build/` folder. Some CMake build options are availables (explore more with `ccmake`). @@ -94,7 +130,7 @@ http://opensource.org/licenses/Zlib Richard Geldreich removed copyright on all his work on Crunch in order to put it in public domain on 2020-09-15 with commit [`crunch@57353fa`](https://github.com/BinomialLLC/crunch/commit/57353fa9ac0908893215bc30ba106adfb80c4c95) but this repository also contains commits by Alexander Suvorov from Unity Technologies and from Dæmon Developers and contributors. Portions of this software make use of public domain code originally -written by Igor Pavlov (LZMA), RYG (crn_ryg_dxt*), and Sean Barrett (stb_image.c). +written by Igor Pavlov (LZMA), RYG (`crn_ryg_dxt*`), and Sean Barrett (`stb_image*.h`). If you use this software in a product, an acknowledgment in the product documentation would be highly appreciated but is not required. @@ -122,33 +158,33 @@ between 100-250 megatexels/sec. The current library supports PC (Win32/x64) and Xbox 360. Fast random access to individual mipmap levels is supported. -crnlib can also generates standard .DDS files at specified quality +crnlib can also generates standard DDS files at specified quality setting, which results in files that are much more compressible by LZMA/Deflate/etc. compared to files generated by standard DXTn texture tools (see below). This feature allows easy integration into any engine -or graphics library that already supports .DDS files. +or graphics library that already supports DDS files. -The .CRN file format supports the following core DXTn texture formats: +The CRN file format supports the following core DXTn texture formats: DXT1 (but not DXT1A), DXT5, DXT5A, and DXN/3DC It also supports several popular swizzled variants (several are also supported by AMD's Compressonator): DXT5_XGBR, DXT5_xGxR, DXT5_AGBR, and DXT5_CCxY (experimental luma-chroma YCoCg). -## Recommended Software +## Recommended software AMD's [Compressonator tool](https://github.com/GPUOpen-Tools/Compressonator) -is recommended to view the .DDS files created by the crunch tool and the included example projects. +is recommended to view the DDS files created by the crunch tool and the included example projects. -Note: Some of the swizzled DXTn .DDS output formats (such as DXT5_xGBR) +Note: Some of the swizzled DXTn DDS output formats (such as DXT5_xGBR) read/written by the crunch tool or examples deviate from the DX9 DDS -standard, so DXSDK tools such as DXTEX.EXE won't load them at all or +standard, so DXSDK tools such as `DXTEX.EXE` won't load them at all or they won't be properly displayed. -## Compression Algorithm Details +## Compression algorithm details -The compression process employed in creating both .CRN and -clustered .DDS files utilizes a very high quality, scalable DXTn +The compression process employed in creating both CRN and +clustered DDS files utilizes a very high quality, scalable DXTn endpoint optimizer capable of processing any number of pixels (instead of the typical hard coded 16), optional adaptive switching between several macroblock sizes/configurations (currently any combination of @@ -160,70 +196,70 @@ passes are performed between the clusterization and VQ steps to optimize quality, and several steps use a brute force refinement approach to improve quality. The majority of compression steps are multithreaded. -The .CRN format currently utilizes canonical Huffman coding for speed +The CRN format currently utilizes canonical Huffman coding for speed (similar to Deflate but with much larger tables), but the next major version will also utilize adaptive binary arithmetic coding and higher order context modeling using already developed tech from the my LZHAM compression library. -## Supported File Formats +## Supported file formats -crnlib supports two compressed texture file formats. The first -format (clustered .DDS) is simple to integrate into an existing project +crnlib supports three compressed texture file formats. The first +format (clustered DDS) is simple to integrate into an existing project (typically, no code changes are required), but it doesn't offer the highest quality/compression ratio that crnlib is capable of. Integrating -the second, higher quality custom format (.CRN) requires a few +the second, higher quality custom format (CRN) requires a few typically straightforward engine modifications to integrate the -.CRN->DXTn transcoder header file library into your tools/engine. +CRN→DXTn transcoder header file library into your tools/engine. -### .DDS +### DDS -crnlib can compress textures to standard DX9-style .DDS files using +crnlib can compress textures to standard DX9-style `.dds` files using clustered DXTn compression, which is a subset of the approach used to -create .CRN files.(For completeness, crnlib also supports vanilla, block +create CRN files.(For completeness, crnlib also supports vanilla, block by block DXTn compression too, but that's not very interesting.) -Clustered DXTn compressed .DDS files are much more compressible than +Clustered DXTn compressed DDS files are much more compressible than files created by other libraries/tools. Apart from increased -compressibility, the .DDS files generated by this process are completely +compressibility, the DDS files generated by this process are completely standard so they should be fairly easy to add to a project with little to no code changes. -To actually benefit from clustered DXTn .DDS files, your engine needs to -further losslessly compress the .DDS data generated by crnlib using a +To actually benefit from clustered DXTn DDS files, your engine needs to +further losslessly compress the DDS data generated by crnlib using a lossless codec such as zlib, lzo, LZMA, LZHAM, etc. Most likely, your engine does this already. (If not, you definitely should because DXTn compressed textures generally contain a large amount of highly redundant data.) -Clustered .DDS files are intended to be the simplest/fastest way to +Clustered DDS files are intended to be the simplest/fastest way to integrate crnlib's tech into a project. -### .CRN +### CRN -The second, better, option is to compress your textures to .CRN files -using crnlib. To read the resulting .CRN data, you must add the .CRN +The second, better, option is to compress your textures to `.crn` files +using crnlib. To read the resulting CRN data, you must add the CRN transcoder library (located in the included single file, stand-alone -header file library inc/crn_decomp.h) into your application. .CRN files +header file library inc/crn_decomp.h) into your application. CRN files provide noticeably higher quality at the same effective bitrate compared -to clustered DXTn compressed .DDS files. Also, .CRN files don't require +to clustered DXTn compressed DDS files. Also, CRN files don't require further lossless compression because they're already highly compressed. -.CRN files are a bit more difficult/risky to integrate into a project, but -the resulting compression ratio and quality is superior vs. clustered .DDS files. +CRN files are a bit more difficult/risky to integrate into a project, but +the resulting compression ratio and quality is superior vs. clustered DDS files. -### .KTX +### KTX -crnlib and crunch can read/write the .KTX file format in various pixel formats. +crnlib and crunch can read/write the `.ktx` file format in various pixel formats. Rate distortion optimization (clustered DXTc compression) is not yet supported -when writing .KTX files. +when writing KTX files. -The .KTX file format is just like .DDS, except it's a fairly well specified -standard created by the Khronos Group. Unfortunately, almost all of the tools I've -found that support .KTX are fairly (to very) buggy, or are limited to only a handful -of pixel formats, so there's no guarantee that the .KTX files written by crnlib can -be reliably read by other tools. +The KTX file format is just like DDS, except it's a fairly well specified +standard created by the Khronos Group. Unfortunately, almost all of the tools +the original Crunch author found that supported KTX were fairly (to very) buggy, +or were limited to only a handful of pixel formats, so there's no guarantee that +the KTX files written by crnlib can be reliably read by other tools. -## Building the Examples +## Building the examples This release contains the source code and projects for three simple example projects: @@ -237,39 +273,41 @@ way hasn't been tested much. ### example1 Demonstrates how to use crnlib's high-level C-helper -compression/decompression/transcoding functions in inc/crnlib.h. It's a +compression/decompression/transcoding functions in `inc/crnlib.h`. It's a fairly complete example of crnlib's functionality. ### example2 -Shows how to transcodec .CRN files to .DDS using **only** -the functionality in inc/crn_decomp.h. It does not link against against -crnlib.lib or depend on it in any way. (Note: The complete source code, -approx. 4800 lines, to the CRN transcoder is included in inc/crn_decomp.h.) -example2 is intended to show how simple it is to integrate CRN textures +Shows how to transcodec CRN files to DDS using **only** +the functionality in `inc/crn_decomp.h`. It does not link against against +`crnlib.lib` or depend on it in any way. (Note: The complete source code, +approx. 4800 lines, to the CRN transcoder is included in `inc/crn_decomp.h`.) + +`example2` is intended to show how simple it is to integrate CRN textures into your application. ### example3 + Shows how to use the regular, low-level DXTn block compressor -functions in inc/crnlib.h. This functionality is included for +functions in `inc/crnlib.h`. This functionality is included for completeness. (Your engine or toolchain most likely already has its own DXTn compressor. crnlib's compressor is typically very competitive or superior to most available closed and open source CPU-based compressors.) -## Creating Compressed Textures from the Command Line (crunch.exe) +## Creating compressed textures with crunch The simplest way to create compressed textures using crnlib is to -integrate the bin\crunch.exe or bin\crunch_x64.exe) command line tool +integrate the `crunch` (or `crunch.exe`) command line tool into your texture build toolchain or export process. It can write DXTn -compressed 2D/cubemap textures to regular DXTn compressed .DDS, -clustered (or reduced entropy) DXTn compressed .DDS, or .CRN files. It +compressed 2D/cubemap textures to regular DXTn compressed DDS, +clustered (or reduced entropy) DXTn compressed DDS, or CRN files. It can also transcode or decompress files to several standard image -formats, such as TGA or BMP. Run crunch.exe with no options for help. +formats, such as TGA or BMP. Run `crunch --help` for help. -The .CRN files created by crunch.exe can be efficiently transcoded to +The `.crn` files created by `crunch` can be efficiently transcoded to DXTn using the included CRN transcoding library, located in full source -form under inc/crn_decomp.h. +form under `inc/crn_decomp.h`. Here are a few example crunch.exe command lines: @@ -330,11 +368,11 @@ void *crn_compress( const crn_comp_params &comp_params, float *pActual_bitrate = NULL); ``` -You can also transcode/uncompress .DDS/.CRN files to raw 32bpp images +You can also transcode/uncompress DDS/CRN files to raw 32bpp images using `crn_decompress_crn_to_dds()` and `crn_decompress_dds_to_images()`. Internally, crnlib just uses inc/crn_decomp.h to transcode textures to -DXTn. If you only need to transcode .CRN format files to raw DXTn bits +DXTn. If you only need to transcode CRN format files to raw DXTn bits at runtime (and not compress), you don't actually need to compile or link against crnlib at all. Just include inc/crn_decomp.h, which contains a completely self-contained CRN transcoder in the "crnd" @@ -346,31 +384,31 @@ GPU you're using. (See example2.) Important note: When compiling under native client, be sure to define the `PLATFORM_NACL` macro before including the `inc/crn_decomp.h` header file library. -## Known Issues/Bugs +## Known issues/Bugs * crnlib currently assumes you'll be further losslessly compressing its -output .DDS files using LZMA. However, some engines use weaker codecs +output DDS files using LZMA. However, some engines use weaker codecs such as LZO, zlib, or custom codecs, so crnlib's bitrate measurements will be inaccurate. It should be easy to allow the caller to plug-in custom lossless compressors for bitrate measurement. * Compressing to a desired bitrate can be time consuming, especially when -processing large (2k or 4k) images to the .CRN format. There are several -high-level optimizations employed when compressing to clustered DXTn .DDS -files using multiple trials, but not so for .CRN. +processing large (2k or 4k) images to the CRN format. There are several +high-level optimizations employed when compressing to clustered DXTn DDS +files using multiple trials, but not so for CRN. -* The .CRN compressor does not currently use 3 color (transparent) DXT1 +* The CRN compressor does not currently use 3 color (transparent) DXT1 blocks at all, only 4 color blocks. So it doesn't support DXT1A transparency, and its output quality suffers a little due to this limitation. (Note that the clustered DXTn compressor used when -writing clustered .DDS files does *not* have this limitation.) +writing clustered DDS files does _not_ have this limitation.) * Clustered DXT5/DXT5A compressor is able to group DXT5A blocks into clusters only if they use absolute (black/white) selector indices. This hurts performance at very low bitrates, because too many bits are effectively given to alpha. -* DXT3 is not supported when writing .CRN or clustered DXTn DDS files. +* DXT3 is not supported when writing CRN or clustered DXTn DDS files. (DXT3 is supported by crnlib's when compressing to regular DXTn DDS files.) You'll get DXT5 files if you request DXT3. However, DXT3 is supported by the regular DXTn block compressor. (DXT3's 4bpp fixed alpha @@ -380,15 +418,20 @@ sucks verses DXT5 alpha blocks, so I don't see this as a bug deal.) hasn't been tuned for max. quality yet. * Clustered (or rate distortion optimized) DXTc compression is only -supported when writing to .DDS, not .KTX. Also, only plain block by block -compression is supported when writing to ETC1, and .CRN does not support ETC1. +supported when writing to DDS, not KTX. Also, only plain block by block +compression is supported when writing to ETC1, and CRN does not support ETC1. -## Compile to Javascript with Emscripten +## Compile to Javascript and WebAssembly with Emscripten Download and install Emscripten: - http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html -From the root directory, run: -```c - emcc -O3 emscripten/crunch_lib.cpp -I./inc -s EXPORTED_FUNCTIONS="['_malloc', '_free', '_crn_get_width', '_crn_get_height', '_crn_get_levels', '_crn_get_dxt_format', '_crn_get_bytes_per_block', '_crn_get_uncompressed_size', '_crn_decompress']" -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s ELIMINATE_DUPLICATE_FUNCTIONS=1 -s ALLOW_MEMORY_GROWTH=1 --memory-init-file 0 -o crunch.js +- https://emscripten.org/docs/getting_started/downloads.html + +From the repository directory, run: + +```sh +cmake -Semscripten -Bbuild-emscripten +cmake --build build-emscripten ``` + +You’ll then find a `crunch.js` script and a `crunch.wasm` binary in the `build-emscripten/` folder.