From 2081dc0d84c2d84cf70278dc0a7e6393eeb8d92a Mon Sep 17 00:00:00 2001 From: ZeaS Date: Tue, 28 Mar 2017 00:14:40 +0800 Subject: [PATCH] Version 1.3.2 New Features: - support dynamic texture compression(ETC2/PVRTC/lz4/lz4+tlg5/ASTC?) - support loading graphic format .pvr - support unicode font name - render manager texture data passthrough - XP3 repacker (in progress) - custom skin support (in progress) - file manage menu(in progress) - guide user to patch lib Refactor: - using TVP_stat for file info fetch - TVPDeleteFile/TVPRenameFile using narrow string to pass filename Bug fix: - video closed but picture leave in some case - crash in async graphic loading - sound mute on deactive but never resume - filename is full-path when file selector dialog showed - colorize error when using OpenGL renderer --- cocos/kr2/Resources/res/locale/en_us.xml | 20 +- cocos/kr2/Resources/res/locale/ja_jp.xml | 19 +- cocos/kr2/Resources/res/locale/zh_cn.xml | 22 +- cocos/kr2/Resources/res/locale/zh_tw.xml | 108 + cocos/kr2/cocosstudio/img/back_icon.png | Bin 0 -> 1842 bytes cocos/kr2/cocosstudio/img/right_triangle.png | Bin 0 -> 768 bytes cocos/kr2/cocosstudio/ui/FileItem.csd | 24 +- cocos/kr2/kr2.ccs | 3 + project/android/AndroidManifest.xml | 7 +- project/android/jni/Application.mk | 4 +- readme.md | 10 +- src/core/base/7zArchive.cpp | 6 +- src/core/base/EventIntf.cpp | 5 - src/core/base/ScriptMgnIntf.cpp | 9 +- src/core/base/SysInitIntf.cpp | 13 + src/core/base/XP3Archive.cpp | 34 +- src/core/base/XP3Archive.h | 16 +- src/core/base/win32/FileSelector.cpp | 8 +- src/core/base/win32/PluginImpl.cpp | 1 - src/core/base/win32/StorageImpl.cpp | 30 +- src/core/base/win32/StorageImpl.h | 5 + src/core/base/win32/win32io.h | 11 +- src/core/environ/DumpSend.cpp | 6 +- src/core/environ/Platform.h | 19 +- src/core/environ/XP3ArchiveRepack.cpp | 436 +++ src/core/environ/XP3ArchiveRepack.h | 20 + src/core/environ/android/AndroidUtils.cpp | 76 +- src/core/environ/cocos2d/AppDelegate.cpp | 5 + src/core/environ/cocos2d/CustomFileUtils.cpp | 196 ++ src/core/environ/cocos2d/CustomFileUtils.mm | 1 + src/core/environ/cocos2d/MainScene.cpp | 12 +- src/core/environ/ui/BaseForm.cpp | 40 +- src/core/environ/ui/BaseForm.h | 22 +- src/core/environ/ui/FileSelectorForm.cpp | 289 +- src/core/environ/ui/FileSelectorForm.h | 45 +- src/core/environ/ui/GlobalPreferenceForm.cpp | 3 +- .../environ/ui/IndividualPreferenceForm.cpp | 5 +- src/core/environ/ui/MainFileSelectorForm.cpp | 10 +- src/core/environ/ui/PreferenceConfig.h | 64 +- src/core/environ/ui/PreferenceForm.h | 10 +- src/core/environ/ui/XP3RepackForm.cpp | 205 ++ src/core/environ/ui/XP3RepackForm.h | 4 + src/core/environ/win32/Platform.cpp | 56 +- src/core/movie/ffmpeg/KRMoviePlayer.cpp | 8 + src/core/movie/ffmpeg/KRMoviePlayer.h | 1 + src/core/sound/win32/WaveImpl.cpp | 3 +- src/core/tjs2/tjs.h | 1 - src/core/utils/minizip/ioapi.cpp | 9 +- src/core/visual/FontImpl.cpp | 79 +- src/core/visual/FontImpl.h | 2 +- src/core/visual/FreeTypeFontRasterizer.cpp | 45 +- src/core/visual/FreeTypeFontRasterizer.h | 2 + src/core/visual/GraphicsLoadThread.cpp | 6 +- src/core/visual/GraphicsLoaderIntf.cpp | 259 +- src/core/visual/GraphicsLoaderIntf.h | 2 +- src/core/visual/LayerManager.cpp | 36 +- src/core/visual/LayerManager.h | 1 + src/core/visual/LoadJPEG.cpp | 4 +- src/core/visual/LoadJXR.cpp | 4 +- src/core/visual/LoadPVRv3.cpp | 152 ++ src/core/visual/RenderManager.cpp | 384 ++- src/core/visual/RenderManager.h | 8 + src/core/visual/ogl/RenderManager_ogl.cpp | 628 ++++- src/core/visual/ogl/astcrt.cpp | 2361 +++++++++++++++++ src/core/visual/ogl/astcrt.h | 16 + src/core/visual/ogl/etcpak.cpp | 2324 ++++++++++++++++ src/core/visual/ogl/etcpak.h | 11 + src/core/visual/ogl/imagepacker.cpp | 348 +++ src/core/visual/ogl/imagepacker.h | 92 + src/core/visual/ogl/ogl_common.h | 1 - src/core/visual/ogl/pvr.h | 65 + src/core/visual/ogl/pvrtc.cpp | 957 +++++++ src/core/visual/ogl/pvrtc.h | 5 + src/core/visual/tvpgl.cpp | 1 + src/core/visual/win32/BasicDrawDevice.cpp | 8 + src/core/visual/win32/LayerBitmapImpl.cpp | 15 +- src/core/visual/win32/VideoOvlImpl.cpp | 13 +- src/core/visual/win32/WindowImpl.cpp | 11 +- src/core/visual/win32/WindowImpl.h | 2 +- src/plugins/dirlist.cpp | 1 - src/plugins/xp3filter.cpp | 49 +- 81 files changed, 9295 insertions(+), 498 deletions(-) create mode 100644 cocos/kr2/Resources/res/locale/zh_tw.xml create mode 100644 cocos/kr2/cocosstudio/img/back_icon.png create mode 100644 cocos/kr2/cocosstudio/img/right_triangle.png create mode 100644 src/core/environ/XP3ArchiveRepack.cpp create mode 100644 src/core/environ/XP3ArchiveRepack.h create mode 100644 src/core/environ/cocos2d/CustomFileUtils.cpp create mode 100644 src/core/environ/cocos2d/CustomFileUtils.mm create mode 100644 src/core/environ/ui/XP3RepackForm.cpp create mode 100644 src/core/environ/ui/XP3RepackForm.h create mode 100644 src/core/visual/LoadPVRv3.cpp create mode 100644 src/core/visual/ogl/astcrt.cpp create mode 100644 src/core/visual/ogl/astcrt.h create mode 100644 src/core/visual/ogl/etcpak.cpp create mode 100644 src/core/visual/ogl/etcpak.h create mode 100644 src/core/visual/ogl/imagepacker.cpp create mode 100644 src/core/visual/ogl/imagepacker.h create mode 100644 src/core/visual/ogl/pvr.h create mode 100644 src/core/visual/ogl/pvrtc.cpp create mode 100644 src/core/visual/ogl/pvrtc.h diff --git a/cocos/kr2/Resources/res/locale/en_us.xml b/cocos/kr2/Resources/res/locale/en_us.xml index 083877e5..8f503357 100644 --- a/cocos/kr2/Resources/res/locale/en_us.xml +++ b/cocos/kr2/Resources/res/locale/en_us.xml @@ -58,6 +58,8 @@ + + @@ -67,12 +69,13 @@ - + + @@ -85,6 +88,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cocos/kr2/Resources/res/locale/ja_jp.xml b/cocos/kr2/Resources/res/locale/ja_jp.xml index d0455c71..d6abfbd8 100644 --- a/cocos/kr2/Resources/res/locale/ja_jp.xml +++ b/cocos/kr2/Resources/res/locale/ja_jp.xml @@ -67,7 +67,7 @@ - + @@ -84,7 +84,22 @@ - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cocos/kr2/Resources/res/locale/zh_cn.xml b/cocos/kr2/Resources/res/locale/zh_cn.xml index e23a1edd..c04ae8f8 100644 --- a/cocos/kr2/Resources/res/locale/zh_cn.xml +++ b/cocos/kr2/Resources/res/locale/zh_cn.xml @@ -58,6 +58,8 @@ + + @@ -67,12 +69,13 @@ - + + @@ -84,7 +87,22 @@ - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cocos/kr2/Resources/res/locale/zh_tw.xml b/cocos/kr2/Resources/res/locale/zh_tw.xml new file mode 100644 index 00000000..fe2ea9ee --- /dev/null +++ b/cocos/kr2/Resources/res/locale/zh_tw.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cocos/kr2/cocosstudio/img/back_icon.png b/cocos/kr2/cocosstudio/img/back_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..504450ed7d4610e252888ecbc9c6775e7aaa9f8b GIT binary patch literal 1842 zcmV-22hI42P)WFU8GbZ8()Nlj2>E@cM*00x#xL_t(|+U=cLs9x0> zhM(_vG!8Ku6N9!Er$nSgoUo}ePOVtyi7FZ)qG%kz2t_Eg2x4#AUQ|Q1rRjwb>QqEU zq9RU6h@xVpP>VTnh&aU)V`6fmNj*7lFaGU>dQ4nv|Nmb9ejoTb=VmYV{@!LLD=-n*z50I;7zH)}>#Dyaz)Ilt>h}ilYDBzU!nH>W#oht< z32+Q>5-_V0H#h5n=Yhw8Wx#V0@u>~a8_~B1P6W;e&Ib1E!D$Qw4*>T9PesJ24N$M> z0sI`e5I6^z(xB5@1KbbX6%kLm2Vyz9(5&tyc_Co7?$|K~Z5%F;Dh6f6(^$UR|Efb&WdY4FAC}~3LGJvFM)k~D4 zedoioz}XS8x)lbHbP(_;op3#mNq?;to=u$bGUX4|}--T=Y zx1>qM8K6tz4@tVRSHrH7c9b+<(knGz=RHLkK++kKHq{uARg&gasksR^xKW+Ysv58P zx*`lvbI%`>v`Es_hCb;`k@VLL@&6`1k#uOI4N$ZAt0bLT!o$3zlO?@dLkyNnn%GDK z)GYo()!0EXNSZ3?!5U)mr^Xqen#568&A&0%Q%R5R4gQch9;1>DZ;SzI6n~4P1^w`T zx0()?^S)r&xEgiK0FsWcDeaF*n%_?^`6XSSIYx7PYJi%>zos8YdNP-Tx4(5FN!Nx!9KNIEZ5>|U(h05w17|B99(>7Gn6Ji0am)GYq>w3w4r?>B!gKEAn7DYAJ-)Q?=1c2TJ2m-Xi4St&)Uo}Mt&Tfk2dOgf4bS^RNXoS}^qn`nT)xaV&bPXliywsCyZ3~&dqz~XOJ85pCp z%2*0>nq+{*z`PtEX0(VW5|96Zq&=HpfPVm&RjxK$&r%>YW;1$ifCnn^-SfA-46FrS zPHbj+FAZ=v@XIEypIZ?BN<99cfgbjJ*Zg!|EwKbV23+KxpM}>FkKcQ+2PeH5SS;mv z+KY$_Y=H00#9kQS40qgYe3*Frt^K02{ytFa@(Y@%RZgK-o-8Jbt4MP&TQX zU2U)dYyeX*``Q5QCUvR1wKhQ69GrOk3LBtoX4(KPrCI@OR$?>DZGdt)hR(|6DGb>F z<#Iye@f(4+Y=AP;e*hOHHnTJ$whTsuO;8@QGrR+M3JTgb&U}T7Gqmv}^zrubeVqO$ z(b*hgazZoffhiF&>I_gGS0{=O@I*w6I+O)I`>V6Ncq-?8)}}K+SzJZ>HhLcc4?C3Q z_5S@cEyJ_u%k4S?l*7&BS%&8hhqAohe{RNIMBM2MO#SOJ8Q>B}v@G#=ko0^e@!yd2 zwZS8&gJ$zb;HXS(-Qw>t%j*5V$++0&u%ula)pEqYJoCOmS2(KWh<{CH@t-brCpC+o z&Zm+Nab(*mz9jwGku5{~dDSM5Iaw)bvZLG1p1(=bk&bRV#h12a`*_oL;@{`H{&v=U zNg9$g)e&xI&tEC&hmLSN#a|=o$BuA2#UGJ0%Mot2_!rky^EXR6E9Klg9NZmV2G(V6 za}#i8L_Fy5mXQSx%QU51l0K1ivZLBg6IlEfo513?*aQ~8#U`-$EjEG0?{gCzK8^`2 zev3@7#^T!qYrZ7@FC3M2n&2Ia-)<93aukd)#u#IaF~%5Uj4{R-V~jDz7-Nhv#u#Ia gF~%5Uj4|W$fAxb<`j9E=G5`Po07*qoM6N<$g3y;{V*mgE literal 0 HcmV?d00001 diff --git a/cocos/kr2/cocosstudio/img/right_triangle.png b/cocos/kr2/cocosstudio/img/right_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..3c32d061dad99ce4fd4f038dfd0ea325091ab5f6 GIT binary patch literal 768 zcmWmCNlX(_9LMoDWnzXcnYtXLpwn`pN@C+hT);wyrBsVrX~AKpkQjvo%fY0gfp*jr zkf;Yw4Pbg9cA(XR0mEWqdLYtj0>lL>B^(SkTHv6GF%kT|L%;pJ|NCc>X*zJpSznk} zlt&0Dv^R)X@U7Ahw-1e1btDMU&D+K5tG&b7rBnXm>H@N8+750u3zdxrKPQuu_alwu zI=4HY_0KRzTM|`!$<(J4-O{tYpQg?)o53kA^!q&FtH-w(qJNdqgF~a>)9piG<`Dt2 zW#GFHlt0SBNQ^Rb3>-)^#MH1pL#%AXz!34tnV6tl!0wKJg;BY*rX&2@ZyMgEP1_Gf_g@Vu8lzVMxam$G+hF%&WVEhx3X4H xk5occYmoBKDyV^eo1l*N8+q%xPf)kk0?hmThk=&mr3(DBiM`e-#%kIh{0F77?AZVS literal 0 HcmV?d00001 diff --git a/cocos/kr2/cocosstudio/ui/FileItem.csd b/cocos/kr2/cocosstudio/ui/FileItem.csd index 39915478..f8d8dd65 100644 --- a/cocos/kr2/cocosstudio/ui/FileItem.csd +++ b/cocos/kr2/cocosstudio/ui/FileItem.csd @@ -13,12 +13,12 @@ - + - + @@ -29,7 +29,7 @@ - + @@ -46,10 +46,24 @@ + + + + + + + + + + + + + + - + @@ -62,7 +76,7 @@ - + diff --git a/cocos/kr2/kr2.ccs b/cocos/kr2/kr2.ccs index c21c2b7e..44b5fa31 100644 --- a/cocos/kr2/kr2.ccs +++ b/cocos/kr2/kr2.ccs @@ -7,6 +7,7 @@ + @@ -29,6 +30,7 @@ + @@ -56,6 +58,7 @@ + diff --git a/project/android/AndroidManifest.xml b/project/android/AndroidManifest.xml index 523d0cbe..30c815b2 100644 --- a/project/android/AndroidManifest.xml +++ b/project/android/AndroidManifest.xml @@ -9,7 +9,7 @@ android:installLocation="auto"> - + @@ -19,11 +19,6 @@ - diff --git a/project/android/jni/Application.mk b/project/android/jni/Application.mk index d428bd97..c74b9978 100644 --- a/project/android/jni/Application.mk +++ b/project/android/jni/Application.mk @@ -3,7 +3,7 @@ APP_CPPFLAGS := -fexceptions -std=c++11 APP_CPPFLAGS += -O3 -DNDEBUG -Wno-inconsistent-missing-override #APP_CPPFLAGS += -D__DO_PROF__ -g -pg APP_ABI := armeabi-v7a -#APP_ABI += arm64-v8a -APP_PLATFORM := android-9 +APP_ABI += arm64-v8a +APP_PLATFORM := android-10 #NDK_TOOLCHAIN_VERSION := 4.9 NDK_TOOLCHAIN_VERSION := clang \ No newline at end of file diff --git a/readme.md b/readme.md index a4e8e599..01d74187 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,10 @@ Kirikiroid2 - A cross-platform port of Kirikiri2/KirikiriZ ========================================================== -Depend on most code from Kirikiri2 and KirikiriZ(https://github.com/krkrz/krkrz) -Video playback module modified from kodi(https://github.com/xbmc/xbmc) -Some string code from glibc and Apple Libc. \ No newline at end of file +Based on most code from [Kirikiri2](http://kikyou.info/tvp/) and [KirikiriZ](https://github.com/krkrz/krkrz) + +Video playback module modified from [kodi](https://github.com/xbmc/xbmc) + +Some string code from [glibc](https://www.gnu.org/s/libc) and [Apple Libc](https://opensource.apple.com/source/Libc). + +Real-time texture codec modified from [etcpak](https://bitbucket.org/wolfpld/etcpak.git), [pvrtccompressor](https://bitbucket.org/jthlim/pvrtccompressor), [astcrt](https://github.com/daoo/astcrt) diff --git a/src/core/base/7zArchive.cpp b/src/core/base/7zArchive.cpp index d4f9aadb..b49d7da1 100644 --- a/src/core/base/7zArchive.cpp +++ b/src/core/base/7zArchive.cpp @@ -3,9 +3,9 @@ #include "UtilStreams.h" #include extern "C" { -#include "7zip/7z.h" -#include "7zip/7zFile.h" -#include "7zip/7zCrc.h" +#include "7zip/C/7z.h" +#include "7zip/C/7zFile.h" +#include "7zip/C/7zCrc.h" } static ISzAlloc allocImp = { diff --git a/src/core/base/EventIntf.cpp b/src/core/base/EventIntf.cpp index ee5218b5..6ce61ab2 100644 --- a/src/core/base/EventIntf.cpp +++ b/src/core/base/EventIntf.cpp @@ -577,8 +577,6 @@ static bool _TVPDeliverAllEvents() return ret_value; } //--------------------------------------------------------------------------- -void start_profile(); -void stop_profile(); void TVPDeliverAllEvents() { bool r; @@ -590,7 +588,6 @@ void TVPDeliverAllEvents() } TVPEventInterrupting = false; - start_profile(); try { try @@ -633,7 +630,6 @@ void TVPDeliverAllEvents() TVPDeliverContinuousEvent(); } - stop_profile(); try { try @@ -645,7 +641,6 @@ void TVPDeliverAllEvents() } TVP_CATCH_AND_SHOW_SCRIPT_EXCEPTION(TJS_W("window update")); } else { - stop_profile(); } if(TVPEventQueue.size() == 0) diff --git a/src/core/base/ScriptMgnIntf.cpp b/src/core/base/ScriptMgnIntf.cpp index 7f724c2a..0208e62a 100644 --- a/src/core/base/ScriptMgnIntf.cpp +++ b/src/core/base/ScriptMgnIntf.cpp @@ -875,7 +875,7 @@ void TVPDumpScriptEngine() bool TVPStartupSuccess = false; - +void TVPOpenPatchLibUrl(); //--------------------------------------------------------------------------- // TVPExecuteStartupScript //--------------------------------------------------------------------------- @@ -914,7 +914,12 @@ void TVPExecuteStartupScript() ttstr msg = LocaleConfigManager::GetInstance()->GetText("startup_patch_fail"); msg += "\n"; msg += strPatchError; - TVPShowSimpleMessageBox(msg, TVPGetPackageVersionString()); + std::vector btns; + btns.emplace_back(LocaleConfigManager::GetInstance()->GetText("msgbox_ok")); + btns.emplace_back(LocaleConfigManager::GetInstance()->GetText("browse_patch_lib")); + if (TVPShowSimpleMessageBox(msg, TVPGetPackageVersionString()) == 1) { + TVPOpenPatchLibUrl(); + } } // execute "startup.tjs" diff --git a/src/core/base/SysInitIntf.cpp b/src/core/base/SysInitIntf.cpp index 50371128..b752b085 100644 --- a/src/core/base/SysInitIntf.cpp +++ b/src/core/base/SysInitIntf.cpp @@ -18,6 +18,7 @@ #include "SysInitIntf.h" #include "ScriptMgnIntf.h" #include "tvpgl.h" +#include "Protect.h" //--------------------------------------------------------------------------- @@ -35,6 +36,18 @@ extern void TVPGL_C_Init(); //--------------------------------------------------------------------------- void TVPSystemInit(void) { +#if CC_TARGET_PLATFORM != CC_PLATFORM_WIN32 +#ifndef CC_TARGET_OS_IPHONE + if (!TVPProtectInit()) return; +#endif +//#else +#ifdef USING_PROTECT + while (!TVPProtectInit()) { + TVPUpdateLicense(); + } +#endif +#endif + TVPBeforeSystemInit(); TVPInitScriptEngine(); diff --git a/src/core/base/XP3Archive.cpp b/src/core/base/XP3Archive.cpp index 5d892205..96c17621 100644 --- a/src/core/base/XP3Archive.cpp +++ b/src/core/base/XP3Archive.cpp @@ -228,7 +228,7 @@ static tTVPAtExit TVPShutdownArchiveCacheAtExit archive. */ //--------------------------------------------------------------------------- -static bool TVPGetXP3ArchiveOffset(tTJSBinaryStream *st, const ttstr name, +bool TVPGetXP3ArchiveOffset(tTJSBinaryStream *st, const ttstr name, tjs_uint64 & offset, bool raise) { st->SetPosition(0); @@ -339,16 +339,12 @@ bool TVPIsXP3Archive(const ttstr &name) return false; } } + //--------------------------------------------------------------------------- -tTVPXP3Archive::tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st, tjs_int64 off) : tTVPArchive(name) +void tTVPXP3Archive::Init(tTJSBinaryStream *st, tjs_int64 off, bool normalizeName) { - Name = name; - Count = 0; - tjs_uint64 offset = off; - if(!st) st = TVPCreateStream(name); - tjs_uint8 *indexdata = NULL; static const tjs_uint8 cn_File[] = @@ -360,13 +356,13 @@ tTVPXP3Archive::tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st, tjs_int static const tjs_uint8 cn_adlr[] = { 0x61/*'a'*/, 0x64/*'d'*/, 0x6c/*'l'*/, 0x72/*'r'*/ }; - TVPAddLog( TVPFormatMessage(TVPInfoTryingToReadXp3VirtualFileSystemInformationFrom, name) ); + TVPAddLog( TVPFormatMessage(TVPInfoTryingToReadXp3VirtualFileSystemInformationFrom, ArchiveName) ); int segmentcount = 0; try { // retrieve archive offset - if(off < 0) TVPGetXP3ArchiveOffset(st, name, offset, true); + if(off < 0) TVPGetXP3ArchiveOffset(st, ArchiveName, offset, true); // read index position and seek st->SetPosition(11 + offset); @@ -466,7 +462,8 @@ tTVPXP3Archive::tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st, tjs_int ttstr name = TVPStringFromBMPUnicode( (const tjs_uint16 *)(indexdata + ch_info_start + 22), len); item.Name = name; - NormalizeInArchiveStorageName(item.Name); + if (normalizeName) + NormalizeInArchiveStorageName(item.Name); // find 'segm' sub-chunk // Each of in-archive storages can be splitted into some segments. @@ -543,6 +540,13 @@ tTVPXP3Archive::tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st, tjs_int TVPAddLog( TVPFormatMessage( TVPInfoDoneWithContains, ttstr(Count), ttstr(segmentcount) ) ); } + +//--------------------------------------------------------------------------- +tTVPXP3Archive::tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st, tjs_int64 offset) : tTVPArchive(name) +{ + if (!st) st = TVPCreateStream(name); + Init(st, offset); +} //--------------------------------------------------------------------------- tTVPXP3Archive::~tTVPXP3Archive() { @@ -570,22 +574,22 @@ tTJSBinaryStream * tTVPXP3Archive::CreateStreamByIndex(tjs_uint idx) tArchiveItem &item = ItemVector[idx]; - tTJSBinaryStream *stream = TVPGetCachedArchiveHandle(this, Name); + tTJSBinaryStream *stream = TVPGetCachedArchiveHandle(this, ArchiveName); - tTJSBinaryStream *out; + tTVPXP3ArchiveStream *out; try { out = new tTVPXP3ArchiveStream(this, idx, &(item.Segments), stream, item.OrgSize); if (TVPXP3ArchiveContentFilter) { - tjs_int result = TVPXP3ArchiveContentFilter(item.Name, Name, item.OrgSize); + tjs_int result = TVPXP3ArchiveContentFilter(item.Name, ArchiveName, item.OrgSize, &out->GetFilterContext()); #define XP3_CONTENT_FILTER_FETCH_FULLDATA 1 if (result == XP3_CONTENT_FILTER_FETCH_FULLDATA) { tTVPMemoryStream *memstr = new tTVPMemoryStream(); memstr->SetSize(item.OrgSize); out->ReadBuffer(memstr->GetInternalBuffer(), item.OrgSize); delete out; - out = memstr; + return memstr; } } } @@ -1044,7 +1048,7 @@ tjs_uint TJS_INTF_METHOD tTVPXP3ArchiveStream::Read(void *buffer, tjs_uint read_ tTVPXP3ExtractionFilterInfo info(CurPos, (tjs_uint8*)buffer + write_size, one_size, Owner->GetFileHash(StorageIndex), Owner->GetName(StorageIndex)); TVPXP3ArchiveExtractionFilter - ( (tTVPXP3ExtractionFilterInfo*) &info ); + ( (tTVPXP3ExtractionFilterInfo*) &info, &FilterContext ); } // adjust members diff --git a/src/core/base/XP3Archive.h b/src/core/base/XP3Archive.h index a301c626..49beade4 100644 --- a/src/core/base/XP3Archive.h +++ b/src/core/base/XP3Archive.h @@ -50,9 +50,9 @@ struct tTVPXP3ExtractionFilterInfo // for backward application compatibility. typedef void (TVP_tTVPXP3ArchiveExtractionFilter_CONVENTION * - tTVPXP3ArchiveExtractionFilter)(tTVPXP3ExtractionFilterInfo *info); + tTVPXP3ArchiveExtractionFilter)(tTVPXP3ExtractionFilterInfo *info, tTJSVariant *ctx); typedef tjs_int (TVP_tTVPXP3ArchiveExtractionFilter_CONVENTION * - tTVPXP3ArchiveContentFilter)(const ttstr &filepath, const ttstr &archivename, tjs_uint64 filesize); + tTVPXP3ArchiveContentFilter)(const ttstr &filepath, const ttstr &archivename, tjs_uint64 filesize, tTJSVariant *ctx); /*]*/ //--------------------------------------------------------------------------- @@ -94,8 +94,7 @@ struct tTVPXP3ArchiveSegment //--------------------------------------------------------------------------- class tTVPXP3Archive : public tTVPArchive { - ttstr Name; - +public: struct tArchiveItem { ttstr Name; @@ -109,10 +108,13 @@ class tTVPXP3Archive : public tTVPArchive } }; - tjs_int Count; + tjs_int Count = 0; std::vector ItemVector; + void Init(tTJSBinaryStream *st, tjs_int64 offset, bool normalizeName = true); + public: + tTVPXP3Archive(const ttstr & name, int) : tTVPArchive(name) {} tTVPXP3Archive(const ttstr & name, tTJSBinaryStream *st = nullptr, tjs_int64 offset = -1); ~tTVPXP3Archive(); @@ -123,7 +125,7 @@ class tTVPXP3Archive : public tTVPArchive tjs_uint32 GetFileHash(tjs_uint idx) const { return ItemVector[idx].FileHash; } ttstr GetName(tjs_uint idx) { return ItemVector[idx].Name; } - const ttstr & GetName() const { return Name; } + const ttstr & GetName() const { return ArchiveName; } tTJSBinaryStream * CreateStreamByIndex(tjs_uint idx); @@ -168,12 +170,14 @@ class tTVPXP3ArchiveStream : public tTJSBinaryStream tTVPSegmentData *SegmentData; // uncompressed segment data bool SegmentOpened; + tTJSVariant FilterContext; public: tTVPXP3ArchiveStream(tTVPXP3Archive *owner, tjs_int storageindex, std::vector *segments, tTJSBinaryStream *stream, tjs_uint64 orgsize); ~tTVPXP3ArchiveStream(); + tTJSVariant &GetFilterContext() { return FilterContext; } private: void EnsureSegment(); // ensure accessing to current segment diff --git a/src/core/base/win32/FileSelector.cpp b/src/core/base/win32/FileSelector.cpp index 763f1474..4ddcbe63 100644 --- a/src/core/base/win32/FileSelector.cpp +++ b/src/core/base/win32/FileSelector.cpp @@ -353,12 +353,16 @@ bool TVPSelectFile(iTJSDispatch2 *params) if (lname.IndexOf('/') >= 0) { lname = TVPNormalizeStorageName(lname); TVPGetLocalName(lname); - initialdir.clear(); + ttstr path = TVPExtractStoragePath(lname); + ttstr name = TVPExtractStorageName(lname); + lname = name; + initialdir = path.AsStdString(); } else { } if (!defaultext.empty() && TVPExtractStorageExt(lname).IsEmpty()) { - lname += TJS_W("."); + if (defaultext[0] != '.') + lname += TJS_W("."); lname += defaultext.c_str(); } filename = tTJSNarrowStringHolder(lname.c_str()); diff --git a/src/core/base/win32/PluginImpl.cpp b/src/core/base/win32/PluginImpl.cpp index d0d94d58..77667adc 100644 --- a/src/core/base/win32/PluginImpl.cpp +++ b/src/core/base/win32/PluginImpl.cpp @@ -44,7 +44,6 @@ #include "FilePathUtil.h" #include "Application.h" -#include #include "SysInitImpl.h" #include #ifdef _MSC_VER diff --git a/src/core/base/win32/StorageImpl.cpp b/src/core/base/win32/StorageImpl.cpp index 507dc498..c63424bf 100644 --- a/src/core/base/win32/StorageImpl.cpp +++ b/src/core/base/win32/StorageImpl.cpp @@ -37,35 +37,8 @@ #include "TickCount.h" #include #include -#include "win32io.h" #include "combase.h" -#include -#ifdef CC_TARGET_OS_IPHONE -#define lseek64 lseek -#endif - -#ifdef WIN32 -typedef struct _stat64 tTVP_stat; -#else -typedef struct ::stat64 tTVP_stat; -#endif - -static bool TVP_stat(const tjs_char *name, tTVP_stat &s) { -#ifdef WIN32 - return !_wstat64(name, &s); -#else - tTJSNarrowStringHolder holder(name); - return !::stat64(holder, &s); -#endif -} -static bool TVP_stat(const char *name, tTVP_stat &s) { -#ifdef WIN32 - ttstr filename(name); - return !_wstat64(filename.c_str(), &s); -#else - return !::stat64(name, &s); -#endif -} +#include "win32io.h" //--------------------------------------------------------------------------- // tTVPFileMedia @@ -355,7 +328,6 @@ void TJS_INTF_METHOD tTVPFileMedia::GetLocallyAccessibleName(ttstr &name) DIR *dirp; struct dirent *direntp; - struct stat stat_buf; newname += "/"; if ((dirp = opendir( tTJSNarrowStringHolder(newname.c_str()) ))) { bool found = false; diff --git a/src/core/base/win32/StorageImpl.h b/src/core/base/win32/StorageImpl.h index cb849c4f..31c7cf0b 100644 --- a/src/core/base/win32/StorageImpl.h +++ b/src/core/base/win32/StorageImpl.h @@ -26,6 +26,11 @@ void TVPUnloadArchiveSPI(HINSTANCE inst); //--------------------------------------------------------------------------- #endif +#ifndef S_IFMT +#define S_IFDIR 0x4000 // Directory +#define S_IFREG 0x8000 // Regular +#endif + struct tTVPLocalFileInfo { const char * NativeName; unsigned short Mode; // S_IFMT diff --git a/src/core/base/win32/win32io.h b/src/core/base/win32/win32io.h index 1544a73c..b5b7c3e3 100644 --- a/src/core/base/win32/win32io.h +++ b/src/core/base/win32/win32io.h @@ -1,17 +1,12 @@ #pragma once #ifdef WIN32 -#include // posix io api extern "C" { - extern int __cdecl close(int _FileHandle); - extern int __cdecl read(int _FileHandle, void * _DstBuf, unsigned int _MaxCharCount); extern __int64 __cdecl lseek64(int _FileHandle, __int64 _Offset, int _Origin); - extern int __cdecl write(int _Filehandle, const void * _Buf, unsigned int _MaxCharCount); - extern int __cdecl open_win32(const char * _Filename, int _OpenFlag, int access = 0); -#define open(...) open_win32(__VA_ARGS__) extern void* valloc(int n); extern void vfree(void *p); - extern void logStack(std::string &out); - FILE * fopen(const char * _Filename, const char * _Mode); } +#endif +#ifdef CC_TARGET_OS_IPHONE +#define lseek64 lseek #endif \ No newline at end of file diff --git a/src/core/environ/DumpSend.cpp b/src/core/environ/DumpSend.cpp index 7c9a6b1b..77c7947d 100644 --- a/src/core/environ/DumpSend.cpp +++ b/src/core/environ/DumpSend.cpp @@ -6,11 +6,9 @@ #include "Platform.h" #include "SysInitIntf.h" #include "ConfigManager/LocaleConfigManager.h" -#include #include "StorageImpl.h" #include "minizip/ioapi.h" #include "minizip/zip.h" -#include "win32io.h" #include #include #include @@ -120,8 +118,8 @@ static void SendDumps(std::string dumpdir, std::vector allDumps, st for (const std::string &filename : allDumps) { std::string fullpath = dumpdir + "/" + filename; do { - struct stat stat_buf; - if (stat(fullpath.c_str(), &stat_buf) == -1) { + struct tTVP_stat stat_buf; + if (!TVP_stat(fullpath.c_str(), stat_buf)) { break; } diff --git a/src/core/environ/Platform.h b/src/core/environ/Platform.h index 16611c37..37cb72d9 100644 --- a/src/core/environ/Platform.h +++ b/src/core/environ/Platform.h @@ -32,11 +32,26 @@ std::string TVPGetPackageVersionString(); void TVPExitApplication(int code); void TVPCheckMemory(); const std::string &TVPGetInternalPreferencePath(); -bool TVPDeleteFile(const ttstr &filename); -bool TVPRenameFile(const ttstr &from, const ttstr &to); +bool TVPDeleteFile(const std::string &filename); +bool TVPRenameFile(const std::string &from, const std::string &to); +bool TVPCopyFile(const std::string &from, const std::string &to); void TVPShowIME(int x, int y, int w, int h); void TVPHideIME(); void TVPRelinquishCPU(); void TVPPrintLog(const char *str); + +void TVPFetchSDCardPermission(); // for android only + +struct tTVP_stat { + uint16_t st_mode; + uint64_t st_size; + uint64_t st_atime; + uint64_t st_mtime; + uint64_t st_ctime; +}; + +bool TVP_stat(const tjs_char *name, tTVP_stat &s); +bool TVP_stat(const char *name, tTVP_stat &s); + diff --git a/src/core/environ/XP3ArchiveRepack.cpp b/src/core/environ/XP3ArchiveRepack.cpp new file mode 100644 index 00000000..0280ff9e --- /dev/null +++ b/src/core/environ/XP3ArchiveRepack.cpp @@ -0,0 +1,436 @@ +#include "XP3ArchiveRepack.h" +#include +#include "7zip/CPP/7zip/Archive/7z/7zOut.h" +#include "7zip/CPP/7zip/Common/StreamObjects.h" +extern "C" { +#include "7zip/C/7zCrc.h" +} +#include "TextStream.h" +#include "StorageImpl.h" +#include "XP3Archive.h" +#include +#include +#include +#include +#include +#include "win32io.h" +#include "GraphicsLoaderIntf.h" +#include "ogl/etcpak.h" + +using namespace TJS; +using namespace NArchive::N7z; + +#define COMPRESS_THRESHOLD 4 * 1024 * 1024 + +void TVPSetXP3FilterScript(ttstr content); + +class tTVPXP3ArchiveEx : public tTVPXP3Archive { +public: + tTVPXP3ArchiveEx(const ttstr & name, tTJSBinaryStream *st) : tTVPXP3Archive(name, 0) { + Init(st, -1, false); + } +}; + +class OutStreamFor7z : public IOutStream { + long fp; + unsigned int RefCount = 1; + +public: + OutStreamFor7z(const std::string &path) { + open(path.c_str(), O_RDWR | O_CREAT, 0666); + } + ~OutStreamFor7z() { + close(fp); + } + + ULONG AddRef() { return ++RefCount; } + ULONG Release() { + if (RefCount == 1) { + delete this; + return 0; + } + return --RefCount; + } + HRESULT QueryInterface(REFIID iid, void **ppOut) { + if (!memcmp(&iid, &IID_IOutStream, sizeof(IID_IOutStream))) { + *ppOut = (IOutStream*)this; + return S_OK; + } + *ppOut = nullptr; + return E_NOTIMPL; + } + + HRESULT Write(const void *data, UInt32 size, UInt32 *processedSize) { + *processedSize = write(fp, data, size); + return S_OK; + } + HRESULT Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) override { + int64_t pos = lseek64(fp, offset, seekOrigin); + if (newPosition) *newPosition = pos; + return S_OK; + } + HRESULT SetSize(UInt64 newSize) { + return S_OK; + } +}; + +class OutStreamMemory : public IOutStream, public tTVPMemoryStream { + unsigned int RefCount = 1; + +public: + ULONG AddRef() { return ++RefCount; } + ULONG Release() { + if (RefCount == 1) { + delete this; + return 0; + } + return --RefCount; + } + HRESULT QueryInterface(REFIID iid, void **ppOut) { + if (!memcmp(&iid, &IID_IOutStream, sizeof(IID_IOutStream))) { + *ppOut = (IOutStream*)this; + return S_OK; + } + *ppOut = nullptr; + return E_NOTIMPL; + } + + HRESULT Write(const void *data, UInt32 size, UInt32 *processedSize) override { + *processedSize = tTVPMemoryStream::Write(data, size); + return S_OK; + } + HRESULT Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) override { + *newPosition = tTVPMemoryStream::Seek(offset, seekOrigin); + return S_OK; + } + HRESULT SetSize(UInt64 newSize) { + tTVPMemoryStream::SetSize(newSize); + return S_OK; + } +}; + +class SequentialInStreamFor7z : public ISequentialInStream { + tTJSBinaryStream *fp; + unsigned int RefCount = 1; + +public: + SequentialInStreamFor7z(tTJSBinaryStream *s) { + fp = s; + } + ~SequentialInStreamFor7z() { + fp = nullptr; + } + + ULONG AddRef() { return ++RefCount; } + ULONG Release() { + if (RefCount == 1) { + delete this; + return 0; + } + return --RefCount; + } + HRESULT QueryInterface(REFIID iid, void **ppOut) { + if (!memcmp(&iid, &IID_IInStream, sizeof(IID_IInStream))) { + *ppOut = (ISequentialInStream*)this; + return S_OK; + } + *ppOut = nullptr; + return E_NOTIMPL; + } + + HRESULT Read(void *data, UInt32 size, UInt32 *processedSize) { + *processedSize = fp->Read(data, size); + return S_OK; + } +}; + +XP3ArchiveRepackAsync::XP3ArchiveRepackAsync(const std::string &xp3filter) +{ + if (!g_CrcTable[1]) CrcGenerateTable(); + ttstr xp3filterScript; + iTJSTextReadStream * stream = TVPCreateTextStreamForRead(xp3filter, ""); + stream->Read(xp3filterScript, 0); + delete stream; + TVPSetXP3FilterScript(xp3filterScript); +} + +XP3ArchiveRepackAsync::~XP3ArchiveRepackAsync() +{ + std::thread* t = (std::thread*)Thread; + if (t && t->joinable()) { + t->join(); + delete t; + } + Clear(); +} + +uint64_t XP3ArchiveRepackAsync::AddTask(const std::string &src/*, const std::string &dst*/) +{ + uint64_t totalSize = 0; + ttstr path(src); + tTVPLocalFileStream *str = new tTVPLocalFileStream(path, path, TJS_BS_READ); + tTVPXP3ArchiveEx *xp3arc = new tTVPXP3ArchiveEx(path, str); + for (const tTVPXP3Archive::tArchiveItem &item : xp3arc->ItemVector) { + totalSize += item.OrgSize; + } + ConvFileList.emplace_back(xp3arc, src); + return totalSize; +} + +void XP3ArchiveRepackAsync::Clear() +{ + for (auto & it : ConvFileList) { + delete it.first; + } + ConvFileList.clear(); + TVPSetXP3FilterScript(TJS_W("")); +} + +void XP3ArchiveRepackAsync::Start() +{ + if (!Thread) { + Thread = new std::thread(std::bind(&XP3ArchiveRepackAsync::DoConv, this)); + } +} + +void XP3ArchiveRepackAsync::DoConv() +{ + for (auto &it : ConvFileList) { + tTVPXP3ArchiveEx *xp3arc = it.first; + tjs_uint totalFile = xp3arc->GetCount(); + + std::string tmpname = it.second + ".7z"; + OutStreamFor7z *strout = new OutStreamFor7z(tmpname); + + const int nCodecMethod = 0x30101, // LZMA + nCodecMethodCopy = 0; + CCreatedCoder coder, coderCopy; + CreateCoder(nCodecMethod, true, coder); + CreateCoder(nCodecMethodCopy, true, coderCopy); + CMyComPtr writeCoderProperties; + CMyComPtr setCoderProperties; + coder.Coder->QueryInterface(IID_ICompressWriteCoderProperties, (void **)&writeCoderProperties); + coder.Coder->QueryInterface(IID_ICompressSetCoderProperties, (void **)&setCoderProperties); + CByteBuffer props; + + if (setCoderProperties) { + const int nProp = 1; + PROPVARIANT propvars[nProp]; + PROPID props[nProp] = { + NCoderPropID::kDictionarySize, + }; + propvars[0].vt = VT_UI4; + propvars[0].ulVal = 4 * 1024 * 1024; + setCoderProperties->SetCoderProperties(props, propvars, nProp); + } + if (writeCoderProperties) { + CDynBufSeqOutStream *outStreamSpec = new CDynBufSeqOutStream; + CMyComPtr dynOutStream(outStreamSpec); + outStreamSpec->Init(); + writeCoderProperties->WriteCoderProperties(dynOutStream); + outStreamSpec->CopyToBuffer(props); + } + + COutArchive archive; + archive.Create(strout, false); + archive.SkipPrefixArchiveHeader(); + CArchiveDatabaseOut newDatabase; + + for (const tTVPXP3Archive::tArchiveItem &item : xp3arc->ItemVector) { + if (!item.OrgSize) { + CFileItem file; + CFileItem2 file2; + file2.Init(); + UString name(item.Name.c_str()); + file.Size = item.OrgSize; + file.HasStream = false; + newDatabase.AddFile(file, file2, name); + } + } + for (const tTVPXP3Archive::tArchiveItem &item : xp3arc->ItemVector) { + if (item.OrgSize) { + CFileItem file; + CFileItem2 file2; + file2.Init(); + UString name(item.Name.c_str()); + file.Size = item.OrgSize; + std::auto_ptr s(xp3arc->CreateStreamByIndex(&item - &xp3arc->ItemVector.front())); + if (UsingETC2 && item.OrgSize > 8192) { + // convert image > 8k + tjs_uint8 header[16]; + s->Read(header, 16); + bool isImage = false, isOpaque = false; + if (!memcmp(header, "BM", 2)) { + isImage = true; + } else if (!memcmp(header, "\x89PNG", 4)) { + isImage = true; + } else if (!memcmp(header, "\xFF\xD8\xFF", 3)) { // JPEG + isImage = true; + } else if (!memcmp(header, "TLG", 3)) { + isImage = true; + } else if (!memcmp(header, "\x49\x49\xbc\x01", 4)) { // JXR + isImage = true; + } else if (!memcmp(header, "BPG", 3)) { + isImage = true; + } else if (!memcmp(header, "RIFF", 4)) { + if (!memcmp(header + 8, "WEBPVP8", 7)) { + isImage = true; + } + } + // actual TVPLoadGraphicRouter + static tTVPGraphicHandlerType *handler = TVPGetGraphicLoadHandler(TJS_W(".png")); + if (isImage) { + // skip 8-bit image + s->SetPosition(0); + iTJSDispatch2 *dic = nullptr; + handler->Header(s.get(), &dic); + if (dic) { + dic->Release(); + tTJSVariant val; + dic->PropGet(0, TJS_W("bpp"), 0, &val, dic); + int bpp = val.AsInteger(); + isOpaque = bpp == 24; + isImage = isOpaque || bpp == 32; + } else { + isImage = false; // unknown error, skip + } + } + if (isImage) { + // TODO merge mask file + s->SetPosition(0); + typedef std::tuple > PicInfo; + PicInfo picinfo; + handler->Load(nullptr, &picinfo, [](void *callbackdata, tjs_uint w, tjs_uint h, tTVPGraphicPixelFormat fmt)->int { + PicInfo &picinfo = *(PicInfo *)callbackdata; + std::get<0>(picinfo) = w; + std::get<1>(picinfo) = h; + int pitch = w * 4; + std::get<2>(picinfo) = pitch; + std::get<3>(picinfo).resize(pitch * h); + return w * 4; + }, [](void *callbackdata, int y)->void* { + PicInfo &picinfo = *(PicInfo *)callbackdata; + int pitch = std::get<2>(picinfo); + return &std::get<3>(picinfo)[pitch * y]; + }, [](void *callbackdata, const ttstr & name, const ttstr & value) { + }, s.get(), 0, glmNormal); + + int w = std::get<0>(picinfo); + int h = std::get<1>(picinfo); + int pitch = std::get<2>(picinfo); + uint8_t *imgdata = nullptr; + size_t datalen; + uint64_t pvr_pixfmt; + if (isOpaque) { + imgdata = (uint8_t *)ETCPacker::convert(&std::get<3>(picinfo).front(), w, h, pitch, true, datalen); + pvr_pixfmt = 22; // ETC2_RGB + } else { + imgdata = (uint8_t *)ETCPacker::convertWithAlpha(&std::get<3>(picinfo).front(), w, h, pitch, datalen); + pvr_pixfmt = 23; // ETC2_RGBA + } + + tTVPMemoryStream * memstr = new tTVPMemoryStream; + // in pvr v3 container + memstr->WriteBuffer("PVR\3", 4); + memstr->WriteBuffer("\0\0\0\0", 4); // no premultiply alpha + memstr->WriteBuffer(&pvr_pixfmt, sizeof(pvr_pixfmt)); + memstr->WriteBuffer("\0\0\0\0", 4); // colorSpace + memstr->WriteBuffer("\0\0\0\0", 4); // channelType + memstr->WriteBuffer(&h, sizeof(h)); + memstr->WriteBuffer(&w, sizeof(w)); + memstr->WriteBuffer("\1\0\0\0", 4); // depth + memstr->WriteBuffer("\1\0\0\0", 4); // numberOfSurfaces + memstr->WriteBuffer("\1\0\0\0", 4); // numberOfFaces + memstr->WriteBuffer("\1\0\0\0", 4); // numberOfMipmaps + memstr->WriteBuffer("\0\0\0\0", 4); // metadataLength + memstr->WriteBuffer(imgdata, datalen); + delete[] imgdata; + memstr->SetPosition(0); + s.reset(memstr); + } + } + + SequentialInStreamFor7z str(s.get()); + tjs_uint64 origSize = s->GetSize(); + UInt64 inSizeForReduce = origSize; + UInt64 newSize = origSize; + bool compressable = origSize > 64 && origSize < 4 * 1024 * 1024; + if (compressable) { + // compressable quick check + tjs_uint8 header[16]; + s->Read(header, 16); + if (!memcmp(header, "\x89PNG", 4)) { + compressable = false; + } else if (!memcmp(header, "OggS\x00", 5)) { + compressable = false; + } else if (!memcmp(header, "\xFF\xD8\xFF", 3)) { // JPEG + compressable = false; + } else if (!memcmp(header, "TLG", 3)) { + compressable = false; + } else if (!memcmp(header, "0&\xB2\x75\x8E\x66\xCF\x11", 8)) { // wmv/wma + compressable = false; + } else if (!memcmp(header, "AJPM", 4)) { // AMV + compressable = false; + } else if (!memcmp(header, "\x49\x49\xbc\x01", 4)) { // JXR + compressable = false; + } else if (!memcmp(header, "BPG", 3)) { + compressable = false; + } else if (!memcmp(header, "RIFF", 4)) { + if (!memcmp(header + 8, "WEBPVP8", 7)) { + compressable = false; + } + } + s->SetPosition(0); + } + if (compressable) { + UInt64 expectedSize = origSize * 4 / 5; + OutStreamMemory tmp; + coder.Coder->Code(&str, &tmp, &inSizeForReduce, &newSize, nullptr); + if (tmp.GetSize() < expectedSize) { + UInt32 writed; + strout->Write(tmp.GetInternalBuffer(), tmp.GetSize(), &writed); + newDatabase.PackSizes.Add(tmp.GetSize()); + newDatabase.CoderUnpackSizes.Add(origSize); + newDatabase.NumUnpackStreamsVector.Add(1); + CFolder &folder = newDatabase.Folders.AddNew(); + folder.Bonds.SetSize(0); + folder.Coders.SetSize(1); + CCoderInfo &cod = folder.Coders[0]; + cod.MethodID = nCodecMethod; + cod.NumStreams = 1; + cod.Props = props; + folder.PackStreams.SetSize(1); + folder.PackStreams[0] = 0; // only 1 stream + newDatabase.AddFile(file, file2, name); + continue; + } + s->SetPosition(0); + } + // direct copy + newSize = origSize; + coderCopy.Coder->Code(&str, strout, &inSizeForReduce, &newSize, nullptr); + newDatabase.PackSizes.Add(s->GetSize()); + newDatabase.CoderUnpackSizes.Add(origSize); + newDatabase.NumUnpackStreamsVector.Add(1); + CFolder &folder = newDatabase.Folders.AddNew(); + folder.Bonds.SetSize(0); + folder.Coders.SetSize(1); + CCoderInfo &cod = folder.Coders[0]; + cod.MethodID = nCodecMethodCopy; + cod.NumStreams = 1; + folder.PackStreams.SetSize(1); + folder.PackStreams[0] = 0; // only 1 stream + newDatabase.AddFile(file, file2, name); + } + } + CCompressionMethodMode mode; + CHeaderOptions hdropt; + hdropt.CompressMainHeader = true; + archive.WriteDatabase(newDatabase, &mode, hdropt); + archive.Close(); + delete xp3arc; + remove(it.second.c_str()); + rename(tmpname.c_str(), it.second.c_str()); + } + ConvFileList.clear(); +} diff --git a/src/core/environ/XP3ArchiveRepack.h b/src/core/environ/XP3ArchiveRepack.h new file mode 100644 index 00000000..e0f76f53 --- /dev/null +++ b/src/core/environ/XP3ArchiveRepack.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +class tTVPXP3ArchiveEx; +class XP3ArchiveRepackAsync { + std::vector > ConvFileList; + void * /*std::thread*/ Thread = nullptr; + +public: + XP3ArchiveRepackAsync(const std::string &xp3filter); + ~XP3ArchiveRepackAsync(); + uint64_t AddTask(const std::string &src/*, const std::string &dst*/); + void Clear(); + void Start(); + void DoConv(); + +public: + bool UsingETC2 = false; +}; diff --git a/src/core/environ/android/AndroidUtils.cpp b/src/core/environ/android/AndroidUtils.cpp index 049e66cd..2c494bfc 100644 --- a/src/core/environ/android/AndroidUtils.cpp +++ b/src/core/environ/android/AndroidUtils.cpp @@ -26,6 +26,11 @@ #include #include #include "TickCount.h" +#include "StorageImpl.h" +#include "ConfigManager/IndividualConfigManager.h" +#include "EventIntf.h" +#include "RenderManager.h" +#include USING_NS_CC; @@ -595,6 +600,34 @@ void TVPForceSwapBuffer() { eglSwapBuffers(eglGetCurrentDisplay(), eglGetCurrentSurface(EGL_DRAW)); } +static bool _IsLollipop() { + JNIEnv *pEnv = JniHelper::getEnv(); + jclass classID = pEnv->FindClass("android/os/Build$VERSION"); + jfieldID idSDK_INT = pEnv->GetStaticFieldID(classID, "SDK_INT", "I"); + jint sdkid = pEnv->GetStaticIntField(classID, idSDK_INT); + return sdkid >= 21; +} + +static bool IsLollipop() { + static bool result = _IsLollipop(); + return result; +} + +void TVPFetchSDCardPermission() { + if (!IsLollipop()) + return; + std::vector paths; + GetExternalStoragePath(paths); + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, "org/tvp/kirikiri2/KR2Activity", "requireLEXA", "(Ljava/lang/String;)V")) { + jstring jstrPath = methodInfo.env->NewStringUTF(paths.back().c_str()); + methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jstrPath); + methodInfo.env->DeleteLocalRef(jstrPath); + return; + } + return; +} + bool TVPCheckStartupPath(const std::string &path) { // check writing permission first int pos = path.find_last_of('/'); @@ -630,22 +663,14 @@ bool TVPCheckStartupPath(const std::string &path) { } std::vector btns; btns.push_back(LocaleConfigManager::GetInstance()->GetText("continue_run")); - JNIEnv *pEnv = JniHelper::getEnv(); - jclass classID = pEnv->FindClass("android/os/Build$VERSION"); - jfieldID idSDK_INT = methodInfo.env->GetStaticFieldID(classID, "SDK_INT", "I"); - jint sdkid = pEnv->GetStaticIntField(classID, idSDK_INT); - bool isLOLLIPOP = sdkid >= 21; + bool isLOLLIPOP = IsLollipop(); if (isLOLLIPOP) btns.push_back(LocaleConfigManager::GetInstance()->GetText("get_sdcard_permission")); else btns.push_back(LocaleConfigManager::GetInstance()->GetText("cancel")); int result = TVPShowSimpleMessageBox(msg, LocaleConfigManager::GetInstance()->GetText("readonly_storage"), btns); if (isLOLLIPOP && result == 1) { - if (JniHelper::getStaticMethodInfo(methodInfo, "org/tvp/kirikiri2/KR2Activity", "requireLEXA", "(Ljava/lang/String;)V")) { - jstring jstrPath = methodInfo.env->NewStringUTF(paths.back().c_str()); - methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jstrPath); - methodInfo.env->DeleteLocalRef(jstrPath); - } + TVPFetchSDCardPermission(); } if (result != 0) return false; @@ -708,6 +733,9 @@ std::string TVPGetCurrentLanguage() { } void TVPExitApplication(int code) { + TVPDeliverCompactEvent(TVP_COMPACT_LEVEL_MAX); + if (!TVPIsSoftwareRenderManager()) + iTVPTexture2D::RecycleProcess(); JniMethodInfo t; if (JniHelper::getStaticMethodInfo(t, "org/tvp/kirikiri2/KR2Activity", "exit", "()V")) { t.env->CallStaticVoidMethod(t.classID, t.methodID); @@ -732,10 +760,10 @@ void TVPShowIME(int x, int y, int w, int h) { void TVPProcessInputEvents() {} -bool TVPDeleteFile(const ttstr &filename) { +bool TVPDeleteFile(const std::string &filename) { JniMethodInfo methodInfo; if (JniHelper::getStaticMethodInfo(methodInfo, "org/tvp/kirikiri2/KR2Activity", "DeleteFile", "(Ljava/lang/String;)Z")) { - jstring jstr = methodInfo.env->NewStringUTF(filename.AsStdString().c_str()); + jstring jstr = methodInfo.env->NewStringUTF(filename.c_str()); bool ret = methodInfo.env->CallStaticBooleanMethod(methodInfo.classID, methodInfo.methodID, jstr); methodInfo.env->DeleteLocalRef(jstr); methodInfo.env->DeleteLocalRef(methodInfo.classID); @@ -744,11 +772,11 @@ bool TVPDeleteFile(const ttstr &filename) { return false; } -bool TVPRenameFile(const ttstr &from, const ttstr &to) { +bool TVPRenameFile(const std::string &from, const std::string &to) { JniMethodInfo methodInfo; if (JniHelper::getStaticMethodInfo(methodInfo, "org/tvp/kirikiri2/KR2Activity", "RenameFile", "(Ljava/lang/String;Ljava/lang/String;)Z")) { - jstring jstr = methodInfo.env->NewStringUTF(from.AsStdString().c_str()); - jstring jstr2 = methodInfo.env->NewStringUTF(to.AsStdString().c_str()); + jstring jstr = methodInfo.env->NewStringUTF(from.c_str()); + jstring jstr2 = methodInfo.env->NewStringUTF(to.c_str()); bool ret = methodInfo.env->CallStaticBooleanMethod(methodInfo.classID, methodInfo.methodID, jstr, jstr2); methodInfo.env->DeleteLocalRef(jstr); methodInfo.env->DeleteLocalRef(jstr2); @@ -765,4 +793,20 @@ tjs_uint32 TVPGetRoughTickCount32() if (clock_gettime(CLOCK_MONOTONIC, &on) == 0) uptime = on.tv_sec * 1000 + on.tv_nsec / 1000000; return uptime; -} \ No newline at end of file +} + +bool TVP_stat(const tjs_char *name, tTVP_stat &s) { + tTJSNarrowStringHolder holder(name); + return TVP_stat(holder, s); +} + +bool TVP_stat(const char *name, tTVP_stat &s) { + struct stat64 t; + bool ret = !stat64(name, &t); + s.st_mode = t.st_mode; + s.st_size = t.st_size; + s.st_atime = t.st_atime; + s.st_mtime = t.st_mtime; + s.st_ctime = t.st_ctime; + return ret; +} diff --git a/src/core/environ/cocos2d/AppDelegate.cpp b/src/core/environ/cocos2d/AppDelegate.cpp index 6a485146..b6638a43 100644 --- a/src/core/environ/cocos2d/AppDelegate.cpp +++ b/src/core/environ/cocos2d/AppDelegate.cpp @@ -15,6 +15,7 @@ USING_NS_CC; static Size designResolutionSize(960, 640); bool TVPCheckStartupArg(); std::string TVPGetCurrentLanguage(); +cocos2d::FileUtils *TVPCreateCustomFileUtils(); void TVPAppDelegate::applicationWillEnterForeground() { ::Application->OnActivate(); @@ -29,6 +30,7 @@ void TVPAppDelegate::applicationDidEnterBackground() { bool TVPAppDelegate::applicationDidFinishLaunching() { cocos2d::log("applicationDidFinishLaunching"); // initialize director + FileUtils::setDelegate(TVPCreateCustomFileUtils()); auto director = Director::getInstance(); auto glview = director->getOpenGLView(); if (!glview) { @@ -134,3 +136,6 @@ void TVPAppDelegate::applicationScreenSizeChanged(int newWidth, int newHeight) // director->getOpenGLView()->setFrameSize(newWidth, newHeight); } +void TVPOpenPatchLibUrl() { + cocos2d::Application::getInstance()->openURL("https://zeas2.github.io/Kirikiroid2_patch/patch"); +} diff --git a/src/core/environ/cocos2d/CustomFileUtils.cpp b/src/core/environ/cocos2d/CustomFileUtils.cpp new file mode 100644 index 00000000..f97e15cb --- /dev/null +++ b/src/core/environ/cocos2d/CustomFileUtils.cpp @@ -0,0 +1,196 @@ +#include "cocos2d.h" +#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 +#include "platform/win32/CCFileUtils-win32.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_IOS +#import +#import "platform/apple/CCFileUtils-apple.h" +#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID +#include "platform/android/CCFileUtils-android.h" +#endif +#ifdef MINIZIP_FROM_SYSTEM +#include +#else // from our embedded sources +#include "external/unzip/unzip.h" +#endif + +NS_CC_BEGIN + +typedef +#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 +FileUtilsWin32 +#elif CC_TARGET_PLATFORM == CC_PLATFORM_IOS +FileUtilsApple +#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID +FileUtilsAndroid +#endif +FileUtilsInherit; +class CustomFileUtils : public FileUtilsInherit +{ +public: + CustomFileUtils(); + + void addAutoSearchArchive(const std::string& path); + virtual std::string fullPathForFilename(const std::string &filename) const override; + virtual std::string getStringFromFile(const std::string& filename) override; + virtual Data getDataFromFile(const std::string& filename) override; + virtual unsigned char* getFileData(const std::string& filename, const char* mode, ssize_t *size) override; + virtual bool isFileExistInternal(const std::string& strFilePath) const override; + virtual bool isDirectoryExistInternal(const std::string& dirPath) const override; + virtual bool init() override { + return FileUtilsInherit::init(); + } + +private: + unsigned char* getFileDataFromArchive(const std::string& filename, ssize_t *size); + + std::unordered_map > _autoSearchArchive; + std::mutex _lock; +}; + +CustomFileUtils::CustomFileUtils() +{ +} + +void CustomFileUtils::addAutoSearchArchive(const std::string& path) +{ + if (!this->isFileExist(path)) return; + unzFile file = nullptr; + file = unzOpen(FileUtils::getInstance()->getSuitableFOpen(path).c_str()); + unz_file_info file_info; + do { + unz_file_pos entry; + if (unzGetFilePos(file, &entry) == UNZ_OK) { + char filename_inzip[1024]; + if (unzGetCurrentFileInfo(file, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0) == UNZ_OK) { + _autoSearchArchive[filename_inzip] = std::make_pair(file, entry); + } + } + } while (unzGoToNextFile(file) == UNZ_OK); +} + +std::string CustomFileUtils::fullPathForFilename(const std::string &filename) const +{ + auto it = _autoSearchArchive.find(filename); + if (_autoSearchArchive.end() != it) { + return filename; + } + return FileUtilsInherit::fullPathForFilename(filename); +} + +unsigned char* CustomFileUtils::getFileData(const std::string& filename, const char* mode, ssize_t *size) +{ + unsigned char* ret = getFileDataFromArchive(filename, size); + if (ret) return ret; + return FileUtilsInherit::getFileData(filename, mode, size); +} + +bool CustomFileUtils::isFileExistInternal(const std::string& strFilePath) const +{ + auto it = _autoSearchArchive.find(strFilePath); + if (_autoSearchArchive.end() != it) { + return true; + } + return FileUtilsInherit::isFileExistInternal(strFilePath); +} + +bool CustomFileUtils::isDirectoryExistInternal(const std::string& dirPath) const +{ + for (auto &it : _autoSearchArchive) { + if (it.first.size() <= dirPath.size()) continue; + if (!strncmp(it.first.c_str(), dirPath.c_str(), dirPath.size()) && it.first[dirPath.size()] == '/') { + return true; + } + } + return FileUtilsInherit::isDirectoryExistInternal(dirPath); +} + +unsigned char* CustomFileUtils::getFileDataFromArchive(const std::string& filename, ssize_t *size) +{ + auto it = _autoSearchArchive.find(filename); + if (_autoSearchArchive.end() != it) { + _lock.lock(); + if (unzGoToFilePos(it->second.first, &it->second.second) != UNZ_OK) return nullptr; + unz_file_info fileInfo; + if (unzGetCurrentFileInfo(it->second.first, &fileInfo, NULL, 0, NULL, 0, NULL, 0) != UNZ_OK) return nullptr; + unsigned char *buffer = (unsigned char*)malloc(fileInfo.uncompressed_size); + int readedSize = unzReadCurrentFile(it->second.first, buffer, static_cast(fileInfo.uncompressed_size)); + _lock.unlock(); + CCASSERT(readedSize == 0 || readedSize == (int)fileInfo.uncompressed_size, "the file size is wrong"); + *size = fileInfo.uncompressed_size; + return buffer; + } + return nullptr; +} + +cocos2d::Data CustomFileUtils::getDataFromFile(const std::string& filename) +{ + ssize_t size; + unsigned char* buffer = getFileDataFromArchive(filename, &size); + if (buffer) { + Data ret; + ret.fastSet(buffer, size); + return ret; + } + return FileUtilsInherit::getDataFromFile(filename); +} + +std::string CustomFileUtils::getStringFromFile(const std::string& filename) +{ + Data data = getDataFromFile(filename); + if (data.isNull()) + return ""; + + std::string ret((const char*)data.getBytes()); + return ret; +} + +NS_CC_END + +cocos2d::FileUtils *TVPCreateCustomFileUtils() { + cocos2d::CustomFileUtils *ret = new cocos2d::CustomFileUtils; + ret->init(); + return ret; +} + +#include "StorageImpl.h" +#include "Platform.h" +static bool TVPCopyFolder(const std::string &from, const std::string &to) { + if (!TVPCheckExistentLocalFolder(to) && !TVPCreateFolders(to)) { + return false; + } + + bool success = true; + TVPListDir(from, [&](const std::string &_name, int mask) { + if (_name == "." || _name == "..") return; + if (!success) return; + if (mask & S_IFREG) { + success = TVPCopyFile(from + "/" + _name, to + "/" + _name); + } + else if (mask & S_IFDIR) { + success = TVPCopyFolder(from + "/" + _name, to + "/" + _name); + } + }); + return success; +} + +bool TVPCopyFile(const std::string &from, const std::string &to) +{ + FILE * ffrom = fopen(from.c_str(), "rb"); + if (!ffrom) { // try folder copy + return TVPCopyFolder(from, to); + } + FILE * fto = fopen(to.c_str(), "wb"); + if (!fto) { + if (ffrom) fclose(ffrom); + return false; + } + const int bufSize = 1 * 1024 * 1024; + std::vector buffer; buffer.resize(bufSize); + int readed = 0; + while ((readed = fread(&buffer.front(), 1, bufSize, ffrom))) { + fwrite(&buffer.front(), 1, readed, fto); + } + fclose(ffrom); + fclose(fto); + return true; +} diff --git a/src/core/environ/cocos2d/CustomFileUtils.mm b/src/core/environ/cocos2d/CustomFileUtils.mm new file mode 100644 index 00000000..bff0a7fb --- /dev/null +++ b/src/core/environ/cocos2d/CustomFileUtils.mm @@ -0,0 +1 @@ +#include "CustomFileUtils.cpp" \ No newline at end of file diff --git a/src/core/environ/cocos2d/MainScene.cpp b/src/core/environ/cocos2d/MainScene.cpp index b3a38d57..8527fa49 100644 --- a/src/core/environ/cocos2d/MainScene.cpp +++ b/src/core/environ/cocos2d/MainScene.cpp @@ -356,7 +356,7 @@ class TVPWindowLayer : public cocos2d::extension::ScrollView, public iWindowLaye float _drawSpriteScaleX = 1.0f, _drawSpriteScaleY = 1.0f; float _drawTextureScaleX = 1.f, _drawTextureScaleY = 1.f; bool UseMouseKey = false, MouseLeftButtonEmulatedPushed = false, MouseRightButtonEmulatedPushed = false; - bool LastMouseMoved = false; + bool LastMouseMoved = false, Visible = false; tjs_uint32 LastMouseKeyTick = 0; tjs_int MouseKeyXAccel = 0; tjs_int MouseKeyYAccel = 0; @@ -764,6 +764,7 @@ class TVPWindowLayer : public cocos2d::extension::ScrollView, public iWindowLaye } virtual void SetVisible(bool bVisible) { + Visible = bVisible; setVisible(bVisible); if (bVisible) { BringToFront(); @@ -798,6 +799,7 @@ class TVPWindowLayer : public cocos2d::extension::ScrollView, public iWindowLaye size = size / scale; //DrawSprite->setTextureRect(Rect(0, 0, size.width, size.height)); DrawSprite->setScale(_drawTextureScaleX, _drawTextureScaleY); + DrawSprite->setTextureRect(Rect(0, 0, LayerWidth, LayerHeight)); DrawSprite->setPosition(Vec2(0, size.height)); PrimaryLayerArea->setContentSize(size); PrimaryLayerArea->setScale(scale); @@ -1498,17 +1500,17 @@ class TVPWindowManagerOverlay : public iTVPBaseForm { if (!_currentWindowLayer) return; if (_left) { TVPWindowLayer *pLay = _currentWindowLayer->_prevWindow; - while (pLay && !pLay->isVisible()) { + while (pLay && !pLay->Visible) { pLay = pLay->_prevWindow; } - _left->setVisible(pLay && pLay->isVisible()); + _left->setVisible(pLay && pLay->Visible); } if (_right) { TVPWindowLayer *pLay = _currentWindowLayer->_nextWindow; - while (pLay && !pLay->isVisible()) { + while (pLay && !pLay->Visible) { pLay = pLay->_nextWindow; } - _right->setVisible(pLay && pLay->isVisible()); + _right->setVisible(pLay && pLay->Visible); } } diff --git a/src/core/environ/ui/BaseForm.cpp b/src/core/environ/ui/BaseForm.cpp index f75dbcc3..1eca06fb 100644 --- a/src/core/environ/ui/BaseForm.cpp +++ b/src/core/environ/ui/BaseForm.cpp @@ -10,6 +10,7 @@ #include "ui/UIListView.h" #include "Platform.h" #include "cocostudio/ActionTimeline/CCActionTimeline.h" +#include "extensions/GUI/CCScrollView/CCTableView.h" using namespace cocos2d; using namespace cocos2d::ui; @@ -19,7 +20,8 @@ NodeMap::NodeMap(const char *filename, cocos2d::Node* node) : FileName(filename) initFromNode(node); } -cocos2d::Node * NodeMap::findController(const std::string &name, bool notice) const { +template <> +cocos2d::Node * NodeMap::findController(const std::string &name, bool notice) const { auto it = this->find(name); if (it != this->end()) return it->second; @@ -43,10 +45,6 @@ void NodeMap::initFromNode(cocos2d::Node* node) { } } -cocos2d::ui::Widget * NodeMap::findWidget(const std::string &name) const { - return static_cast(findController(name)); -} - Node* CSBReader::Load(const char *filename) { clear(); FileName = filename; @@ -223,4 +221,36 @@ int TVPShowSimpleInputBox(ttstr &text, const ttstr &caption, const ttstr &prompt return 0; } + #endif + +void iTVPFloatForm::rearrangeLayout() +{ + float scale = TVPMainScene::GetInstance()->getUIScale(); + Size sceneSize = TVPMainScene::GetInstance()->getUINodeSize(); + setContentSize(sceneSize); + Vec2 center = sceneSize / 2; + sceneSize.height *= 0.75f; + sceneSize.width *= 0.75f; + if (RootNode) { + sceneSize.width /= scale; + sceneSize.height /= scale; + RootNode->setContentSize(sceneSize); + ui::Helper::doLayout(RootNode); + RootNode->setScale(scale); + RootNode->setAnchorPoint(sceneSize / 2); + RootNode->setPosition(center); + } +} + +void ReloadTableViewAndKeepPos(cocos2d::extension::TableView *pTableView) +{ + Vec2 off = pTableView->getContentOffset(); + float origHeight = pTableView->getContentSize().height; + pTableView->reloadData(); + off.y += origHeight - pTableView->getContentSize().height; + bool bounceable = pTableView->isBounceable(); + pTableView->setBounceable(false); + pTableView->setContentOffset(off); + pTableView->setBounceable(bounceable); +} diff --git a/src/core/environ/ui/BaseForm.h b/src/core/environ/ui/BaseForm.h index fca06d74..4cd039e7 100644 --- a/src/core/environ/ui/BaseForm.h +++ b/src/core/environ/ui/BaseForm.h @@ -17,6 +17,7 @@ namespace cocos2d { class TextField; class Slider; class ScrollView; + class LoadingBar; } namespace extension { class TableView; @@ -35,10 +36,16 @@ class NodeMap : public std::unordered_map { public: NodeMap(); NodeMap(const char *filename, cocos2d::Node* node); - cocos2d::Node *findController(const std::string &name, bool notice = true) const; - cocos2d::ui::Widget *findWidget(const std::string &name) const; + template + T *findController(const std::string &name, bool notice = true) const { + return dynamic_cast(findController(name, notice)); + } + cocos2d::ui::Widget *findWidget(const std::string &name, bool notice = true) const { + return findController(name, notice); + } void initFromNode(cocos2d::Node* node); }; +template<> cocos2d::Node *NodeMap::findController(const std::string &name, bool notice) const; class CSBReader : public NodeMap { public: @@ -58,7 +65,9 @@ class iTVPBaseForm : public cocos2d::Node { protected: bool initFromFile(const char *navibar, const char *body, const char *bottombar, cocos2d::Node *parent = nullptr); - //void initBottomBar(std::vector > > args); + bool initFromFile(const char *body) { + return initFromFile(nullptr, body, nullptr); + } virtual void bindBodyController(const NodeMap &allNodes) {} virtual void bindFooterController(const NodeMap &allNodes) {} @@ -129,3 +138,10 @@ class TCommonTableCell : public cocos2d::extension::TableViewCell { protected: TTouchEventRouter *_router; }; + +class iTVPFloatForm : public iTVPBaseForm { +public: + virtual void rearrangeLayout() override; +}; + +void ReloadTableViewAndKeepPos(cocos2d::extension::TableView *pTableView); diff --git a/src/core/environ/ui/FileSelectorForm.cpp b/src/core/environ/ui/FileSelectorForm.cpp index 7292b504..7230c75d 100644 --- a/src/core/environ/ui/FileSelectorForm.cpp +++ b/src/core/environ/ui/FileSelectorForm.cpp @@ -5,13 +5,13 @@ #include "ui/UIButton.h" #include "ui/UIText.h" #include "ui/UITextField.h" +#include "ui/UICheckBox.h" #include "Platform.h" #include "cocos2d/MainScene.h" #include "ConfigManager/LocaleConfigManager.h" #include "CCFileUtils.h" #include "base/CCDirector.h" #include "MessageBox.h" -#include #include "platform/CCDevice.h" using namespace cocos2d; @@ -31,6 +31,14 @@ static float convertDistanceFromPointToInch(const Vec2& dis) return distance; } +static bool IsPathExist(const std::string &path) { + tTVP_stat s; + if (!TVP_stat(path.c_str(), s)) { + return false; // not exist + } + return true; +} + std::pair TVPBaseFileSelectorForm::PathSplit(const std::string &path) { std::pair ret; if (path.size() <= 1) { @@ -112,6 +120,7 @@ void TVPBaseFileSelectorForm::bindBodyController(const NodeMap &allNodes) { static const std::string _path_current("."); static const std::string _path_parent(".."); static const std::string str_diricon("dir_icon"); +static const std::string str_select("select_check"); static const std::string str_filename("filename"); void TVPBaseFileSelectorForm::ListDir(std::string path) { std::pair split_path = PathSplit(path); @@ -170,10 +179,21 @@ void TVPBaseFileSelectorForm::ListDir(std::string path) { } std::sort(CurrentDirList.begin(), CurrentDirList.end()); + // update + bool keepPos = CurrentPath == path; CurrentPath = path; + if (keepPos) { + ReloadTableViewAndKeepPos(FileList); + } else { + FileList->reloadData(); + } - // update - FileList->reloadData(); + if (!_selectedFileIndex.empty()) { + _selectedFileIndex.clear(); + updateFileMenu(); + } else if (_clipboardForFileManager.size()) { + updateFileMenu(); + } } void TVPBaseFileSelectorForm::onCellClicked(int idx) { @@ -185,6 +205,83 @@ void TVPBaseFileSelectorForm::onCellClicked(int idx) { void TVPBaseFileSelectorForm::onCellLongPress(int idx) { + if (_fileOperateMenuNode) { // full file function + if (!_selectedFileIndex.empty()) { + return; + } + if (!_fileOperateMenu) { + CSBReader reader; + _fileOperateMenu = reader.Load("ui/FileManageMenu.csb"); + LocaleConfigManager::GetInstance()->initText(reader.findController("title")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleUnselect")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleView", false)); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleCopy")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleCut")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titlePaste")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleUnpack", false)); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleRepack", false)); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleDelete")); + LocaleConfigManager::GetInstance()->initText(reader.findController("titleSendTo")); + _fileOperateMenulist = reader.findController("list"); + _fileOperateCell_unselect = reader.findWidget("unselect"); + _fileOperateCell_view = reader.findWidget("view", false); + _fileOperateCell_copy = reader.findWidget("copy"); + _fileOperateCell_cut = reader.findWidget("cut"); + _fileOperateCell_paste = reader.findWidget("paste"); + _fileOperateCell_delete = reader.findWidget("delete"); + _fileOperateCell_repack = reader.findWidget("repack", false); + _fileOperateCell_unpack = reader.findWidget("unpack", false); + _fileOperateCell_sendto = reader.findWidget("sendto"); + Widget *btn;; + if ((btn = reader.findWidget("btnUnselect"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onUnselectClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnView", false))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onViewClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnCopy"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onCopyClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnCut"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onCutClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnPaste"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onPasteClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnUnpack", false))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onUnpackClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnRepack", false))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onRepackClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnDelete"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onDeleteClicked, this, std::placeholders::_1)); + } + if ((btn = reader.findWidget("btnSendTo"))) { + btn->addClickEventListener(std::bind(&TVPBaseFileSelectorForm::onSendToClicked, this, std::placeholders::_1)); + } + _fileOperateMenulist->removeAllItems(); + float scale = 1.5; + _fileOperateMenu->setScale(1 / scale); + _fileOperateMenu->setContentSize(_fileOperateMenuNode->getContentSize() * scale); + _fileOperateMenu->setPosition(_fileOperateMenuNode->getPosition()); + _fileOperateMenuNode->getParent()->addChild(_fileOperateMenu); + ui::Helper::doLayout(_fileOperateMenu); + _fileOperateMenu->setVisible(false); + } + if (!_fileOperateMenu->isVisible()) { + _fileOperateMenu->setVisible(true); + // _fileOperateMenu->setAnchorPoint(Vec2(1, 0)); + } + + FileList->setTouchEnabled(false); // trick to release all touches + FileList->setTouchEnabled(true); + _selectedFileIndex.clear(); + _selectedFileIndex.insert(idx); + updateFileMenu(); + return; + } + // simple file manage LocaleConfigManager *localeMgr = LocaleConfigManager::GetInstance(); const char *btnTitles[] = { localeMgr->GetText("delete").c_str(), @@ -197,12 +294,9 @@ void TVPBaseFileSelectorForm::onCellLongPress(int idx) const FileInfo &info = CurrentDirList[idx]; switch (n) { case 0: - if (TVPShowSimpleMessageBoxYesNo(info.FullPath, localeMgr->GetText("ensure_to_delete_file")) == 0) { - TVPDeleteFile(info.FullPath); - scheduleOnce([this](float){ - ListDir(CurrentPath); - }, 0, "refresh_path"); - } + _selectedFileIndex.insert(idx); + onDeleteClicked(nullptr); + _selectedFileIndex.clear(); break; default: break; @@ -264,8 +358,7 @@ void TVPBaseFileSelectorForm::onBackClicked(cocos2d::Ref *owner) { TVPBaseFileSelectorForm::FileItemCellImpl* TVPBaseFileSelectorForm::FetchCell(FileItemCellImpl* CellModel, cocos2d::extension::TableView *table, ssize_t idx) { if (!CellModel) { - CellModel = FileItemCellImpl::create(FileName_Cell, table->getViewSize().width, - CurrentDirList[idx]); + CellModel = FileItemCellImpl::create(FileName_Cell, table->getViewSize().width); CellModel->setAnchorPoint(Vec2::ZERO); CellModel->setEventFunc([this](Widget::TouchEventType ev, Widget* sender, Touch *touch){ Vec2 touchPoint = touch->getLocation(); @@ -290,9 +383,9 @@ TVPBaseFileSelectorForm::FileItemCellImpl* TVPBaseFileSelectorForm::FetchCell(Fi } }); CellModel->retain(); - } else { - CellModel->setInfo(CurrentDirList[idx]); } + bool selected = _selectedFileIndex.find(idx) != _selectedFileIndex.end(); + CellModel->setInfo(CurrentDirList[idx], selected, !_selectedFileIndex.empty()); return CellModel; } @@ -304,7 +397,7 @@ TableViewCell* TVPBaseFileSelectorForm::tableCellAtIndex(TableView *table, ssize } else { cell = FileItemCell::create(this); } - if (idx >= CurrentDirList.size()) { + if ((size_t)idx >= CurrentDirList.size()) { cell->setVisible(false); return cell; } @@ -319,10 +412,19 @@ ssize_t TVPBaseFileSelectorForm::numberOfCellsInTableView(TableView *table) { } Size TVPBaseFileSelectorForm::tableCellSizeForIndex(TableView *table, ssize_t idx) { - if (idx >= CurrentDirList.size()) { + if ((size_t)idx >= CurrentDirList.size()) { return Size(table->getContentSize().width, 200); } - return FetchCell(CellTemplateForSize, table, idx)->getContentSize(); + FileInfo &info = CurrentDirList[idx]; + if (info.CellSize.width == 0.f) { + if (!CellTemplateForSize) { + CellTemplateForSize = FetchCell(nullptr, table, idx); + } else { + CellTemplateForSize->setInfo(CurrentDirList[idx], false, false); + } + info.CellSize = CellTemplateForSize->getContentSize(); + } + return info.CellSize; } void TVPBaseFileSelectorForm::rearrangeLayout() { @@ -330,6 +432,145 @@ void TVPBaseFileSelectorForm::rearrangeLayout() { if (FileList) FileList->setViewSize(FileList->getParent()->getContentSize()); } +void TVPBaseFileSelectorForm::onUnselectClicked(cocos2d::Ref *owner) +{ + clearFileMenu(); +} + +void TVPBaseFileSelectorForm::onViewClicked(cocos2d::Ref *owner) +{ + +} + +void TVPBaseFileSelectorForm::onCopyClicked(cocos2d::Ref *owner) +{ + _clipboardForMoving = false; + _clipboardForFileManager.clear(); + for (int idx : _selectedFileIndex) { + _clipboardForFileManager.emplace_back(CurrentDirList[idx].FullPath); + } + _clipboardPath = CurrentPath; + _selectedFileIndex.clear(); + updateFileMenu(); +} + +void TVPBaseFileSelectorForm::onCutClicked(cocos2d::Ref *owner) +{ + onCopyClicked(owner); + _clipboardForMoving = true; +} + +void TVPBaseFileSelectorForm::onPasteClicked(cocos2d::Ref *owner) +{ + // TODO progress bar + auto func = _clipboardForMoving ? TVPRenameFile : TVPCopyFile; + for (const std::string &path : _clipboardForFileManager) { + auto split_path = PathSplit(path); + std::string target = CurrentPath + "/" + split_path.second; + if (IsPathExist(target)) { + LocaleConfigManager *localeMgr = LocaleConfigManager::GetInstance(); + if (TVPShowSimpleMessageBoxYesNo(target, localeMgr->GetText("ensure_to_override_file")) != 0) { + continue; + } + } + if (!func(path, target)) { + LocaleConfigManager *localeMgr = LocaleConfigManager::GetInstance(); + TVPShowSimpleMessageBox(target, localeMgr->GetText("file_operate_failed")); + break; + } + } + clearFileMenu(); + ListDir(CurrentPath); +} + +void TVPBaseFileSelectorForm::onUnpackClicked(cocos2d::Ref *owner) +{ + + clearFileMenu(); +} + +void TVPBaseFileSelectorForm::onRepackClicked(cocos2d::Ref *owner) +{ + + clearFileMenu(); +} + +void TVPBaseFileSelectorForm::onDeleteClicked(cocos2d::Ref *owner) +{ + LocaleConfigManager *localeMgr = LocaleConfigManager::GetInstance(); + std::string content; + if (_selectedFileIndex.size() == 1) { + const FileInfo &info = CurrentDirList[*_selectedFileIndex.begin()]; + content = info.FullPath; + } else { + content = localeMgr->GetText("ensure_item_count"); + char tmp[64]; + snprintf(tmp, 64, content.c_str(), _selectedFileIndex.size()); + content = tmp; + } + if (TVPShowSimpleMessageBoxYesNo(content, localeMgr->GetText("ensure_to_delete_file")) == 0) { + for (int idx : _selectedFileIndex) { + TVPDeleteFile(CurrentDirList[idx].FullPath); + } + scheduleOnce([this](float) { + ListDir(CurrentPath); + }, 0, "refresh_path"); + } +} + +void TVPBaseFileSelectorForm::onSendToClicked(cocos2d::Ref *owner) +{ + clearFileMenu(); +} + +void TVPBaseFileSelectorForm::updateFileMenu() +{ + ReloadTableViewAndKeepPos(FileList); + if (_selectedFileIndex.empty() && _clipboardForFileManager.empty()) { // close menu + if (_fileOperateMenu->isVisible()) { + _fileOperateMenu->setVisible(false); + // _fileOperateMenu->setAnchorPoint(Vec2(0, 0)); + } + return; + } + _fileOperateMenulist->removeAllItems(); + _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_unselect.get()); + if (!_clipboardForFileManager.empty() && _clipboardPath != CurrentPath) { + _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_paste.get()); + } + if (_selectedFileIndex.size() == 1) { + if (_fileOperateCell_view) _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_view.get()); + // _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_unpack.get()); + // _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_repack.get()); + // _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_sendto.get()); + } + if (!_selectedFileIndex.empty()) { + _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_copy.get()); + _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_cut.get()); + _fileOperateMenulist->pushBackCustomItem(_fileOperateCell_delete.get()); + } +} + +void TVPBaseFileSelectorForm::clearFileMenu() +{ + _selectedFileIndex.clear(); + _clipboardForFileManager.clear(); + updateFileMenu(); +} + +void TVPBaseFileSelectorForm::_onCellClicked(int idx) +{ + if (_selectedFileIndex.empty()) + return onCellClicked(idx); + auto it = _selectedFileIndex.find(idx); + if (it == _selectedFileIndex.end()) { + _selectedFileIndex.insert(idx); + } else { + _selectedFileIndex.erase(idx); + } + updateFileMenu(); +} + bool TVPBaseFileSelectorForm::FileInfo::operator<(const FileInfo &rhs) const { if (IsDir != rhs.IsDir) return IsDir > rhs.IsDir; return NameForCompare < rhs.NameForCompare; @@ -506,6 +747,8 @@ void TVPBaseFileSelectorForm::FileItemCellImpl::initFromFile(const char * filena _root->setContentSize(OrigCellModelSize); setContentSize(OrigCellModelSize); DirIcon = reader.findController(str_diricon); + SelectBox = reader.findController(str_select); + SelectBox->setTouchEnabled(false); FileNameNode = static_cast(reader.findController(str_filename)); if (DirIcon && FileNameNode) { CellTextAreaSize = DirIcon->getContentSize(); @@ -526,9 +769,13 @@ void TVPBaseFileSelectorForm::FileItemCellImpl::initFromFile(const char * filena case Widget::TouchEventType::BEGAN: sender->scheduleOnce([this, sender](float){ if (sender->isHighlighted()) { - sender->scheduleOnce([this](float){_owner->onLongPress(); }, 0, "delay_call"); + sender->scheduleOnce([this, sender](float){ + _owner->onLongPress(); + sender->setHighlighted(false); + // sender->interceptTouchEvent(TouchEventType::CANCELED, sender, touch); + }, 0, "delay_call"); } - }, 1.5f, str_long_press); + }, 1.0f, str_long_press); break; case Widget::TouchEventType::CANCELED: sender->unschedule(str_long_press); @@ -538,7 +785,7 @@ void TVPBaseFileSelectorForm::FileItemCellImpl::initFromFile(const char * filena } } -void TVPBaseFileSelectorForm::FileItemCellImpl::setInfo(const FileInfo &info) { +void TVPBaseFileSelectorForm::FileItemCellImpl::setInfo(const FileInfo &info, bool selected, bool showSelect) { if (FileNameNode) { FileNameNode->ignoreContentAdaptWithSize(true); FileNameNode->setTextAreaSize(CellTextAreaSize); @@ -549,7 +796,9 @@ void TVPBaseFileSelectorForm::FileItemCellImpl::setInfo(const FileInfo &info) { setContentSize(size); FileNameNode->ignoreContentAdaptWithSize(false); } - if (DirIcon) DirIcon->setVisible(info.IsDir); + if (DirIcon) DirIcon->setVisible(info.IsDir && !showSelect); + SelectBox->setVisible(showSelect); + if (showSelect) SelectBox->setSelected(selected); ui::Helper::doLayout(_root); _set = true; } diff --git a/src/core/environ/ui/FileSelectorForm.h b/src/core/environ/ui/FileSelectorForm.h index 2609b002..f088ca2c 100644 --- a/src/core/environ/ui/FileSelectorForm.h +++ b/src/core/environ/ui/FileSelectorForm.h @@ -1,6 +1,7 @@ #pragma once #include "BaseForm.h" #include "GUI/CCScrollView/CCTableView.h" +#include "base/CCRefPtr.h" class TVPListForm : public cocos2d::Node { public: @@ -19,6 +20,7 @@ class TVPListForm : public cocos2d::Node { cocos2d::Node *_root; }; +class TVPFileOperateMenu; class TVPBaseFileSelectorForm : public iTVPBaseForm, public cocos2d::extension::TableViewDataSource { public: TVPBaseFileSelectorForm(); @@ -42,15 +44,47 @@ class TVPBaseFileSelectorForm : public iTVPBaseForm, public cocos2d::extension:: void onCellItemClicked(cocos2d::Ref *owner); void onTitleClicked(cocos2d::Ref *owner); void onBackClicked(cocos2d::Ref *owner); + void _onCellClicked(int idx); cocos2d::extension::TableView *FileList; cocos2d::ui::Button *_title; + cocos2d::Node *_fileOperateMenuNode = nullptr; + cocos2d::Node *_fileOperateMenu = nullptr; + cocos2d::ui::ListView *_fileOperateMenulist; + cocos2d::RefPtr + _fileOperateCell_unselect, + _fileOperateCell_view, + _fileOperateCell_copy, + _fileOperateCell_cut, + _fileOperateCell_paste, + _fileOperateCell_unpack, + _fileOperateCell_repack, // TODO + _fileOperateCell_delete, + _fileOperateCell_sendto; + + std::vector _clipboardForFileManager; + std::string _clipboardPath; + bool _clipboardForMoving = false; + std::set _selectedFileIndex; + void onUnselectClicked(cocos2d::Ref *owner); + void onViewClicked(cocos2d::Ref *owner); + void onCopyClicked(cocos2d::Ref *owner); + void onCutClicked(cocos2d::Ref *owner); + void onPasteClicked(cocos2d::Ref *owner); + void onUnpackClicked(cocos2d::Ref *owner); + void onRepackClicked(cocos2d::Ref *owner); + void onDeleteClicked(cocos2d::Ref *owner); + void onSendToClicked(cocos2d::Ref *owner); + void updateFileMenu(); + void clearFileMenu(); + struct FileInfo { std::string FullPath; std::string NameForDisplay; std::string NameForCompare; bool IsDir; + cocos2d::Size CellSize; bool operator < (const FileInfo &rhs) const; }; @@ -63,18 +97,17 @@ class TVPBaseFileSelectorForm : public iTVPBaseForm, public cocos2d::extension:: public: FileItemCellImpl() : _set(false), _owner(nullptr) {} - static FileItemCellImpl *create(const char * filename, float width, const FileInfo &info) { + static FileItemCellImpl *create(const char * filename, float width) { FileItemCellImpl* ret = new FileItemCellImpl(); ret->autorelease(); ret->init(); ret->initFromFile(filename, width); - ret->setInfo(info); return ret; } void initFromFile(const char * filename, float width); - void setInfo(const FileInfo &info); + void setInfo(const FileInfo &info, bool selected, bool showSelect); void reset() { _set = false; @@ -95,8 +128,10 @@ class TVPBaseFileSelectorForm : public iTVPBaseForm, public cocos2d::extension:: cocos2d::Size OrigCellModelSize, CellTextAreaSize, OrigCellTextSize; cocos2d::ui::Text *FileNameNode; cocos2d::Node *DirIcon, *_root; + cocos2d::ui::CheckBox *SelectBox; FileItemCell *_owner; - } *CellTemplateForSize = nullptr; + }; + cocos2d::RefPtr CellTemplateForSize; FileItemCellImpl* FetchCell(FileItemCellImpl* CellModel, cocos2d::extension::TableView *table, ssize_t idx); class FileItemCell : public cocos2d::extension::TableViewCell { @@ -134,7 +169,7 @@ class TVPBaseFileSelectorForm : public iTVPBaseForm, public cocos2d::extension:: } void onClicked() { - _owner->onCellClicked(getIdx()); + _owner->_onCellClicked(getIdx()); } void onLongPress() { diff --git a/src/core/environ/ui/GlobalPreferenceForm.cpp b/src/core/environ/ui/GlobalPreferenceForm.cpp index 723da9a1..85aa690a 100644 --- a/src/core/environ/ui/GlobalPreferenceForm.cpp +++ b/src/core/environ/ui/GlobalPreferenceForm.cpp @@ -5,6 +5,7 @@ #include "ui/UIListView.h" #include "ConfigManager/GlobalConfigManager.h" #include "platform/CCFileUtils.h" +#include "Platform.h" using namespace cocos2d; using namespace cocos2d::ui; @@ -13,9 +14,7 @@ using namespace cocos2d::ui; const char * const FileName_NaviBar = "ui/NaviBar.csb"; const char * const FileName_Body = "ui/ListView.csb"; -namespace { #include "PreferenceConfig.h" -}; TVPGlobalPreferenceForm * TVPGlobalPreferenceForm::create(const tPreferenceScreen *config) { Initialize(); diff --git a/src/core/environ/ui/IndividualPreferenceForm.cpp b/src/core/environ/ui/IndividualPreferenceForm.cpp index 16edc20a..0daee1a4 100644 --- a/src/core/environ/ui/IndividualPreferenceForm.cpp +++ b/src/core/environ/ui/IndividualPreferenceForm.cpp @@ -5,6 +5,7 @@ #include "ui/UIListView.h" #include "platform/CCFileUtils.h" #include "ConfigManager/IndividualConfigManager.h" +#include "Platform.h" using namespace cocos2d; using namespace cocos2d::ui; @@ -14,9 +15,9 @@ const char * const FileName_NaviBar = "ui/NaviBar.csb"; const char * const FileName_Body = "ui/ListView.csb"; #define GlobalConfigManager IndividualConfigManager #define TVPGlobalPreferenceForm IndividualPreferenceForm -namespace { + #include "PreferenceConfig.h" -}; + #undef TVPGlobalPreferenceForm #undef GlobalConfigManager diff --git a/src/core/environ/ui/MainFileSelectorForm.cpp b/src/core/environ/ui/MainFileSelectorForm.cpp index 00c862df..f4d8e073 100644 --- a/src/core/environ/ui/MainFileSelectorForm.cpp +++ b/src/core/environ/ui/MainFileSelectorForm.cpp @@ -19,7 +19,6 @@ #include "tinyxml2/tinyxml2.h" #include "StorageImpl.h" #include "TipsHelpForm.h" -#include using namespace cocos2d; using namespace cocos2d::ui; @@ -226,6 +225,8 @@ void TVPMainFileSelectorForm::initFromFile() Node *root = reader.Load("ui/MainFileSelector.csb"); _fileList = reader.findController("fileList"); _historyList = static_cast(reader.findController("recentList")); + // TODO new node + _fileOperateMenuNode = _historyList; LocaleConfigManager::GetInstance()->initText(static_cast(reader.findController("recentTitle", false))); addChild(root); Size sceneSize = TVPMainScene::GetInstance()->getUINodeSize(); @@ -259,6 +260,7 @@ void TVPMainFileSelectorForm::doStartup(const std::string &path) { } std::string TVPGetOpenGLInfo(); +void TVPOpenPatchLibUrl(); void TVPMainFileSelectorForm::showMenu(Ref*) { if (!_menu) { @@ -353,6 +355,7 @@ void TVPMainFileSelectorForm::showMenu(Ref*) { const char * pszBtnText[] = { LocaleConfigManager::GetInstance()->GetText("ok").c_str(), + LocaleConfigManager::GetInstance()->GetText("browse_patch_lib").c_str(), LocaleConfigManager::GetInstance()->GetText("device_info").c_str(), }; @@ -361,7 +364,10 @@ void TVPMainFileSelectorForm::showMenu(Ref*) { sizeof(pszBtnText) / sizeof(pszBtnText[0]), pszBtnText); switch (n) { - case 1: { + case 1: + TVPOpenPatchLibUrl(); + break; + case 2: { std::string text = TVPGetOpenGLInfo(); const char *pOK = LocaleConfigManager::GetInstance()->GetText("ok").c_str(); TVPShowSimpleMessageBox(text.c_str(), diff --git a/src/core/environ/ui/PreferenceConfig.h b/src/core/environ/ui/PreferenceConfig.h index 401a6971..68eda1cb 100644 --- a/src/core/environ/ui/PreferenceConfig.h +++ b/src/core/environ/ui/PreferenceConfig.h @@ -1,3 +1,7 @@ +extern void TVPInitTextureFormatList(); +extern bool TVPIsSupportTextureFormat(GLenum fmt); + +namespace { static tPreferenceScreen RootPreference; static tPreferenceScreen OpenglOptPreference, SoftRendererOptPreference; static Size PrefListSize; @@ -42,7 +46,7 @@ class tTVPPreferenceInfoSelectList : public tTVPPreferenceInfo, tPr item->_setter = [this](std::string v){ onSetValue(v); }; }); } - virtual const std::vector >& getListInfo() const override { + virtual const std::vector >& getListInfo() override { return ListInfo; } virtual void onSetValue(const std::string &v) { @@ -51,6 +55,32 @@ class tTVPPreferenceInfoSelectList : public tTVPPreferenceInfo, tPr std::vector > ListInfo; }; +class tTVPPreferenceInfoTextureCompressionSelectList : public tTVPPreferenceInfoSelectList { +public: + tTVPPreferenceInfoTextureCompressionSelectList(const std::string &cap, const std::string &key, const std::string &defval, + const std::initializer_list > &listinfo) + : tTVPPreferenceInfoSelectList(cap, key, defval, std::initializer_list >()) + , TextFmtList(listinfo) + {} + + virtual const std::vector >& getListInfo() override { + if (!ListInited) { + ListInited = true; + TVPInitTextureFormatList(); + for (const auto &it : TextFmtList) { + unsigned int fmt = std::get<2>(it); + if (!fmt || TVPIsSupportTextureFormat(fmt)) { + ListInfo.emplace_back(std::get<0>(it), std::get<1>(it)); + } + } + } + return ListInfo; + } + + bool ListInited = false; + std::vector > TextFmtList; +}; + // class tTVPPreferenceInfoSelectRenderer : public tTVPPreferenceInfoSelectList { // typedef tTVPPreferenceInfoSelectList inherit; // public: @@ -95,7 +125,7 @@ class tTVPPreferenceInfoRendererSubPref : public iTVPPreferenceInfo { virtual iPreferenceItem *createItem() override { LocaleConfigManager *locmgr = LocaleConfigManager::GetInstance(); iPreferenceItem *ret = CreatePreferenceItem(PrefListSize, locmgr->GetText(Caption)); - ret->addClickEventListener([](Ref*){ + ret->addClickEventListener([](Ref*) { TVPMainScene::GetInstance()->pushUIForm(TVPGlobalPreferenceForm::create(GetSubPreferenceInfo())); }); return ret; @@ -163,6 +193,20 @@ class tTVPPreferenceInfoSliderText : public tTVPPreferenceInfo { } }; +class tTVPPreferenceInfoFetchSDCardPermission : public iTVPPreferenceInfo { +public: + tTVPPreferenceInfoFetchSDCardPermission(const std::string &cap) : iTVPPreferenceInfo(cap, "") {} + virtual iPreferenceItem *createItem() override { + LocaleConfigManager *locmgr = LocaleConfigManager::GetInstance(); + tPreferenceItemConstant* ret = CreatePreferenceItem(PrefListSize, locmgr->GetText(Caption)); + ret->setTouchEnabled(true); + ret->addClickEventListener([](Ref*) { + TVPFetchSDCardPermission(); + }); + return ret; + } +}; + static void initAllConfig() { if (!RootPreference.Preferences.empty()) return; RootPreference.Title = "preference_title"; @@ -192,6 +236,7 @@ static void initAllConfig() { new tTVPPreferenceInfoCheckBox("preference_remember_last_path", "remember_last_path", true), #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID new tTVPPreferenceInfoCheckBox("preference_hide_android_sys_btn", "hide_android_sys_btn", false), + new tTVPPreferenceInfoFetchSDCardPermission("preference_android_fetch_sdcard_permission"), #endif #endif #if 0 @@ -216,8 +261,8 @@ static void initAllConfig() { new tTVPPreferenceInfoSelectList("preference_software_compress_tex", "software_compress_tex", "none", { { "preference_soft_compress_tex_none", "none" }, { "preference_soft_compress_tex_halfline", "halfline" }, -// { "preference_soft_compress_tex_quarter", "quarter" }, -// { "preference_soft_compress_tex_lz4", "lz4" } + { "lz4", "lz4" }, + { "lz4+TLG5", "lz4+tlg5" } }), }; @@ -250,9 +295,11 @@ static void initAllConfig() { { "preference_ogl_texsize_8192", "8192" }, { "preference_ogl_texsize_16384", "16384" } }), - new tTVPPreferenceInfoSelectList("preference_ogl_compress_tex", "ogl_compress_tex", "none", { - { "preference_ogl_compress_tex_none", "none" }, - { "preference_ogl_compress_tex_half", "half" }, + new tTVPPreferenceInfoTextureCompressionSelectList("preference_ogl_compress_tex", "ogl_compress_tex", "none", { + std::make_tuple("preference_ogl_compress_tex_none", "none", 0), + std::make_tuple("preference_ogl_compress_tex_half", "half", 0), + std::make_tuple("ETC2", "etc2", 0x9278), // GL_COMPRESSED_RGBA8_ETC2_EAC + std::make_tuple("PVRTC", "pvrtc", 0x8C02), // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG }), // new tTVPPreferenceInfoSelectList("preference_ogl_render_tex_quality", "ogl_render_tex_quality", "1", { // { "preference_ogl_render_tex_quality_100", "1" }, @@ -260,4 +307,5 @@ static void initAllConfig() { // { "preference_ogl_render_tex_quality_50", "0.5" } // }), }; -} \ No newline at end of file +} +}; diff --git a/src/core/environ/ui/PreferenceForm.h b/src/core/environ/ui/PreferenceForm.h index c61cb04b..59c4ea7d 100644 --- a/src/core/environ/ui/PreferenceForm.h +++ b/src/core/environ/ui/PreferenceForm.h @@ -90,7 +90,7 @@ class tPreferenceItem : public iPreferenceItem { }; template // factory function -iPreferenceItem* CreatePreferenceItem(const cocos2d::Size &size, const std::string &title, const std::function &initer) { +T* CreatePreferenceItem(const cocos2d::Size &size, const std::string &title, const std::function &initer) { T *ret = new T; ret->autorelease(); initer(ret); @@ -99,7 +99,7 @@ iPreferenceItem* CreatePreferenceItem(const cocos2d::Size &size, const std::stri } template -iPreferenceItem* CreatePreferenceItem(const cocos2d::Size &size, const std::string &title) { +T* CreatePreferenceItem(const cocos2d::Size &size, const std::string &title) { T *ret = new T; ret->autorelease(); ret->initFromInfo(size, title); @@ -140,14 +140,14 @@ class tPreferenceItemConstant : public tPreferenceItemSubDir { class tPreferenceItemSelectListInfo { public: - virtual const std::vector >& getListInfo() const = 0; + virtual const std::vector >& getListInfo() = 0; }; class tPreferenceItemSelectList : public tPreferenceItem { public: tPreferenceItemSelectList(); - void initInfo(const tPreferenceItemSelectListInfo* info) { CurInfo = info; } + void initInfo(tPreferenceItemSelectListInfo* info) { CurInfo = info; } protected: virtual void initController(const NodeMap &allNodes) override; @@ -161,7 +161,7 @@ class tPreferenceItemSelectList : public tPreferenceItem { cocos2d::Node *highlight = nullptr; cocos2d::ui::Text *selected = nullptr; - const tPreferenceItemSelectListInfo* CurInfo; + tPreferenceItemSelectListInfo* CurInfo; std::string highlightTid; }; diff --git a/src/core/environ/ui/XP3RepackForm.cpp b/src/core/environ/ui/XP3RepackForm.cpp new file mode 100644 index 00000000..176e8117 --- /dev/null +++ b/src/core/environ/ui/XP3RepackForm.cpp @@ -0,0 +1,205 @@ +#include "XP3RepackForm.h" +#include "StorageImpl.h" +#include "cocos2d/MainScene.h" +#include "PreferenceForm.h" +#include "ConfigManager/LocaleConfigManager.h" +#include "XP3ArchiveRepack.h" +#include "ui/UIListView.h" +#include "ui/UILoadingBar.h" +#include "ui/UIText.h" +#include "ui/UIButton.h" +#include "ui/UICheckBox.h" + +using namespace cocos2d; +using namespace cocos2d::ui; +bool TVPGetXP3ArchiveOffset(tTJSBinaryStream *st, const ttstr name, tjs_uint64 & offset, bool raise); + +static void WalkDir(const ttstr &dir, const std::function& cb) { + std::vector subdirs; + std::vector > files; + TVPGetLocalFileListAt(dir, [&](const ttstr& path, tTVPLocalFileInfo* info) { + if (info->Mode & S_IFDIR) { + subdirs.emplace_back(path); + } else { + files.emplace_back(info->NativeName, info->Size); + } + }); + for (std::pair &it : files) { + cb(it.first, it.second); + } + for (ttstr &path : subdirs) { + WalkDir(path, cb); + } +} + +class TVPXP3RepackForm : public iTVPFloatForm { +public: + virtual void bindBodyController(const NodeMap &allNodes); + static TVPXP3RepackForm* show(); + void initData(std::vector &filelist, const std::string &xp3filter); + +private: + cocos2d::ui::LoadingBar *ProgressBarTotal, *ProgressBarCurrent; + cocos2d::ui::Text *TextTotalProgress, *TextCurrentProgress; // "%d/%d(%d/%d MB)" + cocos2d::ui::Text *TextCurrentFileName, *TextSpeed; // "~XXX MB/s" + + XP3ArchiveRepackAsync *Packer = nullptr; + + uint64_t TotalSize; +}; + +void TVPXP3RepackForm::bindBodyController(const NodeMap &allNodes) +{ + LocaleConfigManager *locmgr = LocaleConfigManager::GetInstance(); + ProgressBarTotal = + static_cast(allNodes.findController("bar_total")); + ProgressBarCurrent = + static_cast(allNodes.findController("bar_current")); + TextTotalProgress = + static_cast(allNodes.findController("text_total")); + TextCurrentProgress = + static_cast(allNodes.findController("text_current")); + TextCurrentFileName = + static_cast(allNodes.findController("text_filename")); + TextSpeed = + static_cast(allNodes.findController("text_speed")); + Text* text = static_cast(allNodes.findController("title")); + if (text) { + text->setString(locmgr->GetText(text->getString())); + } +} + +TVPXP3RepackForm* TVPXP3RepackForm::show() +{ + TVPXP3RepackForm *form = new TVPXP3RepackForm; + form->initFromFile("ui/XP3RepackDialog.csb"); + TVPMainScene::GetInstance()->pushUIForm(form, TVPMainScene::eEnterFromBottom); + return form; +} + +void TVPXP3RepackForm::initData(std::vector &filelist, const std::string &xp3filter) +{ + if (Packer) delete Packer; + Packer = new XP3ArchiveRepackAsync(xp3filter); + TotalSize = 0; + for (const std::string &name : filelist) { + TotalSize += Packer->AddTask(name); + } + +} + +class TVPXP3RepackFileListForm : public iTVPFloatForm { +public: + virtual void bindBodyController(const NodeMap &allNodes); + static TVPXP3RepackFileListForm* show(std::vector &filelist, const std::string &dir); + void initData(std::vector &filelist, const std::string &dir); + void close(); + +private: + void onOkClicked(Ref*); + + cocos2d::ui::ListView *listView; + + std::string RootDir; + std::vector FileList; +}; + +void TVPXP3RepackFileListForm::bindBodyController(const NodeMap &allNodes) +{ + LocaleConfigManager *locmgr = LocaleConfigManager::GetInstance(); + Button *btn = static_cast(allNodes.findWidget("close")); + if (btn) { + btn->setTitleText(locmgr->GetText(btn->getTitleText())); + btn->addClickEventListener([this](Ref*) { close(); }); + } + btn = static_cast(allNodes.findWidget("cancel")); + if (btn) { + btn->setTitleText(locmgr->GetText(btn->getTitleText())); + btn->addClickEventListener([this](Ref*) { close(); }); + } + btn = static_cast(allNodes.findWidget("ok")); + if (btn) { + btn->setTitleText(locmgr->GetText(btn->getTitleText())); + btn->addClickEventListener([this](Ref*) { close(); }); + } + btn->addClickEventListener(std::bind(&TVPXP3RepackFileListForm::onOkClicked, this, std::placeholders::_1)); + listView = static_cast(allNodes.findController("list")); + Text* text = static_cast(allNodes.findController("desc")); + if (text) { + text->setString(locmgr->GetText(text->getString())); + } +} + +TVPXP3RepackFileListForm* TVPXP3RepackFileListForm::show(std::vector &filelist, const std::string &dir) +{ + TVPXP3RepackFileListForm *form = new TVPXP3RepackFileListForm; + form->initFromFile("ui/CheckListDialog.csb"); + form->initData(filelist, dir); + TVPMainScene::GetInstance()->pushUIForm(form, TVPMainScene::eEnterAniNone); + return form; +} + +void TVPXP3RepackFileListForm::initData(std::vector &filelist, const std::string &dir) +{ + RootDir = dir; + FileList.swap(filelist); + int tag = 256; + Size size = listView->getContentSize(); + for (const std::string &filename : FileList) { + tPreferenceItemCheckBox *cell = CreatePreferenceItem(size, filename, + [](tPreferenceItemCheckBox* p) { + p->_getter = []()->bool { return true; }; + p->_setter = [](bool v) {}; + }); + cell->setTag(tag++); + listView->pushBackCustomItem(cell); + } +} + +void TVPXP3RepackFileListForm::close() +{ +// TVPMainScene::GetInstance()->popUIForm(this, TVPMainScene::eLeaveAniNone); + removeFromParent(); +} + +void TVPXP3RepackFileListForm::onOkClicked(Ref*) +{ + class HackPreferenceItemCheckBox : public tPreferenceItemCheckBox { + public: + bool getCheckBoxState() { + return checkbox->getSelectedState(); + } + }; + std::vector filelist; filelist.reserve(FileList.size()); + auto &allCell = listView->getItems(); + for (int i = 0; i < allCell.size(); ++i) { + Widget *cell = allCell.at(i); + if (static_cast(cell)->getCheckBoxState()) { + filelist.push_back(FileList[cell->getTag() - 256]); + } + } + if (filelist.empty()) + return; + TVPXP3RepackForm *form = TVPXP3RepackForm::show(); + form->initData(filelist, RootDir + "/xp3filter.tjs"); + close(); +} + +void TVPProcessXP3Repack(const std::string &dir) +{ + std::vector filelist; + WalkDir(dir, [&](const std::string& path, tjs_uint64) { + tjs_uint64 offset; + ttstr strpath(path); + tTVPLocalFileStream *st = new tTVPLocalFileStream(strpath, strpath, TJS_BS_READ); + if (TVPGetXP3ArchiveOffset(st, strpath, offset, false)) { + filelist.emplace_back(path); + } + delete st; + }); + if (filelist.empty()) { + // TODO warning + } else { + TVPXP3RepackFileListForm::show(filelist, dir); + } +} diff --git a/src/core/environ/ui/XP3RepackForm.h b/src/core/environ/ui/XP3RepackForm.h new file mode 100644 index 00000000..92aec236 --- /dev/null +++ b/src/core/environ/ui/XP3RepackForm.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void TVPProcessXP3Repack(const std::string &dir); diff --git a/src/core/environ/win32/Platform.cpp b/src/core/environ/win32/Platform.cpp index 7bb904a2..f5955671 100644 --- a/src/core/environ/win32/Platform.cpp +++ b/src/core/environ/win32/Platform.cpp @@ -13,6 +13,8 @@ #include "EventIntf.h" #include "cocos/base/CCDirector.h" #include +#include "XP3ArchiveRepack.h" +#include "RenderManager.h" #pragma comment(lib,"psapi.lib") @@ -86,17 +88,11 @@ std::string TVPGetDefaultFileDir() { int TVPCheckArchive(const ttstr &localname); void TVPCheckAndSendDumps(const std::string &dumpdir, const std::string &packageName, const std::string &versionStr); bool TVPCheckStartupArg() { - wchar_t **argv = __wargv, **env; - int argc = __argc; - struct - { - int newmode; - } info = { 0 }; - argv = CommandLineToArgvW(GetCommandLineW(), &argc); + int argc; + wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc); // __wgetmainargs(&argc, &argv, &env, 0, &info); TVPCheckAndSendDumps(TVPGetDefaultFileDir() + "/dumps", "win32-test", "test"); if (argc > 1) { - std::wstring_convert> converter; if (TVPCheckExistentLocalFile(argv[1])) { if (TVPCheckArchive(argv[1]) == 1) { TVPMainScene::GetInstance()->startupFrom(converter.to_bytes(argv[1])); @@ -287,10 +283,13 @@ void TVPReleaseFontLibrary(); void TVPExitApplication(int code) { // clear some static data for memory leak detect TVPDeliverCompactEvent(TVP_COMPACT_LEVEL_MAX); - if (TVPScriptEngine) TVPScriptEngine->Cleanup(); - TVPReleaseFontLibrary(); - delete ::Application; - TVPMainScene::GetInstance()->removeFromParent(); + if (!TVPIsSoftwareRenderManager()) + iTVPTexture2D::RecycleProcess(); + +// if (TVPScriptEngine) TVPScriptEngine->Cleanup(); +// TVPReleaseFontLibrary(); +// delete ::Application; +// TVPMainScene::GetInstance()->removeFromParent(); exit(code); } @@ -299,22 +298,17 @@ void TVPExitApplication(int code) { // return ret; // } -bool TVPDeleteFile(const ttstr &filename) +bool TVPDeleteFile(const std::string &filename) { - return _wunlink(filename.c_str()) == 0; -#ifdef WIN32 - tjs_int ret = _wunlink(filename.c_str()); -#else - tjs_int ret = unlink(tTJSNarrowStringHolder(filename.c_str())); -#endif + return _wunlink(ttstr(filename).c_str()) == 0; } -bool TVPRenameFile(const ttstr &from, const ttstr &to) +bool TVPRenameFile(const std::string &from, const std::string &to) { #ifdef WIN32 - tjs_int ret = _wrename(from.c_str(), to.c_str()); + tjs_int ret = _wrename(ttstr(from).c_str(), ttstr(to).c_str()); #else - tjs_int ret = rename(tTJSNarrowStringHolder(fromFile.c_str()), tTJSNarrowStringHolder(toFile.c_str())); + tjs_int ret = rename(from.c_str(), to.c_str()); #endif return !ret; } @@ -332,4 +326,20 @@ tjs_uint32 TVPGetRoughTickCount32() void TVPPrintLog(const char *str) { printf("%s", str); -} \ No newline at end of file +} + +bool TVP_stat(const tjs_char *name, tTVP_stat &s) { + struct _stat64 t; + bool ret = !_wstat64(name, &t); + s.st_mode = t.st_mode; + s.st_size = t.st_size; + s.st_atime = t.st_atime; + s.st_mtime = t.st_mtime; + s.st_ctime = t.st_ctime; + return ret; +} + +bool TVP_stat(const char *name, tTVP_stat &s) { + ttstr filename(name); + return TVP_stat(filename.c_str(), s); +} diff --git a/src/core/movie/ffmpeg/KRMoviePlayer.cpp b/src/core/movie/ffmpeg/KRMoviePlayer.cpp index 1219dbfc..5797bf68 100644 --- a/src/core/movie/ffmpeg/KRMoviePlayer.cpp +++ b/src/core/movie/ffmpeg/KRMoviePlayer.cpp @@ -365,6 +365,14 @@ const tTVPRect & MoviePlayerOverlay::GetBounds() return m_pCallbackWin->GetBounds(); } +void KRMovie::MoviePlayerOverlay::SetVisible(bool b) +{ + VideoPresentOverlay::SetVisible(b); + if (m_pRootNode) { + m_pRootNode->setVisible(b); + } +} + void MoviePlayerOverlay::OnPlayEvent(KRMovieEvent msg, void *p) { if (msg == KRMovieEvent::Ended) { diff --git a/src/core/movie/ffmpeg/KRMoviePlayer.h b/src/core/movie/ffmpeg/KRMoviePlayer.h index ccb9a4b9..6d5c97b4 100644 --- a/src/core/movie/ffmpeg/KRMoviePlayer.h +++ b/src/core/movie/ffmpeg/KRMoviePlayer.h @@ -184,6 +184,7 @@ class MoviePlayerOverlay : public VideoPresentOverlay { const tjs_char * streamname, const tjs_char *type, uint64_t size); virtual const tTVPRect &GetBounds() override; + virtual void SetVisible(bool b) override; }; class VideoPresentOverlay2 : public VideoPresentOverlay { diff --git a/src/core/sound/win32/WaveImpl.cpp b/src/core/sound/win32/WaveImpl.cpp index 490ec2ba..6b2f6ed4 100644 --- a/src/core/sound/win32/WaveImpl.cpp +++ b/src/core/sound/win32/WaveImpl.cpp @@ -3202,7 +3202,7 @@ void tTJSNI_WaveSoundBuffer::SetVolumeToSoundBuffer() tTVPSoundGlobalFocusMode mode = GlobalFocusMode > TVPSoundGlobalFocusModeByOption ? GlobalFocusMode : TVPSoundGlobalFocusModeByOption; - +#if 0 // useless on mobile device switch(mode) { case sgfmNeverMute: @@ -3217,6 +3217,7 @@ void tTJSNI_WaveSoundBuffer::SetVolumeToSoundBuffer() mutevol = TVPSoundGlobalFocusMuteVolume; break; } +#endif } // compute volume for each buffer diff --git a/src/core/tjs2/tjs.h b/src/core/tjs2/tjs.h index e3acb313..aa68d56a 100644 --- a/src/core/tjs2/tjs.h +++ b/src/core/tjs2/tjs.h @@ -184,7 +184,6 @@ class tTJSString; class iTJSTextReadStream { public: - virtual ~iTJSTextReadStream() {} // add by ZeaS virtual tjs_uint TJS_INTF_METHOD Read(tTJSString & targ, tjs_uint size) = 0; virtual void TJS_INTF_METHOD Destruct() = 0; // must delete itself }; diff --git a/src/core/utils/minizip/ioapi.cpp b/src/core/utils/minizip/ioapi.cpp index e9f4a9c0..c4112e42 100644 --- a/src/core/utils/minizip/ioapi.cpp +++ b/src/core/utils/minizip/ioapi.cpp @@ -11,16 +11,15 @@ */ #include "ioapi.h" +#include #ifdef WIN32 #define _POSIX_ #include -#include "win32io.h" -#else -#include #endif #include #include +#include "win32io.h" //namespace cocos2d { @@ -119,9 +118,7 @@ static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const voi { return (uLong)write((int64_t)stream, buf, size); } -#ifdef __APPLE__ -#define lseek64 lseek -#endif + static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) { return (ZPOS64_T)lseek64((int64_t)stream, 0, SEEK_CUR); diff --git a/src/core/visual/FontImpl.cpp b/src/core/visual/FontImpl.cpp index e062e4be..d459748e 100644 --- a/src/core/visual/FontImpl.cpp +++ b/src/core/visual/FontImpl.cpp @@ -1,5 +1,7 @@ #include "FontImpl.h" #include +#include FT_TRUETYPE_IDS_H +#include FT_SFNT_NAMES_H #include FT_FREETYPE_H #include "StorageIntf.h" #include "DebugIntf.h" @@ -17,7 +19,6 @@ #pragma comment(lib,"freetype.lib") #endif #include "platform/CCFileUtils.h" -#include #include "StorageImpl.h" #include "BinaryStream.h" @@ -93,13 +94,59 @@ int TVPEnumFontsProc(const ttstr &FontPath) } } if(FT_IS_SCALABLE(fontface)) { - ttstr fontname((tjs_nchar*)fontface->family_name); - TVPFontNamePathInfo info; - info.Path = FontPath; - info.Index = i; - TVPFontNames.Add(fontname, info); - ++faceCount; - } + FT_UInt namecount = FT_Get_Sfnt_Name_Count(fontface); + int addCount = 0; + for (FT_UInt i = 0; i < namecount; ++i) { + FT_SfntName name; + if (FT_Get_Sfnt_Name(fontface, i, &name)) { + continue; + } + if (name.name_id != TT_NAME_ID_FONT_FAMILY) { + continue; + } + if (name.platform_id != TT_PLATFORM_MICROSOFT) { + continue; + } + switch (name.language_id) { // for CJK names + case TT_MS_LANGID_JAPANESE_JAPAN: + case TT_MS_LANGID_CHINESE_GENERAL: + case TT_MS_LANGID_CHINESE_TAIWAN: + case TT_MS_LANGID_CHINESE_PRC: + case TT_MS_LANGID_CHINESE_HONG_KONG: + case TT_MS_LANGID_CHINESE_SINGAPORE: + case TT_MS_LANGID_KOREAN_EXTENDED_WANSUNG_KOREA: + case TT_MS_LANGID_KOREAN_JOHAB_KOREA: + break; + default: + continue; + } + ttstr fontname; + if (name.encoding_id == TT_MS_ID_UNICODE_CS) { + std::vector tmp; + int namelen = name.string_len / 2; + tmp.resize(namelen + 1); + for (int j = 0; j < namelen; ++j) { + tmp[j] = (name.string[j * 2] << 8) | (name.string[j * 2 + 1]); + } + fontname = &tmp.front(); + } else { + continue; + } + TVPFontNamePathInfo info; + info.Path = FontPath; + info.Index = i; + TVPFontNames.Add(fontname, info); + addCount = 1; + } + /*if (!addCount)*/ { + ttstr fontname((tjs_nchar*)fontface->family_name); + TVPFontNamePathInfo info; + info.Path = FontPath; + info.Index = i; + TVPFontNames.Add(fontname, info); + } + ++faceCount; + } FT_Done_Face(fontface); } @@ -109,7 +156,7 @@ int TVPEnumFontsProc(const ttstr &FontPath) tTJSBinaryStream* TVPCreateFontStream(const ttstr &fontname) { - TVPFontNamePathInfo *info = TVPFontNames.Find(fontname); + TVPFontNamePathInfo *info = TVPFindFont(fontname); if (!info) { info = TVPFontNames.Find(TVPDefaultFontName); if (!info) return nullptr; @@ -118,7 +165,6 @@ tTJSBinaryStream* TVPCreateFontStream(const ttstr &fontname) } //--------------------------------------------------------------------------- -void TVPGetListAt(const ttstr &name, iTVPStorageLister * lister); #ifdef __ANDROID__ extern std::vector Android_GetExternalStoragePath(); extern ttstr Android_GetInternalStoragePath(); @@ -195,14 +241,19 @@ void TVPInitFontNames() TVPFontNamesInit = true; } //--------------------------------------------------------------------------- -bool TVPFontExists(const ttstr &name) +TVPFontNamePathInfo* TVPFindFont(const ttstr &fontname) { // check existence of font TVPInitFontNames(); - TVPFontNamePathInfo * t = TVPFontNames.Find(name); - - return t != NULL; + TVPFontNamePathInfo *info = nullptr; + if (!fontname.IsEmpty() && fontname[0] == TJS_W('@')) { // vertical version + info = TVPFontNames.Find(fontname.c_str() + 1); + } + if (!info) { + info = TVPFontNames.Find(fontname); + } + return info; } tjs_uint32 tTVPttstrHash::Make( const ttstr &val ) diff --git a/src/core/visual/FontImpl.h b/src/core/visual/FontImpl.h index bd96d4a6..0a5ad8b3 100644 --- a/src/core/visual/FontImpl.h +++ b/src/core/visual/FontImpl.h @@ -2,7 +2,6 @@ #include "tjs.h" #include "tjsHashSearch.h" -bool TVPFontExists(const ttstr &name); void TVPInitFontNames(); int TVPEnumFontsProc(const ttstr &FontPath); tTJSBinaryStream* TVPCreateFontStream(const ttstr &fontname); @@ -10,6 +9,7 @@ struct TVPFontNamePathInfo { ttstr Path; int Index; }; +TVPFontNamePathInfo* TVPFindFont(const ttstr &name); //--------------------------------------------------------------------------- // font enumeration and existence check diff --git a/src/core/visual/FreeTypeFontRasterizer.cpp b/src/core/visual/FreeTypeFontRasterizer.cpp index bc94c4d5..a6b9db2e 100644 --- a/src/core/visual/FreeTypeFontRasterizer.cpp +++ b/src/core/visual/FreeTypeFontRasterizer.cpp @@ -12,6 +12,34 @@ extern void TVPUninitializeFreeFont(); extern FontSystem* TVPFontSystem; +extern const ttstr &TVPGetDefaultFontName(); +void FreeTypeFontRasterizer::ApplyFallbackFace() +{ + if (!FaceFallback && Face && Face->GetFontName() != TVPGetDefaultFontName()) { + FaceFallback = new tFreeTypeFace(TVPGetDefaultFontName(), 0); + } + FaceFallback->SetHeight(CurrentFont.Height < 0 ? -CurrentFont.Height : CurrentFont.Height); + if (CurrentFont.Flags & TVP_TF_ITALIC) { + FaceFallback->SetOption(TVP_TF_ITALIC); + } else { + FaceFallback->ClearOption(TVP_TF_ITALIC); + } + if (CurrentFont.Flags & TVP_TF_BOLD) { + FaceFallback->SetOption(TVP_TF_BOLD); + } else { + FaceFallback->ClearOption(TVP_TF_BOLD); + } + if (CurrentFont.Flags & TVP_TF_UNDERLINE) { + FaceFallback->SetOption(TVP_TF_UNDERLINE); + } else { + FaceFallback->ClearOption(TVP_TF_UNDERLINE); + } + if (CurrentFont.Flags & TVP_TF_STRIKEOUT) { + FaceFallback->SetOption(TVP_TF_STRIKEOUT); + } else { + FaceFallback->ClearOption(TVP_TF_STRIKEOUT); + } +} FreeTypeFontRasterizer::FreeTypeFontRasterizer() : RefCount(0), Face(NULL), LastBitmap(NULL) { AddRef(); @@ -19,6 +47,10 @@ FreeTypeFontRasterizer::FreeTypeFontRasterizer() : RefCount(0), Face(NULL), Last FreeTypeFontRasterizer::~FreeTypeFontRasterizer() { if( Face ) delete Face; Face = NULL; + if (FaceFallback) { + delete FaceFallback; + FaceFallback = nullptr; + } TVPUninitializeFreeFont(); } void FreeTypeFontRasterizer::AddRef() { @@ -31,7 +63,10 @@ void FreeTypeFontRasterizer::Release() { if( RefCount == 0 ) { if( Face ) delete Face; Face = NULL; - + if (FaceFallback) { + delete FaceFallback; + FaceFallback = nullptr; + } delete this; } } @@ -124,6 +159,12 @@ tTVPCharacterData* FreeTypeFontRasterizer::GetBitmap( const tTVPFontAndCharacter //Face->ClearOption( TVP_FACE_OPTIONS_FORCE_AUTO_HINTING ); } tTVPCharacterData* data = Face->GetGlyphFromCharcode(font.Character); + if (!data) { + ApplyFallbackFace(); + if (FaceFallback) { + data = FaceFallback->GetGlyphFromCharcode(font.Character); + } + } if( data == NULL ) { data = Face->GetGlyphFromCharcode( Face->GetDefaultChar() ); } @@ -153,6 +194,8 @@ tTVPCharacterData* FreeTypeFontRasterizer::GetBitmap( const tTVPFontAndCharacter data->Blured = font.Blured; data->BlurWidth = font.BlurWidth; data->BlurLevel = font.BlurLevel; + data->OriginX += aofsx; // for vertical text +// data->OriginY += aofsy; // apply blur if(font.Blured) data->Blur(); // nasty ... diff --git a/src/core/visual/FreeTypeFontRasterizer.h b/src/core/visual/FreeTypeFontRasterizer.h index ad281311..6b479bf8 100644 --- a/src/core/visual/FreeTypeFontRasterizer.h +++ b/src/core/visual/FreeTypeFontRasterizer.h @@ -9,8 +9,10 @@ class FreeTypeFontRasterizer : public FontRasterizer { tjs_int RefCount; class tFreeTypeFace* Face; //!< FaceIuWFNg + class tFreeTypeFace* FaceFallback = nullptr; class tTVPNativeBaseBitmap * LastBitmap; tTVPFont CurrentFont; + void ApplyFallbackFace(); public: FreeTypeFontRasterizer(); diff --git a/src/core/visual/GraphicsLoadThread.cpp b/src/core/visual/GraphicsLoadThread.cpp index d2cf7be7..b28e437a 100644 --- a/src/core/visual/GraphicsLoadThread.cpp +++ b/src/core/visual/GraphicsLoadThread.cpp @@ -158,16 +158,16 @@ void tTVPAsyncImageLoader::HandleLoadedImage() { iTJSDispatch2* metainfo = TVPMetaInfoPairsToDictionary(cmd->dest_->MetaInfo); cmd->bmp_->SetSizeAndImageBuffer(cmd->dest_->bmp); - cmd->dest_->bmp->Release(); - cmd->dest_->bmp = NULL; // Ǎ݊ɂLbV`FbN(񓯊Ȃ̂ŊOɓǂݍ܂Ô\) if( TVPHasImageCache( cmd->path_, glmNormal, 0, 0, TVP_clNone ) == false ) { - TVPPushGraphicCache( cmd->path_, cmd->bmp_->GetBitmap(), cmd->dest_->MetaInfo ); + TVPPushGraphicCache( cmd->path_, cmd->dest_->bmp, cmd->dest_->MetaInfo ); cmd->dest_->MetaInfo = NULL; } else { delete cmd->dest_->MetaInfo; cmd->dest_->MetaInfo = NULL; } + cmd->dest_->bmp->Release(); + cmd->dest_->bmp = NULL; tTJSVariant param[4]; param[0] = tTJSVariant(metainfo,metainfo); diff --git a/src/core/visual/GraphicsLoaderIntf.cpp b/src/core/visual/GraphicsLoaderIntf.cpp index 3d2ded0c..6fe097b4 100644 --- a/src/core/visual/GraphicsLoaderIntf.cpp +++ b/src/core/visual/GraphicsLoaderIntf.cpp @@ -38,6 +38,12 @@ #include #include +void TVPLoadPVRv3(void* formatdata, void *callbackdata, + tTVPGraphicSizeCallback sizecallback, tTVPGraphicScanLineCallback scanlinecallback, + tTVPMetaInfoPushCallback metainfopushcallback, tTJSBinaryStream *src, tjs_int keyidx, + tTVPGraphicLoadMode mode); +void TVPLoadHeaderPVRv3(void* formatdata, tTJSBinaryStream *src, iTJSDispatch2** dic); + static void TVPLoadGraphicRouter(void* formatdata, void *callbackdata, tTVPGraphicSizeCallback sizecallback, tTVPGraphicScanLineCallback scanlinecallback, tTVPMetaInfoPushCallback metainfopushcallback, tTJSBinaryStream *src, tjs_int keyidx, tTVPGraphicLoadMode mode) { @@ -68,6 +74,9 @@ static void TVPLoadGraphicRouter(void* formatdata, void *callbackdata, tTVPGraph if (!memcmp(header, "\x49\x49\xbc\x01", 4)) { return CALL_LOAD_FUNC(TVPLoadJXR); } + if (!memcmp(header, "PVR\3", 4)) { + return CALL_LOAD_FUNC(TVPLoadPVRv3); + } #undef CALL_LOAD_FUNC } TVPThrowExceptionMessage(TVPImageLoadError, TJS_W("Invalid image")); @@ -101,6 +110,9 @@ static void TVPLoadHeaderRouter(void* formatdata, tTJSBinaryStream *src, iTJSDis if (!memcmp(header, "\x49\x49\xbc\x01", 4)) { return CALL_LOAD_FUNC(TVPLoadHeaderJXR); } + if (!memcmp(header, "PVR\3", 4)) { + return CALL_LOAD_FUNC(TVPLoadHeaderPVRv3); + } #undef CALL_LOAD_FUNC } TVPThrowExceptionMessage(TVPImageLoadError, TJS_W("Invalid image")); @@ -168,6 +180,8 @@ class tTVPGraphicType TJS_W(".bpg"), TVPLoadGraphicRouter, TVPLoadHeaderRouter, nullptr, nullptr, NULL)); Handlers.push_back(tTVPGraphicHandlerType( TJS_W(".webp"), TVPLoadGraphicRouter, TVPLoadHeaderRouter, nullptr, nullptr, NULL)); + Handlers.push_back(tTVPGraphicHandlerType( + TJS_W(".pvr"), TVPLoadGraphicRouter, TVPLoadHeaderRouter, nullptr, nullptr, NULL)); ReCreateHash(); Avail = true; } @@ -1528,7 +1542,7 @@ struct tTVPClearGraphicCacheCallback : public tTVPCompactEventCallbackIntf } static TVPClearGraphicCacheCallback; static bool TVPClearGraphicCacheCallbackInit = false; //--------------------------------------------------------------------------- -void TVPPushGraphicCache( const ttstr& nname, tTVPBaseBitmap* bmp, std::vector* meta ) +void TVPPushGraphicCache( const ttstr& nname, tTVPBitmap* bmp, std::vector* meta ) { if( TVPGraphicCacheEnabled ) { // graphic compact initialization @@ -1552,7 +1566,7 @@ void TVPPushGraphicCache( const ttstr& nname, tTVPBaseBitmap* bmp, std::vectorAssignTexture(bmp->GetTexture()); + data->AssignBitmap(bmp); data->ProvinceName = TJS_W(""); data->MetaInfo = meta; meta = NULL; @@ -1629,26 +1643,14 @@ bool TVPHasImageCache( const ttstr& nname, tTVPGraphicLoadMode mode, tjs_uint dw return false; } //--------------------------------------------------------------------------- -static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, - tjs_uint32 keyidx, tjs_uint desw, tjs_int desh, std::vector * * MetaInfo, - tTVPGraphicLoadMode mode, ttstr *provincename) -{ - // name must be normalized. - // if "provincename" is non-null, this function set it to province storage - // name ( with _p suffix ) for convinience. - // desw and desh are desired size. if the actual picture is smaller than - // the given size, the graphic is to be tiled. give 0,0 to obtain default - // size graphic. - - +tTVPGraphicHandlerType* TVPFindGraphicLoadHandler(ttstr &_name, ttstr *maskname, ttstr *provincename) { // graphic compact initialization - if(!TVPClearGraphicCacheCallbackInit) + if (!TVPClearGraphicCacheCallbackInit) { TVPAddCompactEventHook(&TVPClearGraphicCacheCallback); TVPClearGraphicCacheCallbackInit = true; } - // search according with its extension tjs_int namelen = _name.GetLen(); ttstr name(_name); @@ -1657,15 +1659,14 @@ static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, int extlen = ext.GetLen(); tTVPGraphicHandlerType * handler; - if(ext.IsEmpty()) - { + if (ext.IsEmpty()) { // missing extension // suggest registered extensions tTJSHashTable::tIterator i; - for(i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) + for (i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) { ttstr newname = name + i.GetKey(); - if(TVPIsExistentStorage(newname)) + if (TVPIsExistentStorage(newname)) { // file found name = newname; @@ -1673,17 +1674,15 @@ static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, } i++; } - if(i.IsNull()) + if (i.IsNull()) { // not found ttstr fmt = LocaleConfigManager::GetInstance()->GetText("err_cannot_suggest_graph_ext"); TVPThrowExceptionMessage(fmt.c_str(), name); } - handler = & i.GetValue(); - } - else - { + handler = &i.GetValue(); + } else { handler = TVPGraphicType.Hash.Find(ext); if (!handler) { static const ttstr ext_bmp(TJS_W(".bmp")); @@ -1691,9 +1690,85 @@ static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, } } - if(!handler) TVPThrowExceptionMessage(TVPUnknownGraphicFormat, name); + if (!handler) TVPThrowExceptionMessage(TVPUnknownGraphicFormat, name); + ttstr retname(name); + if (maskname) { + // mask image handling ( addding _m suffix with the filename ) + while (true) { + name = ttstr(_name, namelen - extlen) + TJS_W("_m") + ext; + if (ext.IsEmpty()) { + // missing extension + // suggest registered extensions + tTJSHashTable::tIterator i; + for (i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) { + ttstr newname = name; + newname += i.GetKey(); + if (TVPIsExistentStorage(newname)) + { + // file found + name = newname; + break; + } + i++; + } + if (i.IsNull()) { + // not found + maskname->Clear(); + break; + } + *maskname = name; + break; + } else { + if (!TVPIsExistentStorage(name)) { + // not found + ext.Clear(); + continue; // retry searching + } + *maskname = name; + break; + } + } + } + if (provincename) { + // set province name + *provincename = ttstr(_name, namelen - extlen) + TJS_W("_p"); + + // search extensions + tTJSHashTable::tIterator i; + for (i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) { + ttstr newname = *provincename + i.GetKey(); + if (TVPIsExistentStorage(newname)) + { + // file found + *provincename = newname; + break; + } + i++; + } + if (i.IsNull()) { + // not found + provincename->Clear(); + } + } + _name = retname; + return handler; +} +//--------------------------------------------------------------------------- +static tTVPBitmap* TVPInternalLoadBitmap(const ttstr &_name, + tjs_uint32 keyidx, tjs_uint desw, tjs_int desh, std::vector * * MetaInfo, + tTVPGraphicLoadMode mode, ttstr *provincename) +{ + // name must be normalized. + // if "provincename" is non-null, this function set it to province storage + // name ( with _p suffix ) for convinience. + // desw and desh are desired size. if the actual picture is smaller than + // the given size, the graphic is to be tiled. give 0,0 to obtain default + // size graphic. + + ttstr name(_name), maskname; + tTVPGraphicHandlerType * handler = TVPFindGraphicLoadHandler(name, &maskname, mode == glmNormal ? provincename : nullptr); tTVPStreamHolder holder(name); // open a storage named "name" // load the image @@ -1734,86 +1809,16 @@ static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, TVPMakeAlphaFromAdaptiveColor(data.Dest); } - if(mode != glmNormal) return data.Dest; - if(provincename) - { - // set province name - *provincename = ttstr(_name, namelen-extlen) + TJS_W("_p"); - - // search extensions - tTJSHashTable::tIterator i; - for(i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) - { - ttstr newname = *provincename + i.GetKey(); - if(TVPIsExistentStorage(newname)) - { - // file found - *provincename = newname; - break; - } - i++; - } - if(i.IsNull()) - { - // not found - provincename->Clear(); - } - } - - // mask image handling ( addding _m suffix with the filename ) - while(true) - { - name = ttstr(_name, namelen-extlen) + TJS_W("_m") + ext; - if(ext.IsEmpty()) - { - // missing extension - // suggest registered extensions - tTJSHashTable::tIterator i; - for(i = TVPGraphicType.Hash.GetFirst(); !i.IsNull(); /*i++*/) - { - ttstr newname = name; - newname += i.GetKey(); - if(TVPIsExistentStorage(newname)) - { - // file found - name = newname; - break; - } - i++; - } - if(i.IsNull()) - { - // not found - handler = NULL; - break; - } - - handler = & i.GetValue(); - break; - } - else - { - if(!TVPIsExistentStorage(name)) - { - // not found - ext.Clear(); - continue; // retry searching - } - handler = TVPGraphicType.Hash.Find(ext); - break; - } - } - - if(handler) + if(!maskname.IsEmpty() && (handler = TVPFindGraphicLoadHandler(maskname, nullptr, nullptr))) { // open the mask file - holder.Open(name); + holder.Open(maskname); // fill "data"'s member data.Type = lgtMask; - data.Name = name; +// data.Name = name; data.Buffer = NULL; data.DesW = desw; data.DesH = desh; @@ -1847,6 +1852,21 @@ static tTVPBitmap* TVPInternalLoadGraphic(const ttstr &_name, return data.Dest; } //--------------------------------------------------------------------------- +iTVPTexture2D* TVPLoadPVRv3(tTJSBinaryStream *s, const std::function &cb); +static iTVPTexture2D *TVPInternalLoadTexture(const ttstr &_name, + std::vector * * MetaInfo, ttstr *provincename) { + ttstr name(_name), maskname; + tTVPGraphicHandlerType * handler = TVPFindGraphicLoadHandler(name, &maskname, provincename); + if (!maskname.IsEmpty()) { + // mask merge is not supported + return nullptr; + } + tTVPStreamHolder holder(name); + return TVPLoadPVRv3(holder.Get(), [MetaInfo](const ttstr& k, const tTJSVariant& v) { + if (!*MetaInfo) *MetaInfo = new std::vector; + (*MetaInfo)->emplace_back(k, v); + }); +} void TVPLoadGraphicProvince(tTVPBaseBitmap *dest, const ttstr &name, tjs_int keyidx, tjs_uint desw, tjs_uint desh) @@ -1879,7 +1899,7 @@ void TVPLoadGraphicProvince(tTVPBaseBitmap *dest, const ttstr &name, tjs_int key ttstr pn; std::vector * mi = nullptr; try { - tTVPBitmap *bmp = TVPInternalLoadGraphic(nname, keyidx, desw, desh, &mi, glmPalettized, &pn); + tTVPBitmap *bmp = TVPInternalLoadBitmap(nname, keyidx, desw, desh, &mi, glmPalettized, &pn); dest->AssignBitmap(bmp); if (TVPGraphicCacheEnabled) { data = new tTVPGraphicImageData(); @@ -1939,7 +1959,8 @@ int TVPLoadGraphic(iTVPBaseBitmap *dest, const ttstr &name, tjs_int32 keyidx, if(ptr) { // found in cache - ptr->GetObjectNoAddRef()->AssignToTexture(dest); + if (dest) + ptr->GetObjectNoAddRef()->AssignToTexture(dest); if(provincename) *provincename = ptr->GetObjectNoAddRef()->ProvinceName; if(metainfo) *metainfo = TVPMetaInfoPairsToDictionary(ptr->GetObjectNoAddRef()->MetaInfo); @@ -1960,7 +1981,14 @@ int TVPLoadGraphic(iTVPBaseBitmap *dest, const ttstr &name, tjs_int32 keyidx, int ret = 0; try { - tTVPBitmap *bmp = TVPInternalLoadGraphic(nname, keyidx, desw, desh, &mi, mode, &pn); + tTVPBitmap *bmp = nullptr; + iTVPTexture2D *texture = nullptr; + if (mode == glmNormal && keyidx == TVP_clNone && !desw && !desh) { + texture = TVPInternalLoadTexture(nname, &mi, &pn); + } + if (!texture) { + bmp = TVPInternalLoadBitmap(nname, keyidx, desw, desh, &mi, mode, &pn); + } if(provincename) *provincename = pn; if(metainfo) @@ -1969,7 +1997,11 @@ int TVPLoadGraphic(iTVPBaseBitmap *dest, const ttstr &name, tjs_int32 keyidx, if(TVPGraphicCacheEnabled) { data = new tTVPGraphicImageData(); - data->AssignBitmap(bmp); + if (texture) { + data->AssignTexture(texture); + } else { + data->AssignBitmap(bmp); + } if(dest) { data->AssignToTexture(dest); } @@ -1989,12 +2021,21 @@ int TVPLoadGraphic(iTVPBaseBitmap *dest, const ttstr &name, tjs_int32 keyidx, TVPGraphicCache.AddWithHash(searchdata, hash, holder); // } } else if(dest) { - tTVPGraphicImageData data; - data.AssignBitmap(bmp); - data.AssignToTexture(dest); + tTVPGraphicImageData data; + if (texture) { + data.AssignTexture(texture); + } else { + data.AssignBitmap(bmp); + } + data.AssignToTexture(dest); + } + if (texture) { + ret = texture->GetInternalWidth() * texture->GetInternalHeight() * 4; // assume that always RGBA + texture->Release(); + } else { + ret = bmp->GetWidth() * bmp->GetHeight() * bmp->GetBPP() / 8; + bmp->Release(); } - ret = bmp->GetWidth() * bmp->GetHeight() * bmp->GetBPP() / 8; - bmp->Release(); } catch(...) { @@ -2256,7 +2297,7 @@ void TVPTouchImages(const std::vector & storages, tjs_int64 limit, if((tjs_uint64)-limit >= TVPGraphicCacheLimit) return; limitbytes = TVPGraphicCacheLimit + limit; } - if (!timeout && storages.size() > 1) { // using async touching for multi images + if (/*!timeout &&*/ storages.size()/* > 1*/) { // using async touching for multi images for (const ttstr &name : storages) { ttstr nname = TVPNormalizeStorageName(name); tjs_uint32 hash; diff --git a/src/core/visual/GraphicsLoaderIntf.h b/src/core/visual/GraphicsLoaderIntf.h index 2e0cd1bc..32aaea21 100644 --- a/src/core/visual/GraphicsLoaderIntf.h +++ b/src/core/visual/GraphicsLoaderIntf.h @@ -442,7 +442,7 @@ struct tTVPGraphicMetaInfoPair }; extern iTJSDispatch2 * TVPMetaInfoPairsToDictionary( std::vector *vec ); -extern void TVPPushGraphicCache( const ttstr& nname, tTVPBaseBitmap* bmp, +extern void TVPPushGraphicCache( const ttstr& nname, class tTVPBitmap* bmp, std::vector* meta ); extern tTVPGraphicHandlerType* TVPGetGraphicLoadHandler( const ttstr& ext ); extern bool TVPCheckImageCache( const ttstr& nname, tTVPBaseBitmap* dest, diff --git a/src/core/visual/LayerManager.cpp b/src/core/visual/LayerManager.cpp index efdb5a13..8dd3ae62 100644 --- a/src/core/visual/LayerManager.cpp +++ b/src/core/visual/LayerManager.cpp @@ -79,11 +79,7 @@ void tTVPLayerManager::UnregisterSelfFromWindow() void tTVPLayerManager::SetHoldAlpha(bool b) { HoldAlpha = b; - if (!DrawBuffer) { - tjs_int w, h; - if (!GetPrimaryLayerSize(w, h)) return; - DrawBuffer = new tTVPDestTexture(w, h); - } + if (!DrawBuffer) return; static_cast(DrawBuffer)->SetHoldAlpha(b); } @@ -101,18 +97,19 @@ tTVPBaseTexture * tTVPLayerManager::GetDrawTargetBitmap(const tTVPRect &rect, const tTVPRect & rc = Primary->GetRect(); w = rc.get_width(); h = rc.get_height(); - } + } DrawBuffer = new tTVPDestTexture(w, h); DrawBuffer->Fill(tTVPRect(0, 0, w, h), 0xFF000000); + static_cast(DrawBuffer)->SetHoldAlpha(HoldAlpha); } else { tjs_int bw = DrawBuffer->GetWidth(); tjs_int bh = DrawBuffer->GetHeight(); - if(bw < w || bh < h) - { + if(bw < w || bh < h) { // insufficient size; resize the draw buffer - tjs_uint neww = bw > w ? bw:w; + tjs_uint neww = bw > w ? bw:w, newh = bh > h ? bh : h; neww += (neww & 1); // align to even - DrawBuffer->SetSize(neww, bh > h ? bh:h, false); + DrawBuffer->SetSize(neww, newh, false); + DrawBuffer->Fill(tTVPRect(0, 0, neww, newh), 0xFF000000); } } @@ -140,20 +137,35 @@ void tTVPLayerManager::DrawCompleted(const tTVPRect &destrect, // create draw buffer DrawBuffer = new tTVPDestTexture(w, h); DrawBuffer->Fill(tTVPRect(0, 0, w, h), 0xFF000000); + static_cast(DrawBuffer)->SetHoldAlpha(HoldAlpha); } else { tjs_int bw = DrawBuffer->GetWidth(); tjs_int bh = DrawBuffer->GetHeight(); if (bw < w || bh < h) { // insufficient size; resize the draw buffer - tjs_uint neww = bw > w ? bw : w; + tjs_uint neww = bw > w ? bw : w, newh = bh > h ? bh : h; neww += (neww & 1); // align to even - DrawBuffer->SetSize(neww, bh > h ? bh : h); + DrawBuffer->SetSize(neww, newh, false); + DrawBuffer->Fill(tTVPRect(0, 0, neww, newh), 0xFF000000); } } DrawBuffer->Blt(destrect.left, destrect.top, bmp, cliprect, type, opacity, HoldAlpha); #endif } + +tTVPBaseTexture* tTVPLayerManager::GetOrCreateDrawBuffer() +{ + if (!DrawBuffer) { + tjs_int w, h; + if (!GetPrimaryLayerSize(w, h)) return nullptr; + DrawBuffer = new tTVPDestTexture(w, h); + DrawBuffer->Fill(tTVPRect(0, 0, w, h), 0xFF000000); + static_cast(DrawBuffer)->SetHoldAlpha(HoldAlpha); + } + return DrawBuffer; +} + //--------------------------------------------------------------------------- void tTVPLayerManager::AttachPrimary(tTJSNI_BaseLayer *pri) { diff --git a/src/core/visual/LayerManager.h b/src/core/visual/LayerManager.h index e7da4318..4373f26c 100644 --- a/src/core/visual/LayerManager.h +++ b/src/core/visual/LayerManager.h @@ -305,6 +305,7 @@ class tTVPLayerManager : public iTVPLayerManager, public tTVPDrawable tTVPBaseTexture *bmp, const tTVPRect &cliprect, tTVPLayerType type, tjs_int opacity) override; virtual tTVPBaseTexture *GetDrawBuffer() { return DrawBuffer; } + tTVPBaseTexture* GetOrCreateDrawBuffer(); public: void AttachPrimary(tTJSNI_BaseLayer *pri); // attach primary layer to the manager diff --git a/src/core/visual/LoadJPEG.cpp b/src/core/visual/LoadJPEG.cpp index 92b35329..07c61eee 100644 --- a/src/core/visual/LoadJPEG.cpp +++ b/src/core/visual/LoadJPEG.cpp @@ -631,7 +631,9 @@ void TVPLoadHeaderJPG(void* formatdata, tTJSBinaryStream *src, iTJSDispatch2** d tTJSVariant val((tjs_int64)cinfo.image_width); (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("width"), 0, &val, (*dic) ); val = tTJSVariant((tjs_int64)cinfo.image_height); - (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("height"), 0, &val, (*dic) ); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("height"), 0, &val, (*dic)); + val = tTJSVariant((tjs_int64)24); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("bpp"), 0, &val, (*dic)); jpeg_destroy_decompress(&cinfo); } diff --git a/src/core/visual/LoadJXR.cpp b/src/core/visual/LoadJXR.cpp index 6496aade..62aca2e0 100644 --- a/src/core/visual/LoadJXR.cpp +++ b/src/core/visual/LoadJXR.cpp @@ -776,7 +776,9 @@ void TVPLoadHeaderJXR(void* formatdata, tTJSBinaryStream *src, iTJSDispatch2** d tTJSVariant val(width); (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("width"), 0, &val, (*dic) ); val = tTJSVariant(height); - (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("height"), 0, &val, (*dic) ); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("height"), 0, &val, (*dic)); + val = tTJSVariant((tjs_int64)(pDecoder->WMP.wmiSCP.uAlphaMode ? 32 : 24)); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("bpp"), 0, &val, (*dic)); if( pDecoder ) pDecoder->Release(&pDecoder); if( pStream ) free( pStream ); diff --git a/src/core/visual/LoadPVRv3.cpp b/src/core/visual/LoadPVRv3.cpp new file mode 100644 index 00000000..ce8f238a --- /dev/null +++ b/src/core/visual/LoadPVRv3.cpp @@ -0,0 +1,152 @@ +#include "tjsCommHead.h" +#include "GraphicsLoaderIntf.h" +#include "tjsDictionary.h" +#include "MsgIntf.h" +#include "ogl/etcpak.h" +#include "ogl/pvr.h" +#include "UtilStreams.h" +#include "RenderManager.h" + +// pvr format only used as normal picture or univ trans rule ( not as province image ) + +static ttstr TVPUnserializeString(tTJSBinaryStream * s) { + tjs_uint16 l = s->ReadI16LE(); + std::vector strbuf; strbuf.resize(l + 1); + l += l; + if (s->Read(&strbuf.front(), l) != l) { + return ttstr(); + } + return &strbuf.front(); +} + +static tTJSVariant TVPUnserializeVariable(tTJSBinaryStream * s) { + tjs_uint8 typ = 0; + s->ReadBuffer(&typ, 1); + switch (typ) { + case tvtVoid: + return tTJSVariant(); + case tvtString: + return TVPUnserializeString(s); + } +} + +static int TVPUnserializePVRv3Metadata(tTJSBinaryStream *s, const std::function &cb) { + char tmp[4]; + s->ReadBuffer(tmp, 4); + if (memcmp(tmp, "tags", 4)) return 0; + tjs_uint32 count = s->ReadI32LE(); + for (tjs_uint32 i = 0; i < count; ++i) { + ttstr name = TVPUnserializeString(s); + if (name.IsEmpty()) { + return i; + } + cb(name, TVPUnserializeVariable(s)); + } + return count; +} + +// load texture directly +iTVPTexture2D* TVPLoadPVRv3(tTJSBinaryStream *src, const std::function &cb) { + iTVPTexture2D* ret = TVPGetRenderManager()->CreateTexture2D(src); + if (!ret) return nullptr; + src->SetPosition(0); + PVRv3Header hdr; + src->ReadBuffer(&hdr, sizeof(hdr)); + if (hdr.metadataLength > 8) { + tTVPMemoryStream metadata; metadata.SetSize(hdr.metadataLength); + src->ReadBuffer(metadata.GetInternalBuffer(), hdr.metadataLength); + TVPUnserializePVRv3Metadata(&metadata, cb); + } +} + +void TVPLoadPVRv3(void* formatdata, void *callbackdata, + tTVPGraphicSizeCallback sizecallback, tTVPGraphicScanLineCallback scanlinecallback, + tTVPMetaInfoPushCallback metainfopushcallback, tTJSBinaryStream *src, tjs_int keyidx, + tTVPGraphicLoadMode mode) { + if (mode == glmPalettized) { + TVPThrowExceptionMessage(TJS_W("PVR Load Error/Unsupport palettized image")); + } + PVRv3Header hdr; + src->ReadBuffer(&hdr, sizeof(hdr)); + if (hdr.version != 0x03525650) { + TVPThrowExceptionMessage(TJS_W("PVR Load Error/Invalid image")); + } + + tjs_uint64 dataoff = src->GetPosition() + hdr.metadataLength; + + if (hdr.metadataLength > 8) { + tTVPMemoryStream metadata; metadata.SetSize(hdr.metadataLength); + src->ReadBuffer(metadata.GetInternalBuffer(), hdr.metadataLength); + TVPUnserializePVRv3Metadata(&metadata, [metainfopushcallback, callbackdata](const ttstr& k, const tTJSVariant& v) { + metainfopushcallback(callbackdata, k, v); + }); + } + + src->SetPosition(dataoff); + int blkh = hdr.height / 4 + !!(hdr.height & 3); + int blkw = hdr.width / 4 + !!(hdr.width & 3); + int blksize = 0; + tTVPGraphicPixelFormat fmt; + switch (hdr.pixelFormat) { + case 22: // ETC2_RGB + blksize = 8; + fmt = gpfRGB; + break; + case 23: // ETC2_RGBA + blksize = 16; + fmt = gpfRGBA; + break; + default: + TVPThrowExceptionMessage(TJS_W("PVR Load Error/Unsupport format [%1]"), + ttstr((tjs_int)hdr.pixelFormat)); + } + // ignore mipmap + size_t size = blkw * blkh * blksize; + tjs_int pitch = sizecallback(callbackdata, hdr.width, hdr.height, fmt); + std::vector buf; buf.resize(size); + src->ReadBuffer(&buf.front(), size); + void *pixel = scanlinecallback(callbackdata, 0); + switch (hdr.pixelFormat) { + case 22: // ETC2_RGB + ETCPacker::decode(&buf.front(), pixel, pitch, hdr.height, blkw, blkh); + break; + case 23: // ETC2_RGBA + ETCPacker::decodeWithAlpha(&buf.front(), pixel, pitch, hdr.height, blkw, blkh); + break; + } + scanlinecallback(callbackdata, -1); +} + +void TVPLoadHeaderPVRv3(void* formatdata, tTJSBinaryStream *src, iTJSDispatch2** dic) { + PVRv3Header hdr; + src->ReadBuffer(&hdr, sizeof(hdr)); + if (hdr.version != 0x03525650) { + TVPThrowExceptionMessage(TJS_W("PVR Load Error/Invalid image")); + } + *dic = TJSCreateDictionaryObject(); + tTJSVariant val((tjs_int32)hdr.width); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("width"), 0, &val, (*dic)); + val = tTJSVariant((tjs_int32)hdr.height); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("height"), 0, &val, (*dic)); + tjs_int64 bpp = 0; + switch (hdr.pixelFormat) { + case 22: // ETC2_RGB + bpp = 24; + break; + case 23: // ETC2_RGBA + bpp = 32; + break; + default: + TVPThrowExceptionMessage(TJS_W("PVR Load Error/Unsupport format [%1]"), + ttstr((tjs_int)hdr.pixelFormat)); + } + if (hdr.metadataLength > 8) { + tTVPMemoryStream metadata; metadata.SetSize(hdr.metadataLength); + src->ReadBuffer(metadata.GetInternalBuffer(), hdr.metadataLength); + TVPUnserializePVRv3Metadata(&metadata, [dic](const ttstr& k, const tTJSVariant& v) { + (*dic)->PropSet(TJS_MEMBERENSURE, k.c_str(), const_cast(k).GetHint(), &v, (*dic)); + }); + } + val = tTJSVariant(bpp); + (*dic)->PropSet(TJS_MEMBERENSURE, TJS_W("bpp"), 0, &val, (*dic)); +} diff --git a/src/core/visual/RenderManager.cpp b/src/core/visual/RenderManager.cpp index 5ea994d1..01dd4708 100644 --- a/src/core/visual/RenderManager.cpp +++ b/src/core/visual/RenderManager.cpp @@ -26,6 +26,7 @@ extern "C" { #include "xxhash/xxhash.h" #include "tjsHashSearch.h" #include "EventIntf.h" +#include "lz4/lz4.h" #ifdef _MSC_VER #pragma comment(lib,"opencv_ts300d.lib") @@ -303,6 +304,8 @@ void iTVPTexture2D::RecycleProcess() } _toDeleteTextures.clear(); } +static tTVPAtExit + TVPReleaseTexture2D(TVP_ATEXIT_PRI_RELEASE + 500, iTVPTexture2D::RecycleProcess); void iTVPTexture2D::Release() { if (RefCount == 1) @@ -374,21 +377,103 @@ class tTVPSoftwareTexture2D_static : public iTVPSoftwareTexture2D { static const tjs_uint8 __empty_line[EMPTY_LINE_BYTES + 32] = {}; // at most 2048 pixels per line static const tjs_uint8* _empty_line = (const tjs_uint8*)(((intptr_t)(&__empty_line) + 15)&~15); -class tTVPSoftwareTexture2D_half : public tTVPSoftwareTexture2D_static, public tTVPContinuousEventCallbackIntf { - std::vector _scanline; - std::vector _scanlineData; - //tTVPBitmap *Bitmap = nullptr; - tjs_uint8 *PixelData = nullptr; +class tTVPSoftwareTexture2D_compress : public tTVPSoftwareTexture2D_static, public tTVPContinuousEventCallbackIntf { +protected: tjs_int PixelFrameLife = 0; + tTVPSoftwareTexture2D_compress(int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) + : tTVPSoftwareTexture2D_static(nullptr, pitch, w, h, format) {} + + ~tTVPSoftwareTexture2D_compress() { + if (BmpData) { + TVPFreeBitmapBits(BmpData); + BmpData = nullptr; + TVPRemoveContinuousEventHook(this); + } + } + static bool all_of_zero(const tjs_uint8 *p, int n) { while (--n > 0 && !p[n]); return n < 0; } + // return filled lines + virtual tjs_uint DecompressLineData(tjs_uint line, tjs_uint8 *buf) = 0; + +public: + virtual const void * GetPixelData() override { + if (!BmpData) { + BmpData = (tjs_uint8*)TVPAllocBitmapBits(Pitch * Height, Width, Height); + tjs_uint8 *dst = BmpData; + tjs_int w = Width * (GetFormat() == TVPTextureFormat::RGBA ? sizeof(tjs_uint32) : sizeof(tjs_uint8)); + tjs_uint8 *dstend = dst + Pitch * Height; + tjs_uint line = 0; + while (dst < dstend) { + tjs_uint decodedLines = DecompressLineData(line, dst); + dst += decodedLines * Pitch; + line += decodedLines; + } + } + if (PixelFrameLife == 0) TVPAddContinuousEventHook(this); + PixelFrameLife = 3; // free pixel if not used in next 3 frames + return BmpData; + } + + virtual void OnContinuousCallback(tjs_uint64 tick) override { + if (--PixelFrameLife) return; + if (BmpData) { + TVPFreeBitmapBits(BmpData); + BmpData = nullptr; + } + PixelFrameLife = 0; + TVPRemoveContinuousEventHook(this); + } + + virtual uint32_t GetPoint(int x, int y) override { + GetPixelData(); + if (Format == TVPTextureFormat::RGBA) + return *((const tjs_uint32*)(BmpData + y * Pitch) + x); // 32bpp + else if (Format == TVPTextureFormat::Gray) + return *((const tjs_uint8*)(BmpData + y * Pitch) + x); // 8bpp + return 0; + } + + virtual const void * GetScanLineForRead(tjs_uint l) override { + GetPixelData(); + return BmpData + l * Pitch; + } + + virtual void * GetScanLineForWrite(tjs_uint l) { + assert(0); + return nullptr; + } + + virtual void Update(const void *pixel, TVPTextureFormat::e format, int pitch, const tTVPRect& rc) override { + assert(0); + } + + virtual cocos2d::Texture2D* GetAdapterTexture(cocos2d::Texture2D* origTex) override { + GetPixelData(); + if (!origTex || origTex->getPixelsWide() != Width || origTex->getPixelsHigh() != Height) { + origTex = new cocos2d::Texture2D; + origTex->autorelease(); + origTex->initWithData(BmpData, Pitch * Height, + CCPixelFormat::RGBA8888, Width, Height, + cocos2d::Size::ZERO); + } else { + origTex->updateWithData(BmpData, 0, 0, Width, Height); + } + return origTex; + } +}; + +class tTVPSoftwareTexture2D_half : public tTVPSoftwareTexture2D_compress { + std::vector _scanline; + std::vector _scanlineData; + public: tTVPSoftwareTexture2D_half(tTVPBitmap *bmp, const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) - : tTVPSoftwareTexture2D_static(nullptr, pitch, w, h, format) + : tTVPSoftwareTexture2D_compress(pitch, w, h, format) { const tjs_uint8 *src = static_cast(pixel); if (format == TVPTextureFormat::RGB) w *= 3; @@ -421,14 +506,9 @@ class tTVPSoftwareTexture2D_half : public tTVPSoftwareTexture2D_static, public t if (Format == TVPTextureFormat::RGB) w *= 3; else if (Format == TVPTextureFormat::RGBA) w *= 4; _totalVMemSize -= _scanlineData.size() * w; - if (BmpData) { - TVPFreeBitmapBits(BmpData); - BmpData = nullptr; - } for (tjs_uint8* line : _scanlineData) { TVPFreeBitmapBits(line); } - if (PixelData) delete[] PixelData; } static iTVPTexture2D* Create(tTVPBitmap *bmp, const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) { @@ -447,13 +527,9 @@ class tTVPSoftwareTexture2D_half : public tTVPSoftwareTexture2D_static, public t return 0; } -// virtual tjs_int GetPitch() const { -// //assert(0); -// return 0; -// } - - virtual void Update(const void *pixel, TVPTextureFormat::e format, int pitch, const tTVPRect& rc) { - assert(0); + virtual tjs_uint DecompressLineData(tjs_uint line, tjs_uint8 *buf) override { + memcpy(buf, tTVPSoftwareTexture2D_half::GetScanLineForRead(line), Pitch); + return 1; } virtual cocos2d::Texture2D* GetAdapterTexture(cocos2d::Texture2D* origTex) override { @@ -472,42 +548,229 @@ class tTVPSoftwareTexture2D_half : public tTVPSoftwareTexture2D_static, public t return origTex; } - virtual void * GetScanLineForWrite(tjs_uint l) { - assert(0); - return nullptr; + virtual tjs_uint GetInternalHeight() const override { return _scanline.size(); } + + virtual size_t GetBitmapSize() override { return Pitch * _scanlineData.size() * (Format == TVPTextureFormat::RGBA ? 4 : 1); } +}; + +class tTVPSoftwareTexture2D_lz4 : public tTVPSoftwareTexture2D_compress { +protected: + struct Block { + char * Data; + tjs_uint Length, Height; + }; + + std::vector CompressedBlock; + tjs_uint ShiftH = 0, DataSize = 0; + +public: + tTVPSoftwareTexture2D_lz4(int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) + : tTVPSoftwareTexture2D_compress(pitch, w, h, format) { } + + ~tTVPSoftwareTexture2D_lz4() { + for (Block& blk : CompressedBlock) { + delete []blk.Data; + } + _totalVMemSize -= DataSize; } - virtual tjs_uint GetInternalHeight() const override { return _scanline.size(); } + void Init(tTVPBitmap *bmp, const void *pixel, unsigned int w, unsigned int h) { + tjs_uint BlockH = 16384 / Pitch; + if (BlockH > h) { + BlockH = h; + } else if (BlockH == 0) { + BlockH = 1; + } else { + ShiftH = 31; + tjs_uint test = 1 << 31; + for (; ShiftH > 1; ShiftH--, test >>= 1) { + if (test & BlockH) + break; + } + BlockH = test; + } + const char *src = (const char *)pixel; + tjs_uint blkSize = BlockH * Pitch; + std::vector tmp; tmp.resize(blkSize + w); + tjs_uint y = 0; + for (; y < h - BlockH + 1; y += BlockH, src += blkSize) { + tjs_uint dstSize = LZ4_compress_default(src, &tmp.front(), blkSize, blkSize + w); + Block blk = { + new char[dstSize], + dstSize, BlockH + }; + memcpy(blk.Data, &tmp.front(), dstSize); + CompressedBlock.emplace_back(blk); + DataSize += dstSize; + } + if (h > y) { + h -= y; + blkSize = h * Pitch; + tjs_uint dstSize = LZ4_compress_default(src, &tmp.front(), blkSize, blkSize + w); + Block blk = { + new char[dstSize], + dstSize, h + }; + memcpy(blk.Data, &tmp.front(), dstSize); + CompressedBlock.emplace_back(blk); + DataSize += dstSize; + } + _totalVMemSize += DataSize; + } - virtual const void * GetPixelData() { - if (!PixelData) { - PixelData = new tjs_uint8[Pitch * Height]; - tjs_uint8 *dst = PixelData; - tjs_int w = Width * (GetFormat() == TVPTextureFormat::RGBA ? sizeof(tjs_uint32) : sizeof(tjs_uint8)); - tjs_uint8 *dstend = dst + Pitch * Height; - for (const tjs_uint8* src : _scanline) { - memcpy(dst, src, w); - dst += Pitch; - if (dst >= dstend) break; - memcpy(dst, src, w); - dst += Pitch; + virtual tjs_uint DecompressLineData(tjs_uint line, tjs_uint8 *buf) override { + size_t n = line >> ShiftH; + if (n >= CompressedBlock.size()) + n = CompressedBlock.size() - 1; + Block &blk = CompressedBlock[n]; + n = LZ4_decompress_fast(blk.Data, (char*)buf, blk.Height * Pitch); + assert(n == blk.Length); + return blk.Height; + } + + static iTVPTexture2D* Create(tTVPBitmap *bmp, const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) { + tTVPSoftwareTexture2D_lz4 *tex = new tTVPSoftwareTexture2D_lz4(pitch, w, h, format); + tex->Init(bmp, pixel, w, h); + return tex; + } + + virtual size_t GetBitmapSize() override { return DataSize; } +}; + +class tTVPSoftwareTexture2D_lz4_tlg5 : public tTVPSoftwareTexture2D_lz4 { + static const tjs_uint BlockSize = 4; + +public: + tTVPSoftwareTexture2D_lz4_tlg5(int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) + : tTVPSoftwareTexture2D_lz4(pitch, w, h, format) { } + + void CompressBlock(const char *src, tjs_uint w, tjs_uint h, char* tmp, char *tranbuf) { + tjs_uint blkSize = BlockSize * Pitch; + int clrBlkSize = blkSize / 4; + char *tran[4]/*, *prev[4] = { 0 }*/, *curline[4]; + const char * prev = nullptr; + tran[0] = tranbuf; // R + tran[1] = tran[0] + clrBlkSize; // G + tran[2] = tran[1] + clrBlkSize; // B + tran[3] = tran[2] + clrBlkSize; // A + curline[0] = tran[0]; + curline[1] = tran[1]; + curline[2] = tran[2]; + curline[3] = tran[3]; + for (tjs_uint line = 0; line < h; ++line) { + char prevcl[4] = { 0 }, val[4] = { 0 }; + const char *cursrc = src; + for (tjs_uint x = 0; x < w; ++x) { + char cl[4]; + cl[0] = *src++; + cl[1] = *src++; + cl[2] = *src++; + cl[3] = *src++; + if (prev) { + cl[0] -= *prev++; + cl[1] -= *prev++; + cl[2] -= *prev++; + cl[3] -= *prev++; + } + val[0] = cl[0] - prevcl[0]; + val[1] = cl[1] - prevcl[1]; + val[2] = cl[2] - prevcl[2]; + val[3] = cl[3] - prevcl[3]; + prevcl[0] = cl[0]; + prevcl[1] = cl[1]; + prevcl[2] = cl[2]; + prevcl[3] = cl[3]; + *curline[0]++ = val[0] - val[1]; + *curline[1]++ = val[1]; + *curline[2]++ = val[2] - val[1]; + *curline[3]++ = val[3]; } + prev = cursrc; + tran[0] = curline[0]; + tran[1] = curline[1]; + tran[2] = curline[2]; + tran[3] = curline[3]; } - if (PixelFrameLife == 0) TVPAddContinuousEventHook(this); - PixelFrameLife = 3; // free pixel if not used in next 3 frames - return PixelData; + tjs_uint dstSize = LZ4_compress_default(tranbuf, tmp, blkSize, blkSize); + Block blk = { + new char[dstSize], + dstSize, h + }; + memcpy(blk.Data, tmp, dstSize); + CompressedBlock.emplace_back(blk); + DataSize += dstSize; } - virtual size_t GetBitmapSize() override { return Pitch * _scanlineData.size() * (Format == TVPTextureFormat::RGBA ? 4 : 1); } + void Init(tTVPBitmap *bmp, const void *pixel, unsigned int w, unsigned int h) { + ShiftH = 2; // block size is always 4 + bool isOpaque = bmp->IsOpaque; + tjs_uint blkSize = BlockSize * Pitch; + w = Pitch / 4; + std::vector tmp; tmp.resize(blkSize + w); + char *tranbuf = (char *)TVPAllocBitmapBits(blkSize, w, BlockSize); + const char *src = (const char *)pixel; + tjs_uint y = 0; + for (; y < h - BlockSize + 1; y += BlockSize, src += blkSize) { + CompressBlock(src, w, BlockSize, &tmp.front(), tranbuf); + } + if (h > y) { + memset(tranbuf, 0, blkSize); + CompressBlock(src, w, h - y, &tmp.front(), tranbuf); + } + TVPFreeBitmapBits(tranbuf); + _totalVMemSize += DataSize; + } - virtual void OnContinuousCallback(tjs_uint64 tick) override { - if (--PixelFrameLife) return; - if (PixelData) { - delete[] PixelData; - PixelData = nullptr; + static iTVPTexture2D* Create(tTVPBitmap *bmp, const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format) { + if (format == TVPTextureFormat::RGBA) { + tTVPSoftwareTexture2D_lz4_tlg5 *tex = new tTVPSoftwareTexture2D_lz4_tlg5(pitch, w, h, format); + tex->Init(bmp, pixel, w, h); + return tex; } - PixelFrameLife = 0; - TVPRemoveContinuousEventHook(this); + return tTVPSoftwareTexture2D_lz4::Create(bmp, pixel, pitch, w, h, format); + } + + virtual tjs_uint DecompressLineData(tjs_uint line, tjs_uint8 *buf) override { + size_t n = line >> ShiftH; + if (n >= CompressedBlock.size()) + n = CompressedBlock.size() - 1; + Block &blk = CompressedBlock[n]; + tjs_uint w = Pitch / 4; + tjs_uint blkSize = BlockSize * Pitch; + tjs_uint clrBlkSize = blkSize / 4; + tjs_uint8 *tranbuf = (tjs_uint8 *)TVPAllocBitmapBits(blkSize, w, BlockSize); + n = LZ4_decompress_fast(blk.Data, (char*)tranbuf, blkSize); + assert(n == blk.Length); + tjs_uint8 *current = buf, *prevline = buf, *outbufp[4]; + outbufp[2] = tranbuf; + outbufp[1] = outbufp[2] + clrBlkSize; + outbufp[0] = outbufp[1] + clrBlkSize; + outbufp[3] = outbufp[0] + clrBlkSize; + // first line + tjs_uint8 pr = 0, pg = 0, pb = 0, pa = 0; + for (tjs_uint x = 0; x < w; x++) { + tjs_uint8 r = *outbufp[0]++; + tjs_uint8 g = *outbufp[1]++; + tjs_uint8 b = *outbufp[2]++; + tjs_uint8 a = *outbufp[3]++; + b += g; r += g; + *current++ = pr += b; + *current++ = pg += g; + *current++ = pb += r; + *current++ = pa += a; + } + // remain lines + for (tjs_uint y = 1; y < blk.Height; ++y) { + TVPTLG5ComposeColors4To4(current, prevline, outbufp, w); + outbufp[0] += w; + outbufp[1] += w; + outbufp[2] += w; + outbufp[3] += w; + prevline = current; + current += Pitch; + } + TVPFreeBitmapBits(tranbuf); + return blk.Height; } }; @@ -2373,6 +2636,8 @@ class tTVPSoftwareRenderManager : public iTVPRenderManager { _createStaticTexture2D = tTVPSoftwareTexture2D::Create; std::string compTexMethod = IndividualConfigManager::GetInstance()->GetValue("software_compress_tex", "none"); if (compTexMethod == "halfline") _createStaticTexture2D = tTVPSoftwareTexture2D_half::Create; + else if (compTexMethod == "lz4") _createStaticTexture2D = tTVPSoftwareTexture2D_lz4::Create; + else if (compTexMethod == "lz4+tlg5") _createStaticTexture2D = tTVPSoftwareTexture2D_lz4_tlg5::Create; Register_1(); Register_2(); @@ -2429,6 +2694,10 @@ class tTVPSoftwareRenderManager : public iTVPRenderManager { return new tTVPSoftwareTexture2D(tex, neww, newh); } + virtual iTVPTexture2D* CreateTexture2D(tTJSBinaryStream* s) { + return nullptr; + } + virtual const char *GetName() { return "Software"; } virtual bool IsSoftware() { return true; } @@ -2547,6 +2816,25 @@ class tTVPSoftwareRenderManager : public iTVPRenderManager { virtual void OperateRect(iTVPRenderMethod* method, iTVPTexture2D *tar, iTVPTexture2D *reftar, const tTVPRect& rctar, const tRenderTexRectArray &textures) { +#ifdef _DEBUG + static bool check = false; + cv::Mat _src[3], _tar; + if (check) { + for (int i = 0; i < textures.size(); ++i) { + iTVPTexture2D* tex = textures[i].first; + unsigned int fmt = tex->GetFormat() == TVPTextureFormat::RGBA ? CV_8UC4 : CV_8UC1; + _src[i] = cv::Mat(tex->GetHeight(), tex->GetWidth(), fmt, (void*)tex->GetPixelData(), tex->GetPitch()); + } + unsigned int fmt = tar->GetFormat() == TVPTextureFormat::RGBA ? CV_8UC4 : CV_8UC1; + _tar = cv::Mat(tar->GetHeight(), tar->GetWidth(), fmt, (void*)tar->GetPixelData(), tar->GetPitch()); + _tar.type(); + } +#endif + + for (int i = 0; i < textures.size(); ++i) { + textures[i].first->GetScanLineForRead(0); // prepare pixel data for compressed texture + } + ++_drawCount; switch (textures.size()) { case 0: // fill tar @@ -2836,6 +3124,9 @@ class tTVPSoftwareRenderManager : public iTVPRenderManager { const tRenderTexQuadArray &textures) override { ++_drawCount; assert(textures.size() == 1); + for (int i = 0; i < textures.size(); ++i) { + textures[i].first->GetScanLineForRead(0); // prepare pixel data for compressed texture + } iTVPTexture2D *dst = target; const tTVPPointD *dstpt = pttar; iTVPTexture2D *src = textures[0].first; @@ -3950,6 +4241,9 @@ class tTVPSoftwareRenderManager : public iTVPRenderManager { iTVPTexture2D *target, iTVPTexture2D *reftar, const tTVPRect& rcclip, const tTVPPointD* pttar/*quad*/, const tRenderTexQuadArray &textures) { assert(textures.size() == 1); + for (int i = 0; i < textures.size(); ++i) { + textures[i].first->GetScanLineForRead(0); // prepare pixel data for compressed texture + } iTVPTexture2D *dst = target; const tTVPPointD *dstpt = pttar; iTVPTexture2D *src = textures[0].first; diff --git a/src/core/visual/RenderManager.h b/src/core/visual/RenderManager.h index 6b77be66..4875361a 100644 --- a/src/core/visual/RenderManager.h +++ b/src/core/visual/RenderManager.h @@ -85,6 +85,9 @@ struct TVPTextureFormat { Gray = 1, RGB = 3, RGBA = 4, + // for opengl compressed texture, the argument pitch = block_width | (block_height << 16) or block_size + Compressed = 0x10000, + CompressedEnd = 0x20000, }; }; @@ -180,6 +183,9 @@ typedef tRenderTextureArray tRenderTexRectArray; typedef tRenderTextureArray tRenderTexQuadArray; class tTVPBitmap; +namespace TJS { + class tTJSBinaryStream; +} class iTVPRenderManager { protected: @@ -194,9 +200,11 @@ class iTVPRenderManager public: #define RENDER_CREATE_TEXTURE_FLAG_ANY 0 #define RENDER_CREATE_TEXTURE_FLAG_STATIC 1 +#define RENDER_CREATE_TEXTURE_FLAG_NO_COMPRESS 2 virtual iTVPTexture2D* CreateTexture2D(const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format, int flags = RENDER_CREATE_TEXTURE_FLAG_ANY) = 0; virtual iTVPTexture2D* CreateTexture2D(tTVPBitmap* bmp) = 0; // for province image + virtual iTVPTexture2D* CreateTexture2D(TJS::tTJSBinaryStream* s) = 0; // for compressed or special image format virtual iTVPTexture2D* CreateTexture2D( // create and copy content from exist texture unsigned int neww, unsigned int newh, iTVPTexture2D* tex) = 0; diff --git a/src/core/visual/ogl/RenderManager_ogl.cpp b/src/core/visual/ogl/RenderManager_ogl.cpp index 6ced7806..6c07502a 100644 --- a/src/core/visual/ogl/RenderManager_ogl.cpp +++ b/src/core/visual/ogl/RenderManager_ogl.cpp @@ -23,8 +23,29 @@ #include #include #include "ConfigManager/LocaleConfigManager.h" +#include "etcpak.h" +#include "pvrtc.h" +#include "pvr.h" //#define TEST_SHADER_ENABLED +#ifndef GL_ETC1_RGB8_OES +#define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG 0x9138 +#endif namespace TJS { void TVPConsoleLog(const tjs_char *l); @@ -150,6 +171,21 @@ typedef void (GLAPIENTRY fAlphaFunc)(GLenum func, GLclampf ref); static fAlphaFunc *glAlphaFunc; } +static std::set TVPTextureFormats; +void TVPInitTextureFormatList() { + if (TVPTextureFormats.empty()) { + GLint nTexFormats; glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &nTexFormats); + std::vector texFormats; texFormats.resize(nTexFormats); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, &texFormats.front()); + for (GLint f : texFormats) { + TVPTextureFormats.insert(f); + } + } +} +bool TVPIsSupportTextureFormat(GLenum fmt) { + return TVPTextureFormats.end() != TVPTextureFormats.find(fmt); +} + static void TVPInitGLExtensionFunc() { #ifdef _MSC_VER GL::glGetProcAddress = wglGetProcAddress; @@ -222,6 +258,122 @@ std::string TVPGetOpenGLInfo() { ret << *name; } } + + TVPInitTextureFormatList(); + if (!TVPTextureFormats.empty()) { + ret << "\n\n"; + ret << "Support texture formats:\n"; + std::map mapFormatName({ + { 0x87EE, "ATC_RGBA_INTERPOLATED_ALPHA_AMD" }, + + { 0x87F9, "3DC_X_AMD" }, + { 0x87FA, "3DC_XY_AMD" }, + + { 0x83F0, "S3TC_DXT1_RGB" }, + { 0x83F1, "S3TC_DXT1_RGBA" }, + { 0x83F2, "S3TC_DXT3_RGBA" }, + { 0x83F3, "S3TC_DXT5_RGBA" }, + + { 0x8B90, "PALETTE4_RGB8_OES" }, + { 0x8B91, "PALETTE4_RGBA8_OES" }, + { 0x8B92, "PALETTE4_R5_G6_B5_OES" }, + { 0x8B93, "PALETTE4_RGBA4_OES" }, + { 0x8B94, "PALETTE4_RGB5_A1_OES" }, + { 0x8B95, "PALETTE8_RGB8_OES" }, + { 0x8B96, "PALETTE8_RGBA8_OES" }, + { 0x8B97, "PALETTE8_R5_G6_B5_OES" }, + { 0x8B98, "PALETTE8_RGBA4_OES" }, + { 0x8B99, "PALETTE8_RGB5_A1_OES" }, + + { 0x8C00, "PVRTC_RGB_4BPPV1" }, + { 0x8C01, "PVRTC_RGB_2BPPV1" }, + { 0x8C02, "PVRTC_RGBA_4BPPV1" }, + { 0x8C03, "PVRTC_RGBA_2BPPV1" }, + + { 0x8C92, "ATC_RGB_AMD" }, + { 0x8C93, "ATC_RGBA_EXPLICIT_ALPHA_AMD" }, + + { 0x8D64, "ETC1_RGB8" }, + + { 0x9137, "PVRTC_2BPPV2" }, + { 0x9138, "PVRTC_4BPPV2" }, + + { 0x9270, "EAC_R11" }, + { 0x9271, "EAC_SIGNED_R11" }, + { 0x9272, "EAC_RG11" }, + { 0x9273, "EAC_SIGNED_RG11" }, + { 0x9274, "ETC2_RGB8" }, + { 0x9275, "ETC2_SRGB8" }, + { 0x9276, "ETC2_RGB8_PUNCHTHROUGH_ALPHA1" }, + { 0x9277, "ETC2_SRGB8_PUNCHTHROUGH_ALPHA1" }, + { 0x9278, "ETC2_RGBA8_EAC" }, + { 0x9279, "ETC2_SRGB8_ALPHA8_EAC" }, + + { 0x93B0, "ASTC_RGBA_4x4" }, + { 0x93B1, "ASTC_RGBA_5x4" }, + { 0x93B2, "ASTC_RGBA_5x5" }, + { 0x93B3, "ASTC_RGBA_6x5" }, + { 0x93B4, "ASTC_RGBA_6x6" }, + { 0x93B5, "ASTC_RGBA_8x5" }, + { 0x93B6, "ASTC_RGBA_8x6" }, + { 0x93B7, "ASTC_RGBA_8x8" }, + { 0x93B8, "ASTC_RGBA_10x5" }, + { 0x93B9, "ASTC_RGBA_10x6" }, + { 0x93BA, "ASTC_RGBA_10x8" }, + { 0x93BB, "ASTC_RGBA_10x10" }, + { 0x93BC, "ASTC_RGBA_12x10" }, + { 0x93BD, "ASTC_RGBA_12x12" }, + + { 0x93C0, "ASTC_RGBA_3x3x3" }, + { 0x93C1, "ASTC_RGBA_4x3x3" }, + { 0x93C2, "ASTC_RGBA_4x4x3" }, + { 0x93C3, "ASTC_RGBA_4x4x4" }, + { 0x93C4, "ASTC_RGBA_5x4x4" }, + { 0x93C5, "ASTC_RGBA_5x5x4" }, + { 0x93C6, "ASTC_RGBA_5x5x5" }, + { 0x93C7, "ASTC_RGBA_6x5x5" }, + { 0x93C8, "ASTC_RGBA_6x6x5" }, + { 0x93C9, "ASTC_RGBA_6x6x6" }, + + { 0x93D0, "ASTC_SRGB8_ALPHA8_4x4" }, + { 0x93D1, "ASTC_SRGB8_ALPHA8_5x4" }, + { 0x93D2, "ASTC_SRGB8_ALPHA8_5x5" }, + { 0x93D3, "ASTC_SRGB8_ALPHA8_6x5" }, + { 0x93D4, "ASTC_SRGB8_ALPHA8_6x6" }, + { 0x93D5, "ASTC_SRGB8_ALPHA8_8x5" }, + { 0x93D6, "ASTC_SRGB8_ALPHA8_8x6" }, + { 0x93D7, "ASTC_SRGB8_ALPHA8_8x8" }, + { 0x93D8, "ASTC_SRGB8_ALPHA8_10x5" }, + { 0x93D9, "ASTC_SRGB8_ALPHA8_10x6" }, + { 0x93DA, "ASTC_SRGB8_ALPHA8_10x8" }, + { 0x93DB, "ASTC_SRGB8_ALPHA8_10x10" }, + { 0x93DC, "ASTC_SRGB8_ALPHA8_12x10" }, + { 0x93DD, "ASTC_SRGB8_ALPHA8_12x12" }, + + { 0x93E0, "ASTC_SRGB8_ALPHA8_3x3x3" }, + { 0x93E1, "ASTC_SRGB8_ALPHA8_4x3x3" }, + { 0x93E2, "ASTC_SRGB8_ALPHA8_4x4x3" }, + { 0x93E3, "ASTC_SRGB8_ALPHA8_4x4x4" }, + { 0x93E4, "ASTC_SRGB8_ALPHA8_5x4x4" }, + { 0x93E5, "ASTC_SRGB8_ALPHA8_5x5x4" }, + { 0x93E6, "ASTC_SRGB8_ALPHA8_5x5x5" }, + { 0x93E7, "ASTC_SRGB8_ALPHA8_6x5x5" }, + { 0x93E8, "ASTC_SRGB8_ALPHA8_6x6x5" }, + { 0x93E9, "ASTC_SRGB8_ALPHA8_6x6x6" }, + }); + for (GLenum f : TVPTextureFormats) { + auto it = mapFormatName.find(f); + if (mapFormatName.end() != it) { + ret << it->second; + ret << "\n"; + } else { + ret << "TexFormat["; + ret << (int)f; + ret << "]\n"; + } + } + } + return ret.str(); } @@ -271,7 +423,7 @@ static unsigned int GetMaxTextureHeight() { return TVPMaxTextureSize; } -static int power_of_two(int input, int value = 32) +static unsigned int power_of_two(unsigned int input, unsigned int value = 32) { while (value < input) { value <<= 1; @@ -563,10 +715,46 @@ static tjs_uint8 *TVPShrink_8(tjs_uint *dpitch, const tjs_uint8 *src, tjs_int sp return tmp; } +static tjs_uint8 *TVPShrinkImage(TVPTextureFormat::e fmt, tjs_uint &dpitch, const tjs_uint8 *src, tjs_int spitch, tjs_uint srcw, tjs_uint srch, tjs_uint dstw, tjs_uint dsth) { + if (srch == dsth && srcw == dstw) return nullptr; + return (fmt == TVPTextureFormat::RGBA ? TVPShrink : TVPShrink_8)(&dpitch, src, spitch, srcw, srch, dstw, dsth); +} + +static bool TVPCheckOpaqueRGBA(const tjs_uint8 *pixel, tjs_int pitch, tjs_int w, tjs_int h) { + const tjs_uint8* p = pixel; + for (int y = 0; y < h; ++y) { + const tjs_uint8* line = p; + for (int x = 0; x < w; ++x) { + if (line[3] != 0xFF) { + return false; + } + line += 4; + } + p += pitch; + } + return true; +} + +static bool TVPCheckSolidPixel(const tjs_uint8 *pixel, tjs_int pitch, tjs_int w, tjs_int h) { + const tjs_uint8* p = pixel; + tjs_uint32 clr = *(const tjs_uint32*)p; + for (int y = 0; y < h; ++y) { + const tjs_uint32* line = (const tjs_uint32*)p; + for (int x = 0; x < w; ++x) { + if (line[x] != clr) { + return false; + } + } + p += pitch; + } + return true; +} + class tTVPOGLTexture2D : public iTVPTexture2D { friend class TVPRenderManager_OpenGL; public: GLuint texture = 0; + bool IsCompressed = false; protected: TVPTextureFormat::e Format; unsigned int internalW; @@ -641,6 +829,7 @@ class tTVPOGLTexture2D : public iTVPTexture2D { TVPCheckMemory(); _glBindTexture2D(texture); glTexImage2D(GL_TEXTURE_2D, 0, internalfmt, intw, inth, 0, pixfmt, GL_UNSIGNED_BYTE, pixel); + internalW = intw; internalH = inth; _totalVMemSize += internalW * internalH * getPixelSize(); CHECK_GL_ERROR_DEBUG(); @@ -1173,6 +1362,12 @@ class tTVPOGLTexture2D_split : public tTVPOGLTexture2D { class tTVPOGLTexture2D_static : public tTVPOGLTexture2D { public: + // for manual init + tTVPOGLTexture2D_static(TVPTextureFormat::e format, unsigned int tw, unsigned int th, float sw, float sh, GLint mode = GL_LINEAR) + : tTVPOGLTexture2D(tw, th, format, mode) { + _scaleW = sw; _scaleH = sh; + } + tTVPOGLTexture2D_static(const void *pixel, int pitch, unsigned int iw, unsigned int ih, TVPTextureFormat::e format, unsigned int tw, unsigned int th, float sw, float sh, GLint mode = GL_LINEAR) : tTVPOGLTexture2D(tw, th, format, mode) @@ -1203,6 +1398,42 @@ class tTVPOGLTexture2D_static : public tTVPOGLTexture2D { //_totalVMemSize += internalW * internalH * pixsize; } + // for compressed texture format + tTVPOGLTexture2D_static(const void *data, int len, GLenum format, unsigned int tw, unsigned int th, unsigned int iw, unsigned int ih, float sw, float sh) + : tTVPOGLTexture2D(tw, th, TVPTextureFormat::RGBA, GL_LINEAR) + { + _scaleW = sw; _scaleH = sh; + InitCompressedPixel(data, len, format, iw, ih); + } + + void InitCompressedPixel(const void *data, unsigned int len, GLenum format, unsigned int width, unsigned int height) { + TVPCheckMemory(); + _glBindTexture2D(texture); + glCompressedTexImage2D(GL_TEXTURE_2D, 0, format/*GL_ETC1_RGB8_OES*/, width, height, 0, len, data); + IsCompressed = true; + internalW = width; internalH = height; + _totalVMemSize += internalW * internalH * getPixelSize()/*len*/; + CHECK_GL_ERROR_DEBUG(); + } + + void InitPixel(const void* pixel, unsigned int pitch, GLenum format, GLenum pixfmt, unsigned int intw, unsigned int inth) { + TVPCheckMemory(); + _glBindTexture2D(texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, intw, inth, 0, pixfmt, GL_UNSIGNED_BYTE, pixel); + + switch (pitch & 7) { + case 0: glPixelStorei(GL_UNPACK_ALIGNMENT, 8); break; + case 2: glPixelStorei(GL_UNPACK_ALIGNMENT, 2); break; + case 4: glPixelStorei(GL_UNPACK_ALIGNMENT, 4); break; + case 6: glPixelStorei(GL_UNPACK_ALIGNMENT, 2); break; + default: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); break; + } + + internalW = intw; internalH = inth; + _totalVMemSize += internalW * internalH * getPixelSize(); + CHECK_GL_ERROR_DEBUG(); + } + virtual void Update(const void *pixel, TVPTextureFormat::e format, int pitch, const tTVPRect& rc) { if (PixelData) { delete[] PixelData; @@ -1665,13 +1896,18 @@ class tTVPOGLRenderMethod_AlphaTest : public tTVPOGLRenderMethod_Script { virtual void SetParameterOpa(int id, int Value) { if (id == 0xA19A1E21) { - glEnable(GL_ALPHA_TEST); + // glEnable(GL_ALPHA_TEST); GL::glAlphaFunc(GL_GREATER, Value / 255.f); } else { return inherit::SetParameterOpa(id, Value); } } + virtual void Apply() override { + inherit::Apply(); + glEnable(GL_ALPHA_TEST); + } + virtual void onFinish() { glDisable(GL_ALPHA_TEST); } @@ -1910,7 +2146,8 @@ const void * tTVPOGLTexture2D::GetScanLineForRead(tjs_uint l) #endif void TVPSetPostUpdateEvent(void(*f)()); -static iTVPTexture2D * (*_CreateStaticTexture2D)(tTVPBitmap* bmp); +static iTVPTexture2D * (*_CreateStaticTexture2D)(const void *dib, tjs_uint tw, tjs_uint th, tjs_int pitch, + TVPTextureFormat::e fmt, bool isOpaque); static iTVPTexture2D * (*_CreateMutableTexture2D)(const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format); static const char *_glExtensions = nullptr; //static bool _duplicateTargetTexture = true; @@ -2041,19 +2278,7 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { sw = (float)(dstw - (tw & 1)) / tw; sh = (float)(dsth - (th & 1)) / th; if (!isOpaque) { - isOpaque = true; - const tjs_uint8* p = pixel; - for (int y = 0; y < h; ++y) { - const tjs_uint8* line = p; - for (int x = 0; x < w; ++x) { - if (line[3] != 0xFF) { - isOpaque = false; - x = w; y = h; - } - line += 4; - } - p += dpitch; - } + isOpaque = TVPCheckOpaqueRGBA(pixel, dpitch, w, h); } } if (fmt == TVPTextureFormat::RGBA && isOpaque) { @@ -2077,25 +2302,139 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { return ret; } - static iTVPTexture2D *CreateStaticTexture2D_normal(tTVPBitmap* bmp) { - tjs_uint w = bmp->GetWidth(), h = bmp->GetHeight(); + static iTVPTexture2D *CreateStaticTexture2D_normal(const void *dib, tjs_uint w, tjs_uint h, tjs_int pitch, + TVPTextureFormat::e fmt, bool isOpaque) { + int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); + int tw = (w + n - 1) / n; + n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); + int th = (h + n - 1) / n; + return CreateStaticTexture2D(dib, w, h, pitch, fmt, tw, th, isOpaque); + } + + static iTVPTexture2D *CreateStaticTexture2D_solid(const void *dib, tjs_uint w, tjs_uint h, tjs_int pitch, + TVPTextureFormat::e fmt) { + if (TVPCheckSolidPixel((const tjs_uint8*)dib, pitch, w, h)) { + tTVPOGLTexture2D_static *ret = new tTVPOGLTexture2D_static(dib, 4, 1, 1, fmt, w, h, 1.0f / w, 1.0f / h, GL_NEAREST); + return ret; + } + return nullptr; + } + + static iTVPTexture2D *CreateStaticTexture2D_half(const void *dib, tjs_uint w, tjs_uint h, tjs_int pitch, + TVPTextureFormat::e fmt, bool isOpaque) { + iTVPTexture2D *ret = CreateStaticTexture2D_solid(dib, w, h, pitch, fmt); + if (ret) return ret; + int tw = w, th = h; if (w > GetMaxTextureWidth()) { - if (w > GetMaxTextureWidth() * 2 && GL_CHECK_unpack_subimage) { - return new tTVPOGLTexture2D_split(bmp); + int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); + tw = (w + n - 1) / n; + } else if (w > 64) { + tw = (w + 1) / 2; + } + if (h > GetMaxTextureHeight()) { + int n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); + th = (h + n - 1) / n; + } else if (h > 64) { + th = (h + 1) / 2; + } + return CreateStaticTexture2D(dib, w, h, pitch, fmt, tw, th, isOpaque); + } + + static iTVPTexture2D *CreateStaticTexture2D_ETC2(const void *dib, tjs_uint w, tjs_uint h, tjs_int pitch, + TVPTextureFormat::e fmt, bool isOpaque) { + iTVPTexture2D *ret = CreateStaticTexture2D_solid(dib, w, h, pitch, fmt); + if (ret) return ret; + if (w > GetMaxTextureWidth() || h > GetMaxTextureHeight()) { + int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); + int tw = (w + n - 1) / n; + n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); + int th = (h + n - 1) / n; + return CreateStaticTexture2D(dib, w, h, pitch, fmt, tw, th, isOpaque); + } + if (w * h < 16 * 16) { + return CreateStaticTexture2D(dib, w, h, pitch, fmt, w, h, isOpaque); + } + if (!isOpaque) { + isOpaque = TVPCheckOpaqueRGBA((const tjs_uint8*)dib, pitch, w, h); + } + uint8_t *pixel = nullptr; int tw, th; + size_t pvrsize; + GLenum texfmt; + tw = w; + th = h; + if (isOpaque) { + pixel = (uint8_t *)ETCPacker::convert(dib, w, h, pitch, true, pvrsize); + texfmt = GL_COMPRESSED_RGB8_ETC2; + } else { + pixel = (uint8_t*)ETCPacker::convertWithAlpha(dib, w, h, pitch, pvrsize); + texfmt = GL_COMPRESSED_RGBA8_ETC2_EAC; + } + + ret = new tTVPOGLTexture2D_static(pixel, pvrsize, texfmt, w, h, tw, th, 1, 1); + if (pixel) delete[] pixel; + return ret; + } + + static iTVPTexture2D *CreateStaticTexture2D_PVRTC(const void *dib, tjs_uint w, tjs_uint h, tjs_int pitch, + TVPTextureFormat::e fmt, bool isOpaque) { + iTVPTexture2D *ret = CreateStaticTexture2D_solid(dib, w, h, pitch, fmt); + if (ret) return ret; + if (w > GetMaxTextureWidth() || h > GetMaxTextureHeight()) { + int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); + int tw = (w + n - 1) / n; + n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); + int th = (h + n - 1) / n; + return CreateStaticTexture2D(dib, w, h, pitch, fmt, tw, th, isOpaque); + } + if (w * h < 16 * 16) { + return CreateStaticTexture2D(dib, w, h, pitch, fmt, w, h, isOpaque); + } + + // 4bpp / 4x4 -> 64bit, ratio = 1/8 + // square only + tjs_uint totalPixel = w * h; + tjs_uint pw = power_of_two(w, 16), ph = power_of_two(h, 16); + tjs_uint texsize = std::max(pw, ph); + tjs_uint pvrPixels = texsize * texsize; + if (totalPixel / 8 >= pvrPixels) { + return CreateStaticTexture2D(dib, w, h, pitch, fmt, w, h, isOpaque); + } + tjs_uint pvrSize = pvrPixels / 2; + tjs_uint32 *inBuf = (tjs_uint32*)TJSAlignedAlloc(pvrPixels * sizeof(tjs_uint32), 4); + tjs_uint8 *outBuf = (tjs_uint8*)TJSAlignedAlloc(pvrSize, 4); + + const tjs_uint8 *src = (const tjs_uint8 *)dib; + tjs_uint8* dst = (tjs_uint8*)inBuf; + tjs_uint dpitch = pw * 4; + for (tjs_uint y = 0; y < h; ++y) { + memcpy(dst, src, w * 4); + src += pitch; + dst += dpitch; + } + if (w < pw) { + dst = (tjs_uint8*)inBuf; + dst += w * 4; + tjs_uint remain = (pw - w) * 4; + for (tjs_uint y = 0; y < h; ++y) { + memset(dst, 0xFF, remain); + dst += dpitch; } - } else if (h > GetMaxTextureHeight() * 2) { - return new tTVPOGLTexture2D_split(bmp); } - int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); - w = (w + n - 1) / n; - n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); - h = (h + n - 1) / n; + if (h < ph) { + dst = (tjs_uint8*)inBuf; + memset(dst + dpitch * h, 0xFF, dpitch * (ph - h)); + } - return CreateStaticTexture2D(bmp->GetBits(), bmp->GetWidth(), bmp->GetHeight(), bmp->GetPitch(), - bmp->Is32bit() ? TVPTextureFormat::RGBA : TVPTextureFormat::Gray, w, h, bmp->IsOpaque); + PvrTcEncoder::EncodeRgba4Bpp(inBuf, outBuf, texsize, texsize, isOpaque); + TJSAlignedDealloc(inBuf); + ret = new tTVPOGLTexture2D_static(outBuf, pvrSize, + isOpaque ? GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG : GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, + w, h, texsize, texsize, 1, 1); + TJSAlignedDealloc(outBuf); + return ret; } - static iTVPTexture2D *CreateStaticTexture2D_half(tTVPBitmap* bmp) { + static iTVPTexture2D *CreateStaticTexture2D(tTVPBitmap* bmp) { tjs_uint w = bmp->GetWidth(), h = bmp->GetHeight(); if (w > GetMaxTextureWidth()) { if (w > GetMaxTextureWidth() * 2 && GL_CHECK_unpack_subimage) { @@ -2104,20 +2443,8 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { } else if (h > GetMaxTextureHeight() * 2) { return new tTVPOGLTexture2D_split(bmp); } - if (w > GetMaxTextureWidth()) { - int n = (w + GetMaxTextureWidth() - 1) / GetMaxTextureWidth(); - w = (w + n - 1) / n; - } else if (w > 64) { - w = (w + 1) / 2; - } - if (h > GetMaxTextureHeight()) { - int n = (h + GetMaxTextureHeight() - 1) / GetMaxTextureHeight(); - h = (h + n - 1) / n; - } else if (h > 64) { - h = (h + 1) / 2; - } - return CreateStaticTexture2D(bmp->GetBits(), bmp->GetWidth(), bmp->GetHeight(), bmp->GetPitch(), - bmp->Is32bit() ? TVPTextureFormat::RGBA : TVPTextureFormat::Gray, w, h, bmp->IsOpaque); + return _CreateStaticTexture2D(bmp->GetBits(), bmp->GetWidth(), bmp->GetHeight(), bmp->GetPitch(), + bmp->Is32bit() ? TVPTextureFormat::RGBA : TVPTextureFormat::Gray, bmp->IsOpaque); } static iTVPTexture2D *CreateMutableTexture2D(const void *pixel, int pitch, unsigned int w, unsigned int h, float sw, float sh, TVPTextureFormat::e fmt) { @@ -2163,16 +2490,22 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { void InitGL() { glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_screenFrameBuffer); + TVPInitTextureFormatList(); _CreateStaticTexture2D = CreateStaticTexture2D_normal; _CreateMutableTexture2D = CreateMutableTexture2D_normal; - // test - //_CreateStaticTexture2D = CreateStaticTexture2D_half; - //_CreateMutableTexture2D = CreateMutableTexture2D_half; std::string compTexMethod = IndividualConfigManager::GetInstance()->GetValue("ogl_compress_tex", "none"); if (compTexMethod == "half") { _CreateStaticTexture2D = CreateStaticTexture2D_half; // _CreateMutableTexture2D = CreateMutableTexture2D_half; + } else if (compTexMethod == "etc2") { + if (TVPIsSupportTextureFormat(GL_COMPRESSED_RGB8_ETC2)) { + _CreateStaticTexture2D = CreateStaticTexture2D_ETC2; + } + } else if (compTexMethod == "pvrtc") { + if (TVPIsSupportTextureFormat(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG)) { + _CreateStaticTexture2D = CreateStaticTexture2D_PVRTC; + } } GLint maxTextSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextSize); @@ -2427,7 +2760,7 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { "uniform vec3 u_amp;\n" // rgb "void main() {\n"; std::string shader_AdjustGamma_a_2 = - " vec3 t = (s.rgb / (s.a + 0.00001) - 1.0) * step(s.rgb, s.aaa + 0.001) + 1.0;\n" + " vec3 t = (s.rgb / (s.a + 0.001) - 1.0) * step(s.rgb, s.aaa + 0.001) + 1.0;\n" " t = clamp(pow(t, u_gamma) * u_amp + u_floor, 0.0, 1.0);\n" " s.rgb = t * s.a + clamp(s.rgb - s.a, 0.0, 1.0);\n" " gl_FragColor = s;\n" @@ -2814,7 +3147,7 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { TEST_SHADER_BLTO(PsHardLightBlend); CompileAndRegRegularBlendMethod("PsSoftLightBlend", opacityPrefix, - " vec3 t = 0.5 / (s.rgb + 0.00001);\n" // avoid n / 0 + " vec3 t = 0.5 / (s.rgb + 0.001);\n" // avoid n / 0 " s.rgb = pow(d.rgb, (1.0 - step(0.5, s.rgb)) * ((1.0 - s.rgb) * 2.0 - t) + t);\n" " d.rgb = mix(d.rgb, s.rgb, s.a * opacity);\n" " gl_FragColor = d;\n" @@ -2822,7 +3155,7 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { TEST_SHADER_BLTO(PsSoftLightBlend); CompileAndRegRegularBlendMethod("PsColorDodgeBlend", opacityPrefix, - " s.rgb -= 0.00001;\n" // avoid n / 0 + " s.rgb -= 0.001;\n" // avoid n / 0 " s.rgb = d.rgb / max(1.0 - s.rgb, d.rgb);\n" " d.rgb = mix(d.rgb, s.rgb, s.a * opacity);\n" " gl_FragColor = d;\n" @@ -2831,14 +3164,14 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { //pass CompileAndRegRegularBlendMethod("PsColorDodge5Blend", opacityPrefix, - " s.rgb = s.rgb * s.a * opacity - 0.00001;\n" + " s.rgb = s.rgb * s.a * opacity - 0.001;\n" " d.rgb = d.rgb / max(1.0 - s.rgb, d.rgb);\n" " gl_FragColor = d;\n" "}"); TEST_SHADER_BLTO(PsColorDodge5Blend); CompileAndRegRegularBlendMethod("PsColorBurnBlend", opacityPrefix, - " s.rgb += 0.00001;\n" + " s.rgb += 0.001;\n" " s.rgb = 1.0 - min(1.0 - d.rgb, s.rgb) / s.rgb;\n" " d.rgb = mix(d.rgb, s.rgb, s.a * opacity);\n" " gl_FragColor = d;\n" @@ -3035,13 +3368,66 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { virtual iTVPTexture2D* CreateTexture2D(const void *pixel, int pitch, unsigned int w, unsigned int h, TVPTextureFormat::e format, int flags) override { - if (pixel || (flags & RENDER_CREATE_TEXTURE_FLAG_STATIC)) - return CreateStaticTexture2D(pixel, w, h, pitch, format, w, h, false); + if (format > TVPTextureFormat::Compressed) { + int block_width = pitch; + int block_height = ((unsigned int)pitch) >> 16; + if (!block_height) block_height = block_width; + int blkw = (w + block_width - 1) / block_width; + int blkh = (h + block_height - 1) / block_height; + int blksize = 0; + switch (format) { + case TVPTextureFormat::Compressed + GL_COMPRESSED_RGB8_ETC2: + blksize = 8; break; + case TVPTextureFormat::Compressed + GL_COMPRESSED_RGBA8_ETC2_EAC: + blksize = 16; break; + default: + TVPThrowExceptionMessage(TJS_W("Unsupported compressed texture format [%1]"), (tjs_int)(format - TVPTextureFormat::Compressed)); + break; + } + if (w <= GetMaxTextureWidth() && h <= GetMaxTextureHeight()) { + return new tTVPOGLTexture2D_static(pixel, blkw * blkh * blksize, format - TVPTextureFormat::Compressed, w, h, w, h, 1, 1); + } + // too large texture, decompress and resize through default route + pitch = blkw * block_width * 4; + tjs_uint32* pixeldata = (tjs_uint32*)TJSAlignedAlloc(pitch * blkh * block_height, 4); + bool opaque = false; + switch (format) { + case TVPTextureFormat::Compressed + GL_COMPRESSED_RGB8_ETC2: + ETCPacker::decode(pixel, pixeldata, pitch, h, blkw, blkh); + opaque = true; + break; + case TVPTextureFormat::Compressed + GL_COMPRESSED_RGBA8_ETC2_EAC: + ETCPacker::decodeWithAlpha(pixel, pixeldata, pitch, h, blkw, blkh); + break; + default: + TVPThrowExceptionMessage(TJS_W("Unsupported compressed texture format [%1]"), (tjs_int)(format - TVPTextureFormat::Compressed)); + break; + } + + iTVPTexture2D *ret = _CreateStaticTexture2D(pixeldata, w, h, pitch, TVPTextureFormat::RGBA, opaque); + TJSAlignedDealloc(pixeldata); + return ret; + } + if (pixel || (flags & RENDER_CREATE_TEXTURE_FLAG_STATIC)) { + if (!pixel || (flags & RENDER_CREATE_TEXTURE_FLAG_NO_COMPRESS)) { + return CreateStaticTexture2D(pixel, w, h, pitch, format, w, h, false); + } + return _CreateStaticTexture2D(pixel, w, h, pitch, format, false); + } return _CreateMutableTexture2D(pixel, pitch, w, h, format); } virtual iTVPTexture2D* CreateTexture2D(tTVPBitmap* bmp) override { - return _CreateStaticTexture2D(bmp); + tjs_uint w = bmp->GetWidth(), h = bmp->GetHeight(); + if (w > GetMaxTextureWidth()) { + if (w > GetMaxTextureWidth() * 2 && GL_CHECK_unpack_subimage) { + return new tTVPOGLTexture2D_split(bmp); + } + } else if (h > GetMaxTextureHeight() * 2) { + return new tTVPOGLTexture2D_split(bmp); + } + return _CreateStaticTexture2D(bmp->GetBits(), bmp->GetWidth(), bmp->GetHeight(), bmp->GetPitch(), + bmp->Is32bit() ? TVPTextureFormat::RGBA : TVPTextureFormat::Gray, bmp->IsOpaque); } virtual iTVPTexture2D* CreateTexture2D(unsigned int neww, unsigned int newh, iTVPTexture2D* tex) override { @@ -3050,8 +3436,90 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { return newtex; } + virtual iTVPTexture2D* CreateTexture2D(tTJSBinaryStream* src) { + // support PVRv3 only so far + PVRv3Header header; + if (src->Read(&header, sizeof(header)) != sizeof(header)) { + return nullptr; + } + + if (memcmp(&header, "PVR\3", 4)) { + return nullptr; + } + + if (header.width > GetMaxTextureWidth() || header.height > GetMaxTextureHeight()) { + return nullptr; + } + + // skip metainfo + src->SetPosition(src->GetPosition() + header.metadataLength); + + // normal texture + TVPTextureFormat::e texfmt; + GLenum fmt = 0, pixtype = 0; + tjs_uint pixsize = 0; + switch ((PVR3TexturePixelFormat)header.pixelFormat) { + case PVR3TexturePixelFormat::BGRA8888: texfmt = TVPTextureFormat::RGBA; fmt = GL_BGRA_EXT; pixsize = 4; pixtype = GL_UNSIGNED_BYTE; break; + case PVR3TexturePixelFormat::RGBA8888: texfmt = TVPTextureFormat::RGBA; fmt = GL_RGBA; pixsize = 4; pixtype = GL_UNSIGNED_BYTE; break; + case PVR3TexturePixelFormat::RGBA4444: texfmt = TVPTextureFormat::RGBA; fmt = GL_RGBA; pixsize = 2; pixtype = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PVR3TexturePixelFormat::RGBA5551: texfmt = TVPTextureFormat::RGBA; fmt = GL_RGBA; pixsize = 2; pixtype = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PVR3TexturePixelFormat::RGB565: texfmt = TVPTextureFormat::RGB; fmt = GL_RGB; pixsize = 2; pixtype = GL_UNSIGNED_SHORT_5_6_5; break; + case PVR3TexturePixelFormat::RGB888: texfmt = TVPTextureFormat::RGB; fmt = GL_RGB; pixsize = 3; pixtype = GL_UNSIGNED_BYTE; break; + case PVR3TexturePixelFormat::A8: texfmt = TVPTextureFormat::Gray; fmt = GL_ALPHA; pixsize = 1; pixtype = GL_UNSIGNED_BYTE; break; + case PVR3TexturePixelFormat::L8: texfmt = TVPTextureFormat::Gray; fmt = GL_LUMINANCE; pixsize = 1; pixtype = GL_UNSIGNED_BYTE; break; + case PVR3TexturePixelFormat::LA88: texfmt = TVPTextureFormat::RGBA; fmt = GL_LUMINANCE_ALPHA; pixsize = 2; pixtype = GL_UNSIGNED_BYTE; break; + default: break; + } + + if (fmt) { + tjs_uint pitch = header.width * pixsize; + pixsize = pitch * header.height; + std::vector buf; buf.resize(pixsize); + if (src->Read(&buf.front(), pixsize) != pixsize) return nullptr; + if (fmt == GL_BGRA_EXT && + !TVPCheckGLExtension("GL_EXT_texture_format_BGRA8888") && + !TVPCheckGLExtension("GL_EXT_bgra")) { + TVPReverseRGB((tjs_uint32*)&buf.front(), (tjs_uint32*)&buf.front(), pixsize / 4); + } + tTVPOGLTexture2D_static *ret = new tTVPOGLTexture2D_static(texfmt, header.width, header.height,1, 1); + ret->InitPixel(&buf.front(), pitch, fmt, pixtype, header.width, header.height); + return ret; + } + + // compressed texture + fmt = 0; + unsigned int eachblkw, eachblkh, blksize; + switch ((PVR3TexturePixelFormat)header.pixelFormat) { + case PVR3TexturePixelFormat::PVRTC2BPP_RGB: fmt = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; eachblkw = 8; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::PVRTC2BPP_RGBA: fmt = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; eachblkw = 8; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::PVRTC4BPP_RGB: fmt = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; eachblkw = 4; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::PVRTC4BPP_RGBA: fmt = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; eachblkw = 4; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::PVRTC2_2BPP_RGBA: fmt = GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG; eachblkw = 8; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::PVRTC2_4BPP_RGBA: fmt = GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG; eachblkw = 8; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::ETC1: fmt = GL_ETC1_RGB8_OES; eachblkw = 4; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::ETC2_RGB: fmt = GL_COMPRESSED_RGB8_ETC2; eachblkw = 4; eachblkh = 4; blksize = 8; break; + case PVR3TexturePixelFormat::ETC2_RGBA: fmt = GL_COMPRESSED_RGBA8_ETC2_EAC; eachblkw = 4; eachblkh = 4; blksize = 16; break; + case PVR3TexturePixelFormat::ETC2_RGBA1: fmt = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; eachblkw = 4; eachblkh = 4; blksize = 8; break; + // TODO ASTC + default: return nullptr; + } + + if (TVPIsSupportTextureFormat(fmt)) { + unsigned int blkw = (header.width + eachblkw - 1) / eachblkw; + unsigned int blkh = (header.height + eachblkh - 1) / eachblkh; + unsigned int size = blkw * blkh * blksize; + std::vector buf; buf.resize(size); + if (src->Read(&buf.front(), pixsize) != pixsize) return nullptr; + tTVPOGLTexture2D_static *ret = new tTVPOGLTexture2D_static(texfmt, header.width, header.height, 1, 1); + ret->InitCompressedPixel(&buf.front(), size, fmt, blkw * eachblkw, blkh * eachblkh); + return ret; + } + + return nullptr; + } + void CopyTexture(tTVPOGLTexture2D *dst, tTVPOGLTexture2D *src, const tTVPRect &rcsrc) { - if (GL::glCopyImageSubData && + if (GL::glCopyImageSubData && !src->IsCompressed && src->_scaleW == dst->_scaleW && src->_scaleH == dst->_scaleH && src->Format == dst->Format) { tTVPRect rc; @@ -3059,17 +3527,18 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { rc.right = rcsrc.right * src->_scaleW; rc.top = rcsrc.top * src->_scaleH + 0.5f; rc.bottom = rcsrc.bottom * src->_scaleH; - tTVPRect rcdst, rcsrc; + tTVPRect rcsrc; rcsrc.left = 0; rcsrc.top = 0; rcsrc.right = src->GetInternalWidth(); rcsrc.bottom = src->GetInternalHeight(); - rcdst.left = 0; - rcdst.top = 0; - rcdst.right = dst->GetInternalWidth(); - rcdst.bottom = dst->GetInternalHeight(); TVPIntersectRect(&rc, rc, rcsrc); - TVPIntersectRect(&rc, rc, rcdst); + if (dst->GetInternalWidth() < rc.get_width()) { + rc.set_width(dst->GetInternalWidth()); + } + if (dst->GetInternalHeight() < rc.get_height()) { + rc.set_height(dst->GetInternalHeight()); + } if (rc.get_width() == 0 || rc.get_height() == 0) return; GL::glCopyImageSubData( @@ -3258,7 +3727,11 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { GL::glGetTextureImage(texlist[i].tex->texture, 0, GL_BGRA, GL_UNSIGNED_BYTE, texlist[i].tex->internalH * texlist[i].tex->internalW * 4, _src[i]->ptr(0, 0)); } cv::Mat _tar(tar->internalH, tar->internalW, CV_8UC4); + cv::Mat _stencil(tar->internalH, tar->internalW, CV_8U); GL::glGetTextureImage(tar->texture, 0, GL_BGRA, GL_UNSIGNED_BYTE, tar->internalH * tar->internalW * 4, _tar.ptr(0, 0)); + if (glIsEnabled(GL_STENCIL_TEST)) { + glReadPixels(0, 0, tar->internalW, tar->internalH, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, _stencil.ptr(0, 0)); + } tar = tar; for (unsigned int i = 0; i < texlist.size(); ++i) { delete _src[i]; @@ -3297,18 +3770,31 @@ class TVPRenderManager_OpenGL : public iTVPRenderManager { if (method->tar_as_src) { tTVPOGLTexture2D *tex = tar; GLVertexInfo &texitem = texlist.back(); - // if (_duplicateTargetTexture) { - tTVPOGLTexture2D *newtex; - tTVPRect rc = rcclip; - if (reftar) newtex = (tTVPOGLTexture2D *)reftar; - else { - newtex = GetTempTexture2D(tex, rc); - rc.set_offsets(0, 0); + if (reftar) { + static_cast(reftar)->ApplyVertex(texitem, _pttar, ptcount); + } else { + double l = rcclip.right, t = rcclip.bottom, r = rcclip.left, b = rcclip.top; + for (int i = 0; i < ptcount; ++i) { + const tTVPPointD& pt = _pttar[i]; + if (pt.x < l) l = pt.x; + if (pt.x > r) r = pt.x; + if (pt.y < t) t = pt.y; + if (pt.y > b) b = pt.y; + } + tTVPRect rc(l, t, std::ceil(r), std::ceil(b)); + if (!TVPIntersectRect(&rc, rcclip, rc)) { + // nothing to draw + return; + } + l = rc.left; t = rc.top; + tTVPOGLTexture2D *newtex = GetTempTexture2D(tex, rc); + std::vector pttar; pttar.reserve(ptcount); + for (int i = 0; i < ptcount; ++i) { + pttar.emplace_back(tTVPPointD{ _pttar[i].x - l, _pttar[i].y - t }); + } + newtex->ApplyVertex(texitem, &pttar.front(), ptcount); } - newtex->ApplyVertex(texitem, _pttar, ptcount); -// } else { -// tex->ApplyVertex(texitem, _pttar, ptcount); } } std::vector pttar; diff --git a/src/core/visual/ogl/astcrt.cpp b/src/core/visual/ogl/astcrt.cpp new file mode 100644 index 00000000..3d143ef7 --- /dev/null +++ b/src/core/visual/ogl/astcrt.cpp @@ -0,0 +1,2361 @@ +#include "astcrt.h" +#include +#include +#include +#define DCHECK(x) assert(x) + +namespace ASTCRealTimeCodec { + +const size_t BLOCK_WIDTH = 4; +const size_t BLOCK_HEIGHT = 4; +const size_t BLOCK_TEXEL_COUNT = BLOCK_WIDTH * BLOCK_HEIGHT; +const size_t BLOCK_BYTES = 16; + +const size_t MAXIMUM_ENCODED_WEIGHT_BITS = 96; +const size_t MAXIMUM_ENCODED_WEIGHT_BYTES = 12; + +const size_t MAXIMUM_ENCODED_COLOR_ENDPOINT_BYTES = 12; + +const size_t MAX_ENDPOINT_VALUE_COUNT = 18; + +const int APPROX_COLOR_EPSILON = 50; + +template +T min(T a, T b) { + return a <= b ? a : b; +} +template +T max(T a, T b) { + return a >= b ? a : b; +} + +template +T clamp(T a, T b, T x) { + if (x < a) { + return a; + } + + if (x > b) { + return b; + } + + return x; +} + +inline bool approx_equal(float x, float y, float epsilon) { + return fabs(x - y) < epsilon; +} + +template +union vec3_t { +public: + vec3_t() {} + vec3_t(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {} + + struct { + T x, y, z; + }; + struct { + T r, g, b; + }; + T components[3]; +}; + +typedef vec3_t vec3f_t; +typedef vec3_t vec3i_t; + +template +vec3_t operator+(vec3_t a, vec3_t b) { + vec3_t result; + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; + return result; +} + +template +vec3_t operator-(vec3_t a, vec3_t b) { + vec3_t result; + result.x = a.x - b.x; + result.y = a.y - b.y; + result.z = a.z - b.z; + return result; +} + +template +vec3_t operator*(vec3_t a, vec3_t b) { + vec3_t result; + result.x = a.x * b.x; + result.y = a.y * b.y; + result.z = a.z * b.z; + return result; +} + +template +vec3_t operator*(vec3_t a, T b) { + vec3_t result; + result.x = a.x * b; + result.y = a.y * b; + result.z = a.z * b; + return result; +} + +template +vec3_t operator/(vec3_t a, T b) { + vec3_t result; + result.x = a.x / b; + result.y = a.y / b; + result.z = a.z / b; + return result; +} + +template +vec3_t operator/(vec3_t a, vec3_t b) { + vec3_t result; + result.x = a.x / b.x; + result.y = a.y / b.y; + result.z = a.z / b.z; + return result; +} + +template +bool operator==(vec3_t a, vec3_t b) { + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +template +bool operator!=(vec3_t a, vec3_t b) { + return a.x != b.x || a.y != b.y || a.z != b.z; +} + +template +T dot(vec3_t a, vec3_t b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +template +T quadrance(vec3_t a) { + return dot(a, a); +} + +template +T norm(vec3_t a) { + return static_cast(sqrt(quadrance(a))); +} + +template +T distance(vec3_t a, vec3_t b) { + return norm(a - b); +} + +template +T qd(vec3_t a, vec3_t b) { + return quadrance(a - b); +} + +template +vec3_t signorm(vec3_t a) { + T x = norm(a); + DCHECK(x != 0.0); + return a / x; +} + +template +vec3_t min(vec3_t a, vec3_t b) { + vec3_t result; + result.x = min(a.x, b.x); + result.y = min(a.y, b.y); + result.z = min(a.z, b.z); + return result; +} + +template +vec3_t max(vec3_t a, vec3_t b) { + vec3_t result; + result.x = max(a.x, b.x); + result.y = max(a.y, b.y); + result.z = max(a.z, b.z); + return result; +} + +template +T qd_to_line(vec3_t m, vec3_t k, T kk, vec3_t p) { + T t = dot(p - m, k) / kk; + vec3_t q = k * t + m; + return qd(p, q); +} + +union unorm8_t { + struct RgbaColorType { + uint8_t b, g, r, a; + } channels; + uint8_t components[4]; + uint32_t bits; +}; + +union unorm16_t { + struct RgbaColorType { + uint16_t b, g, r, a; + } channels; + uint16_t components[4]; + uint64_t bits; +}; + +inline bool is_greyscale(vec3i_t color) { + // integer equality is transitive + return color.r == color.g && color.g == color.b; +} + +inline int luminance(vec3i_t color) { + return (color.r + color.g + color.b) / 3; +} + +inline bool approx_equal(vec3i_t a, vec3i_t b) { + return quadrance(a - b) <= APPROX_COLOR_EPSILON; +} + +inline vec3i_t clamp_rgb(vec3i_t color) { + vec3i_t result; + result.r = clamp(0, 255, color.r); + result.g = clamp(0, 255, color.g); + result.b = clamp(0, 255, color.b); + return result; +} + +inline vec3f_t clamp_rgb(vec3f_t color) { + vec3f_t result; + result.r = clamp(0.0f, 255.0f, color.r); + result.g = clamp(0.0f, 255.0f, color.g); + result.b = clamp(0.0f, 255.0f, color.b); + return result; +} + +inline bool is_rgb(float color) { + return color >= 0.0f && color <= 255.0f; +} + +inline bool is_rgb(vec3f_t color) { + return is_rgb(color.r) && is_rgb(color.g) && is_rgb(color.b); +} + +inline vec3i_t floor(vec3f_t color) { + vec3i_t result; + result.r = static_cast(std::floor(color.r)); + result.g = static_cast(std::floor(color.g)); + result.b = static_cast(std::floor(color.b)); + return result; +} + +inline vec3i_t round(vec3f_t color) { + vec3i_t result; + result.r = static_cast(roundf(color.r)); + result.g = static_cast(roundf(color.g)); + result.b = static_cast(roundf(color.b)); + return result; +} + +inline vec3i_t to_vec3i(unorm8_t color) { + vec3i_t result; + result.r = color.channels.r; + result.g = color.channels.g; + result.b = color.channels.b; + return result; +} + +inline vec3i_t to_vec3i(vec3f_t color) { + vec3i_t result; + result.r = static_cast(color.r); + result.g = static_cast(color.g); + result.b = static_cast(color.b); + return result; +} + +inline vec3f_t to_vec3f(unorm8_t color) { + vec3f_t result; + result.r = color.channels.r; + result.g = color.channels.g; + result.b = color.channels.b; + return result; +} + +inline vec3f_t to_vec3f(vec3i_t color) { + vec3f_t result; + result.r = static_cast(color.r); + result.g = static_cast(color.g); + result.b = static_cast(color.b); + return result; +} + +inline unorm8_t to_unorm8(vec3i_t color) { + unorm8_t result; + result.channels.r = static_cast(color.r); + result.channels.g = static_cast(color.g); + result.channels.b = static_cast(color.b); + result.channels.a = 255; + return result; +} + +inline unorm16_t unorm8_to_unorm16(unorm8_t c8) { + // (x / 255) * (2^16-1) = x * 65535 / 255 = x * 257 + unorm16_t result; + result.channels.r = static_cast(c8.channels.r * 257); + result.channels.g = static_cast(c8.channels.g * 257); + result.channels.b = static_cast(c8.channels.b * 257); + result.channels.a = static_cast(c8.channels.a * 257); + return result; +} + +inline bool getbit(size_t number, size_t n) { + return (number >> n) & 1; +} + +inline uint8_t getbits(uint8_t number, uint8_t msb, uint8_t lsb) { + int count = msb - lsb + 1; + return static_cast((number >> lsb) & ((1 << count) - 1)); +} + +inline size_t getbits(size_t number, size_t msb, size_t lsb) { + size_t count = msb - lsb + 1; + return (number >> lsb) & (static_cast(1 << count) - 1); +} + +inline void orbits8_ptr(uint8_t* ptr, + size_t bitoffset, + size_t number, + size_t bitcount) { + DCHECK(bitcount <= 8); + DCHECK((number >> bitcount) == 0); + + size_t index = bitoffset / 8; + size_t shift = bitoffset % 8; + + // Depending on the offset we might have to consider two bytes when + // writing, for instance if we are writing 8 bits and the offset is 4, + // then we have to write 4 bits to the first byte (ptr[index]) and 4 bits + // to the second byte (ptr[index+1]). + // + // FIXME: Writing to the last byte when the number of bytes is a multiple of 2 + // will write past the allocated memory. + + uint8_t* p = ptr + index; + size_t mask = number << shift; + + DCHECK((p[0] & mask) == 0); + DCHECK((p[1] & (mask >> 8)) == 0); + + p[0] |= static_cast(mask & 0xFF); + p[1] |= static_cast((mask >> 8) & 0xFF); +} + +inline void orbits16_ptr(uint8_t* ptr, + size_t bitoffset, + size_t number, + size_t bitcount) { + DCHECK(bitcount > 8 && bitcount <= 16); + + size_t index = bitoffset / 8; + size_t shift = bitoffset % 8; + + uint8_t* p = ptr + index; + size_t mask = number << shift; + + p[0] |= static_cast(mask & 0xFF); + p[1] |= static_cast((mask >> 8) & 0xFF); + p[2] |= static_cast((mask >> 16) & 0xFF); + p[3] |= static_cast((mask >> 24) & 0xFF); +} + +inline uint16_t getbytes2(const uint8_t* ptr, size_t byteoffset) { + const uint8_t* p = ptr + byteoffset; + return static_cast((p[1] << 8) | p[0]); +} + +inline void setbytes2(uint8_t* ptr, size_t byteoffset, uint16_t bytes) { + ptr[byteoffset + 0] = static_cast(bytes & 0xFF); + ptr[byteoffset + 1] = static_cast((bytes >> 8) & 0xFF); +} + +inline void split_high_low(uint8_t n, size_t i, uint8_t& high, uint8_t& low) { + DCHECK(i < 8); + + uint8_t low_mask = static_cast((1 << i) - 1); + + low = n & low_mask; + high = static_cast(n >> i); +} + +class bitwriter { +public: + explicit bitwriter(uint8_t* ptr) : ptr_(ptr), bitoffset_(0) { + // assumption that all bits in ptr are zero after the offset + + // writing beyound the bounds of the allocated memory is undefined + // behaviour + } + + // Specialized function that can't write more than 8 bits. + void write8(uint8_t number, size_t bitcount) { + orbits8_ptr(ptr_, bitoffset_, number, bitcount); + + bitoffset_ += bitcount; + } + + size_t offset() const { return bitoffset_; } + +private: + uint8_t* ptr_; + size_t bitoffset_; // in bits +}; + +const uint8_t bit_reverse_table[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, + 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, + 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, + 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, + 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, + 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, + 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, + 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, + 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, + 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, + 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, + 0x3F, 0xBF, 0x7F, 0xFF }; + +/** +* Reverse a byte, total function. +*/ +inline uint8_t reverse_byte(uint8_t number) { + return bit_reverse_table[number]; +} + +/** +* Reverse a sequence of bytes. +* +* Assumes that the bits written to (using bitwise or) are zero and that they +* will not clash with bits already written to target sequence. That is it is +* possible to write to a non-zero byte as long as the bits that are actually +* written to are zero. +*/ +inline void reverse_bytes(const uint8_t* source, + size_t bytecount, + uint8_t* target) { + for (int i = 0; i < static_cast(bytecount); ++i) { + DCHECK((reverse_byte(source[i]) & target[-i]) == 0); + target[-i] = target[-i] | reverse_byte(source[i]); + } +} + +inline void copy_bytes(const uint8_t* source, + size_t bytecount, + uint8_t* target, + size_t bitoffset) { + for (size_t i = 0; i < bytecount; ++i) { + orbits8_ptr(target, bitoffset + i * 8, source[i], 8); + } +} + +struct PhysicalBlock { + uint8_t data[BLOCK_BYTES]; +}; + +inline void void_extent_to_physical(unorm16_t color, PhysicalBlock* pb) { + pb->data[0] = 0xFC; + pb->data[1] = 0xFD; + pb->data[2] = 0xFF; + pb->data[3] = 0xFF; + pb->data[4] = 0xFF; + pb->data[5] = 0xFF; + pb->data[6] = 0xFF; + pb->data[7] = 0xFF; + + setbytes2(pb->data, 8, color.channels.r); + setbytes2(pb->data, 10, color.channels.g); + setbytes2(pb->data, 12, color.channels.b); + setbytes2(pb->data, 14, color.channels.a); +} + +enum color_endpoint_mode_t { + CEM_LDR_LUMINANCE_DIRECT = 0, + CEM_LDR_LUMINANCE_BASE_OFFSET = 1, + CEM_HDR_LUMINANCE_LARGE_RANGE = 2, + CEM_HDR_LUMINANCE_SMALL_RANGE = 3, + CEM_LDR_LUMINANCE_ALPHA_DIRECT = 4, + CEM_LDR_LUMINANCE_ALPHA_BASE_OFFSET = 5, + CEM_LDR_RGB_BASE_SCALE = 6, + CEM_HDR_RGB_BASE_SCALE = 7, + CEM_LDR_RGB_DIRECT = 8, + CEM_LDR_RGB_BASE_OFFSET = 9, + CEM_LDR_RGB_BASE_SCALE_PLUS_TWO_ALPHA = 10, + CEM_HDR_RGB = 11, + CEM_LDR_RGBA_DIRECT = 12, + CEM_LDR_RGBA_BASE_OFFSET = 13, + CEM_HDR_RGB_LDR_ALPHA = 14, + CEM_HDR_RGB_HDR_ALPHA = 15, + CEM_MAX = 16 +}; + +/** +* Define normalized (starting at zero) numeric ranges that can be represented +* with 8 bits or less. +*/ +enum range_t { + RANGE_2, + RANGE_3, + RANGE_4, + RANGE_5, + RANGE_6, + RANGE_8, + RANGE_10, + RANGE_12, + RANGE_16, + RANGE_20, + RANGE_24, + RANGE_32, + RANGE_40, + RANGE_48, + RANGE_64, + RANGE_80, + RANGE_96, + RANGE_128, + RANGE_160, + RANGE_192, + RANGE_256, + RANGE_MAX +}; + +/** +* Table of maximum value for each range, minimum is always zero. +*/ +const uint8_t range_max_table[RANGE_MAX] = { + 1, 2, 3, 4, 5, 7, 9, + 11, 15, 19, 23, 31, 39, 47, + 63, 79, 95, 127, 159, 191, 255 }; + + +/** +* Table that describes the number of trits or quints along with bits required +* for storing each range. +*/ +const uint8_t bits_trits_quints_table[RANGE_MAX][3] = { + { 1, 0, 0 }, // RANGE_2 + { 0, 1, 0 }, // RANGE_3 + { 2, 0, 0 }, // RANGE_4 + { 0, 0, 1 }, // RANGE_5 + { 1, 1, 0 }, // RANGE_6 + { 3, 0, 0 }, // RANGE_8 + { 1, 0, 1 }, // RANGE_10 + { 2, 1, 0 }, // RANGE_12 + { 4, 0, 0 }, // RANGE_16 + { 2, 0, 1 }, // RANGE_20 + { 3, 1, 0 }, // RANGE_24 + { 5, 0, 0 }, // RANGE_32 + { 3, 0, 1 }, // RANGE_40 + { 4, 1, 0 }, // RANGE_48 + { 6, 0, 0 }, // RANGE_64 + { 4, 0, 1 }, // RANGE_80 + { 5, 1, 0 }, // RANGE_96 + { 7, 0, 0 }, // RANGE_128 + { 5, 0, 1 }, // RANGE_160 + { 6, 1, 0 }, // RANGE_192 + { 8, 0, 0 } // RANGE_256 +}; + +const uint8_t integer_from_trits[3][3][3][3][3] = { + { { { { 0, 1, 2 },{ 4, 5, 6 },{ 8, 9, 10 } }, + { { 16, 17, 18 },{ 20, 21, 22 },{ 24, 25, 26 } }, + { { 3, 7, 15 },{ 19, 23, 27 },{ 12, 13, 14 } } }, + { { { 32, 33, 34 },{ 36, 37, 38 },{ 40, 41, 42 } }, + { { 48, 49, 50 },{ 52, 53, 54 },{ 56, 57, 58 } }, + { { 35, 39, 47 },{ 51, 55, 59 },{ 44, 45, 46 } } }, + { { { 64, 65, 66 },{ 68, 69, 70 },{ 72, 73, 74 } }, + { { 80, 81, 82 },{ 84, 85, 86 },{ 88, 89, 90 } }, + { { 67, 71, 79 },{ 83, 87, 91 },{ 76, 77, 78 } } } }, + { { { { 128, 129, 130 },{ 132, 133, 134 },{ 136, 137, 138 } }, + { { 144, 145, 146 },{ 148, 149, 150 },{ 152, 153, 154 } }, + { { 131, 135, 143 },{ 147, 151, 155 },{ 140, 141, 142 } } }, + { { { 160, 161, 162 },{ 164, 165, 166 },{ 168, 169, 170 } }, + { { 176, 177, 178 },{ 180, 181, 182 },{ 184, 185, 186 } }, + { { 163, 167, 175 },{ 179, 183, 187 },{ 172, 173, 174 } } }, + { { { 192, 193, 194 },{ 196, 197, 198 },{ 200, 201, 202 } }, + { { 208, 209, 210 },{ 212, 213, 214 },{ 216, 217, 218 } }, + { { 195, 199, 207 },{ 211, 215, 219 },{ 204, 205, 206 } } } }, + { { { { 96, 97, 98 },{ 100, 101, 102 },{ 104, 105, 106 } }, + { { 112, 113, 114 },{ 116, 117, 118 },{ 120, 121, 122 } }, + { { 99, 103, 111 },{ 115, 119, 123 },{ 108, 109, 110 } } }, + { { { 224, 225, 226 },{ 228, 229, 230 },{ 232, 233, 234 } }, + { { 240, 241, 242 },{ 244, 245, 246 },{ 248, 249, 250 } }, + { { 227, 231, 239 },{ 243, 247, 251 },{ 236, 237, 238 } } }, + { { { 28, 29, 30 },{ 60, 61, 62 },{ 92, 93, 94 } }, + { { 156, 157, 158 },{ 188, 189, 190 },{ 220, 221, 222 } }, + { { 31, 63, 127 },{ 159, 191, 255 },{ 252, 253, 254 } } } } }; + +/** +* Encode a group of 5 numbers using trits and bits. +*/ +inline void encode_trits(size_t bits, + uint8_t b0, + uint8_t b1, + uint8_t b2, + uint8_t b3, + uint8_t b4, + bitwriter& writer) { + uint8_t t0, t1, t2, t3, t4; + uint8_t m0, m1, m2, m3, m4; + + split_high_low(b0, bits, t0, m0); + split_high_low(b1, bits, t1, m1); + split_high_low(b2, bits, t2, m2); + split_high_low(b3, bits, t3, m3); + split_high_low(b4, bits, t4, m4); + + DCHECK(t0 < 3); + DCHECK(t1 < 3); + DCHECK(t2 < 3); + DCHECK(t3 < 3); + DCHECK(t4 < 3); + + uint8_t packed = integer_from_trits[t4][t3][t2][t1][t0]; + + writer.write8(m0, bits); + writer.write8(getbits(packed, 1, 0), 2); + writer.write8(m1, bits); + writer.write8(getbits(packed, 3, 2), 2); + writer.write8(m2, bits); + writer.write8(getbits(packed, 4, 4), 1); + writer.write8(m3, bits); + writer.write8(getbits(packed, 6, 5), 2); + writer.write8(m4, bits); + writer.write8(getbits(packed, 7, 7), 1); +} + +const uint8_t integer_from_quints[5][5][5] = +{ { { 0, 1, 2, 3, 4 }, + { 8, 9, 10, 11, 12 }, + { 16, 17, 18, 19, 20 }, + { 24, 25, 26, 27, 28 }, + { 5, 13, 21, 29, 6 } }, + { { 32, 33, 34, 35, 36 }, + { 40, 41, 42, 43, 44 }, + { 48, 49, 50, 51, 52 }, + { 56, 57, 58, 59, 60 }, + { 37, 45, 53, 61, 14 } }, + { { 64, 65, 66, 67, 68 }, + { 72, 73, 74, 75, 76 }, + { 80, 81, 82, 83, 84 }, + { 88, 89, 90, 91, 92 }, + { 69, 77, 85, 93, 22 } }, + { { 96, 97, 98, 99, 100 }, + { 104, 105, 106, 107, 108 }, + { 112, 113, 114, 115, 116 }, + { 120, 121, 122, 123, 124 }, + { 101, 109, 117, 125, 30 } }, + { { 102, 103, 70, 71, 38 }, + { 110, 111, 78, 79, 46 }, + { 118, 119, 86, 87, 54 }, + { 126, 127, 94, 95, 62 }, + { 39, 47, 55, 63, 31 } } }; + +/** +* Encode a group of 3 numbers using quints and bits. +*/ +inline void encode_quints(size_t bits, + uint8_t b0, + uint8_t b1, + uint8_t b2, + bitwriter& writer) { + uint8_t q0, q1, q2; + uint8_t m0, m1, m2; + + split_high_low(b0, bits, q0, m0); + split_high_low(b1, bits, q1, m1); + split_high_low(b2, bits, q2, m2); + + DCHECK(q0 < 5); + DCHECK(q1 < 5); + DCHECK(q2 < 5); + + uint8_t packed = integer_from_quints[q2][q1][q0]; + + writer.write8(m0, bits); + writer.write8(getbits(packed, 2, 0), 3); + writer.write8(m1, bits); + writer.write8(getbits(packed, 4, 3), 2); + writer.write8(m2, bits); + writer.write8(getbits(packed, 6, 5), 2); +} + +/** +* Encode a sequence of numbers using using one trit and a custom number of +* bits per number. +*/ +inline void encode_trits(const uint8_t* numbers, + size_t count, + bitwriter& writer, + size_t bits) { + for (size_t i = 0; i < count; i += 5) { + uint8_t b0 = numbers[i + 0]; + uint8_t b1 = i + 1 >= count ? 0 : numbers[i + 1]; + uint8_t b2 = i + 2 >= count ? 0 : numbers[i + 2]; + uint8_t b3 = i + 3 >= count ? 0 : numbers[i + 3]; + uint8_t b4 = i + 4 >= count ? 0 : numbers[i + 4]; + + encode_trits(bits, b0, b1, b2, b3, b4, writer); + } +} + +/** +* Encode a sequence of numbers using one quint and the custom number of bits +* per number. +*/ +inline void encode_quints(const uint8_t* numbers, + size_t count, + bitwriter& writer, + size_t bits) { + for (size_t i = 0; i < count; i += 3) { + uint8_t b0 = numbers[i + 0]; + uint8_t b1 = i + 1 >= count ? 0 : numbers[i + 1]; + uint8_t b2 = i + 2 >= count ? 0 : numbers[i + 2]; + encode_quints(bits, b0, b1, b2, writer); + } +} + +/** +* Encode a sequence of numbers using binary representation with the selected +* bit count. +*/ +inline void encode_binary(const uint8_t* numbers, + size_t count, + bitwriter& writer, + size_t bits) { + DCHECK(count > 0); + for (size_t i = 0; i < count; ++i) { + writer.write8(numbers[i], bits); + } +} + +/** +* Encode a sequence of numbers in a specific range using the binary integer +* sequence encoding. The numbers are assumed to be in the correct range and +* the memory we are writing to is assumed to be zero-initialized. +*/ +inline void integer_sequence_encode(const uint8_t* numbers, + size_t count, + range_t range, + bitwriter writer) { +#ifndef NDEBUG + for (size_t i = 0; i < count; ++i) { + DCHECK(numbers[i] <= range_max_table[range]); + } +#endif + + size_t bits = bits_trits_quints_table[range][0]; + size_t trits = bits_trits_quints_table[range][1]; + size_t quints = bits_trits_quints_table[range][2]; + + if (trits == 1) { + encode_trits(numbers, count, writer, bits); + } else if (quints == 1) { + encode_quints(numbers, count, writer, bits); + } else { + encode_binary(numbers, count, writer, bits); + } +} + +inline void integer_sequence_encode(const uint8_t* numbers, + size_t count, + range_t range, + uint8_t* output) { + integer_sequence_encode(numbers, count, range, bitwriter(output)); +} + +/** +* Compute the number of bits required to store a number of items in a specific +* range using the binary integer sequence encoding. +*/ +inline size_t compute_ise_bitcount(size_t items, range_t range) { + size_t bits = bits_trits_quints_table[range][0]; + size_t trits = bits_trits_quints_table[range][1]; + size_t quints = bits_trits_quints_table[range][2]; + + if (trits) { + return ((8 + 5 * bits) * items + 4) / 5; + } + + if (quints) { + return ((7 + 3 * bits) * items + 2) / 3; + } + + return items * bits; +} + +inline void symbolic_to_physical( + color_endpoint_mode_t color_endpoint_mode, + range_t endpoint_quant, + range_t weight_quant, + + size_t partition_count, + size_t partition_index, + + const uint8_t endpoint_ise[MAXIMUM_ENCODED_COLOR_ENDPOINT_BYTES], + + // FIXME: +1 needed here because orbits_8ptr breaks when the offset reaches + // the last byte which always happens if the weight mode is RANGE_32. + const uint8_t weights_ise[MAXIMUM_ENCODED_WEIGHT_BYTES + 1], + + PhysicalBlock* pb) { + DCHECK(weight_quant <= RANGE_32); + DCHECK(endpoint_quant < RANGE_MAX); + DCHECK(color_endpoint_mode < CEM_MAX); + DCHECK(partition_count == 1 || partition_index < 1024); + DCHECK(partition_count >= 1 && partition_count <= 4); + DCHECK(compute_ise_bitcount(BLOCK_TEXEL_COUNT, weight_quant) < + MAXIMUM_ENCODED_WEIGHT_BITS); + + size_t n = BLOCK_WIDTH; + size_t m = BLOCK_HEIGHT; + + static const bool h_table[RANGE_32 + 1] = { + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 }; + + static const uint8_t r_table[RANGE_32 + 1] = { + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; + + bool h = h_table[weight_quant]; + size_t r = r_table[weight_quant]; + + // Use the first row of Table 11 in the ASTC specification. Beware that + // this has to be changed if another block-size is used. + size_t a = m - 2; + size_t b = n - 4; + + bool d = 0; // TODO: dual plane + + bool multi_part = partition_count > 1; + + size_t part_value = partition_count - 1; + size_t part_index = multi_part ? partition_index : 0; + + size_t cem_offset = multi_part ? 23 : 13; + size_t ced_offset = multi_part ? 29 : 17; + + size_t cem_bits = multi_part ? 6 : 4; + size_t cem = color_endpoint_mode; + cem = multi_part ? cem << 2 : cem; + + // Block mode + orbits8_ptr(pb->data, 0, getbit(r, 1), 1); + orbits8_ptr(pb->data, 1, getbit(r, 2), 1); + orbits8_ptr(pb->data, 2, 0, 1); + orbits8_ptr(pb->data, 3, 0, 1); + orbits8_ptr(pb->data, 4, getbit(r, 0), 1); + orbits8_ptr(pb->data, 5, a, 2); + orbits8_ptr(pb->data, 7, b, 2); + orbits8_ptr(pb->data, 9, h, 1); + orbits8_ptr(pb->data, 10, d, 1); + + // Partitions + orbits8_ptr(pb->data, 11, part_value, 2); + orbits16_ptr(pb->data, 13, part_index, 10); + + // CEM + orbits8_ptr(pb->data, cem_offset, cem, cem_bits); + + copy_bytes( + endpoint_ise, MAXIMUM_ENCODED_COLOR_ENDPOINT_BYTES, pb->data, ced_offset); + + reverse_bytes(weights_ise, MAXIMUM_ENCODED_WEIGHT_BYTES, pb->data + 15); +} + +const int8_t color_endpoint_range_table[2][12][16] = { + { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 19, 19 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 17, 17, 17, 17 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 16, 16, 16, 16 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 19, 19, 13, 13, 13, 13 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 16, 16, 16, 16, 11, 11, 11, 11 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 14, 14, 14, 14, 10, 10, 10, 10 }, + { 20, 20, 20, 20, 19, 19, 19, 19, 11, 11, 11, 11, 7, 7, 7, 7 } }, + { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 14, 14, 14, 14, 9, 9, 9, 9 }, + { 20, 20, 20, 20, 20, 20, 20, 20, 12, 12, 12, 12, 8, 8, 8, 8 }, + { 20, 20, 20, 20, 19, 19, 19, 19, 11, 11, 11, 11, 7, 7, 7, 7 }, + { 20, 20, 20, 20, 17, 17, 17, 17, 10, 10, 10, 10, 6, 6, 6, 6 }, + { 20, 20, 20, 20, 15, 15, 15, 15, 8, 8, 8, 8, 5, 5, 5, 5 }, + { 20, 20, 20, 20, 13, 13, 13, 13, 7, 7, 7, 7, 4, 4, 4, 4 }, + { 20, 20, 20, 20, 11, 11, 11, 11, 6, 6, 6, 6, 3, 3, 3, 3 }, + { 20, 20, 20, 20, 9, 9, 9, 9, 4, 4, 4, 4, 2, 2, 2, 2 }, + { 17, 17, 17, 17, 7, 7, 7, 7, 3, 3, 3, 3, 1, 1, 1, 1 }, + { 14, 14, 14, 14, 5, 5, 5, 5, 2, 2, 2, 2, 0, 0, 0, 0 }, + { 10, 10, 10, 10, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 } }, +}; + +// FIXME: This is copied from ARM-code +const uint8_t color_unquantize_table[21][256] = { + { 0, 255 }, + { 0, 128, 255 }, + { 0, 85, 170, 255 }, + { 0, 64, 128, 192, 255 }, + { 0, 255, 51, 204, 102, 153 }, + { 0, 36, 73, 109, 146, 182, 219, 255 }, + { 0, 255, 28, 227, 56, 199, 84, 171, 113, 142 }, + { 0, 255, 69, 186, 23, 232, 92, 163, 46, 209, 116, 139 }, + { 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }, + { 0, 255, 67, 188, 13, 242, 80, 175, 27, 228, + 94, 161, 40, 215, 107, 148, 54, 201, 121, 134 }, + { 0, 255, 33, 222, 66, 189, 99, 156, 11, 244, 44, 211, + 77, 178, 110, 145, 22, 233, 55, 200, 88, 167, 121, 134 }, + { 0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, + 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, + 181, 189, 198, 206, 214, 222, 231, 239, 247, 255 }, + { 0, 255, 32, 223, 65, 190, 97, 158, 6, 249, 39, 216, 71, 184, + 104, 151, 13, 242, 45, 210, 78, 177, 110, 145, 19, 236, 52, 203, + 84, 171, 117, 138, 26, 229, 58, 197, 91, 164, 123, 132 }, + { 0, 255, 16, 239, 32, 223, 48, 207, 65, 190, 81, 174, 97, 158, 113, 142, + 5, 250, 21, 234, 38, 217, 54, 201, 70, 185, 86, 169, 103, 152, 119, 136, + 11, 244, 27, 228, 43, 212, 59, 196, 76, 179, 92, 163, 108, 147, 124, 131 }, + { 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, + 52, 56, 60, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, + 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, + 158, 162, 166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, + 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255 }, + { 0, 255, 16, 239, 32, 223, 48, 207, 64, 191, 80, 175, 96, 159, 112, 143, + 3, 252, 19, 236, 35, 220, 51, 204, 67, 188, 83, 172, 100, 155, 116, 139, + 6, 249, 22, 233, 38, 217, 54, 201, 71, 184, 87, 168, 103, 152, 119, 136, + 9, 246, 25, 230, 42, 213, 58, 197, 74, 181, 90, 165, 106, 149, 122, 133, + 13, 242, 29, 226, 45, 210, 61, 194, 77, 178, 93, 162, 109, 146, 125, 130 }, + { 0, 255, 8, 247, 16, 239, 24, 231, 32, 223, 40, 215, 48, 207, + 56, 199, 64, 191, 72, 183, 80, 175, 88, 167, 96, 159, 104, 151, + 112, 143, 120, 135, 2, 253, 10, 245, 18, 237, 26, 229, 35, 220, + 43, 212, 51, 204, 59, 196, 67, 188, 75, 180, 83, 172, 91, 164, + 99, 156, 107, 148, 115, 140, 123, 132, 5, 250, 13, 242, 21, 234, + 29, 226, 37, 218, 45, 210, 53, 202, 61, 194, 70, 185, 78, 177, + 86, 169, 94, 161, 102, 153, 110, 145, 118, 137, 126, 129 }, + { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, + 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, + 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, + 120, 122, 124, 126, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, + 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, + 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, + 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 255 }, + { 0, 255, 8, 247, 16, 239, 24, 231, 32, 223, 40, 215, 48, 207, 56, + 199, 64, 191, 72, 183, 80, 175, 88, 167, 96, 159, 104, 151, 112, 143, + 120, 135, 1, 254, 9, 246, 17, 238, 25, 230, 33, 222, 41, 214, 49, + 206, 57, 198, 65, 190, 73, 182, 81, 174, 89, 166, 97, 158, 105, 150, + 113, 142, 121, 134, 3, 252, 11, 244, 19, 236, 27, 228, 35, 220, 43, + 212, 51, 204, 59, 196, 67, 188, 75, 180, 83, 172, 91, 164, 99, 156, + 107, 148, 115, 140, 123, 132, 4, 251, 12, 243, 20, 235, 28, 227, 36, + 219, 44, 211, 52, 203, 60, 195, 68, 187, 76, 179, 84, 171, 92, 163, + 100, 155, 108, 147, 116, 139, 124, 131, 6, 249, 14, 241, 22, 233, 30, + 225, 38, 217, 46, 209, 54, 201, 62, 193, 70, 185, 78, 177, 86, 169, + 94, 161, 102, 153, 110, 145, 118, 137, 126, 129 }, + { 0, 255, 4, 251, 8, 247, 12, 243, 16, 239, 20, 235, 24, 231, 28, + 227, 32, 223, 36, 219, 40, 215, 44, 211, 48, 207, 52, 203, 56, 199, + 60, 195, 64, 191, 68, 187, 72, 183, 76, 179, 80, 175, 84, 171, 88, + 167, 92, 163, 96, 159, 100, 155, 104, 151, 108, 147, 112, 143, 116, 139, + 120, 135, 124, 131, 1, 254, 5, 250, 9, 246, 13, 242, 17, 238, 21, + 234, 25, 230, 29, 226, 33, 222, 37, 218, 41, 214, 45, 210, 49, 206, + 53, 202, 57, 198, 61, 194, 65, 190, 69, 186, 73, 182, 77, 178, 81, + 174, 85, 170, 89, 166, 93, 162, 97, 158, 101, 154, 105, 150, 109, 146, + 113, 142, 117, 138, 121, 134, 125, 130, 2, 253, 6, 249, 10, 245, 14, + 241, 18, 237, 22, 233, 26, 229, 30, 225, 34, 221, 38, 217, 42, 213, + 46, 209, 50, 205, 54, 201, 58, 197, 62, 193, 66, 189, 70, 185, 74, + 181, 78, 177, 82, 173, 86, 169, 90, 165, 94, 161, 98, 157, 102, 153, + 106, 149, 110, 145, 114, 141, 118, 137, 122, 133, 126, 129 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 } }; + +// FIXME: This is copied from ARM-code +const uint8_t color_quantize_table[21][256] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, 15, 15 }, + { 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, + 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, + 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, + 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, + 25, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, + 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, + 30, 30, 30, 30, 31, 31, 31, 31, 31 }, + { 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 24, 24, + 24, 24, 24, 24, 32, 32, 32, 32, 32, 32, 32, 2, 2, 2, 2, 2, 2, 10, 10, + 10, 10, 10, 10, 10, 18, 18, 18, 18, 18, 18, 26, 26, 26, 26, 26, 26, 26, 34, + 34, 34, 34, 34, 34, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, 12, 12, 20, + 20, 20, 20, 20, 20, 20, 28, 28, 28, 28, 28, 28, 36, 36, 36, 36, 36, 36, 36, + 6, 6, 6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14, 22, 22, 22, 22, 22, 22, + 30, 30, 30, 30, 30, 30, 30, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, + 39, 39, 31, 31, 31, 31, 31, 31, 31, 23, 23, 23, 23, 23, 23, 15, 15, 15, 15, + 15, 15, 15, 7, 7, 7, 7, 7, 7, 37, 37, 37, 37, 37, 37, 37, 29, 29, 29, + 29, 29, 29, 21, 21, 21, 21, 21, 21, 21, 13, 13, 13, 13, 13, 13, 5, 5, 5, + 5, 5, 5, 5, 35, 35, 35, 35, 35, 35, 27, 27, 27, 27, 27, 27, 27, 19, 19, + 19, 19, 19, 19, 11, 11, 11, 11, 11, 11, 11, 3, 3, 3, 3, 3, 3, 33, 33, + 33, 33, 33, 33, 33, 25, 25, 25, 25, 25, 25, 17, 17, 17, 17, 17, 17, 17, 9, + 9, 9, 9, 9, 9, 1, 1, 1, 1 }, + { 0, 0, 0, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 2, 2, 2, 2, 2, + 18, 18, 18, 18, 18, 18, 34, 34, 34, 34, 34, 4, 4, 4, 4, 4, 4, 20, 20, + 20, 20, 20, 36, 36, 36, 36, 36, 6, 6, 6, 6, 6, 6, 22, 22, 22, 22, 22, + 38, 38, 38, 38, 38, 38, 8, 8, 8, 8, 8, 24, 24, 24, 24, 24, 24, 40, 40, + 40, 40, 40, 10, 10, 10, 10, 10, 26, 26, 26, 26, 26, 26, 42, 42, 42, 42, 42, + 12, 12, 12, 12, 12, 12, 28, 28, 28, 28, 28, 44, 44, 44, 44, 44, 14, 14, 14, + 14, 14, 14, 30, 30, 30, 30, 30, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, + 47, 31, 31, 31, 31, 31, 15, 15, 15, 15, 15, 15, 45, 45, 45, 45, 45, 29, 29, + 29, 29, 29, 13, 13, 13, 13, 13, 13, 43, 43, 43, 43, 43, 27, 27, 27, 27, 27, + 27, 11, 11, 11, 11, 11, 41, 41, 41, 41, 41, 25, 25, 25, 25, 25, 25, 9, 9, + 9, 9, 9, 39, 39, 39, 39, 39, 39, 23, 23, 23, 23, 23, 7, 7, 7, 7, 7, + 7, 37, 37, 37, 37, 37, 21, 21, 21, 21, 21, 5, 5, 5, 5, 5, 5, 35, 35, + 35, 35, 35, 19, 19, 19, 19, 19, 19, 3, 3, 3, 3, 3, 33, 33, 33, 33, 33, + 17, 17, 17, 17, 17, 17, 1, 1, 1 }, + { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, + 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, + 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, + 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, + 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, + 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, + 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, + 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, + 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, + 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, + 61, 61, 62, 62, 62, 62, 63, 63, 63 }, + { 0, 0, 16, 16, 16, 32, 32, 32, 48, 48, 48, 48, 64, 64, 64, 2, 2, 2, 18, + 18, 18, 34, 34, 34, 50, 50, 50, 50, 66, 66, 66, 4, 4, 4, 20, 20, 20, 36, + 36, 36, 36, 52, 52, 52, 68, 68, 68, 6, 6, 6, 22, 22, 22, 38, 38, 38, 38, + 54, 54, 54, 70, 70, 70, 8, 8, 8, 24, 24, 24, 24, 40, 40, 40, 56, 56, 56, + 72, 72, 72, 10, 10, 10, 26, 26, 26, 26, 42, 42, 42, 58, 58, 58, 74, 74, 74, + 12, 12, 12, 12, 28, 28, 28, 44, 44, 44, 60, 60, 60, 76, 76, 76, 14, 14, 14, + 14, 30, 30, 30, 46, 46, 46, 62, 62, 62, 78, 78, 78, 78, 79, 79, 79, 79, 63, + 63, 63, 47, 47, 47, 31, 31, 31, 15, 15, 15, 15, 77, 77, 77, 61, 61, 61, 45, + 45, 45, 29, 29, 29, 13, 13, 13, 13, 75, 75, 75, 59, 59, 59, 43, 43, 43, 27, + 27, 27, 27, 11, 11, 11, 73, 73, 73, 57, 57, 57, 41, 41, 41, 25, 25, 25, 25, + 9, 9, 9, 71, 71, 71, 55, 55, 55, 39, 39, 39, 39, 23, 23, 23, 7, 7, 7, + 69, 69, 69, 53, 53, 53, 37, 37, 37, 37, 21, 21, 21, 5, 5, 5, 67, 67, 67, + 51, 51, 51, 51, 35, 35, 35, 19, 19, 19, 3, 3, 3, 65, 65, 65, 49, 49, 49, + 49, 33, 33, 33, 17, 17, 17, 1, 1 }, + { 0, 0, 32, 32, 64, 64, 64, 2, 2, 2, 34, 34, 66, 66, 66, 4, 4, 4, 36, + 36, 68, 68, 68, 6, 6, 6, 38, 38, 70, 70, 70, 8, 8, 8, 40, 40, 40, 72, + 72, 10, 10, 10, 42, 42, 42, 74, 74, 12, 12, 12, 44, 44, 44, 76, 76, 14, 14, + 14, 46, 46, 46, 78, 78, 16, 16, 16, 48, 48, 48, 80, 80, 80, 18, 18, 50, 50, + 50, 82, 82, 82, 20, 20, 52, 52, 52, 84, 84, 84, 22, 22, 54, 54, 54, 86, 86, + 86, 24, 24, 56, 56, 56, 88, 88, 88, 26, 26, 58, 58, 58, 90, 90, 90, 28, 28, + 60, 60, 60, 92, 92, 92, 30, 30, 62, 62, 62, 94, 94, 94, 95, 95, 95, 63, 63, + 63, 31, 31, 93, 93, 93, 61, 61, 61, 29, 29, 91, 91, 91, 59, 59, 59, 27, 27, + 89, 89, 89, 57, 57, 57, 25, 25, 87, 87, 87, 55, 55, 55, 23, 23, 85, 85, 85, + 53, 53, 53, 21, 21, 83, 83, 83, 51, 51, 51, 19, 19, 81, 81, 81, 49, 49, 49, + 17, 17, 17, 79, 79, 47, 47, 47, 15, 15, 15, 77, 77, 45, 45, 45, 13, 13, 13, + 75, 75, 43, 43, 43, 11, 11, 11, 73, 73, 41, 41, 41, 9, 9, 9, 71, 71, 71, + 39, 39, 7, 7, 7, 69, 69, 69, 37, 37, 5, 5, 5, 67, 67, 67, 35, 35, 3, + 3, 3, 65, 65, 65, 33, 33, 1, 1 }, + { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, + 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, + 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, + 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, + 37, 38, 38, 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, + 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, + 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, + 60, 60, 61, 61, 62, 62, 63, 63, 64, 64, 65, 65, 66, 66, 67, + 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, + 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, 82, + 82, 83, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, + 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, + 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 103, 103, 104, 104, + 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 111, 111, 112, + 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118, 119, 119, + 120, 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, 126, 126, 127, + 127 }, + { 0, 32, 32, 64, 96, 96, 128, 128, 2, 34, 34, 66, 98, 98, 130, + 130, 4, 36, 36, 68, 100, 100, 132, 132, 6, 38, 38, 70, 102, 102, + 134, 134, 8, 40, 40, 72, 104, 104, 136, 136, 10, 42, 42, 74, 106, + 106, 138, 138, 12, 44, 44, 76, 108, 108, 140, 140, 14, 46, 46, 78, + 110, 110, 142, 142, 16, 48, 48, 80, 112, 112, 144, 144, 18, 50, 50, + 82, 114, 114, 146, 146, 20, 52, 52, 84, 116, 116, 148, 148, 22, 54, + 54, 86, 118, 118, 150, 150, 24, 56, 56, 88, 120, 120, 152, 152, 26, + 58, 58, 90, 122, 122, 154, 154, 28, 60, 60, 92, 124, 124, 156, 156, + 30, 62, 62, 94, 126, 126, 158, 158, 159, 159, 127, 127, 95, 63, 63, + 31, 157, 157, 125, 125, 93, 61, 61, 29, 155, 155, 123, 123, 91, 59, + 59, 27, 153, 153, 121, 121, 89, 57, 57, 25, 151, 151, 119, 119, 87, + 55, 55, 23, 149, 149, 117, 117, 85, 53, 53, 21, 147, 147, 115, 115, + 83, 51, 51, 19, 145, 145, 113, 113, 81, 49, 49, 17, 143, 143, 111, + 111, 79, 47, 47, 15, 141, 141, 109, 109, 77, 45, 45, 13, 139, 139, + 107, 107, 75, 43, 43, 11, 137, 137, 105, 105, 73, 41, 41, 9, 135, + 135, 103, 103, 71, 39, 39, 7, 133, 133, 101, 101, 69, 37, 37, 5, + 131, 131, 99, 99, 67, 35, 35, 3, 129, 129, 97, 97, 65, 33, 33, + 1 }, + { 0, 64, 128, 128, 2, 66, 130, 130, 4, 68, 132, 132, 6, 70, 134, + 134, 8, 72, 136, 136, 10, 74, 138, 138, 12, 76, 140, 140, 14, 78, + 142, 142, 16, 80, 144, 144, 18, 82, 146, 146, 20, 84, 148, 148, 22, + 86, 150, 150, 24, 88, 152, 152, 26, 90, 154, 154, 28, 92, 156, 156, + 30, 94, 158, 158, 32, 96, 160, 160, 34, 98, 162, 162, 36, 100, 164, + 164, 38, 102, 166, 166, 40, 104, 168, 168, 42, 106, 170, 170, 44, 108, + 172, 172, 46, 110, 174, 174, 48, 112, 176, 176, 50, 114, 178, 178, 52, + 116, 180, 180, 54, 118, 182, 182, 56, 120, 184, 184, 58, 122, 186, 186, + 60, 124, 188, 188, 62, 126, 190, 190, 191, 191, 127, 63, 189, 189, 125, + 61, 187, 187, 123, 59, 185, 185, 121, 57, 183, 183, 119, 55, 181, 181, + 117, 53, 179, 179, 115, 51, 177, 177, 113, 49, 175, 175, 111, 47, 173, + 173, 109, 45, 171, 171, 107, 43, 169, 169, 105, 41, 167, 167, 103, 39, + 165, 165, 101, 37, 163, 163, 99, 35, 161, 161, 97, 33, 159, 159, 95, + 31, 157, 157, 93, 29, 155, 155, 91, 27, 153, 153, 89, 25, 151, 151, + 87, 23, 149, 149, 85, 21, 147, 147, 83, 19, 145, 145, 81, 17, 143, + 143, 79, 15, 141, 141, 77, 13, 139, 139, 75, 11, 137, 137, 73, 9, + 135, 135, 71, 7, 133, 133, 69, 5, 131, 131, 67, 3, 129, 129, 65, + 1 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 } }; + +range_t endpoint_quantization(size_t partitions, + range_t weight_quant, + color_endpoint_mode_t endpoint_mode) { + int8_t ce_range = + color_endpoint_range_table[partitions - 1][weight_quant][endpoint_mode]; + DCHECK(ce_range >= 0 && ce_range <= RANGE_MAX); + return static_cast(ce_range); +} + +int color_channel_sum(vec3i_t color) { + return color.r + color.g + color.b; +} + +uint8_t quantize_color(range_t quant, int c) { + DCHECK(c >= 0 && c <= 255); + return color_quantize_table[quant][c]; +} + +vec3i_t quantize_color(range_t quant, vec3i_t c) { + vec3i_t result; + result.r = color_quantize_table[quant][c.r]; + result.g = color_quantize_table[quant][c.g]; + result.b = color_quantize_table[quant][c.b]; + return result; +} + +uint8_t unquantize_color(range_t quant, int c) { + DCHECK(c >= 0 && c <= 255); + return color_unquantize_table[quant][c]; +} + +vec3i_t unquantize_color(range_t quant, vec3i_t c) { + vec3i_t result; + result.r = color_unquantize_table[quant][c.r]; + result.g = color_unquantize_table[quant][c.g]; + result.b = color_unquantize_table[quant][c.b]; + return result; +} + +void encode_luminance_direct(range_t endpoint_quant, + int v0, + int v1, + uint8_t endpoint_unquantized[2], + uint8_t endpoint_quantized[2]) { + endpoint_quantized[0] = quantize_color(endpoint_quant, v0); + endpoint_quantized[1] = quantize_color(endpoint_quant, v1); + endpoint_unquantized[0] = + unquantize_color(endpoint_quant, endpoint_quantized[0]); + endpoint_unquantized[1] = + unquantize_color(endpoint_quant, endpoint_quantized[1]); +} + +void encode_rgb_direct(range_t endpoint_quant, + vec3i_t e0, + vec3i_t e1, + uint8_t endpoint_quantized[6], + vec3i_t endpoint_unquantized[2]) { + vec3i_t e0q = quantize_color(endpoint_quant, e0); + vec3i_t e1q = quantize_color(endpoint_quant, e1); + vec3i_t e0u = unquantize_color(endpoint_quant, e0q); + vec3i_t e1u = unquantize_color(endpoint_quant, e1q); + + // ASTC uses a different blue contraction encoding when the sum of values for + // the first endpoint is larger than the sum of values in the second + // endpoint. Sort the endpoints to ensure that the normal encoding is used. + if (color_channel_sum(e0u) > color_channel_sum(e1u)) { + endpoint_quantized[0] = static_cast(e1q.r); + endpoint_quantized[1] = static_cast(e0q.r); + endpoint_quantized[2] = static_cast(e1q.g); + endpoint_quantized[3] = static_cast(e0q.g); + endpoint_quantized[4] = static_cast(e1q.b); + endpoint_quantized[5] = static_cast(e0q.b); + + endpoint_unquantized[0] = e1u; + endpoint_unquantized[1] = e0u; + } else { + endpoint_quantized[0] = static_cast(e0q.r); + endpoint_quantized[1] = static_cast(e1q.r); + endpoint_quantized[2] = static_cast(e0q.g); + endpoint_quantized[3] = static_cast(e1q.g); + endpoint_quantized[4] = static_cast(e0q.b); + endpoint_quantized[5] = static_cast(e1q.b); + + endpoint_unquantized[0] = e0u; + endpoint_unquantized[1] = e1u; + } +} + +// FIXME: This is copied from ARM-code +const uint8_t weight_quantize_table[12][1025] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31 } }; + +uint8_t quantize_weight(range_t weight_quant, size_t weight) { + DCHECK(weight_quant <= RANGE_32); + DCHECK(weight <= 1024); + return weight_quantize_table[weight_quant][weight]; +} + +/** +* Project a texel to a line and quantize the result in 1 dimension. +* +* The line is defined by t=k*x + m. This function calculates and quantizes x +* by projecting n=t-m onto k, x=|n|/|k|. Since k and m is derived from the +* minimum and maximum of all texel values the result will be in the range [0, +* 1]. +* +* To quantize the result using the weight_quantize_table the value needs to +* be extended to the range [0, 1024]. +* +* @param k the derivative of the line +* @param m the minimum endpoint +* @param t the texel value +*/ +size_t project(size_t k, size_t m, size_t t) { + DCHECK(k > 0); + return size_t((t - m) * 1024) / k; +} + +/** +* Project a texel to a line and quantize the result in 3 dimensions. +*/ +size_t project(vec3i_t k, int kk, vec3i_t m, vec3i_t t) { + DCHECK(kk > 0); + + return static_cast(clamp(0, 1024, dot(t - m, k) * 1024 / kk)); +} + +void calculate_quantized_weights_luminance( + const uint8_t texels[BLOCK_TEXEL_COUNT], + range_t quant, + uint8_t l0, + uint8_t l1, + uint8_t weights[BLOCK_TEXEL_COUNT]) { + DCHECK(l0 < l1); + + size_t k = l1 - l0; + size_t m = l0; + + for (size_t i = 0; i < BLOCK_TEXEL_COUNT; ++i) { + size_t t = static_cast(texels[i]); + weights[i] = quantize_weight(quant, project(k, m, t)); + } +} + +void calculate_quantized_weights_rgb(const unorm8_t texels[BLOCK_TEXEL_COUNT], + range_t quant, + vec3i_t e0, + vec3i_t e1, + uint8_t weights[BLOCK_TEXEL_COUNT]) { + if (e0 == e1) { + for (size_t i = 0; i < BLOCK_TEXEL_COUNT; ++i) { + weights[i] = 0; // quantize_weight(quant, 0) is always 0 + } + } else { + vec3i_t k = e1 - e0; + vec3i_t m = e0; + + int kk = dot(k, k); + for (size_t i = 0; i < BLOCK_TEXEL_COUNT; ++i) { + weights[i] = + quantize_weight(quant, project(k, kk, m, to_vec3i(texels[i]))); + } + } +} + +/** +* Write void extent block bits for LDR mode and unused extent coordinates. +*/ +void encode_void_extent(vec3i_t color, PhysicalBlock* physical_block) { + void_extent_to_physical(unorm8_to_unorm16(to_unorm8(color)), physical_block); +} + +void encode_luminance(const uint8_t texels[BLOCK_TEXEL_COUNT], + PhysicalBlock* physical_block) { + size_t partition_count = 1; + size_t partition_index = 0; + + color_endpoint_mode_t color_endpoint_mode = CEM_LDR_LUMINANCE_DIRECT; + range_t weight_quant = RANGE_32; + range_t endpoint_quant = + endpoint_quantization(partition_count, weight_quant, color_endpoint_mode); + + uint8_t l0 = 255; + uint8_t l1 = 0; + for (size_t i = 0; i < BLOCK_TEXEL_COUNT; ++i) { + l0 = min(l0, texels[i]); + l1 = max(l1, texels[i]); + } + + uint8_t endpoint_unquantized[2]; + uint8_t endpoint_quantized[2]; + encode_luminance_direct( + endpoint_quant, l0, l1, endpoint_quantized, endpoint_unquantized); + + uint8_t weights_quantized[BLOCK_TEXEL_COUNT]; + calculate_quantized_weights_luminance(texels, + weight_quant, + endpoint_unquantized[0], + endpoint_unquantized[1], + weights_quantized); + + uint8_t endpoint_ise[MAXIMUM_ENCODED_COLOR_ENDPOINT_BYTES] = { 0 }; + integer_sequence_encode(endpoint_quantized, 2, RANGE_256, endpoint_ise); + + uint8_t weights_ise[MAXIMUM_ENCODED_WEIGHT_BYTES + 1] = { 0 }; + integer_sequence_encode( + weights_quantized, BLOCK_TEXEL_COUNT, RANGE_32, weights_ise); + + symbolic_to_physical(color_endpoint_mode, + endpoint_quant, + weight_quant, + partition_count, + partition_index, + endpoint_ise, + weights_ise, + physical_block); +} + +void encode_rgb_single_partition(const unorm8_t texels[BLOCK_TEXEL_COUNT], + vec3f_t e0, + vec3f_t e1, + PhysicalBlock* physical_block) { + size_t partition_index = 0; + size_t partition_count = 1; + + color_endpoint_mode_t color_endpoint_mode = CEM_LDR_RGB_DIRECT; + range_t weight_quant = RANGE_12; + range_t endpoint_quant = + endpoint_quantization(partition_count, weight_quant, color_endpoint_mode); + + vec3i_t endpoint_unquantized[2]; + uint8_t endpoint_quantized[6]; + encode_rgb_direct(endpoint_quant, + round(e0), + round(e1), + endpoint_quantized, + endpoint_unquantized); + + uint8_t weights_quantized[BLOCK_TEXEL_COUNT]; + calculate_quantized_weights_rgb(texels, + weight_quant, + endpoint_unquantized[0], + endpoint_unquantized[1], + weights_quantized); + + uint8_t endpoint_ise[MAXIMUM_ENCODED_COLOR_ENDPOINT_BYTES] = { 0 }; + integer_sequence_encode(endpoint_quantized, 6, endpoint_quant, endpoint_ise); + + uint8_t weights_ise[MAXIMUM_ENCODED_WEIGHT_BYTES + 1] = { 0 }; + integer_sequence_encode( + weights_quantized, BLOCK_TEXEL_COUNT, weight_quant, weights_ise); + + symbolic_to_physical(color_endpoint_mode, + endpoint_quant, + weight_quant, + partition_count, + partition_index, + endpoint_ise, + weights_ise, + physical_block); +} + +bool is_solid(const unorm8_t texels[BLOCK_TEXEL_COUNT], + size_t count, + unorm8_t& color) { + for (size_t i = 0; i < count; ++i) { + if (!approx_equal(to_vec3i(texels[i]), to_vec3i(texels[0]))) { + return false; + } + } + + // TODO: Calculate average color? + color = texels[0]; + return true; +} + +bool is_greyscale(const unorm8_t texels[BLOCK_TEXEL_COUNT], + size_t count, + uint8_t luminances[BLOCK_TEXEL_COUNT]) { + for (size_t i = 0; i < count; ++i) { + vec3i_t color = to_vec3i(texels[i]); + luminances[i] = static_cast(luminance(color)); + vec3i_t lum(luminances[i], luminances[i], luminances[i]); + if (!approx_equal(color, lum)) { + return false; + } + } + + return true; +} + +vec3f_t mean(const unorm8_t texels[BLOCK_TEXEL_COUNT], size_t count) { + vec3i_t sum(0, 0, 0); + for (size_t i = 0; i < count; ++i) { + sum = sum + to_vec3i(texels[i]); + } + + return to_vec3f(sum) / static_cast(count); +} + +void subtract(const unorm8_t texels[BLOCK_TEXEL_COUNT], + size_t count, + vec3f_t v, + vec3f_t output[BLOCK_TEXEL_COUNT]) { + for (size_t i = 0; i < count; ++i) { + output[i] = to_vec3f(texels[i]) - v; + } +} + +struct mat3x3f_t { +public: + mat3x3f_t() {} + + mat3x3f_t(float m00, + float m01, + float m02, + float m10, + float m11, + float m12, + float m20, + float m21, + float m22) { + m[0] = vec3f_t(m00, m01, m02); + m[1] = vec3f_t(m10, m11, m12); + m[2] = vec3f_t(m20, m21, m22); + } + + const vec3f_t& row(size_t i) const { return m[i]; } + + float& at(size_t i, size_t j) { return m[i].components[j]; } + const float& at(size_t i, size_t j) const { return m[i].components[j]; } + +private: + vec3f_t m[3]; +}; + +inline vec3f_t operator*(const mat3x3f_t& a, vec3f_t b) { + vec3f_t tmp; + tmp.x = dot(a.row(0), b); + tmp.y = dot(a.row(1), b); + tmp.z = dot(a.row(2), b); + return tmp; +} + +void eigen_vector(const mat3x3f_t& a, vec3f_t& eig) { + vec3f_t b = signorm(vec3f_t(1, 3, 2)); // FIXME: Magic number + for (size_t i = 0; i < 8; ++i) { + b = signorm(a * b); + } + + eig = b; +} + +mat3x3f_t covariance(const vec3f_t m[BLOCK_TEXEL_COUNT], size_t count) { + mat3x3f_t cov; + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 3; ++j) { + float s = 0; + for (size_t k = 0; k < count; ++k) { + s += m[k].components[i] * m[k].components[j]; + } + cov.at(i, j) = s / static_cast(count - 1); + } + } + + return cov; +} + +void principal_component_analysis(const unorm8_t texels[BLOCK_TEXEL_COUNT], + size_t count, + vec3f_t& line_k, + vec3f_t& line_m) { + // Since we are working with fixed sized blocks count we can cap count. This + // avoids dynamic allocation. + DCHECK(count <= BLOCK_TEXEL_COUNT); + + line_m = mean(texels, count); + + vec3f_t n[BLOCK_TEXEL_COUNT]; + subtract(texels, count, line_m, n); + + mat3x3f_t w = covariance(n, count); + + eigen_vector(w, line_k); +} + +inline void principal_component_analysis_block( + const unorm8_t texels[BLOCK_TEXEL_COUNT], + vec3f_t& line_k, + vec3f_t& line_m) { + principal_component_analysis(texels, BLOCK_TEXEL_COUNT, line_k, line_m); +} + +void find_min_max(const unorm8_t texels[BLOCK_TEXEL_COUNT], + size_t count, + vec3f_t line_k, + vec3f_t line_m, + vec3f_t& e0, + vec3f_t& e1) { + DCHECK(count <= BLOCK_TEXEL_COUNT); + DCHECK(approx_equal(quadrance(line_k), 1.0, 0.0001f)); + + float a, b; + { + float t = dot(to_vec3f(texels[0]) - line_m, line_k); + a = t; + b = t; + } + + for (size_t i = 1; i < count; ++i) { + float t = dot(to_vec3f(texels[i]) - line_m, line_k); + a = min(a, t); + b = max(b, t); + } + + e0 = clamp_rgb(line_k * a + line_m); + e1 = clamp_rgb(line_k * b + line_m); +} + +void find_min_max_block(const unorm8_t texels[BLOCK_TEXEL_COUNT], + vec3f_t line_k, + vec3f_t line_m, + vec3f_t& e0, + vec3f_t& e1) { + find_min_max(texels, BLOCK_TEXEL_COUNT, line_k, line_m, e0, e1); +} + +void compress_block(const unorm8_t texels[BLOCK_TEXEL_COUNT], + PhysicalBlock* physical_block) { + { + unorm8_t color; + if (is_solid(texels, BLOCK_TEXEL_COUNT, color)) { + encode_void_extent(to_vec3i(color), physical_block); + /* encode_void_extent(vec3i_t(0, 0, 0), physical_block); */ + return; + } + } + + { + uint8_t luminances[BLOCK_TEXEL_COUNT]; + if (is_greyscale(texels, BLOCK_TEXEL_COUNT, luminances)) { + encode_luminance(luminances, physical_block); + /* encode_void_extent(vec3i_t(255, 0, 0), physical_block); */ + return; + } + } + + vec3f_t k, m; + principal_component_analysis_block(texels, k, m); + vec3f_t e0, e1; + find_min_max_block(texels, k, m, e0, e1); + encode_rgb_single_partition(texels, e0, e1, physical_block); + /* encode_void_extent(vec3i_t(0, 255, 0), physical_block); */ +} + +void fetch_image_block(const unorm8_t* source, + size_t image_width, + size_t xpos, + size_t ypos, + unorm8_t texels[BLOCK_TEXEL_COUNT]) { + size_t topleft_index = ypos * image_width + xpos; + + const unorm8_t* row0 = source + topleft_index; + const unorm8_t* row1 = row0 + image_width; + const unorm8_t* row2 = row0 + 2 * image_width; + const unorm8_t* row3 = row0 + 3 * image_width; + + texels[0] = row0[0]; + texels[1] = row0[1]; + texels[2] = row0[2]; + texels[3] = row0[3]; + + texels[4] = row1[0]; + texels[5] = row1[1]; + texels[6] = row1[2]; + texels[7] = row1[3]; + + texels[8] = row2[0]; + texels[9] = row2[1]; + texels[10] = row2[2]; + texels[11] = row2[3]; + + texels[12] = row3[0]; + texels[13] = row3[1]; + texels[14] = row3[2]; + texels[15] = row3[3]; +} + +PhysicalBlock physical_block_zero = { 0 }; + +void compress_texture_4x4(const uint8_t* src, + uint8_t* dst, + int width_int, + int height_int) { + const unorm8_t* data = reinterpret_cast(src); + + size_t width = static_cast(width_int); + size_t height = static_cast(height_int); + + PhysicalBlock* dst_re = reinterpret_cast(dst); + + for (size_t ypos = 0; ypos < height; ypos += BLOCK_WIDTH) { + for (size_t xpos = 0; xpos < width; xpos += BLOCK_HEIGHT) { + unorm8_t texels[BLOCK_TEXEL_COUNT]; + fetch_image_block(data, width, xpos, ypos, texels); + + *dst_re = physical_block_zero; + compress_block(texels, dst_re); + + ++dst_re; + } + } +} + +} \ No newline at end of file diff --git a/src/core/visual/ogl/astcrt.h b/src/core/visual/ogl/astcrt.h new file mode 100644 index 00000000..1c461ed2 --- /dev/null +++ b/src/core/visual/ogl/astcrt.h @@ -0,0 +1,16 @@ +#pragma once +#include "stdint.h" + +namespace ASTCRealTimeCodec { + // from https://github.com/daoo/astcrt + + /** + * Compress an texture with the ASTC format. + * + * @param src The source data, width*height*4 bytes with BGRA ordering. + * @param dst The output, width*height bytes. + * @param width The width of the input texture. + * @param height The height of the input texture. + */ + void compress_texture_4x4(const uint8_t* src, uint8_t* dst, int width, int height); +} diff --git a/src/core/visual/ogl/etcpak.cpp b/src/core/visual/ogl/etcpak.cpp new file mode 100644 index 00000000..d2ad16e1 --- /dev/null +++ b/src/core/visual/ogl/etcpak.cpp @@ -0,0 +1,2324 @@ +#include "etcpak.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "ThreadIntf.h" +#include "tvpgl.h" + +#define _bswap(x) ((x&0xFF)<<24)|((x&0xFF00)<<8)|((x&0xFF0000)>>8)|(x>>24) +namespace std { + float _fma_(float x, float y, float z) { return fmaf(x, y, z); } +} +#define fma _fma_ +float _log2_(float n) { + return log(n) / M_LN2; +} +#define log2 _log2_ + +namespace ETCPacker { +typedef int8_t int8; +typedef uint8_t uint8; +typedef int16_t int16; +typedef uint16_t uint16; +typedef int32_t int32; +typedef uint32_t uint32; +typedef int64_t int64; +typedef uint64_t uint64; + +typedef unsigned int uint; + +// takes as input a value, returns the value clamped to the interval [0,255]. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved. +static int clamp(int val) +{ + if (val < 0) + val = 0; + if (val > 255) + val = 255; + return val; +} +static int alphaTableInitialized = 0; +static int alphaTable[256][8]; +static int alphaBase[16][4] = { + { -15,-9,-6,-3 }, + { -13,-10,-7,-3 }, + { -13,-8,-5,-2 }, + { -13,-6,-4,-2 }, + { -12,-8,-6,-3 }, + { -11,-9,-7,-3 }, + { -11,-8,-7,-4 }, + { -11,-8,-5,-3 }, + { -10,-8,-6,-2 }, + { -10,-8,-5,-2 }, + { -10,-8,-4,-2 }, + { -10,-7,-5,-2 }, + { -10,-7,-4,-3 }, + { -10,-3,-2, -1 }, + { -9,-8,-6,-4 }, + { -9,-7,-5,-3 } +}; + +// Table for fast implementationi of clamping to the interval [0,255] +static const int clamp_table[768] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; + +static void setupAlphaTable() +{ + if (alphaTableInitialized) + return; + alphaTableInitialized = 1; + + //read table used for alpha compression + int buf; + for (int i = 16; i < 32; i++) + { + for (int j = 0; j < 8; j++) + { + buf = alphaBase[i - 16][3 - j % 4]; + if (j < 4) + alphaTable[i][j] = buf; + else + alphaTable[i][j] = (-buf - 1); + } + } + + //beyond the first 16 values, the rest of the table is implicit.. so calculate that! + for (int i = 0; i < 256; i++) + { + //fill remaining slots in table with multiples of the first ones. + int mul = i / 16; + int old = 16 + i % 16; + for (int j = 0; j < 8; j++) + { + alphaTable[i][j] = alphaTable[old][j] * mul; + //note: we don't do clamping here, though we could, because we'll be clamped afterwards anyway. + } + } +} + +static uint8 getbit(uint8 input, int frompos, int topos) +{ + uint8 output = 0; + if (frompos > topos) + return ((1 << frompos)&input) >> (frompos - topos); + return ((1 << frompos)&input) << (topos - frompos); +} + +// Compresses the alpha part of a GL_COMPRESSED_RGBA8_ETC2_EAC block. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved. +void compressBlockAlphaFast(uint8 * data, uint8* returnData) +{ + int alphasum = 0; + int maxdist = -2; + bool solid = true; + for (int x = 0; x < 16; x++) { + alphasum += data[x]; + } + int alpha = (int)(((float)alphasum) / 16.0f + 0.5f); //average pixel value, used as guess for base value. + for (int x = 0; x < 16; x++) { + if (abs(alpha - data[x])>maxdist) + maxdist = abs(alpha - data[x]); //maximum distance from average + } + if (maxdist == 0) { + // fast route for solid block + memset(returnData, 0, 8); + returnData[0] = alpha; + return; + } + int approxPos = (maxdist * 255) / 160 - 4; //experimentally derived formula for calculating approximate table position given a max distance from average + if (approxPos > 255) + approxPos = 255; + int startTable = approxPos - 15; //first table to be tested + if (startTable < 0) + startTable = 0; + int endTable = clamp(approxPos + 15); //last table to be tested + + int bestsum = 1000000000; + int besttable = -3; + int bestalpha = 128; + int prevalpha = alpha; + + //main loop: determine best base alpha value and offset table to use for compression + //try some different alpha tables. + for (int table = startTable; table < endTable&&bestsum>0; table++) + { + int tablealpha = prevalpha; + int tablebestsum = 1000000000; + //test some different alpha values, trying to find the best one for the given table. + for (int alphascale = 16; alphascale > 0; alphascale /= 4) + { + int startalpha; + int endalpha; + if (alphascale == 16) + { + startalpha = clamp(tablealpha - alphascale * 4); + endalpha = clamp(tablealpha + alphascale * 4); + } else + { + startalpha = clamp(tablealpha - alphascale * 2); + endalpha = clamp(tablealpha + alphascale * 2); + } + for (alpha = startalpha; alpha <= endalpha; alpha += alphascale) + { + int sum = 0; + int val, diff, bestdiff = 10000000, index; + for (int x = 0; x < 16; x++) + { + // for (int y = 0; y < 4; y++) + { + //compute best offset here, add square difference to sum.. + val = data[x]; + bestdiff = 1000000000; + //the values are always ordered from small to large, with the first 4 being negative and the last 4 positive + //search is therefore made in the order 0-1-2-3 or 7-6-5-4, stopping when error increases compared to the previous entry tested. + if (val > alpha) + { + for (index = 7; index > 3; index--) + { + diff = clamp_table[alpha + (int)(alphaTable[table][index]) + 255] - val; + diff *= diff; + if (diff <= bestdiff) + { + bestdiff = diff; + } else + break; + } + } else + { + for (index = 0; index < 4; index++) + { + diff = clamp_table[alpha + (int)(alphaTable[table][index]) + 255] - val; + diff *= diff; + if (diff < bestdiff) + { + bestdiff = diff; + } else + break; + } + } + + //best diff here is bestdiff, add it to sum! + sum += bestdiff; + //if the sum here is worse than previously best already, there's no use in continuing the count.. + //note that tablebestsum could be used for more precise estimation, but the speedup gained here is deemed more important. + if (sum > bestsum) + { + x = 9999; //just to make it large and get out of the x<4 loop + break; + } + } + } + if (sum < tablebestsum) + { + tablebestsum = sum; + tablealpha = alpha; + } + if (sum < bestsum) + { + bestsum = sum; + besttable = table; + bestalpha = alpha; + } + } + if (alphascale <= 2) + alphascale = 0; + } + } + + alpha = bestalpha; + + //"good" alpha value and table are known! + //store them, then loop through the pixels again and print indices. + + returnData[0] = alpha; + returnData[1] = besttable; + for (int pos = 2; pos < 8; pos++) + { + returnData[pos] = 0; + } + int byte = 2; + int bit = 0; + for (int x = 0; x < 16; x++) + { + // for (int y = 0; y < 4; y++) + { + //find correct index + int besterror = 1000000; + int bestindex = 99; + for (int index = 0; index < 8; index++) //no clever ordering this time, as this loop is only run once per block anyway + { + int error = (clamp(alpha + (int)(alphaTable[besttable][index])) - data[x])*(clamp(alpha + (int)(alphaTable[besttable][index])) - data[x]); + if (error < besterror) + { + besterror = error; + bestindex = index; + } + } + //best table index has been determined. + //pack 3-bit index into compressed data, one bit at a time + for (int numbit = 0; numbit < 3; numbit++) + { + returnData[byte] |= getbit(bestindex, 2 - numbit, 7 - bit); + + bit++; + if (bit > 7) + { + bit = 0; + byte++; + } + } + } + } +} + +template +inline T AlignPOT(T val) +{ + if (val == 0) return 1; + val--; + for (unsigned int i = 1; i < sizeof(T) * 8; i <<= 1) + { + val |= val >> i; + } + return val + 1; +} + +inline int CountSetBits(uint32 val) +{ + val -= (val >> 1) & 0x55555555; + val = ((val >> 2) & 0x33333333) + (val & 0x33333333); + val = ((val >> 4) + val) & 0x0f0f0f0f; + val += val >> 8; + val += val >> 16; + return val & 0x0000003f; +} + +inline int CountLeadingZeros(uint32 val) +{ + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return 32 - CountSetBits(val); +} + +inline float sRGB2linear(float v) +{ + const float a = 0.055f; + if (v <= 0.04045f) + { + return v / 12.92f; + } else + { + return std::pow((v + a) / (1 + a), 2.4f); + } +} + +inline float linear2sRGB(float v) +{ + const float a = 0.055f; + if (v <= 0.0031308f) + { + return 12.92f * v; + } else + { + return (1 + a) * std::pow(v, 1 / 2.4f) - a; + } +} + +template +inline T SmoothStep(T x) +{ + return x*x*(3 - 2 * x); +} + +inline uint8 clampu8(int32 val) +{ + return std::min(std::max(0, val), 255); +} + +template +inline T sq(T val) +{ + return val * val; +} + +static inline int mul8bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +template +struct Vector2 +{ + Vector2() : x(0), y(0) {} + Vector2(T v) : x(v), y(v) {} + Vector2(T _x, T _y) : x(_x), y(_y) {} + + bool operator==(const Vector2& rhs) const { return x == rhs.x && y == rhs.y; } + bool operator!=(const Vector2& rhs) const { return !(*this == rhs); } + + Vector2& operator+=(const Vector2& rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + Vector2& operator-=(const Vector2& rhs) + { + x -= rhs.x; + y -= rhs.y; + return *this; + } + Vector2& operator*=(const Vector2& rhs) + { + x *= rhs.x; + y *= rhs.y; + return *this; + } + + T x, y; +}; + +template +Vector2 operator+(const Vector2& lhs, const Vector2& rhs) +{ + return Vector2(lhs.x + rhs.x, lhs.y + rhs.y); +} + +template +Vector2 operator-(const Vector2& lhs, const Vector2& rhs) +{ + return Vector2(lhs.x - rhs.x, lhs.y - rhs.y); +} + +template +Vector2 operator*(const Vector2& lhs, const float& rhs) +{ + return Vector2(lhs.x * rhs, lhs.y * rhs); +} + +template +Vector2 operator/(const Vector2& lhs, const T& rhs) +{ + return Vector2(lhs.x / rhs, lhs.y / rhs); +} + + +typedef Vector2 v2i; +typedef Vector2 v2f; + + +template +struct Vector3 +{ + Vector3() : x(0), y(0), z(0) {} + Vector3(T v) : x(v), y(v), z(v) {} + Vector3(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {} + template + Vector3(const Vector3& v) : x(T(v.x)), y(T(v.y)), z(T(v.z)) {} + + T Luminance() const { return T(x * 0.3f + y * 0.59f + z * 0.11f); } + void Clamp() + { + x = std::min(T(1), std::max(T(0), x)); + y = std::min(T(1), std::max(T(0), y)); + z = std::min(T(1), std::max(T(0), z)); + } + + bool operator==(const Vector3& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; } + bool operator!=(const Vector2& rhs) const { return !(*this == rhs); } + + T& operator[](uint idx) { assert(idx < 3); return ((T*)this)[idx]; } + const T& operator[](uint idx) const { assert(idx < 3); return ((T*)this)[idx]; } + + Vector3 operator+=(const Vector3& rhs) + { + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + + Vector3 operator*=(const Vector3& rhs) + { + x *= rhs.x; + y *= rhs.y; + z *= rhs.z; + return *this; + } + + Vector3 operator*=(const float& rhs) + { + x *= rhs; + y *= rhs; + z *= rhs; + return *this; + } + + T x, y, z; + T padding; +}; + +template +Vector3 operator+(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); +} + +template +Vector3 operator-(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); +} + +template +Vector3 operator*(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); +} + +template +Vector3 operator*(const Vector3& lhs, const float& rhs) +{ + return Vector3(T(lhs.x * rhs), T(lhs.y * rhs), T(lhs.z * rhs)); +} + +template +Vector3 operator/(const Vector3& lhs, const T& rhs) +{ + return Vector3(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); +} + +template +bool operator<(const Vector3& lhs, const Vector3& rhs) +{ + return lhs.Luminance() < rhs.Luminance(); +} + +typedef Vector3 v3i; +typedef Vector3 v3f; +typedef Vector3 v3b; + + +static inline v3b v3f_to_v3b(const v3f& v) +{ + return v3b(uint8(std::min(1.f, v.x) * 255), uint8(std::min(1.f, v.y) * 255), uint8(std::min(1.f, v.z) * 255)); +} + +template +Vector3 Mix(const Vector3& v1, const Vector3& v2, float amount) +{ + return v1 + (v2 - v1) * amount; +} + +template<> +inline v3b Mix(const v3b& v1, const v3b& v2, float amount) +{ + return v3b(v3f(v1) + (v3f(v2) - v3f(v1)) * amount); +} + +template +Vector3 Desaturate(const Vector3& v) +{ + T l = v.Luminance(); + return Vector3(l, l, l); +} + +template +Vector3 Desaturate(const Vector3& v, float mul) +{ + T l = T(v.Luminance() * mul); + return Vector3(l, l, l); +} + +template +Vector3 pow(const Vector3& base, float exponent) +{ + return Vector3( + std::pow(base.x, exponent), + std::pow(base.y, exponent), + std::pow(base.z, exponent)); +} + +template +Vector3 sRGB2linear(const Vector3& v) +{ + return Vector3( + sRGB2linear(v.x), + sRGB2linear(v.y), + sRGB2linear(v.z)); +} + +template +Vector3 linear2sRGB(const Vector3& v) +{ + return Vector3( + linear2sRGB(v.x), + linear2sRGB(v.y), + linear2sRGB(v.z)); +} + +enum class Channels +{ + RGB, + Alpha +}; + +class Bitmap +{ +public: + Bitmap(const v2i& size); + Bitmap(const v2i& size, const void *pixelData, int pitch, uint lines); + virtual ~Bitmap(); + + uint32* Data() { if (m_load.valid()) m_load.wait(); return m_data; } + const uint32* Data() const { if (m_load.valid()) m_load.wait(); return m_data; } + const v2i& Size() const { return m_size; } + bool Alpha() const { return m_alpha; } + + const uint32* NextBlock(uint& lines, bool& done); + +protected: + Bitmap(const Bitmap& src, uint lines); + + uint32* m_data; + uint32* m_block; + uint m_lines; + uint m_linesLeft; + v2i m_size; + bool m_alpha; +// Semaphore m_sema; +// std::mutex m_lock; + std::future m_load; +}; + +typedef std::shared_ptr BitmapPtr; + +struct DataPart +{ + const uint32* src; + uint width; + uint lines; + uint offset; +}; + +const int32 g_table[8][4] = { + { 2, 8, -2, -8 }, + { 5, 17, -5, -17 }, + { 9, 29, -9, -29 }, + { 13, 42, -13, -42 }, + { 18, 60, -18, -60 }, + { 24, 80, -24, -80 }, + { 33, 106, -33, -106 }, + { 47, 183, -47, -183 } +}; + +const int64 g_table256[8][4] = { + { 2*256, 8*256, -2*256, -8*256 }, + { 5*256, 17*256, -5*256, -17*256 }, + { 9*256, 29*256, -9*256, -29*256 }, + { 13*256, 42*256, -13*256, -42*256 }, + { 18*256, 60*256, -18*256, -60*256 }, + { 24*256, 80*256, -24*256, -80*256 }, + { 33*256, 106*256, -33*256, -106*256 }, + { 47*256, 183*256, -47*256, -183*256 } +}; + +const uint32 g_id[4][16] = { + { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2 }, + { 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4 }, + { 7, 7, 6, 6, 7, 7, 6, 6, 7, 7, 6, 6, 7, 7, 6, 6 } +}; + +const uint32 g_avg2[16] = { + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99, + 0xAA, + 0xBB, + 0xCC, + 0xDD, + 0xEE, + 0xFF +}; + +const uint32 g_flags[64] = { + 0x80800402, 0x80800402, 0x80800402, 0x80800402, + 0x80800402, 0x80800402, 0x80800402, 0x8080E002, + 0x80800402, 0x80800402, 0x8080E002, 0x8080E002, + 0x80800402, 0x8080E002, 0x8080E002, 0x8080E002, + 0x80000402, 0x80000402, 0x80000402, 0x80000402, + 0x80000402, 0x80000402, 0x80000402, 0x8000E002, + 0x80000402, 0x80000402, 0x8000E002, 0x8000E002, + 0x80000402, 0x8000E002, 0x8000E002, 0x8000E002, + 0x00800402, 0x00800402, 0x00800402, 0x00800402, + 0x00800402, 0x00800402, 0x00800402, 0x0080E002, + 0x00800402, 0x00800402, 0x0080E002, 0x0080E002, + 0x00800402, 0x0080E002, 0x0080E002, 0x0080E002, + 0x00000402, 0x00000402, 0x00000402, 0x00000402, + 0x00000402, 0x00000402, 0x00000402, 0x0000E002, + 0x00000402, 0x00000402, 0x0000E002, 0x0000E002, + 0x00000402, 0x0000E002, 0x0000E002, 0x0000E002 +}; + +template +static size_t GetLeastError(const T* err, size_t num) +{ + size_t idx = 0; + for (size_t i = 1; i < num; i++) + { + if (err[i] < err[idx]) + { + idx = i; + } + } + return idx; +} + +static uint64 FixByteOrder(uint64 d) +{ + return ((d & 0x00000000FFFFFFFF)) | + ((d & 0xFF00000000000000) >> 24) | + ((d & 0x000000FF00000000) << 24) | + ((d & 0x00FF000000000000) >> 8) | + ((d & 0x0000FF0000000000) << 8); +} + +template +static uint64 EncodeSelectors(uint64 d, const T terr[2][8], const S tsel[16][8], const uint32* id) +{ + size_t tidx[2]; + tidx[0] = GetLeastError(terr[0], 8); + tidx[1] = GetLeastError(terr[1], 8); + + d |= tidx[0] << 26; + d |= tidx[1] << 29; + for (int i = 0; i < 16; i++) + { + uint64 t = tsel[i][tidx[id[i] % 2]]; + d |= (t & 0x1) << (i + 32); + d |= (t & 0x2) << (i + 47); + } + + return d; +} + +namespace +{ + +typedef std::array v4i; + +void Average( const uint8* data, v4i* a ) +{ +#ifdef __SSE4_1__ + __m128i d0 = _mm_loadu_si128(((__m128i*)data) + 0); + __m128i d1 = _mm_loadu_si128(((__m128i*)data) + 1); + __m128i d2 = _mm_loadu_si128(((__m128i*)data) + 2); + __m128i d3 = _mm_loadu_si128(((__m128i*)data) + 3); + + __m128i d0l = _mm_unpacklo_epi8(d0, _mm_setzero_si128()); + __m128i d0h = _mm_unpackhi_epi8(d0, _mm_setzero_si128()); + __m128i d1l = _mm_unpacklo_epi8(d1, _mm_setzero_si128()); + __m128i d1h = _mm_unpackhi_epi8(d1, _mm_setzero_si128()); + __m128i d2l = _mm_unpacklo_epi8(d2, _mm_setzero_si128()); + __m128i d2h = _mm_unpackhi_epi8(d2, _mm_setzero_si128()); + __m128i d3l = _mm_unpacklo_epi8(d3, _mm_setzero_si128()); + __m128i d3h = _mm_unpackhi_epi8(d3, _mm_setzero_si128()); + + __m128i sum0 = _mm_add_epi16(d0l, d1l); + __m128i sum1 = _mm_add_epi16(d0h, d1h); + __m128i sum2 = _mm_add_epi16(d2l, d3l); + __m128i sum3 = _mm_add_epi16(d2h, d3h); + + __m128i sum0l = _mm_unpacklo_epi16(sum0, _mm_setzero_si128()); + __m128i sum0h = _mm_unpackhi_epi16(sum0, _mm_setzero_si128()); + __m128i sum1l = _mm_unpacklo_epi16(sum1, _mm_setzero_si128()); + __m128i sum1h = _mm_unpackhi_epi16(sum1, _mm_setzero_si128()); + __m128i sum2l = _mm_unpacklo_epi16(sum2, _mm_setzero_si128()); + __m128i sum2h = _mm_unpackhi_epi16(sum2, _mm_setzero_si128()); + __m128i sum3l = _mm_unpacklo_epi16(sum3, _mm_setzero_si128()); + __m128i sum3h = _mm_unpackhi_epi16(sum3, _mm_setzero_si128()); + + __m128i b0 = _mm_add_epi32(sum0l, sum0h); + __m128i b1 = _mm_add_epi32(sum1l, sum1h); + __m128i b2 = _mm_add_epi32(sum2l, sum2h); + __m128i b3 = _mm_add_epi32(sum3l, sum3h); + + __m128i a0 = _mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(b2, b3), _mm_set1_epi32(4)), 3); + __m128i a1 = _mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(b0, b1), _mm_set1_epi32(4)), 3); + __m128i a2 = _mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(b1, b3), _mm_set1_epi32(4)), 3); + __m128i a3 = _mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(b0, b2), _mm_set1_epi32(4)), 3); + + _mm_storeu_si128((__m128i*)&a[0], _mm_packus_epi32(_mm_shuffle_epi32(a0, _MM_SHUFFLE(3, 0, 1, 2)), _mm_shuffle_epi32(a1, _MM_SHUFFLE(3, 0, 1, 2)))); + _mm_storeu_si128((__m128i*)&a[2], _mm_packus_epi32(_mm_shuffle_epi32(a2, _MM_SHUFFLE(3, 0, 1, 2)), _mm_shuffle_epi32(a3, _MM_SHUFFLE(3, 0, 1, 2)))); +#else + uint32 r[4]; + uint32 g[4]; + uint32 b[4]; + + memset(r, 0, sizeof(r)); + memset(g, 0, sizeof(g)); + memset(b, 0, sizeof(b)); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + int index = (j & 2) + (i >> 1); + b[index] += *data++; + g[index] += *data++; + r[index] += *data++; + data++; + } + } + + a[0] = v4i{ uint16( (r[2] + r[3] + 4) / 8 ), uint16( (g[2] + g[3] + 4) / 8 ), uint16( (b[2] + b[3] + 4) / 8 ), 0}; + a[1] = v4i{ uint16( (r[0] + r[1] + 4) / 8 ), uint16( (g[0] + g[1] + 4) / 8 ), uint16( (b[0] + b[1] + 4) / 8 ), 0}; + a[2] = v4i{ uint16( (r[1] + r[3] + 4) / 8 ), uint16( (g[1] + g[3] + 4) / 8 ), uint16( (b[1] + b[3] + 4) / 8 ), 0}; + a[3] = v4i{ uint16( (r[0] + r[2] + 4) / 8 ), uint16( (g[0] + g[2] + 4) / 8 ), uint16( (b[0] + b[2] + 4) / 8 ), 0}; +#endif +} + +void CalcErrorBlock( const uint8* data, uint err[4][4] ) +{ +#ifdef __SSE4_1__ + __m128i d0 = _mm_loadu_si128(((__m128i*)data) + 0); + __m128i d1 = _mm_loadu_si128(((__m128i*)data) + 1); + __m128i d2 = _mm_loadu_si128(((__m128i*)data) + 2); + __m128i d3 = _mm_loadu_si128(((__m128i*)data) + 3); + + __m128i dm0 = _mm_and_si128(d0, _mm_set1_epi32(0x00FFFFFF)); + __m128i dm1 = _mm_and_si128(d1, _mm_set1_epi32(0x00FFFFFF)); + __m128i dm2 = _mm_and_si128(d2, _mm_set1_epi32(0x00FFFFFF)); + __m128i dm3 = _mm_and_si128(d3, _mm_set1_epi32(0x00FFFFFF)); + + __m128i d0l = _mm_unpacklo_epi8(dm0, _mm_setzero_si128()); + __m128i d0h = _mm_unpackhi_epi8(dm0, _mm_setzero_si128()); + __m128i d1l = _mm_unpacklo_epi8(dm1, _mm_setzero_si128()); + __m128i d1h = _mm_unpackhi_epi8(dm1, _mm_setzero_si128()); + __m128i d2l = _mm_unpacklo_epi8(dm2, _mm_setzero_si128()); + __m128i d2h = _mm_unpackhi_epi8(dm2, _mm_setzero_si128()); + __m128i d3l = _mm_unpacklo_epi8(dm3, _mm_setzero_si128()); + __m128i d3h = _mm_unpackhi_epi8(dm3, _mm_setzero_si128()); + + __m128i sum0 = _mm_add_epi16(d0l, d1l); + __m128i sum1 = _mm_add_epi16(d0h, d1h); + __m128i sum2 = _mm_add_epi16(d2l, d3l); + __m128i sum3 = _mm_add_epi16(d2h, d3h); + + __m128i sum0l = _mm_unpacklo_epi16(sum0, _mm_setzero_si128()); + __m128i sum0h = _mm_unpackhi_epi16(sum0, _mm_setzero_si128()); + __m128i sum1l = _mm_unpacklo_epi16(sum1, _mm_setzero_si128()); + __m128i sum1h = _mm_unpackhi_epi16(sum1, _mm_setzero_si128()); + __m128i sum2l = _mm_unpacklo_epi16(sum2, _mm_setzero_si128()); + __m128i sum2h = _mm_unpackhi_epi16(sum2, _mm_setzero_si128()); + __m128i sum3l = _mm_unpacklo_epi16(sum3, _mm_setzero_si128()); + __m128i sum3h = _mm_unpackhi_epi16(sum3, _mm_setzero_si128()); + + __m128i b0 = _mm_add_epi32(sum0l, sum0h); + __m128i b1 = _mm_add_epi32(sum1l, sum1h); + __m128i b2 = _mm_add_epi32(sum2l, sum2h); + __m128i b3 = _mm_add_epi32(sum3l, sum3h); + + __m128i a0 = _mm_add_epi32(b2, b3); + __m128i a1 = _mm_add_epi32(b0, b1); + __m128i a2 = _mm_add_epi32(b1, b3); + __m128i a3 = _mm_add_epi32(b0, b2); + + _mm_storeu_si128((__m128i*)&err[0], a0); + _mm_storeu_si128((__m128i*)&err[1], a1); + _mm_storeu_si128((__m128i*)&err[2], a2); + _mm_storeu_si128((__m128i*)&err[3], a3); +#else + uint terr[4][4]; + + memset(terr, 0, 16 * sizeof(uint)); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + int index = (j & 2) + (i >> 1); + uint d = *data++; + terr[index][0] += d; + d = *data++; + terr[index][1] += d; + d = *data++; + terr[index][2] += d; + data++; + } + } + + for( int i=0; i<3; i++ ) + { + err[0][i] = terr[2][i] + terr[3][i]; + err[1][i] = terr[0][i] + terr[1][i]; + err[2][i] = terr[1][i] + terr[3][i]; + err[3][i] = terr[0][i] + terr[2][i]; + } + for( int i=0; i<4; i++ ) + { + err[i][3] = 0; + } +#endif +} + +uint CalcError( const uint block[4], const v4i& average ) +{ + uint err = 0x3FFFFFFF; // Big value to prevent negative values, but small enough to prevent overflow + err -= block[0] * 2 * average[2]; + err -= block[1] * 2 * average[1]; + err -= block[2] * 2 * average[0]; + err += 8 * ( sq( average[0] ) + sq( average[1] ) + sq( average[2] ) ); + return err; +} + +void ProcessAverages( v4i* a ) +{ +#ifdef __SSE4_1__ + for( int i=0; i<2; i++ ) + { + __m128i d = _mm_loadu_si128((__m128i*)a[i*2].data()); + + __m128i t = _mm_add_epi16(_mm_mullo_epi16(d, _mm_set1_epi16(31)), _mm_set1_epi16(128)); + + __m128i c = _mm_srli_epi16(_mm_add_epi16(t, _mm_srli_epi16(t, 8)), 8); + + __m128i c1 = _mm_shuffle_epi32(c, _MM_SHUFFLE(3, 2, 3, 2)); + __m128i diff = _mm_sub_epi16(c, c1); + diff = _mm_max_epi16(diff, _mm_set1_epi16(-4)); + diff = _mm_min_epi16(diff, _mm_set1_epi16(3)); + + __m128i co = _mm_add_epi16(c1, diff); + + c = _mm_blend_epi16(co, c, 0xF0); + + __m128i a0 = _mm_or_si128(_mm_slli_epi16(c, 3), _mm_srli_epi16(c, 2)); + + _mm_storeu_si128((__m128i*)a[4+i*2].data(), a0); + } + + for( int i=0; i<2; i++ ) + { + __m128i d = _mm_loadu_si128((__m128i*)a[i*2].data()); + + __m128i t0 = _mm_add_epi16(_mm_mullo_epi16(d, _mm_set1_epi16(15)), _mm_set1_epi16(128)); + __m128i t1 = _mm_srli_epi16(_mm_add_epi16(t0, _mm_srli_epi16(t0, 8)), 8); + + __m128i t2 = _mm_or_si128(t1, _mm_slli_epi16(t1, 4)); + + _mm_storeu_si128((__m128i*)a[i*2].data(), t2); + } +#else + for( int i=0; i<2; i++ ) + { + for( int j=0; j<3; j++ ) + { + int32 c1 = mul8bit( a[i*2+1][j], 31 ); + int32 c2 = mul8bit( a[i*2][j], 31 ); + + int32 diff = c2 - c1; + if( diff > 3 ) diff = 3; + else if( diff < -4 ) diff = -4; + + int32 co = c1 + diff; + + a[5+i*2][j] = ( c1 << 3 ) | ( c1 >> 2 ); + a[4+i*2][j] = ( co << 3 ) | ( co >> 2 ); + } + } + + for( int i=0; i<4; i++ ) + { + a[i][0] = g_avg2[mul8bit( a[i][0], 15 )]; + a[i][1] = g_avg2[mul8bit( a[i][1], 15 )]; + a[i][2] = g_avg2[mul8bit( a[i][2], 15 )]; + } +#endif +} + +void EncodeAverages( uint64& _d, const v4i* a, size_t idx ) +{ + auto d = _d; + d |= ( idx << 24 ); + size_t base = idx << 1; + + if( ( idx & 0x2 ) == 0 ) + { + for( int i=0; i<3; i++ ) + { + d |= uint64( a[base+0][i] >> 4 ) << ( i*8 ); + d |= uint64( a[base+1][i] >> 4 ) << ( i*8 + 4 ); + } + } + else + { + for( int i=0; i<3; i++ ) + { + d |= uint64( a[base+1][i] & 0xF8 ) << ( i*8 ); + int32 c = ( ( a[base+0][i] & 0xF8 ) - ( a[base+1][i] & 0xF8 ) ) >> 3; + c &= ~0xFFFFFFF8; + d |= ((uint64)c) << ( i*8 ); + } + } + _d = d; +} + +uint64 CheckSolid( const uint8* src ) +{ +#ifdef __SSE4_1__ + __m128i d0 = _mm_loadu_si128(((__m128i*)src) + 0); + __m128i d1 = _mm_loadu_si128(((__m128i*)src) + 1); + __m128i d2 = _mm_loadu_si128(((__m128i*)src) + 2); + __m128i d3 = _mm_loadu_si128(((__m128i*)src) + 3); + + __m128i c = _mm_shuffle_epi32(d0, _MM_SHUFFLE(0, 0, 0, 0)); + + __m128i c0 = _mm_cmpeq_epi8(d0, c); + __m128i c1 = _mm_cmpeq_epi8(d1, c); + __m128i c2 = _mm_cmpeq_epi8(d2, c); + __m128i c3 = _mm_cmpeq_epi8(d3, c); + + __m128i m0 = _mm_and_si128(c0, c1); + __m128i m1 = _mm_and_si128(c2, c3); + __m128i m = _mm_and_si128(m0, m1); + + if (!_mm_testc_si128(m, _mm_set1_epi32(-1))) + { + return 0; + } +#else + const uint8* ptr = src + 4; + for( int i=1; i<16; i++ ) + { + if( memcmp( src, ptr, 4 ) != 0 ) + { + return 0; + } + ptr += 4; + } +#endif + return 0x02000000 | + ( uint( src[0] & 0xF8 ) << 16 ) | + ( uint( src[1] & 0xF8 ) << 8 ) | + ( uint( src[2] & 0xF8 ) ); +} + +void PrepareAverages( v4i a[8], const uint8* src, uint err[4] ) +{ + Average( src, a ); + ProcessAverages( a ); + + uint errblock[4][4]; + CalcErrorBlock( src, errblock ); + + for( int i=0; i<4; i++ ) + { + err[i/2] += CalcError( errblock[i], a[i] ); + err[2+i/2] += CalcError( errblock[i], a[i+4] ); + } +} + +void FindBestFit( uint64 terr[2][8], uint16 tsel[16][8], v4i a[8], const uint32* id, const uint8* data ) +{ + for( size_t i=0; i<16; i++ ) + { + uint16* sel = tsel[i]; + uint bid = id[i]; + uint64* ter = terr[bid%2]; + + uint8 b = *data++; + uint8 g = *data++; + uint8 r = *data++; + data++; + + int dr = a[bid][0] - r; + int dg = a[bid][1] - g; + int db = a[bid][2] - b; + +#ifdef __SSE4_1__ + // Reference implementation + + __m128i pix = _mm_set1_epi32(dr * 77 + dg * 151 + db * 28); + // Taking the absolute value is way faster. The values are only used to sort, so the result will be the same. + __m128i error0 = _mm_abs_epi32(_mm_add_epi32(pix, g_table256_SIMD[0])); + __m128i error1 = _mm_abs_epi32(_mm_add_epi32(pix, g_table256_SIMD[1])); + __m128i error2 = _mm_abs_epi32(_mm_sub_epi32(pix, g_table256_SIMD[0])); + __m128i error3 = _mm_abs_epi32(_mm_sub_epi32(pix, g_table256_SIMD[1])); + + __m128i index0 = _mm_and_si128(_mm_cmplt_epi32(error1, error0), _mm_set1_epi32(1)); + __m128i minError0 = _mm_min_epi32(error0, error1); + + __m128i index1 = _mm_sub_epi32(_mm_set1_epi32(2), _mm_cmplt_epi32(error3, error2)); + __m128i minError1 = _mm_min_epi32(error2, error3); + + __m128i minIndex0 = _mm_blendv_epi8(index0, index1, _mm_cmplt_epi32(minError1, minError0)); + __m128i minError = _mm_min_epi32(minError0, minError1); + + // Squaring the minimum error to produce correct values when adding + __m128i minErrorLow = _mm_shuffle_epi32(minError, _MM_SHUFFLE(1, 1, 0, 0)); + __m128i squareErrorLow = _mm_mul_epi32(minErrorLow, minErrorLow); + squareErrorLow = _mm_add_epi64(squareErrorLow, _mm_loadu_si128(((__m128i*)ter) + 0)); + _mm_storeu_si128(((__m128i*)ter) + 0, squareErrorLow); + __m128i minErrorHigh = _mm_shuffle_epi32(minError, _MM_SHUFFLE(3, 3, 2, 2)); + __m128i squareErrorHigh = _mm_mul_epi32(minErrorHigh, minErrorHigh); + squareErrorHigh = _mm_add_epi64(squareErrorHigh, _mm_loadu_si128(((__m128i*)ter) + 1)); + _mm_storeu_si128(((__m128i*)ter) + 1, squareErrorHigh); + + // Taking the absolute value is way faster. The values are only used to sort, so the result will be the same. + error0 = _mm_abs_epi32(_mm_add_epi32(pix, g_table256_SIMD[2])); + error1 = _mm_abs_epi32(_mm_add_epi32(pix, g_table256_SIMD[3])); + error2 = _mm_abs_epi32(_mm_sub_epi32(pix, g_table256_SIMD[2])); + error3 = _mm_abs_epi32(_mm_sub_epi32(pix, g_table256_SIMD[3])); + + index0 = _mm_and_si128(_mm_cmplt_epi32(error1, error0), _mm_set1_epi32(1)); + minError0 = _mm_min_epi32(error0, error1); + + index1 = _mm_sub_epi32(_mm_set1_epi32(2), _mm_cmplt_epi32(error3, error2)); + minError1 = _mm_min_epi32(error2, error3); + + __m128i minIndex1 = _mm_blendv_epi8(index0, index1, _mm_cmplt_epi32(minError1, minError0)); + minError = _mm_min_epi32(minError0, minError1); + + // Squaring the minimum error to produce correct values when adding + minErrorLow = _mm_shuffle_epi32(minError, _MM_SHUFFLE(1, 1, 0, 0)); + squareErrorLow = _mm_mul_epi32(minErrorLow, minErrorLow); + squareErrorLow = _mm_add_epi64(squareErrorLow, _mm_loadu_si128(((__m128i*)ter) + 2)); + _mm_storeu_si128(((__m128i*)ter) + 2, squareErrorLow); + minErrorHigh = _mm_shuffle_epi32(minError, _MM_SHUFFLE(3, 3, 2, 2)); + squareErrorHigh = _mm_mul_epi32(minErrorHigh, minErrorHigh); + squareErrorHigh = _mm_add_epi64(squareErrorHigh, _mm_loadu_si128(((__m128i*)ter) + 3)); + _mm_storeu_si128(((__m128i*)ter) + 3, squareErrorHigh); + __m128i minIndex = _mm_packs_epi32(minIndex0, minIndex1); + _mm_storeu_si128((__m128i*)sel, minIndex); +#else + int pix = dr * 77 + dg * 151 + db * 28; + + for( int t=0; t<8; t++ ) + { + const int64* tab = g_table256[t]; + uint idx = 0; + uint64 err = sq( tab[0] + pix ); + for( int j=1; j<4; j++ ) + { + uint64 local = sq( tab[j] + pix ); + if( local < err ) + { + err = local; + idx = j; + } + } + *sel++ = idx; + *ter++ += err; + } +#endif + } +} + +#ifdef __SSE4_1__ +// Non-reference implementation, but faster. Produces same results as the AVX2 version +void FindBestFit( uint32 terr[2][8], uint16 tsel[16][8], v4i a[8], const uint32* id, const uint8* data ) +{ + for( size_t i=0; i<16; i++ ) + { + uint16* sel = tsel[i]; + uint bid = id[i]; + uint32* ter = terr[bid%2]; + + uint8 b = *data++; + uint8 g = *data++; + uint8 r = *data++; + data++; + + int dr = a[bid][0] - r; + int dg = a[bid][1] - g; + int db = a[bid][2] - b; + + // The scaling values are divided by two and rounded, to allow the differences to be in the range of signed int16 + // This produces slightly different results, but is significant faster + __m128i pixel = _mm_set1_epi16(dr * 38 + dg * 76 + db * 14); + __m128i pix = _mm_abs_epi16(pixel); + + // Taking the absolute value is way faster. The values are only used to sort, so the result will be the same. + // Since the selector table is symmetrical, we need to calculate the difference only for half of the entries. + __m128i error0 = _mm_abs_epi16(_mm_sub_epi16(pix, g_table128_SIMD[0])); + __m128i error1 = _mm_abs_epi16(_mm_sub_epi16(pix, g_table128_SIMD[1])); + + __m128i index = _mm_and_si128(_mm_cmplt_epi16(error1, error0), _mm_set1_epi16(1)); + __m128i minError = _mm_min_epi16(error0, error1); + + // Exploiting symmetry of the selector table and use the sign bit + // This produces slightly different results, but is needed to produce same results as AVX2 implementation + __m128i indexBit = _mm_andnot_si128(_mm_srli_epi16(pixel, 15), _mm_set1_epi8(-1)); + __m128i minIndex = _mm_or_si128(index, _mm_add_epi16(indexBit, indexBit)); + + // Squaring the minimum error to produce correct values when adding + __m128i squareErrorLo = _mm_mullo_epi16(minError, minError); + __m128i squareErrorHi = _mm_mulhi_epi16(minError, minError); + + __m128i squareErrorLow = _mm_unpacklo_epi16(squareErrorLo, squareErrorHi); + __m128i squareErrorHigh = _mm_unpackhi_epi16(squareErrorLo, squareErrorHi); + + squareErrorLow = _mm_add_epi32(squareErrorLow, _mm_loadu_si128(((__m128i*)ter) + 0)); + _mm_storeu_si128(((__m128i*)ter) + 0, squareErrorLow); + squareErrorHigh = _mm_add_epi32(squareErrorHigh, _mm_loadu_si128(((__m128i*)ter) + 1)); + _mm_storeu_si128(((__m128i*)ter) + 1, squareErrorHigh); + + _mm_storeu_si128((__m128i*)sel, minIndex); + } +} +#endif + +uint8_t convert6(float f) +{ + int i = (std::min(std::max(static_cast(f), 0), 1023) - 15) >> 1; + return (i + 11 - ((i + 11) >> 7) - ((i + 4) >> 7)) >> 3; +} + +uint8_t convert7(float f) +{ + int i = (std::min(std::max(static_cast(f), 0), 1023) - 15) >> 1; + return (i + 9 - ((i + 9) >> 8) - ((i + 6) >> 8)) >> 2; +} + +std::pair Planar(const uint8* src) +{ + int32 r = 0; + int32 g = 0; + int32 b = 0; + + for (int i = 0; i < 16; ++i) + { + b += src[i * 4 + 0]; + g += src[i * 4 + 1]; + r += src[i * 4 + 2]; + } + + int32 difRyz = 0; + int32 difGyz = 0; + int32 difByz = 0; + int32 difRxz = 0; + int32 difGxz = 0; + int32 difBxz = 0; + + const int32 scaling[] = { -255, -85, 85, 255 }; + + for (int i = 0; i < 16; ++i) + { + int32 difB = (static_cast(src[i * 4 + 0]) << 4) - b; + int32 difG = (static_cast(src[i * 4 + 1]) << 4) - g; + int32 difR = (static_cast(src[i * 4 + 2]) << 4) - r; + + difRyz += difR * scaling[i % 4]; + difGyz += difG * scaling[i % 4]; + difByz += difB * scaling[i % 4]; + + difRxz += difR * scaling[i / 4]; + difGxz += difG * scaling[i / 4]; + difBxz += difB * scaling[i / 4]; + } + + const float scale = -4.0f / ((255 * 255 * 8.0f + 85 * 85 * 8.0f) * 16.0f); + + float aR = difRxz * scale; + float aG = difGxz * scale; + float aB = difBxz * scale; + + float bR = difRyz * scale; + float bG = difGyz * scale; + float bB = difByz * scale; + + float dR = r * (4.0f / 16.0f); + float dG = g * (4.0f / 16.0f); + float dB = b * (4.0f / 16.0f); + + // calculating the three colors RGBO, RGBH, and RGBV. RGB = df - af * x - bf * y; + float cofR = std::fma(aR, 255.0f, std::fma(bR, 255.0f, dR)); + float cofG = std::fma(aG, 255.0f, std::fma(bG, 255.0f, dG)); + float cofB = std::fma(aB, 255.0f, std::fma(bB, 255.0f, dB)); + float chfR = std::fma(aR, -425.0f, std::fma(bR, 255.0f, dR)); + float chfG = std::fma(aG, -425.0f, std::fma(bG, 255.0f, dG)); + float chfB = std::fma(aB, -425.0f, std::fma(bB, 255.0f, dB)); + float cvfR = std::fma(aR, 255.0f, std::fma(bR, -425.0f, dR)); + float cvfG = std::fma(aG, 255.0f, std::fma(bG, -425.0f, dG)); + float cvfB = std::fma(aB, 255.0f, std::fma(bB, -425.0f, dB)); + + // convert to r6g7b6 + int32 coR = convert6(cofR); + int32 coG = convert7(cofG); + int32 coB = convert6(cofB); + int32 chR = convert6(chfR); + int32 chG = convert7(chfG); + int32 chB = convert6(chfB); + int32 cvR = convert6(cvfR); + int32 cvG = convert7(cvfG); + int32 cvB = convert6(cvfB); + + // Error calculation + auto ro0 = coR; + auto go0 = coG; + auto bo0 = coB; + auto ro1 = (ro0 >> 4) | (ro0 << 2); + auto go1 = (go0 >> 6) | (go0 << 1); + auto bo1 = (bo0 >> 4) | (bo0 << 2); + auto ro2 = (ro1 << 2) + 2; + auto go2 = (go1 << 2) + 2; + auto bo2 = (bo1 << 2) + 2; + + auto rh0 = chR; + auto gh0 = chG; + auto bh0 = chB; + auto rh1 = (rh0 >> 4) | (rh0 << 2); + auto gh1 = (gh0 >> 6) | (gh0 << 1); + auto bh1 = (bh0 >> 4) | (bh0 << 2); + + auto rh2 = rh1 - ro1; + auto gh2 = gh1 - go1; + auto bh2 = bh1 - bo1; + + auto rv0 = cvR; + auto gv0 = cvG; + auto bv0 = cvB; + auto rv1 = (rv0 >> 4) | (rv0 << 2); + auto gv1 = (gv0 >> 6) | (gv0 << 1); + auto bv1 = (bv0 >> 4) | (bv0 << 2); + + auto rv2 = rv1 - ro1; + auto gv2 = gv1 - go1; + auto bv2 = bv1 - bo1; + + uint64 error = 0; + + for (int i = 0; i < 16; ++i) + { + int32 cR = clampu8((rh2 * (i / 4) + rv2 * (i % 4) + ro2) >> 2); + int32 cG = clampu8((gh2 * (i / 4) + gv2 * (i % 4) + go2) >> 2); + int32 cB = clampu8((bh2 * (i / 4) + bv2 * (i % 4) + bo2) >> 2); + + int32 difB = static_cast(src[i * 4 + 0]) - cB; + int32 difG = static_cast(src[i * 4 + 1]) - cG; + int32 difR = static_cast(src[i * 4 + 2]) - cR; + + int32 dif = difR * 38 + difG * 76 + difB * 14; + + error += dif * dif; + } + + /**/ + uint32 rgbv = cvB | (cvG << 6) | (cvR << 13); + uint32 rgbh = chB | (chG << 6) | (chR << 13); + uint32 hi = rgbv | ((rgbh & 0x1FFF) << 19); + uint32 lo = (chR & 0x1) | 0x2 | ((chR << 1) & 0x7C); + lo |= ((coB & 0x07) << 7) | ((coB & 0x18) << 8) | ((coB & 0x20) << 11); + lo |= ((coG & 0x3F) << 17) | ((coG & 0x40) << 18); + lo |= coR << 25; + + const auto idx = (coR & 0x20) | ((coG & 0x20) >> 1) | ((coB & 0x1E) >> 1); + + lo |= g_flags[idx]; + + uint64 result = static_cast(_bswap(lo)); + result |= static_cast(static_cast(_bswap(hi))) << 32; + + return std::make_pair(result, error); +} + +template +uint64 EncodeSelectors( uint64 d, const T terr[2][8], const S tsel[16][8], const uint32* id, const uint64 value, const uint64 error) +{ + size_t tidx[2]; + tidx[0] = GetLeastError( terr[0], 8 ); + tidx[1] = GetLeastError( terr[1], 8 ); + + if ((terr[0][tidx[0]] + terr[1][tidx[1]]) >= error) + { + return value; + } + + d |= tidx[0] << 26; + d |= tidx[1] << 29; + for( int i=0; i<16; i++ ) + { + uint64 t = tsel[i][tidx[id[i]%2]]; + d |= ( t & 0x1 ) << ( i + 32 ); + d |= ( t & 0x2 ) << ( i + 47 ); + } + + return FixByteOrder(d); +} +} + +uint64 ProcessRGB( const uint8* src ) +{ + uint64 d = CheckSolid( src ); + if( d != 0 ) return d; + + v4i a[8]; + uint err[4] = {}; + PrepareAverages( a, src, err ); + size_t idx = GetLeastError( err, 4 ); + EncodeAverages( d, a, idx ); + +#if defined __SSE4_1__ && !defined REFERENCE_IMPLEMENTATION + uint32 terr[2][8] = {}; +#else + uint64 terr[2][8] = {}; +#endif + uint16 tsel[16][8]; + auto id = g_id[idx]; + FindBestFit( terr, tsel, a, id, src ); + + return FixByteOrder( EncodeSelectors( d, terr, tsel, id ) ); +} + +uint64 ProcessRGB_ETC2( const uint8* src ) +{ + auto result = Planar( src ); + + uint64 d = 0; + + v4i a[8]; + uint err[4] = {}; + PrepareAverages( a, src, err ); + size_t idx = GetLeastError( err, 4 ); + EncodeAverages( d, a, idx ); + +#if defined __SSE4_1__ && !defined REFERENCE_IMPLEMENTATION + uint32 terr[2][8] = {}; +#else + uint64 terr[2][8] = {}; +#endif + uint16 tsel[16][8]; + auto id = g_id[idx]; + FindBestFit( terr, tsel, a, id, src ); + + return EncodeSelectors( d, terr, tsel, id, result.first, result.second ); +} + +inline int NumberOfMipLevels(const v2i& size) +{ + return (int)floor(log2(std::max(size.x, size.y))) + 1; +} + +Bitmap::Bitmap(const v2i& size) + : m_data(new uint32[size.x*size.y]) + , m_block(nullptr) + , m_lines(1) + , m_linesLeft(size.y / 4) + , m_size(size) +// , m_sema(0) +{ +} + +Bitmap::Bitmap(const Bitmap& src, uint lines) + : m_lines(lines) + , m_alpha(src.Alpha()) +// , m_sema(0) +{ +} + +Bitmap::Bitmap(const v2i& size, const void *pixelData, int pitch, uint lines) + : m_data(nullptr) + , m_block((uint32*)pixelData) + , m_lines(lines) + , m_linesLeft(size.y / 4) + , m_size(size) +// , m_sema(0) +{ + +} + +Bitmap::~Bitmap() +{ + delete[] m_data; +} + +const uint32* Bitmap::NextBlock(uint& lines, bool& done) +{ +// std::lock_guard lock(m_lock); + lines = std::min(m_lines, m_linesLeft); + auto ret = m_block; +// m_sema.lock(); + m_block += m_size.x * 4 * lines; + m_linesLeft -= lines; + done = m_linesLeft == 0; + return ret; +} + +class BitmapDownsampled : public Bitmap +{ +public: + BitmapDownsampled(const Bitmap& bmp, uint lines); + ~BitmapDownsampled(); +}; + +BitmapDownsampled::BitmapDownsampled(const Bitmap& bmp, uint lines) + : Bitmap(bmp, lines) +{ + m_size.x = std::max(1, bmp.Size().x / 2); + m_size.y = std::max(1, bmp.Size().y / 2); + + int w = std::max(m_size.x, 4); + int h = std::max(m_size.y, 4); + +// DBGPRINT("Subbitmap " << m_size.x << "x" << m_size.y); + + m_block = m_data = new uint32[w*h]; + + if (m_size.x < w || m_size.y < h) + { + memset(m_data, 0, w*h*sizeof(uint32)); + m_linesLeft = h / 4; + uint lines = 0; + for (int i = 0; i < h / 4; i++) + { + for (int j = 0; j < 4; j++) + { + lines++; + if (lines > m_lines) + { + lines = 0; + // m_sema.unlock(); + } + } + } + if (lines != 0) + { + // m_sema.unlock(); + } + } else + { + m_linesLeft = h / 4; + m_load = std::async(std::launch::async, [this, &bmp, w, h]() mutable + { + auto ptr = m_data; + auto src1 = bmp.Data(); + auto src2 = src1 + bmp.Size().x; + uint lines = 0; + for (int i = 0; i < h / 4; i++) + { + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < m_size.x; k++) + { + int r = ((*src1 & 0x000000FF) + (*(src1 + 1) & 0x000000FF) + (*src2 & 0x000000FF) + (*(src2 + 1) & 0x000000FF)) / 4; + int g = (((*src1 & 0x0000FF00) + (*(src1 + 1) & 0x0000FF00) + (*src2 & 0x0000FF00) + (*(src2 + 1) & 0x0000FF00)) / 4) & 0x0000FF00; + int b = (((*src1 & 0x00FF0000) + (*(src1 + 1) & 0x00FF0000) + (*src2 & 0x00FF0000) + (*(src2 + 1) & 0x00FF0000)) / 4) & 0x00FF0000; + int a = (((((*src1 & 0xFF000000) >> 8) + ((*(src1 + 1) & 0xFF000000) >> 8) + ((*src2 & 0xFF000000) >> 8) + ((*(src2 + 1) & 0xFF000000) >> 8)) / 4) & 0x00FF0000) << 8; + *ptr++ = r | g | b | a; + src1 += 2; + src2 += 2; + } + src1 += m_size.x * 2; + src2 += m_size.x * 2; + } + lines++; + if (lines >= m_lines) + { + lines = 0; + // m_sema.unlock(); + } + } + + if (lines != 0) + { + // m_sema.unlock(); + } + }); + } +} + +BitmapDownsampled::~BitmapDownsampled() +{ +} + +static uint8 e5[32]; +static uint8 e6[64]; +static uint8 qrb[256 + 16]; +static uint8 qg[256 + 16]; + +void InitDither() +{ + for (int i = 0; i < 32; i++) + { + e5[i] = (i << 3) | (i >> 2); + } + for (int i = 0; i < 64; i++) + { + e6[i] = (i << 2) | (i >> 4); + } + for (int i = 0; i < 256 + 16; i++) + { + int v = std::min(std::max(0, i - 8), 255); + qrb[i] = e5[mul8bit(v, 31)]; + qg[i] = e6[mul8bit(v, 63)]; + } +} + +void Dither(uint8* data) +{ + int err[8]; + int* ep1 = err; + int* ep2 = err + 4; + + for (int ch = 0; ch < 3; ch++) + { + uint8* ptr = data + ch; + uint8* quant = (ch == 1) ? qg + 8 : qrb + 8; + memset(err, 0, sizeof(err)); + + for (int y = 0; y < 4; y++) + { + uint8 tmp; + tmp = quant[ptr[0] + ((3 * ep2[1] + 5 * ep2[0]) >> 4)]; + ep1[0] = ptr[0] - tmp; + ptr[0] = tmp; + tmp = quant[ptr[4] + ((7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]) >> 4)]; + ep1[1] = ptr[4] - tmp; + ptr[4] = tmp; + tmp = quant[ptr[8] + ((7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]) >> 4)]; + ep1[2] = ptr[8] - tmp; + ptr[8] = tmp; + tmp = quant[ptr[12] + ((7 * ep1[2] + 5 * ep2[3] + ep2[2]) >> 4)]; + ep1[3] = ptr[12] - tmp; + ptr[12] = tmp; + ptr += 16; + std::swap(ep1, ep2); + } + } +} + +void Swizzle(const uint8* data, const ptrdiff_t pitch, uint8* output) +{ + for (int i = 0; i < 4; ++i) + { + uint64 d0 = *(const uint64*)(data + i * pitch + 0); + uint64 d1 = *(const uint64*)(data + i * pitch + 8); + + *(uint64*)(output + i * 16 + 0) = d0; + *(uint64*)(output + i * 16 + 8) = d1; + } +} + +class BlockData +{ +public: +// BlockData(const char* fn); +// BlockData(const char* fn, const v2i& size, bool mipmap); + BlockData(const v2i& size, bool mipmap); + ~BlockData(); + + BitmapPtr Decode(); + void Dissect(); + + void Process(const uint32* src, uint32 blocks, size_t offset, size_t width, Channels type, bool dither, bool etc2); + void *Detach(size_t& len) { + uint8* ret = m_data; m_data = nullptr; + len = m_maplen; + return ret; + } + +private: + uint8* m_data; + v2i m_size; + size_t m_dataOffset; +// FILE* m_file; + size_t m_maplen; +}; + +static int AdjustSizeForMipmaps(const v2i& size, int levels) +{ + int len = 0; + v2i current = size; + for (int i = 1; i < levels; i++) + { + assert(current.x != 1 || current.y != 1); + current.x = std::max(1, current.x / 2); + current.y = std::max(1, current.y / 2); + len += std::max(4, current.x) * std::max(4, current.y) / 2; + } + assert(current.x == 1 && current.y == 1); + return len; +} + +static uint8* OpenForWriting(/*const char* fn,*/ size_t len, const v2i& size, /*FILE** f,*/ int levels) +{ + auto ret = new uint8[len]; + auto dst = (uint32*)ret; + + *dst++ = 0x03525650; // version + *dst++ = 0; // flags + *dst++ = 6; // pixelformat[0], value 22 is needed for etc2 + *dst++ = 0; // pixelformat[1] + *dst++ = 0; // colourspace + *dst++ = 0; // channel type + *dst++ = size.y; // height + *dst++ = size.x; // width + *dst++ = 1; // depth + *dst++ = 1; // num surfs + *dst++ = 1; // num faces + *dst++ = levels; // mipmap count + *dst++ = 0; // metadata size + + return ret; +} + +BlockData::BlockData(/*const char* fn,*/ const v2i& size, bool mipmap) + : m_size(size) + , m_dataOffset(52) + , m_maplen(52 + m_size.x*m_size.y / 2) +{ + assert(m_size.x % 4 == 0 && m_size.y % 4 == 0); + + uint32 cnt = m_size.x * m_size.y / 16; +// DBGPRINT(cnt << " blocks"); + + int levels = 1; + + if (mipmap) + { + levels = NumberOfMipLevels(size); + // DBGPRINT("Number of mipmaps: " << levels); + m_maplen += AdjustSizeForMipmaps(size, levels); + } + + m_data = OpenForWriting(/*fn,*/ m_maplen, m_size/*, &m_file*/, levels); +} + +// BlockData::BlockData(const v2i& size, bool mipmap) +// : m_size(size) +// , m_dataOffset(52) +// // , m_file(nullptr) +// , m_maplen(52 + m_size.x*m_size.y / 2) +// { +// assert(m_size.x % 4 == 0 && m_size.y % 4 == 0); +// if (mipmap) +// { +// const int levels = NumberOfMipLevels(size); +// m_maplen += AdjustSizeForMipmaps(size, levels); +// } +// m_data = new uint8[m_maplen]; +// } + +BlockData::~BlockData() +{ +// if (m_file) +// { +// munmap(m_data, m_maplen); +// fclose(m_file); +// } else + { + delete[] m_data; + } +} + +static uint64 _f_rgb(uint8* ptr) +{ + return ProcessRGB(ptr); +} + +#ifdef __SSE4_1__ +static uint64 _f_rgb_avx2(uint8* ptr) +{ + return ProcessRGB_AVX2(ptr); +} +#endif + +static uint64 _f_rgb_dither(uint8* ptr) +{ + Dither(ptr); + return ProcessRGB(ptr); +} + +#ifdef __SSE4_1__ +static uint64 _f_rgb_dither_avx2(uint8* ptr) +{ + Dither(ptr); + return ProcessRGB_AVX2(ptr); +} +#endif + +static uint64 _f_rgb_etc2(uint8* ptr) +{ + return ProcessRGB_ETC2(ptr); +} + +#ifdef __SSE4_1__ +static uint64 _f_rgb_etc2_avx2(uint8* ptr) +{ + return ProcessRGB_ETC2_AVX2(ptr); +} +#endif + +static uint64 _f_rgb_etc2_dither(uint8* ptr) +{ + Dither(ptr); + return ProcessRGB_ETC2(ptr); +} + +#ifdef __SSE4_1__ +static uint64 _f_rgb_etc2_dither_avx2(uint8* ptr) +{ + Dither(ptr); + return ProcessRGB_ETC2_AVX2(ptr); +} +#endif + +namespace +{ + struct BlockColor + { + uint32 r1, g1, b1; + uint32 r2, g2, b2; + }; + + enum class Etc2Mode + { + none, + t, + h, + planar + }; + + Etc2Mode DecodeBlockColor(uint64 d, BlockColor& c) + { + if (d & 0x2) + { + int32 dr, dg, db; + + c.r1 = (d & 0xF8000000) >> 27; + c.g1 = (d & 0x00F80000) >> 19; + c.b1 = (d & 0x0000F800) >> 11; + + dr = (d & 0x07000000) >> 24; + dg = (d & 0x00070000) >> 16; + db = (d & 0x00000700) >> 8; + + if (dr & 0x4) + { + dr |= 0xFFFFFFF8; + } + if (dg & 0x4) + { + dg |= 0xFFFFFFF8; + } + if (db & 0x4) + { + db |= 0xFFFFFFF8; + } + + int32 r = static_cast(c.r1) + dr; + int32 g = static_cast(c.g1) + dg; + int32 b = static_cast(c.b1) + db; + + if ((r < 0) || (r > 31)) + { + return Etc2Mode::t; + } + + if ((g < 0) || (g > 31)) + { + return Etc2Mode::h; + } + + if ((b < 0) || (b > 31)) + { + return Etc2Mode::planar; + } + + c.r2 = c.r1 + dr; + c.g2 = c.g1 + dg; + c.b2 = c.b1 + db; + + c.r1 = (c.r1 << 3) | (c.r1 >> 2); + c.g1 = (c.g1 << 3) | (c.g1 >> 2); + c.b1 = (c.b1 << 3) | (c.b1 >> 2); + c.r2 = (c.r2 << 3) | (c.r2 >> 2); + c.g2 = (c.g2 << 3) | (c.g2 >> 2); + c.b2 = (c.b2 << 3) | (c.b2 >> 2); + } else + { + c.r1 = ((d & 0xF0000000) >> 24) | ((d & 0xF0000000) >> 28); + c.r2 = ((d & 0x0F000000) >> 20) | ((d & 0x0F000000) >> 24); + c.g1 = ((d & 0x00F00000) >> 16) | ((d & 0x00F00000) >> 20); + c.g2 = ((d & 0x000F0000) >> 12) | ((d & 0x000F0000) >> 16); + c.b1 = ((d & 0x0000F000) >> 8) | ((d & 0x0000F000) >> 12); + c.b2 = ((d & 0x00000F00) >> 4) | ((d & 0x00000F00) >> 8); + } + return Etc2Mode::none; + } + + inline int32 expand6(uint32 value) + { + return (value << 2) | (value >> 4); + } + + inline int32 expand7(uint32 value) + { + return (value << 1) | (value >> 6); + } + + void DecodePlanar(uint64 block, uint32** l) + { + const auto bv = expand6((block >> (0 + 32)) & 0x3F); + const auto gv = expand7((block >> (6 + 32)) & 0x7F); + const auto rv = expand6((block >> (13 + 32)) & 0x3F); + + const auto bh = expand6((block >> (19 + 32)) & 0x3F); + const auto gh = expand7((block >> (25 + 32)) & 0x7F); + + const auto rh0 = (block >> (32 - 32)) & 0x01; + const auto rh1 = ((block >> (34 - 32)) & 0x1F) << 1; + const auto rh = expand6(rh0 | rh1); + + const auto bo0 = (block >> (39 - 32)) & 0x07; + const auto bo1 = ((block >> (43 - 32)) & 0x3) << 3; + const auto bo2 = ((block >> (48 - 32)) & 0x1) << 5; + const auto bo = expand6(bo0 | bo1 | bo2); + const auto go0 = (block >> (49 - 32)) & 0x3F; + const auto go1 = ((block >> (56 - 32)) & 0x01) << 6; + const auto go = expand7(go0 | go1); + const auto ro = expand6((block >> (57 - 32)) & 0x3F); + + for (auto j = 0; j < 4; j++) + { + for (auto i = 0; i < 4; i++) + { + uint32 r = clampu8((i * (rh - ro) + j * (rv - ro) + 4 * ro + 2) >> 2); + uint32 g = clampu8((i * (gh - go) + j * (gv - go) + 4 * go + 2) >> 2); + uint32 b = clampu8((i * (bh - bo) + j * (bv - bo) + 4 * bo + 2) >> 2); + + *l[j]++ = r | (g << 8) | (b << 16) | 0xFF000000; + } + } + } + +} + +static void DecodeBlock(uint64 d, uint32** l) { + d = ((d & 0xFF000000FF000000) >> 24) | + ((d & 0x000000FF000000FF) << 24) | + ((d & 0x00FF000000FF0000) >> 8) | + ((d & 0x0000FF000000FF00) << 8); + + BlockColor c; + const auto mode = DecodeBlockColor(d, c); + + if (mode == Etc2Mode::planar) + { + DecodePlanar(d, l); + return; + } + + uint tcw[2]; + tcw[0] = (d & 0xE0) >> 5; + tcw[1] = (d & 0x1C) >> 2; + + uint ra, ga, ba; + uint rb, gb, bb; + uint rc, gc, bc; + uint rd, gd, bd; + + if (d & 0x1) { + int o = 0; + for (int i = 0; i < 4; i++) + { + ra = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ga = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ba = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + + rb = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + gb = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + bb = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + + rc = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + gc = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + bc = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + + rd = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + gd = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + bd = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + + *l[0]++ = ra | (ga << 8) | (ba << 16) | 0xFF000000; + *l[1]++ = rb | (gb << 8) | (bb << 16) | 0xFF000000; + *l[2]++ = rc | (gc << 8) | (bc << 16) | 0xFF000000; + *l[3]++ = rd | (gd << 8) | (bd << 16) | 0xFF000000; + + o += 4; + } + } else { + int o = 0; + for (int i = 0; i < 2; i++) + { + ra = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ga = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ba = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + + rb = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + gb = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + bb = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + + rc = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + gc = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + bc = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + + rd = clampu8(c.r1 + g_table[tcw[0]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + gd = clampu8(c.g1 + g_table[tcw[0]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + bd = clampu8(c.b1 + g_table[tcw[0]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + + *l[0]++ = ra | (ga << 8) | (ba << 16) | 0xFF000000; + *l[1]++ = rb | (gb << 8) | (bb << 16) | 0xFF000000; + *l[2]++ = rc | (gc << 8) | (bc << 16) | 0xFF000000; + *l[3]++ = rd | (gd << 8) | (bd << 16) | 0xFF000000; + + o += 4; + } + for (int i = 0; i < 2; i++) + { + ra = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ga = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + ba = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 32))) >> (o + 32)) | ((d & (1ll << (o + 48))) >> (o + 47))]); + + rb = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + gb = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + bb = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 33))) >> (o + 33)) | ((d & (1ll << (o + 49))) >> (o + 48))]); + + rc = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + gc = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + bc = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 34))) >> (o + 34)) | ((d & (1ll << (o + 50))) >> (o + 49))]); + + rd = clampu8(c.r2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + gd = clampu8(c.g2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + bd = clampu8(c.b2 + g_table[tcw[1]][((d & (1ll << (o + 35))) >> (o + 35)) | ((d & (1ll << (o + 51))) >> (o + 50))]); + + *l[0]++ = ra | (ga << 8) | (ba << 16) | 0xFF000000; + *l[1]++ = rb | (gb << 8) | (bb << 16) | 0xFF000000; + *l[2]++ = rc | (gc << 8) | (bc << 16) | 0xFF000000; + *l[3]++ = rd | (gd << 8) | (bd << 16) | 0xFF000000; + + o += 4; + } + } +} + +static void DecodeAlphaBlock(const unsigned char* data, uint8** l) { + int alpha = data[0]; + int table = data[1]; + + int bit = 0; + int byte = 2; + //extract an alpha value for each pixel. + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + //Extract table index + int index = 0; + for (int bitpos = 0; bitpos < 3; bitpos++) + { + index |= getbit(data[byte], 7 - bit, 2 - bitpos); + bit++; + if (bit > 7) { + bit = 0; + byte++; + } + } + l[y][x * 4] = clampu8(alpha + alphaTable[table][index]); + } + } + l[0] += 16; + l[1] += 16; + l[2] += 16; + l[3] += 16; +} + +void decode(const void *data, void* pixel, int pitch, int h, int blkw, int blkh) +{ + int dpitch = pitch; + uint32 *ret = (uint32 *)pixel; + uint32* l[4]; + l[0] = ret; + l[1] = l[0] + blkw * 4; + l[2] = l[1] + blkw * 4; + l[3] = l[2] + blkw * 4; + + const uint64* src = (const uint64*)data; + if (h & 3) --blkh; + for (int y = 0; y < blkh; y++) + { + for (int x = 0; x < blkw; x++) + { + DecodeBlock(*src++, l); + } + + l[0] += dpitch * 3; + l[1] += dpitch * 3; + l[2] += dpitch * 3; + l[3] += dpitch * 3; + } + if (h & 3) + { + h &= 3; + std::vector dummy; dummy.resize(blkw * 4); + for (int y = 0; y < h; ++y) { + l[3 - y] = &dummy.front(); + } + for (int x = 0; x < blkw; x++) + { + DecodeBlock(*src++, l); + } + } +} + +void decodeWithAlpha(const void *data, void* pixel, int pitch, int h, int blkw, int blkh) +{ + setupAlphaTable(); + int dpitch = pitch; + uint32 *ret = (uint32 *)pixel; + uint32* l[4]; uint8* la[4]; + l[0] = ret; + l[1] = l[0] + blkw * 4; + l[2] = l[1] + blkw * 4; + l[3] = l[2] + blkw * 4; + la[0] = ((uint8*)ret) + 3; + la[1] = la[0] + blkw * 4 * 4; + la[2] = la[1] + blkw * 4 * 4; + la[3] = la[2] + blkw * 4 * 4; + + const uint64* src = (const uint64*)data; + if (h & 3) --blkh; + for (int y = 0; y < blkh; y++) + { + for (int x = 0; x < blkw; x++) + { + const unsigned char *a = (const unsigned char *)src++; + DecodeBlock(*src++, l); + DecodeAlphaBlock(a, la); + } + + l[0] += dpitch * 3; + l[1] += dpitch * 3; + l[2] += dpitch * 3; + l[3] += dpitch * 3; + la[0] += dpitch * 3; + la[1] += dpitch * 3; + la[2] += dpitch * 3; + la[3] += dpitch * 3; + } + if (h & 3) + { + h &= 3; + std::vector dummy; dummy.resize(blkw * 4); + for (int y = 0; y < h; ++y) { + l[3 - y] = &dummy.front(); + la[3 - y] = (uint8*)&dummy.front(); + } + for (int x = 0; x < blkw; x++) + { + const unsigned char *a = (const unsigned char *)src++; + DecodeBlock(*src++, l); + DecodeAlphaBlock(a, la); + } + } +} + +void* convert(const void *_pixel, int w, int h, int pitch, bool etc2, size_t &datalen) +{ + tjs_uint32 *pixel = (tjs_uint32 *)TJSAlignedAlloc(pitch * h, 4); + TVPReverseRGB(pixel, (const tjs_uint32 *)_pixel, pitch * h / 4); + int blkw = w / 4, blkh = h / 4; + int edgew = w % 4, edgeh = h % 4; + int dpitch = (blkw + !!edgew) * 8; + datalen = dpitch * (blkh + !!edgeh); + uint8* data = new uint8[datalen]; + uint8 *src = (uint8 *)pixel, *dst = data; + for (int blky = 0; blky < blkh; ++blky) { + uint8* sline = src, *dline = dst; + for (int blkx = 0; blkx < blkw; ++blkx) { + uint32 buf[4 * 4]; + uint8* line = sline; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + sline += 16; + dline += 8; + } + src += pitch * 4; + dst += dpitch; + } + if (edgew) { + uint8* sline = ((uint8 *)pixel) + blkw * 16, *dline = data + blkw * 8; + for (int blky = 0; blky < blkh; ++blky) { + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < edgew; ++x) { + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + *(uint64*)(dline) = _f_rgb_etc2((uint8*)buf); + sline += 16; + dline += dpitch; + } + } + if (edgeh) { + uint8* sline = ((uint8 *)pixel) + blkh * pitch * 4, *dline = data + blkh * dpitch; + for (int blkx = 0; blkx < blkw; ++blkx) { + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < edgeh; ++y) { + for (int x = 0; x < 4; ++x) { + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + sline += 16; + dline += 8; + } + if (edgew) { + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < edgeh; ++y) { + for (int x = 0; x < edgew; ++x) { + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + } + } + TJSAlignedDealloc(pixel); + return data; +#if 0 + int nThread = TVPGetThreadNum() - 1; // don't use main thread if possible + if (nThread < 1) nThread = 1; + else if (nThread > num) nThread = num; + TVPExecThreadTask(nThread, [&](int n) { + for (int i = n; i < data.size(); i += nThread) { + DataPart& part = data[i]; + bool dither = false; + bd->Process(part.src, part.width / 4 * part.lines, part.offset, part.width, Channels::RGB, dither, etc2); + } + }); + void* pixeldata = bd->Detach(datalen); +// v2i size = dp.ImageData().Size(); + + bd.reset(); + return pixeldata; +#endif +} + +void* convertWithAlpha(const void *_pixel, int w, int h, int pitch, size_t &datalen) +{ + setupAlphaTable(); + tjs_uint32 *pixel = (tjs_uint32 *)TJSAlignedAlloc(pitch * h, 4); + TVPReverseRGB(pixel, (const tjs_uint32 *)_pixel, pitch * h / 4); + int blkw = w / 4, blkh = h / 4; + int edgew = w % 4, edgeh = h % 4; + int dpitch = (blkw + !!edgew) * 16; + datalen = dpitch * (blkh + !!edgeh); + uint8* data = new uint8[datalen]; + uint8 *src = (uint8 *)pixel, *dst = data; + for (int blky = 0; blky < blkh; ++blky) { + uint8* sline = src, *dline = dst; + for (int blkx = 0; blkx < blkw; ++blkx) { + uint8 abuf[4 * 4]; + uint32 buf[4 * 4]; + uint8* line = sline; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + abuf[x * 4 + y] = line[x * 4 + 3]; + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + compressBlockAlphaFast(abuf, dline); + dline += 8; + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + dline += 8; + sline += 16; + } + src += pitch * 4; + dst += dpitch; + } + if (edgew) { + uint8* sline = ((uint8 *)pixel) + blkw * 16, *dline = data + blkw * 16; + for (int blky = 0; blky < blkh; ++blky) { + uint8 abuf[4 * 4] = { 0 }; + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < edgew; ++x) { + abuf[x * 4 + y] = line[x * 4 + 3]; + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + compressBlockAlphaFast(abuf, dline); + *(uint64*)(dline + 8) = _f_rgb_etc2((uint8*)buf); + dline += dpitch; + sline += 16; + } + } + if (edgeh) { + uint8* sline = ((uint8 *)pixel) + blkh * pitch * 4, *dline = data + blkh * dpitch; + for (int blkx = 0; blkx < blkw; ++blkx) { + uint8 abuf[4 * 4] = { 0 }; + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < edgeh; ++y) { + for (int x = 0; x < 4; ++x) { + abuf[x * 4 + y] = line[x * 4 + 3]; + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + compressBlockAlphaFast(abuf, dline); + dline += 8; + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + dline += 8; + sline += 16; + } + if (edgew) { + uint8 abuf[4 * 4] = { 0 }; + uint32 buf[4 * 4] = { 0 }; + uint8* line = sline; + for (int y = 0; y < edgeh; ++y) { + for (int x = 0; x < edgew; ++x) { + abuf[x * 4 + y] = line[x * 4 + 3]; + buf[x * 4 + y] = *(uint32*)(line + x * 4); + } + line += pitch; + } + compressBlockAlphaFast(abuf, dline); + dline += 8; + *(uint64*)dline = _f_rgb_etc2((uint8*)buf); + } + } + + TJSAlignedDealloc(pixel); + return data; +} + +} \ No newline at end of file diff --git a/src/core/visual/ogl/etcpak.h b/src/core/visual/ogl/etcpak.h new file mode 100644 index 00000000..117b58f4 --- /dev/null +++ b/src/core/visual/ogl/etcpak.h @@ -0,0 +1,11 @@ +#pragma once +#include + +// from https://bitbucket.org/wolfpld/etcpak.git + +namespace ETCPacker { + void* convert(const void *pixel, int w, int h, int pitch, bool etc2, size_t &datalen); + void* convertWithAlpha(const void *pixel, int w, int h, int pitch, size_t &datalen); + void decode(const void *data, void* pixel, int pitch, int h, int blkw, int blkh); + void decodeWithAlpha(const void *data, void* pixel, int pitch, int h, int blkw, int blkh); +} diff --git a/src/core/visual/ogl/imagepacker.cpp b/src/core/visual/ogl/imagepacker.cpp new file mode 100644 index 00000000..34a644c3 --- /dev/null +++ b/src/core/visual/ogl/imagepacker.cpp @@ -0,0 +1,348 @@ +#include "imagepacker.h" +#include +#include + +using namespace std; + +namespace ImagePacker { + +bool area(rect_xywhf* a, rect_xywhf* b) { + return a->area() > b->area(); +} + +bool perimeter(rect_xywhf* a, rect_xywhf* b) { + return a->perimeter() > b->perimeter(); +} + +bool max_side(rect_xywhf* a, rect_xywhf* b) { + return std::max(a->w, a->h) > std::max(b->w, b->h); +} + +bool max_width(rect_xywhf* a, rect_xywhf* b) { + return a->w > b->w; +} + +bool max_height(rect_xywhf* a, rect_xywhf* b) { + return a->h > b->h; +} + + +// just add another comparing function name to cmpf to perform another packing attempt +// more functions == slower but probably more efficient cases covered and hence less area wasted + +static bool(*cmpf[])(rect_xywhf*, rect_xywhf*) = { + area, + perimeter, + max_side, + max_width, + max_height +}; + +// if you find the algorithm running too slow you may double this factor to increase speed but also decrease efficiency +// 1 == most efficient, slowest +// efficiency may be still satisfying at 64 or even 256 with nice speedup + +int discard_step = 128; + +/* + +For every sorting function, algorithm will perform packing attempts beginning with a bin with width and height equal to max_side, +and decreasing its dimensions if it finds out that rectangles did actually fit, increasing otherwise. +Although, it's doing that in sort of binary search manner, so for every comparing function it will perform at most log2(max_side) packing attempts looking for the smallest possible bin size. +discard_step = 128 means that the algorithm will break of the searching loop if the rectangles fit but "it may be possible to fit them in a bin smaller by 128" +the bigger the value, the sooner the algorithm will finish but the rectangles will be packed less tightly. +use discard_step = 1 for maximum tightness. + +the algorithm was based on http://www.blackpawn.com/texts/lightmaps/default.html +the algorithm reuses the node tree so it doesn't reallocate them between searching attempts + +*/ + +/*************************************************************************** CHAOS BEGINS HERE */ + +struct node { + struct pnode { + node* pn; + bool fill; + + pnode() : fill(false), pn(0) {} + void set(int l, int t, int r, int b) { + if (!pn) pn = new node(rect_ltrb(l, t, r, b)); + else { + (*pn).rc = rect_ltrb(l, t, r, b); + (*pn).id = false; + } + fill = true; + } + }; + + pnode c[2]; + rect_ltrb rc; + bool id; + node(rect_ltrb rc = rect_ltrb()) : id(false), rc(rc) {} + + void reset(const rect_wh& r) { + id = false; + rc = rect_ltrb(0, 0, r.w, r.h); + delcheck(); + } + + node* insert(rect_xywhf& img) { + if (c[0].pn && c[0].fill) { + node* newn; + if (newn = c[0].pn->insert(img)) return newn; + return c[1].pn->insert(img); + } + + if (id) return 0; + int f = img.fits(rect_xywh(rc)); + + switch (f) { + case 0: return 0; +// case 1: img.flipped = false; break; +// case 2: img.flipped = true; break; + case 3: id = true; /*img.flipped = false;*/ return this; + case 4: id = true; /*img.flipped = true; */ return this; + } + + int iw = img.w, ih = img.h; +// int iw = (img.flipped ? img.h : img.w), ih = (img.flipped ? img.w : img.h); + + if (rc.w() - iw > rc.h() - ih) { + c[0].set(rc.l, rc.t, rc.l + iw, rc.b); + c[1].set(rc.l + iw, rc.t, rc.r, rc.b); + } else { + c[0].set(rc.l, rc.t, rc.r, rc.t + ih); + c[1].set(rc.l, rc.t + ih, rc.r, rc.b); + } + + return c[0].pn->insert(img); + } + + void delcheck() { + if (c[0].pn) { c[0].fill = false; c[0].pn->delcheck(); } + if (c[1].pn) { c[1].fill = false; c[1].pn->delcheck(); } + } + + ~node() { + if (c[0].pn) delete c[0].pn; + if (c[1].pn) delete c[1].pn; + } +}; + +rect_wh _rect2D(rect_xywhf* const * v, int n, int max_s, vector& succ, vector& unsucc) { + node root; + + const int funcs = (sizeof(cmpf) / sizeof(bool(*)(rect_xywhf*, rect_xywhf*))); + + rect_xywhf** order[funcs]; + + for (int f = 0; f < funcs; ++f) { + order[f] = new rect_xywhf*[n]; + memcpy(order[f], v, sizeof(rect_xywhf*) * n); + sort(order[f], order[f] + n, cmpf[f]); + } + + rect_wh min_bin = rect_wh(max_s, max_s); + int min_func = -1, best_func = 0, best_area = 0, _area = 0, step, fit, i; + + bool fail = false; + + for (int f = 0; f < funcs; ++f) { + v = order[f]; + step = min_bin.w / 2; + root.reset(min_bin); + + while (true) { + if (root.rc.w() > min_bin.w) { + if (min_func > -1) break; + _area = 0; + + root.reset(min_bin); + for (i = 0; i < n; ++i) + if (root.insert(*v[i])) + _area += v[i]->area(); + + fail = true; + break; + } + + fit = -1; + + for (i = 0; i < n; ++i) + if (!root.insert(*v[i])) { + fit = 1; + break; + } + + if (fit == -1 && step <= discard_step) + break; + + root.reset(rect_wh(root.rc.w() + fit*step, root.rc.h() + fit*step)); + + step /= 2; + if (!step) + step = 1; + } + + if (!fail && (min_bin.area() >= root.rc.area())) { + min_bin = rect_wh(root.rc); + min_func = f; + } + + else if (fail && (_area > best_area)) { + best_area = _area; + best_func = f; + } + fail = false; + } + + v = order[min_func == -1 ? best_func : min_func]; + + int clip_x = 0, clip_y = 0; + node* ret; + + root.reset(min_bin); + + for (i = 0; i < n; ++i) { + if (ret = root.insert(*v[i])) { + v[i]->x = ret->rc.l; + v[i]->y = ret->rc.t; + +// if (v[i]->flipped) { +// v[i]->flipped = false; +// v[i]->flip(); +// } + + clip_x = std::max(clip_x, ret->rc.r); + clip_y = std::max(clip_y, ret->rc.b); + + succ.push_back(v[i]); + } else { + unsucc.push_back(v[i]); + +// v[i]->flipped = false; + } + } + + for (int f = 0; f < funcs; ++f) + delete[] order[f]; + + return rect_wh(clip_x, clip_y); +} + + +bool pack(rect_xywhf* const * v, int n, int max_s, vector& bins) { + rect_wh _rect(max_s, max_s); + + for (int i = 0; i < n; ++i) + if (!v[i]->fits(_rect)) return false; + + vector vec[2], *p[2] = { vec, vec + 1 }; + vec[0].resize(n); + vec[1].clear(); + memcpy(&vec[0][0], v, sizeof(rect_xywhf*)*n); + + bin* b = 0; + + while (true) { + bins.push_back(bin()); + b = &bins[bins.size() - 1]; + + b->size = _rect2D(&((*p[0])[0]), p[0]->size(), max_s, b->rects, *p[1]); + b->rects.shrink_to_fit(); + p[0]->clear(); + + if (!p[1]->size()) break; + + std::swap(p[0], p[1]); + } + + return true; +} + + +rect_wh::rect_wh(const rect_ltrb& rr) : w(rr.w()), h(rr.h()) {} +rect_wh::rect_wh(const rect_xywh& rr) : w(rr.w), h(rr.h) {} +rect_wh::rect_wh(int w, int h) : w(w), h(h) {} + +int rect_wh::fits(const rect_wh& r) const { + if (w == r.w && h == r.h) return 3; +// if (h == r.w && w == r.h) return 4; + if (w <= r.w && h <= r.h) return 1; +// if (h <= r.w && w <= r.h) return 2; + return 0; +} + +rect_ltrb::rect_ltrb() : l(0), t(0), r(0), b(0) {} +rect_ltrb::rect_ltrb(int l, int t, int r, int b) : l(l), t(t), r(r), b(b) {} + +int rect_ltrb::w() const { + return r - l; +} + +int rect_ltrb::h() const { + return b - t; +} + +int rect_ltrb::area() const { + return w()*h(); +} + +int rect_ltrb::perimeter() const { + return 2 * w() + 2 * h(); +} + +void rect_ltrb::w(int ww) { + r = l + ww; +} + +void rect_ltrb::h(int hh) { + b = t + hh; +} + +rect_xywh::rect_xywh() : x(0), y(0) {} +rect_xywh::rect_xywh(const rect_ltrb& rc) : x(rc.l), y(rc.t) { b(rc.b); r(rc.r); } +rect_xywh::rect_xywh(int x, int y, int w, int h) : x(x), y(y), rect_wh(w, h) {} + +rect_xywh::operator rect_ltrb() { + rect_ltrb rr(x, y, 0, 0); + rr.w(w); rr.h(h); + return rr; +} + +int rect_xywh::r() const { + return x + w; +}; + +int rect_xywh::b() const { + return y + h; +} + +void rect_xywh::r(int right) { + w = right - x; +} + +void rect_xywh::b(int bottom) { + h = bottom - y; +} + +int rect_wh::area() { + return w*h; +} + +int rect_wh::perimeter() { + return 2 * w + 2 * h; +} + + +rect_xywhf::rect_xywhf(const rect_ltrb& rr) : rect_xywh(rr)/*, flipped(false)*/ {} +rect_xywhf::rect_xywhf(int x, int y, int width, int height) : rect_xywh(x, y, width, height)/*, flipped(false)*/ {} +rect_xywhf::rect_xywhf() /*: flipped(false)*/ {} + +// void rect_xywhf::flip() { +// flipped = !flipped; +// std::swap(w, h); +// } + +} \ No newline at end of file diff --git a/src/core/visual/ogl/imagepacker.h b/src/core/visual/ogl/imagepacker.h new file mode 100644 index 00000000..97a74447 --- /dev/null +++ b/src/core/visual/ogl/imagepacker.h @@ -0,0 +1,92 @@ +#ifndef IMAGEPACKER_H +#define IMAGEPACKER_H + +// from https://github.com/TeamHypersomnia/rectpack2D + +#include +#include "ComplexRect.h" + +namespace ImagePacker { + +/* of your interest: + +1. rect_xywhf - structure representing your rectangle object +members: +int x, y, w, h; +bool flipped; + +2. bin - structure representing resultant bin object +3. bool pack(rect_xywhf* const * v, int n, int max_side, std::vector& bins) - actual packing function +Arguments: +input/output: v - pointer to array of pointers to your rectangles (const here means that the pointers will point to the same rectangles after the call) +input: n - rectangles count + +input: max_side - maximum bins' side - algorithm works with square bins (in the end it may trim them to rectangular form). +for the algorithm to finish faster, pass a reasonable value (unreasonable would be passing 1 000 000 000 for packing 4 50x50 rectangles). +output: bins - vector to which the function will push_back() created bins, each of them containing vector to pointers of rectangles from "v" belonging to that particular bin. +Every bin also keeps information about its width and height of course, none of the dimensions is bigger than max_side. + +returns true on success, false if one of the rectangles' dimension was bigger than max_side + +You want to your rectangles representing your textures/glyph objects with GL_MAX_TEXTURE_SIZE as max_side, +then for each bin iterate through its rectangles, typecast each one to your own structure (or manually add userdata) and then memcpy its pixel contents (rotated by 90 degrees if "flipped" rect_xywhf's member is true) +to the array representing your texture atlas to the place specified by the rectangle, then finally upload it with glTexImage2D. + +Algorithm doesn't create any new rectangles. +You just pass an array of pointers - rectangles' x/y/w/h/flipped are modified in place. +There is a vector of pointers for every resultant bin to let you know which ones belong to the particular bin. +The algorithm may swap the w and h fields for the sake of better fitting, the flag "flipped" will be set to true whenever this occurs. + +For description how to tune the algorithm and how it actually works see the .cpp file. + + +*/ + +struct rect_ltrb; +struct rect_xywh; + +struct rect_wh { + rect_wh(const rect_ltrb&); + rect_wh(const rect_xywh&); + rect_wh(int w = 0, int h = 0); + int w, h, area(), perimeter(), + fits(const rect_wh& bigger) const; // 0 - no, 1 - yes, 2 - flipped, 3 - perfectly, 4 perfectly flipped +}; + +// rectangle implementing left/top/right/bottom behaviour + +struct rect_ltrb { + rect_ltrb(); + rect_ltrb(int left, int top, int right, int bottom); + int l, t, r, b, w() const, h() const, area() const, perimeter() const; + void w(int), h(int); +}; + +struct rect_xywh : public rect_wh { + rect_xywh(); + rect_xywh(const rect_ltrb&); + rect_xywh(int x, int y, int width, int height); + operator rect_ltrb(); + + int x, y, r() const, b() const; + void r(int), b(int); +}; + +struct rect_xywhf : public rect_xywh { + rect_xywhf(const rect_ltrb&); + rect_xywhf(int x, int y, int width, int height); + rect_xywhf(); +// void flip(); +// bool flipped = false; +}; + +struct bin { + rect_wh size; + std::vector rects; +}; + +bool pack(rect_xywhf* const * v, int n, int max_side, std::vector& bins); + +} + +#endif // IMAGEPACKER_H diff --git a/src/core/visual/ogl/ogl_common.h b/src/core/visual/ogl/ogl_common.h index 59e64dd6..6975f928 100644 --- a/src/core/visual/ogl/ogl_common.h +++ b/src/core/visual/ogl/ogl_common.h @@ -4,7 +4,6 @@ #define GLEW_STATIC #endif #include "GL/glew.h" -#define GL_ETC1_RGB8_OES 0x8D64 #else #ifndef GL_UNPACK_ROW_LENGTH #define GL_UNPACK_ROW_LENGTH 0x0CF2 diff --git a/src/core/visual/ogl/pvr.h b/src/core/visual/ogl/pvr.h new file mode 100644 index 00000000..17bc5e6d --- /dev/null +++ b/src/core/visual/ogl/pvr.h @@ -0,0 +1,65 @@ +#pragma once +#include + +#pragma pack(push, 1) +struct PVRv3Header +{ + uint32_t version; + uint32_t flags; + uint64_t pixelFormat; + uint32_t colorSpace; + uint32_t channelType; + uint32_t height; + uint32_t width; + uint32_t depth; + uint32_t numberOfSurfaces; + uint32_t numberOfFaces; + uint32_t numberOfMipmaps; + uint32_t metadataLength; +}; +enum class PVR3TexturePixelFormat : uint64_t +{ + PVRTC2BPP_RGB = 0ULL, + PVRTC2BPP_RGBA = 1ULL, + PVRTC4BPP_RGB = 2ULL, + PVRTC4BPP_RGBA = 3ULL, + PVRTC2_2BPP_RGBA = 4ULL, + PVRTC2_4BPP_RGBA = 5ULL, + ETC1 = 6ULL, + DXT1 = 7ULL, + DXT2 = 8ULL, + DXT3 = 9ULL, + DXT4 = 10ULL, + DXT5 = 11ULL, + BC1 = 7ULL, + BC2 = 9ULL, + BC3 = 11ULL, + BC4 = 12ULL, + BC5 = 13ULL, + BC6 = 14ULL, + BC7 = 15ULL, + UYVY = 16ULL, + YUY2 = 17ULL, + BW1bpp = 18ULL, + R9G9B9E5 = 19ULL, + RGBG8888 = 20ULL, + GRGB8888 = 21ULL, + ETC2_RGB = 22ULL, + ETC2_RGBA = 23ULL, + ETC2_RGBA1 = 24ULL, + EAC_R11_Unsigned = 25ULL, + EAC_R11_Signed = 26ULL, + EAC_RG11_Unsigned = 27ULL, + EAC_RG11_Signed = 28ULL, + + BGRA8888 = 0x0808080861726762ULL, + RGBA8888 = 0x0808080861626772ULL, + RGBA4444 = 0x0404040461626772ULL, + RGBA5551 = 0x0105050561626772ULL, + RGB565 = 0x0005060500626772ULL, + RGB888 = 0x0008080800626772ULL, + A8 = 0x0000000800000061ULL, + L8 = 0x000000080000006cULL, + LA88 = 0x000008080000616cULL, +}; +#pragma pack(pop) \ No newline at end of file diff --git a/src/core/visual/ogl/pvrtc.cpp b/src/core/visual/ogl/pvrtc.cpp new file mode 100644 index 00000000..b39fb566 --- /dev/null +++ b/src/core/visual/ogl/pvrtc.cpp @@ -0,0 +1,957 @@ +#include "pvrtc.h" + +#include +#include +#include +#include +#include +#include + +//============================================================================ +// +// Modulation data specifies weightings of colorA to colorB for each pixel +// +// For mode = 0 +// 00: 0/8 +// 01: 3/8 +// 10: 5/8 +// 11: 8/8 +// +// For mode = 1 +// 00: 0/8 +// 01: 4/8 +// 10: 4/8 with alpha punchthrough +// 11: 8/8 +// +// For colorIsOpaque=0 +// 3 bits A +// 4 bits R +// 4 bits G +// 3/4 bits B +// +// For colorIsOpaque=1 +// 5 bits R +// 5 bits G +// 4/5 bits B +// +//============================================================================ + + +namespace PvrTcEncoder { +static const unsigned char MODULATION_LUT[16] = { + 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3 +}; +constexpr unsigned short MORTON_TABLE[256] = +{ + 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, + 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055, + 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, + 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, + 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, + 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455, + 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, + 0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, + 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, + 0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, + 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, + 0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, + 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, + 0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, + 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, + 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, + 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015, + 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, + 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, + 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, + 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, + 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, + 0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, + 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, + 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, + 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, + 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, + 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, + 0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, + 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, + 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, + 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555 +}; +namespace Data { +constexpr uint8_t BITSCALE_5_TO_8[32] = { + 0, 8, 16, 24, 32, 41, 49, 57, 65, 74, + 82, 90, 98, 106, 115, 123, 131, 139, 148, 156, + 164, 172, 180, 189, 197, 205, 213, 222, 230, 238, + 246, 255 }; + +constexpr uint8_t BITSCALE_4_TO_8[16] = { + 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, + 170, 187, 204, 221, 238, 255 }; + +constexpr uint8_t BITSCALE_3_TO_8[8] = { + 0, 36, 72, 109, 145, 182, 218, 255 }; + +constexpr uint8_t BITSCALE_8_TO_5_FLOOR[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, + 10, 11, 11, 11, 11, 11, 11, 11, 11, 12, + 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, + 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, + 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, + 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, + 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, + 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, + 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, + 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, + 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, + 30, 30, 30, 30, 30, 31 }; + +constexpr uint8_t BITSCALE_8_TO_4_FLOOR[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 15 }; + +constexpr uint8_t BITSCALE_8_TO_3_FLOOR[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 7 }; + +constexpr uint8_t BITSCALE_8_TO_5_CEIL[256] = { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, + 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, + 11, 12, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, + 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, + 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, + 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, + 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, + 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, + 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, + 31, 31, 31, 31, 31, 31 }; + +constexpr uint8_t BITSCALE_8_TO_4_CEIL[256] = { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15 }; + +constexpr uint8_t BITSCALE_8_TO_3_CEIL[256] = { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7 }; +} +template +class Point2 { +public: + T x; + T y; + + Point2(int a, int b) + : x(a) + , y(b) { + } +}; + +class BitUtility { +public: + static bool IsPowerOf2(unsigned int x) { + return (x & (x - 1)) == 0; + } + + static unsigned int RotateRight(unsigned int value, unsigned int shift) { + if ((shift &= sizeof(value) * 8 - 1) == 0) { + return value; + } + return (value >> shift) | (value << (sizeof(value) * 8 - shift)); + } +}; + +template +class ColorRgb { +public: + T r; + T g; + T b; + + + ColorRgb() + : r(0) + , g(0) + , b(0) { + } + + ColorRgb(T red, T green, T blue) + : r(red) + , g(green) + , b(blue) { + } + + ColorRgb(const ColorRgb &x) + : r(x.r) + , g(x.g) + , b(x.b) { + } + + ColorRgb operator *(int x) { + return ColorRgb(r * x, g * x, b * x); + } + + ColorRgb operator +(const ColorRgb &x) const { + return ColorRgb(r + (int)x.r, g + (int)x.g, b + (int)x.b); + } + + ColorRgb operator -(const ColorRgb &x) const { + return ColorRgb(r - (int)x.r, g - (int)x.g, b - (int)x.b); + } + + int operator %(const ColorRgb &x) const { + return r * (int)x.r + g * (int)x.g + b * (int)x.b; + } + + bool operator ==(const ColorRgb &x) const { + return r == x.r && g == x.g && b == x.b; + } + + bool operator !=(const ColorRgb &x) const { + return r != x.r || g != x.g || b != x.b; + } + + void SetMin(const ColorRgb &x) { + if (x.r < r) { + r = x.r; + } + if (x.g < g) { + g = x.g; + } + if (x.b < b) { + b = x.b; + } + } + + void SetMax(const ColorRgb &x) { + if (x.r > r) { + r = x.r; + } + if (x.g > g) { + g = x.g; + } + if (x.b > b) { + b = x.b; + } + } +}; + +template +class ColorRgba : public ColorRgb { +public: + T a; + + ColorRgba() : + a(0) { + } + + ColorRgba(T red, T green, T blue, T alpha) + : ColorRgb(red, green, blue) + , a(alpha) { + } + + ColorRgba(const ColorRgba &x) + : ColorRgb(x.r, x.g, x.b) + , a(x.a) { + } + + ColorRgba operator *(int x) { + return ColorRgba(ColorRgb::r * x, + ColorRgb::g * x, + ColorRgb::b * x, + a * x); + } + + ColorRgba operator +(const ColorRgba &x) { + return ColorRgba(ColorRgb::r + (int)x.r, + ColorRgb::g + (int)x.g, + ColorRgb::b + (int)x.b, + a + (int)x.a); + } + + ColorRgba operator -(const ColorRgba &x) { + return ColorRgba(ColorRgb::r - (int)x.r, + ColorRgb::g - (int)x.g, + ColorRgb::b - (int)x.b, + a - (int)x.a); + } + + int operator %(const ColorRgba &x) { + return ColorRgb::r * (int)x.r + + ColorRgb::g * (int)x.g + + ColorRgb::b * (int)x.b + + a * (int)x.a; + } + + bool operator ==(const ColorRgba &x) { + return ColorRgb::r == x.r && ColorRgb::g == x.g && + ColorRgb::b == x.b && a == x.a; + } + + bool operator !=(const ColorRgba &x) { + return ColorRgb::r != x.r || ColorRgb::g != x.g || + ColorRgb::b != x.b || a != x.a; + } + + void SetMin(const ColorRgba &x) { + ColorRgb::SetMin(x); + if (x.a < a) { + a = x.a; + } + } + + void SetMax(const ColorRgba &x) { + ColorRgb::SetMax(x); + if (x.a > a) { + a = x.a; + } + } +}; +//============================================================================ + +struct PvrTcPacket +{ + unsigned int modulationData; + unsigned usePunchthroughAlpha : 1; + unsigned colorA : 14; + unsigned colorAIsOpaque : 1; + unsigned colorB : 15; + unsigned colorBIsOpaque : 1; + + ColorRgb GetColorRgbA() const; + ColorRgb GetColorRgbB() const; + ColorRgba GetColorRgbaA() const; + ColorRgba GetColorRgbaB() const; + + void SetColorA(const ColorRgb& c); + void SetColorB(const ColorRgb& c); + + void SetColorA(const ColorRgba& c); + void SetColorB(const ColorRgba& c); + + static const unsigned char BILINEAR_FACTORS[16][4]; + static const unsigned char WEIGHTS[8][4]; +}; + +const unsigned char PvrTcPacket::BILINEAR_FACTORS[16][4] = +{ + { 4, 4, 4, 4 }, + { 2, 6, 2, 6 }, + { 8, 0, 8, 0 }, + { 6, 2, 6, 2 }, + + { 2, 2, 6, 6 }, + { 1, 3, 3, 9 }, + { 4, 0, 12, 0 }, + { 3, 1, 9, 3 }, + + { 8, 8, 0, 0 }, + { 4, 12, 0, 0 }, + { 16, 0, 0, 0 }, + { 12, 4, 0, 0 }, + + { 6, 6, 2, 2 }, + { 3, 9, 1, 3 }, + { 12, 0, 4, 0 }, + { 9, 3, 3, 1 }, +}; + +// Weights are { colorA, colorB, alphaA, alphaB } +const unsigned char PvrTcPacket::WEIGHTS[8][4] = +{ + // Weights for Mode=0 + { 8, 0, 8, 0 }, + { 5, 3, 5, 3 }, + { 3, 5, 3, 5 }, + { 0, 8, 0, 8 }, + + // Weights for Mode=1 + { 8, 0, 8, 0 }, + { 4, 4, 4, 4 }, + { 4, 4, 0, 0 }, + { 0, 8, 0, 8 }, +}; + +//============================================================================ + +ColorRgb PvrTcPacket::GetColorRgbA() const +{ + if (colorAIsOpaque) + { + unsigned char r = colorA >> 9; + unsigned char g = colorA >> 4 & 0x1f; + unsigned char b = colorA & 0xf; + return ColorRgb(Data::BITSCALE_5_TO_8[r], + Data::BITSCALE_5_TO_8[g], + Data::BITSCALE_4_TO_8[b]); + } else + { + unsigned char r = (colorA >> 7) & 0xf; + unsigned char g = (colorA >> 3) & 0xf; + unsigned char b = colorA & 7; + return ColorRgb(Data::BITSCALE_4_TO_8[r], + Data::BITSCALE_4_TO_8[g], + Data::BITSCALE_3_TO_8[b]); + } +} + +ColorRgb PvrTcPacket::GetColorRgbB() const +{ + if (colorBIsOpaque) + { + unsigned char r = colorB >> 10; + unsigned char g = colorB >> 5 & 0x1f; + unsigned char b = colorB & 0x1f; + return ColorRgb(Data::BITSCALE_5_TO_8[r], + Data::BITSCALE_5_TO_8[g], + Data::BITSCALE_5_TO_8[b]); + } else + { + unsigned char r = colorB >> 8 & 0xf; + unsigned char g = colorB >> 4 & 0xf; + unsigned char b = colorB & 0xf; + return ColorRgb(Data::BITSCALE_4_TO_8[r], + Data::BITSCALE_4_TO_8[g], + Data::BITSCALE_4_TO_8[b]); + } +} + +ColorRgba PvrTcPacket::GetColorRgbaA() const +{ + if (colorAIsOpaque) + { + unsigned char r = colorA >> 9; + unsigned char g = colorA >> 4 & 0x1f; + unsigned char b = colorA & 0xf; + return ColorRgba(Data::BITSCALE_5_TO_8[r], + Data::BITSCALE_5_TO_8[g], + Data::BITSCALE_4_TO_8[b], + 255); + } else + { + unsigned char a = colorA >> 11 & 7; + unsigned char r = colorA >> 7 & 0xf; + unsigned char g = colorA >> 3 & 0xf; + unsigned char b = colorA & 7; + return ColorRgba(Data::BITSCALE_4_TO_8[r], + Data::BITSCALE_4_TO_8[g], + Data::BITSCALE_3_TO_8[b], + Data::BITSCALE_3_TO_8[a]); + } +} + +ColorRgba PvrTcPacket::GetColorRgbaB() const +{ + if (colorBIsOpaque) + { + unsigned char r = colorB >> 10; + unsigned char g = colorB >> 5 & 0x1f; + unsigned char b = colorB & 0x1f; + return ColorRgba(Data::BITSCALE_5_TO_8[r], + Data::BITSCALE_5_TO_8[g], + Data::BITSCALE_5_TO_8[b], + 255); + } else + { + unsigned char a = colorB >> 12 & 7; + unsigned char r = colorB >> 8 & 0xf; + unsigned char g = colorB >> 4 & 0xf; + unsigned char b = colorB & 0xf; + return ColorRgba(Data::BITSCALE_4_TO_8[r], + Data::BITSCALE_4_TO_8[g], + Data::BITSCALE_4_TO_8[b], + Data::BITSCALE_3_TO_8[a]); + } +} + +//============================================================================ + +void PvrTcPacket::SetColorA(const ColorRgb& c) +{ + int r = Data::BITSCALE_8_TO_5_FLOOR[c.r]; + int g = Data::BITSCALE_8_TO_5_FLOOR[c.g]; + int b = Data::BITSCALE_8_TO_4_FLOOR[c.b]; + colorA = r << 9 | g << 4 | b; + colorAIsOpaque = true; +} + +void PvrTcPacket::SetColorB(const ColorRgb& c) +{ + int r = Data::BITSCALE_8_TO_5_CEIL[c.r]; + int g = Data::BITSCALE_8_TO_5_CEIL[c.g]; + int b = Data::BITSCALE_8_TO_5_CEIL[c.b]; + colorB = r << 10 | g << 5 | b; + colorBIsOpaque = true; +} + +void PvrTcPacket::SetColorA(const ColorRgba& c) +{ + int a = Data::BITSCALE_8_TO_3_FLOOR[c.a]; + if (a == 7) + { + int r = Data::BITSCALE_8_TO_5_FLOOR[c.r]; + int g = Data::BITSCALE_8_TO_5_FLOOR[c.g]; + int b = Data::BITSCALE_8_TO_4_FLOOR[c.b]; + colorA = r << 9 | g << 4 | b; + colorAIsOpaque = true; + } else + { + int r = Data::BITSCALE_8_TO_4_FLOOR[c.r]; + int g = Data::BITSCALE_8_TO_4_FLOOR[c.g]; + int b = Data::BITSCALE_8_TO_3_FLOOR[c.b]; + colorA = a << 11 | r << 7 | g << 3 | b; + colorAIsOpaque = false; + } +} + +void PvrTcPacket::SetColorB(const ColorRgba& c) +{ + int a = Data::BITSCALE_8_TO_3_CEIL[c.a]; + if (a == 7) + { + int r = Data::BITSCALE_8_TO_5_CEIL[c.r]; + int g = Data::BITSCALE_8_TO_5_CEIL[c.g]; + int b = Data::BITSCALE_8_TO_5_CEIL[c.b]; + colorB = r << 10 | g << 5 | b; + colorBIsOpaque = true; + } else + { + int r = Data::BITSCALE_8_TO_4_CEIL[c.r]; + int g = Data::BITSCALE_8_TO_4_CEIL[c.g]; + int b = Data::BITSCALE_8_TO_4_CEIL[c.b]; + colorB = a << 12 | r << 8 | g << 4 | b; + colorBIsOpaque = false; + } +} + +//============================================================================ + +class Bitmap { +public: + int width; + int height; + unsigned char *data; + + Bitmap(int w, int h, int bytesPerPixel, unsigned char * pix) + : width(w) + , height(h) + , data(pix) { + } + + virtual ~Bitmap() { + // delete[] data; + } + + Point2 GetSize() const { return Point2(width, height); } + + int GetArea() const { return width * height; } + + int GetBitmapWidth() const { return width; } + + int GetBitmapHeight() const { return height; } + + const unsigned char *GetRawData() const { return data; } +}; + +class RgbaBitmap : public Bitmap { +public: + RgbaBitmap(int w, int h, unsigned char * pix) + : Bitmap(w, h, 4, pix) { + } + + const ColorRgba *GetData() const { + return reinterpret_cast *>(data); + } + + ColorRgba *GetData() { + return reinterpret_cast *>(data); + } +}; + +static unsigned GetMortonNumber(int x, int y) +{ + return MORTON_TABLE[x >> 8] << 17 | MORTON_TABLE[y >> 8] << 16 | MORTON_TABLE[x & 0xFF] << 1 | MORTON_TABLE[y & 0xFF]; +} +//============================================================================ + +template +class Interval { +public: + T min; + T max; + + Interval() { + } + + Interval &operator|=(const T &x) { + min.SetMin(x); + max.SetMax(x); + return *this; + } +}; +typedef Interval> ColorRgbBoundingBox; + +//============================================================================ + +static void CalculateBoundingBox(ColorRgbBoundingBox& cbb, const RgbaBitmap& bitmap, int blockX, int blockY) +{ + int size = bitmap.GetBitmapWidth(); + const ColorRgba* data = bitmap.GetData() + blockY * 4 * size + blockX * 4; + + cbb.min = data[0]; + cbb.max = data[0]; + + cbb |= data[1]; + cbb |= data[2]; + cbb |= data[3]; + + cbb |= data[size]; + cbb |= data[size+1]; + cbb |= data[size+2]; + cbb |= data[size+3]; + + cbb |= data[2*size]; + cbb |= data[2*size+1]; + cbb |= data[2*size+2]; + cbb |= data[2*size+3]; + + cbb |= data[3*size]; + cbb |= data[3*size+1]; + cbb |= data[3*size+2]; + cbb |= data[3*size+3]; +} + +void EncodeRgb4Bpp(void* result, const RgbaBitmap& bitmap) +{ + assert(bitmap.GetBitmapWidth() == bitmap.GetBitmapHeight()); + assert(BitUtility::IsPowerOf2(bitmap.GetBitmapWidth())); + const int size = bitmap.GetBitmapWidth(); + const int blocks = size / 4; + const int blockMask = blocks-1; + + PvrTcPacket* packets = static_cast(result); + + for(int y = 0; y < blocks; ++y) + { + for(int x = 0; x < blocks; ++x) + { + ColorRgbBoundingBox cbb; + CalculateBoundingBox(cbb, bitmap, x, y); + PvrTcPacket* packet = packets + GetMortonNumber(x, y); + packet->usePunchthroughAlpha = 0; + packet->SetColorA(cbb.min); + packet->SetColorB(cbb.max); + } + } + + for(int y = 0; y < blocks; ++y) + { + for(int x = 0; x < blocks; ++x) + { + const unsigned char (*factor)[4] = PvrTcPacket::BILINEAR_FACTORS; + const ColorRgba* data = bitmap.GetData() + y * 4 * size + x * 4; + + uint32_t modulationData = 0; + + for(int py = 0; py < 4; ++py) + { + const int yOffset = (py < 2) ? -1 : 0; + const int y0 = (y + yOffset) & blockMask; + const int y1 = (y0+1) & blockMask; + + for(int px = 0; px < 4; ++px) + { + const int xOffset = (px < 2) ? -1 : 0; + const int x0 = (x + xOffset) & blockMask; + const int x1 = (x0+1) & blockMask; + + const PvrTcPacket* p0 = packets + GetMortonNumber(x0, y0); + const PvrTcPacket* p1 = packets + GetMortonNumber(x1, y0); + const PvrTcPacket* p2 = packets + GetMortonNumber(x0, y1); + const PvrTcPacket* p3 = packets + GetMortonNumber(x1, y1); + + ColorRgb ca = p0->GetColorRgbA() * (*factor)[0] + + p1->GetColorRgbA() * (*factor)[1] + + p2->GetColorRgbA() * (*factor)[2] + + p3->GetColorRgbA() * (*factor)[3]; + + ColorRgb cb = p0->GetColorRgbB() * (*factor)[0] + + p1->GetColorRgbB() * (*factor)[1] + + p2->GetColorRgbB() * (*factor)[2] + + p3->GetColorRgbB() * (*factor)[3]; + + const ColorRgb& pixel = data[py*size + px]; + ColorRgb d = cb - ca; + ColorRgb p{pixel.r*16, pixel.g*16, pixel.b*16}; + ColorRgb v = p - ca; + + // PVRTC uses weightings of 0, 3/8, 5/8 and 1 + // The boundaries for these are 3/16, 1/2 (=8/16), 13/16 + int projection = (v % d) * 16; + int lengthSquared = d % d; + if(projection > 3*lengthSquared) modulationData++; + if(projection > 8*lengthSquared) modulationData++; + if(projection > 13*lengthSquared) modulationData++; + + modulationData = BitUtility::RotateRight(modulationData, 2); + + factor++; + } + } + + PvrTcPacket* packet = packets + GetMortonNumber(x, y); + packet->modulationData = modulationData; + } + } +} + +//============================================================================ + +typedef Interval> ColorRgbaBoundingBox; + +static void CalculateBoundingBox(ColorRgbaBoundingBox& cbb, const RgbaBitmap& bitmap, int blockX, int blockY) +{ + int size = bitmap.GetBitmapWidth(); + const ColorRgba* data = bitmap.GetData() + blockY * 4 * size + blockX * 4; + + cbb.min = data[0]; + cbb.max = data[0]; + + cbb |= data[1]; + cbb |= data[2]; + cbb |= data[3]; + + cbb |= data[size]; + cbb |= data[size+1]; + cbb |= data[size+2]; + cbb |= data[size+3]; + + cbb |= data[2*size]; + cbb |= data[2*size+1]; + cbb |= data[2*size+2]; + cbb |= data[2*size+3]; + + cbb |= data[3*size]; + cbb |= data[3*size+1]; + cbb |= data[3*size+2]; + cbb |= data[3*size+3]; +} + +void EncodeRgba4Bpp(void* result, const RgbaBitmap& bitmap) +{ + assert(bitmap.GetBitmapWidth() == bitmap.GetBitmapHeight()); + assert(BitUtility::IsPowerOf2(bitmap.GetBitmapWidth())); + const int size = bitmap.GetBitmapWidth(); + const int blocks = size / 4; + const int blockMask = blocks-1; + + PvrTcPacket* packets = static_cast(result); + + for(int y = 0; y < blocks; ++y) + { + for(int x = 0; x < blocks; ++x) + { + ColorRgbaBoundingBox cbb; + CalculateBoundingBox(cbb, bitmap, x, y); + PvrTcPacket* packet = packets + GetMortonNumber(x, y); + packet->usePunchthroughAlpha = 0; + packet->SetColorA(cbb.min); + packet->SetColorB(cbb.max); + } + } + + for(int y = 0; y < blocks; ++y) + { + for(int x = 0; x < blocks; ++x) + { + const unsigned char (*factor)[4] = PvrTcPacket::BILINEAR_FACTORS; + const ColorRgba* data = bitmap.GetData() + y * 4 * size + x * 4; + + uint32_t modulationData = 0; + + for(int py = 0; py < 4; ++py) + { + const int yOffset = (py < 2) ? -1 : 0; + const int y0 = (y + yOffset) & blockMask; + const int y1 = (y0+1) & blockMask; + + for(int px = 0; px < 4; ++px) + { + const int xOffset = (px < 2) ? -1 : 0; + const int x0 = (x + xOffset) & blockMask; + const int x1 = (x0+1) & blockMask; + + const PvrTcPacket* p0 = packets + GetMortonNumber(x0, y0); + const PvrTcPacket* p1 = packets + GetMortonNumber(x1, y0); + const PvrTcPacket* p2 = packets + GetMortonNumber(x0, y1); + const PvrTcPacket* p3 = packets + GetMortonNumber(x1, y1); + + ColorRgba ca = p0->GetColorRgbaA() * (*factor)[0] + + p1->GetColorRgbaA() * (*factor)[1] + + p2->GetColorRgbaA() * (*factor)[2] + + p3->GetColorRgbaA() * (*factor)[3]; + + ColorRgba cb = p0->GetColorRgbaB() * (*factor)[0] + + p1->GetColorRgbaB() * (*factor)[1] + + p2->GetColorRgbaB() * (*factor)[2] + + p3->GetColorRgbaB() * (*factor)[3]; + + const ColorRgba& pixel = data[py*size + px]; + ColorRgba d = cb - ca; + ColorRgba p{pixel.r*16, pixel.g*16, pixel.b*16, pixel.a*16}; + ColorRgba v = p - ca; + + // PVRTC uses weightings of 0, 3/8, 5/8 and 1 + // The boundaries for these are 3/16, 1/2 (=8/16), 13/16 + int projection = (v % d) * 16; + int lengthSquared = d % d; + if(projection > 3*lengthSquared) modulationData++; + if(projection > 8*lengthSquared) modulationData++; + if(projection > 13*lengthSquared) modulationData++; + + modulationData = BitUtility::RotateRight(modulationData, 2); + + factor++; + } + } + + PvrTcPacket* packet = packets + GetMortonNumber(x, y); + packet->modulationData = modulationData; + } + } +} + +void EncodeRgba4Bpp(const void *inBuf, void *outBuffer, unsigned int width, unsigned int height, bool isOpaque) { + RgbaBitmap bitmap(width, height, (unsigned char *)inBuf); + + if (isOpaque) { + EncodeRgb4Bpp(outBuffer, bitmap); + } else { + EncodeRgba4Bpp(outBuffer, bitmap); + } +} + +} \ No newline at end of file diff --git a/src/core/visual/ogl/pvrtc.h b/src/core/visual/ogl/pvrtc.h new file mode 100644 index 00000000..98a23103 --- /dev/null +++ b/src/core/visual/ogl/pvrtc.h @@ -0,0 +1,5 @@ +#pragma once +// from https://bitbucket.org/jthlim/pvrtccompressor +namespace PvrTcEncoder { + void EncodeRgba4Bpp(const void *inBuf, void *outBuffer, unsigned int width, unsigned int height, bool isOpaque); +} \ No newline at end of file diff --git a/src/core/visual/tvpgl.cpp b/src/core/visual/tvpgl.cpp index fc37515c..9ba38907 100644 --- a/src/core/visual/tvpgl.cpp +++ b/src/core/visual/tvpgl.cpp @@ -16,6 +16,7 @@ #include #include "tjsTypes.h" #include "tvpgl.h" +#include "Protect.h" #include #define __cdecl diff --git a/src/core/visual/win32/BasicDrawDevice.cpp b/src/core/visual/win32/BasicDrawDevice.cpp index 9ec189ea..ea90be4b 100644 --- a/src/core/visual/win32/BasicDrawDevice.cpp +++ b/src/core/visual/win32/BasicDrawDevice.cpp @@ -546,6 +546,14 @@ void TJS_INTF_METHOD tTVPBasicDrawDevice::NotifyLayerResize(iTVPLayerManager * m //--------------------------------------------------------------------------- void TJS_INTF_METHOD tTVPBasicDrawDevice::Show() { + if (Window) { + iWindowLayer *form = Window->GetForm(); + if (form && !Managers.empty()) { + iTVPBaseBitmap *buf = Managers.back()->GetDrawBuffer(); + if (buf); + form->UpdateDrawBuffer(buf->GetTexture()); + } + } #if 0 if(!TargetWindow) return; if(!Texture) return; diff --git a/src/core/visual/win32/LayerBitmapImpl.cpp b/src/core/visual/win32/LayerBitmapImpl.cpp index 3e4604f8..9c0bc3bf 100644 --- a/src/core/visual/win32/LayerBitmapImpl.cpp +++ b/src/core/visual/win32/LayerBitmapImpl.cpp @@ -331,7 +331,7 @@ static tTVPCharacterData * TVPGetCharacter(const tTVPFontAndCharacterData & font data->Alloc(newpitch * data->BlackBoxY); pfont->Retrieve(pitem, data->GetData(), newpitch); - + data->Gray = 256; // apply blur if(font.Blured) data->Blur(); // nasty ... @@ -848,7 +848,7 @@ bool tTVPNativeBaseBitmap::InternalBlendText( method = _method; opa_id = _opa_id; clr_id = _clr_id; static bool fastGPURoute = !TVPIsSoftwareRenderManager() - && !IndividualConfigManager::GetInstance()->GetValueBool("ogl_accurate_render", false); + && !IndividualConfigManager::GetInstance()->GetValue("ogl_accurate_render", false); iTVPTexture2D *pTexSrc; if (fastGPURoute && dtdata->bltmode == bmAlphaOnAlpha && dtdata->opa > 0) { @@ -873,10 +873,10 @@ bool tTVPNativeBaseBitmap::InternalBlendText( } } if (!_CharacterTextureRGBA) { - _CharacterTextureRGBA = GetRenderManager()->CreateTexture2D(tmp->GetBits(), dpitch, w, h, TVPTextureFormat::RGBA); + _CharacterTextureRGBA = GetRenderManager()->CreateTexture2D(tmp->GetBits(), dpitch, w, h, TVPTextureFormat::RGBA, RENDER_CREATE_TEXTURE_FLAG_NO_COMPRESS); } else if (_CharacterTextureRGBA->GetInternalWidth() < w || _CharacterTextureRGBA->GetInternalHeight() < h) { _CharacterTextureRGBA->Release(); - _CharacterTextureRGBA = GetRenderManager()->CreateTexture2D(tmp->GetBits(), dpitch, w, h, TVPTextureFormat::RGBA); + _CharacterTextureRGBA = GetRenderManager()->CreateTexture2D(tmp->GetBits(), dpitch, w, h, TVPTextureFormat::RGBA, RENDER_CREATE_TEXTURE_FLAG_NO_COMPRESS); } else { _CharacterTextureRGBA->Update(tmp->GetBits(), TVPTextureFormat::RGBA, dpitch, tTVPRect(0, 0, w, h)); } @@ -902,13 +902,12 @@ bool tTVPNativeBaseBitmap::InternalBlendText( // blend to the texture if (!_CharacterTexture) { - _CharacterTexture = GetRenderManager()->CreateTexture2D(bp, pitch, w, h, TVPTextureFormat::Gray); + _CharacterTexture = GetRenderManager()->CreateTexture2D(nullptr, pitch, w, h, TVPTextureFormat::Gray); } else if (_CharacterTexture->GetInternalWidth() < w || _CharacterTexture->GetInternalHeight() < h) { _CharacterTexture->Release(); - _CharacterTexture = GetRenderManager()->CreateTexture2D(bp, pitch, w, h, TVPTextureFormat::Gray); - } else { - _CharacterTexture->Update(bp, TVPTextureFormat::Gray, pitch, tTVPRect(0, 0, w, h)); + _CharacterTexture = GetRenderManager()->CreateTexture2D(nullptr, pitch, w, h, TVPTextureFormat::Gray); } + _CharacterTexture->Update(bp, TVPTextureFormat::Gray, pitch, tTVPRect(0, 0, w, h)); method->SetParameterOpa(opa_id, dtdata->opa); method->SetParameterColor4B(clr_id, color); diff --git a/src/core/visual/win32/VideoOvlImpl.cpp b/src/core/visual/win32/VideoOvlImpl.cpp index 8e602512..96800e7c 100644 --- a/src/core/visual/win32/VideoOvlImpl.cpp +++ b/src/core/visual/win32/VideoOvlImpl.cpp @@ -190,7 +190,7 @@ void tTJSNI_VideoOverlay::Open(const ttstr &_name) CachedOverlay->Release(); CachedOverlay = nullptr; } - if(Mode == vomLayer) + if (Mode == vomLayer) GetVideoLayerObject(EventQueue.GetOwner(), istream, name.c_str(), ext.c_str(), size, &VideoOverlay); else if(Mode == vomMixer) GetMixingVideoOverlayObject(EventQueue.GetOwner(), istream, name.c_str(), ext.c_str(), size, &VideoOverlay); @@ -199,7 +199,6 @@ void tTJSNI_VideoOverlay::Open(const ttstr &_name) else GetVideoOverlayObject(EventQueue.GetOwner(), istream, name.c_str(), ext.c_str(), size, &VideoOverlay); } - if( (Mode == vomOverlay) || (Mode == vomMixer) || (Mode == vomMFEVR) ) { ResetOverlayParams(); @@ -253,6 +252,7 @@ void tTJSNI_VideoOverlay::Close() CachedOverlay->Release(); CachedOverlay = nullptr; } + VideoOverlay->SetVisible(false); VideoOverlay->Pause(); CachedOverlay = VideoOverlay; VideoOverlay = NULL; @@ -286,6 +286,7 @@ void tTJSNI_VideoOverlay::Shutdown() CachedOverlay->Release(); CachedOverlay = nullptr; } + VideoOverlay->SetVisible(false); VideoOverlay->Pause(); CachedOverlay = VideoOverlay; VideoOverlay = NULL; @@ -852,13 +853,13 @@ tjs_int tTJSNI_VideoOverlay::GetAudioBalance() { VideoOverlay->GetAudioBalance( &result ); } - return TVPDSAttenuateToPan( result ); + return /*TVPDSAttenuateToPan*/( result ); } void tTJSNI_VideoOverlay::SetAudioBalance(tjs_int b) { if(VideoOverlay) { - VideoOverlay->SetAudioBalance( TVPPanToDSAttenuate( b ) ); + VideoOverlay->SetAudioBalance( /*TVPPanToDSAttenuate*/( b ) ); } } tjs_int tTJSNI_VideoOverlay::GetAudioVolume() @@ -868,13 +869,13 @@ tjs_int tTJSNI_VideoOverlay::GetAudioVolume() { VideoOverlay->GetAudioVolume( &result ); } - return TVPDSAttenuateToVolume( result ); + return /*TVPDSAttenuateToVolume*/( result ); } void tTJSNI_VideoOverlay::SetAudioVolume(tjs_int b) { if(VideoOverlay) { - VideoOverlay->SetAudioVolume( TVPVolumeToDSAttenuate( b ) ); + VideoOverlay->SetAudioVolume( /*TVPVolumeToDSAttenuate*/( b ) ); } } tjs_uint tTJSNI_VideoOverlay::GetNumberOfAudioStream() diff --git a/src/core/visual/win32/WindowImpl.cpp b/src/core/visual/win32/WindowImpl.cpp index 29a849e6..0811b94f 100644 --- a/src/core/visual/win32/WindowImpl.cpp +++ b/src/core/visual/win32/WindowImpl.cpp @@ -31,8 +31,8 @@ #include "Application.h" #include "TVPScreen.h" #include "tjsDictionary.h" -#include "VSyncTimingThread.h" -#include "MouseCursor.h" +//#include "VSyncTimingThread.h" +//#include "MouseCursor.h" iWindowLayer *TVPCreateAndAddWindow(tTJSNI_Window *w); #define MK_SHIFT 4 @@ -2032,8 +2032,7 @@ void TJS_INTF_METHOD tTJSNI_Window::NotifyBitmapCompleted(class iTVPLayerManager //--------------------------------------------------------------------------- void TJS_INTF_METHOD tTJSNI_Window::EndBitmapCompletion(iTVPLayerManager * manager) { - if (Form) Form->UpdateDrawBuffer(manager->GetDrawBuffer()); -// if( DrawDevice ) DrawDevice->EndBitmapCompletion(manager); + if( DrawDevice ) DrawDevice->EndBitmapCompletion(manager); } //--------------------------------------------------------------------------- void TJS_INTF_METHOD tTJSNI_Window::SetMouseCursor(class iTVPLayerManager* manager, tjs_int cursor) @@ -2154,8 +2153,8 @@ TJS_BEGIN_NATIVE_METHOD_DECL(registerMessageReceiver) if(numparams < 3) return TJS_E_BADPARAMCOUNT; _this->RegisterWindowMessageReceiver((tTVPWMRRegMode)((tjs_int)*param[0]), - reinterpret_cast((tjs_intptr_t)(*param[1])), - reinterpret_cast((tjs_intptr_t)(*param[2]))); + reinterpret_cast(param[1]->AsInteger()), + reinterpret_cast(param[2]->AsInteger())); return TJS_S_OK; } diff --git a/src/core/visual/win32/WindowImpl.h b/src/core/visual/win32/WindowImpl.h index e08d98b7..3ae19ed9 100644 --- a/src/core/visual/win32/WindowImpl.h +++ b/src/core/visual/win32/WindowImpl.h @@ -254,7 +254,7 @@ class tTJSNI_Window : public tTJSNI_BaseWindow bool CanDeliverEvents() const; // tTJSNI_BaseWindow::CanDeliverEvents override public: - TTVPWindowForm * GetForm() const { return Form; } + TTVPWindowForm * GetForm() const override { return Form; } void NotifyWindowClose(); void SendCloseMessage(); void TickBeat(); diff --git a/src/plugins/dirlist.cpp b/src/plugins/dirlist.cpp index a1b3c195..5d29fdb7 100644 --- a/src/plugins/dirlist.cpp +++ b/src/plugins/dirlist.cpp @@ -3,7 +3,6 @@ //#include "tp_stub.h" #include "tp_stub.h" #include "ncbind/ncbind.hpp" -#include //--------------------------------------------------------------------------- #define NCB_MODULE_NAME TJS_W("dirlist.dll") diff --git a/src/plugins/xp3filter.cpp b/src/plugins/xp3filter.cpp index fdeb65ff..0ab06602 100644 --- a/src/plugins/xp3filter.cpp +++ b/src/plugins/xp3filter.cpp @@ -186,7 +186,7 @@ tjs_error CBinaryAccessor::FuncXor(tjs_int numparams, tTJSVariant **param) unsigned char *buf = m_buff + m_curPos + bufoff; unsigned char *pend = buf + len; - if (len>16) + if (len>32) { int PreFragLen = (unsigned char*)((((intptr_t)buf) + 7)&~7) - buf; for (int i = 0; i < PreFragLen; i++) *(buf++) ^= xorval; @@ -319,10 +319,10 @@ static XP3FilterDecoder* AddXP3Decoder() { return decoder; } -#if (defined(_MSC_VER) && _MSC_VER <= 1800) || defined(CC_TARGET_OS_IPHONE) +static std::map _thread_decoders; +#if 1 || (defined(_MSC_VER) /*&& _MSC_VER <= 1800*/) || defined(CC_TARGET_OS_IPHONE) static std::mutex _decoders_mtx; static std::vector _cached_decoders; -static std::map _thread_decoders; static XP3FilterDecoder *FetchXP3Decoder() { std::lock_guard lk(_decoders_mtx); auto it = _thread_decoders.find(std::this_thread::get_id()); @@ -354,12 +354,12 @@ static XP3FilterDecoder *FetchXP3Decoder() { } #else static XP3FilterDecoder *FetchXP3Decoder() { - thread_local std::auto_ptr ret(AddXP3Decoder()); + thread_local std::auto_ptr ret; + if(!ret) ret = AddXP3Decoder(); return ret.get(); } #endif -static void ReleaseXP3Decoder(XP3FilterDecoder *decoder, bool hold = false) { } -tjs_int TVPXP3ArchiveContentFilterWrapper(const ttstr &filepath, const ttstr &archivename, tjs_uint64 filesize) { +tjs_int TVPXP3ArchiveContentFilterWrapper(const ttstr &filepath, const ttstr &archivename, tjs_uint64 filesize, tTJSVariant *ctx) { if (!_ManagedFilterInited) return 0; XP3FilterDecoder* decoder = FetchXP3Decoder(); @@ -372,13 +372,18 @@ tjs_int TVPXP3ArchiveContentFilterWrapper(const ttstr &filepath, const ttstr &ar }; tTJSVariant result; decoder->ManagedFilter.FuncCall(0, NULL, NULL, &result, sizeof(vars) / sizeof(vars[0]), vars, NULL); - tjs_int ret = result.operator tjs_int(); - ReleaseXP3Decoder(decoder, ret); + tjs_int ret = 0; + if (result.Type() == tvtObject) { + iTJSDispatch2* arr = result.AsObjectNoAddRef(); + ncbPropAccessor a(arr); + ret = a.GetValue(0, ncbTypedefs::Tag()); + *ctx = a.GetValue(1, ncbTypedefs::Tag()); + } return ret; } void TVP_tTVPXP3ArchiveExtractionFilter_CONVENTION - TVPXP3ArchiveExtractionFilterWrapper(tTVPXP3ExtractionFilterInfo *info) + TVPXP3ArchiveExtractionFilterWrapper(tTVPXP3ExtractionFilterInfo *info, tTJSVariant *ctx) { if (info->SizeOfSelf != sizeof(tTVPXP3ExtractionFilterInfo)) TVPThrowExceptionMessage(TJS_W("Incompatible tTVPXP3ExtractionFilterInfo size")); @@ -391,14 +396,14 @@ void TVP_tTVPXP3ArchiveExtractionFilter_CONVENTION buf->Release(); tTJSVariant BufferSize((tjs_int64)info->BufferSize); tTJSVariant FileName(info->FileName); - tTJSVariant *vars[5] = { - &FileHash, &Offset, &Buffer, &BufferSize, &FileName + tTJSVariant *vars[] = { + &FileHash, &Offset, &Buffer, &BufferSize, &FileName, ctx }; #if defined(WIN32) && defined(CHECK_CXDEC) unsigned char *pBackup = new unsigned char[info->BufferSize], *pBuffer = (unsigned char*)info->Buffer; memcpy(pBackup, info->Buffer, info->BufferSize); #endif - decoder->ManagedDecoder.FuncCall(0, NULL, NULL, NULL, 5, vars, NULL); + decoder->ManagedDecoder.FuncCall(0, NULL, NULL, NULL, sizeof(vars) / sizeof(vars[0]), vars, NULL); #if defined(WIN32) && defined(CHECK_CXDEC) cxdec_decode(&dec_callback, info->FileHash, info->Offset, pBackup, info->BufferSize); for (int i = 0; i < info->BufferSize; ++i) @@ -411,7 +416,23 @@ void TVP_tTVPXP3ArchiveExtractionFilter_CONVENTION delete []pBackup; #endif } - ReleaseXP3Decoder(decoder); +} + +void TVPSetXP3FilterScript(ttstr content) { + if (sXP3FilterScript != content) { + for (auto it : _thread_decoders) { + delete it.second; + } + _thread_decoders.clear(); + } + if (content.IsEmpty()) { + TVPSetXP3ArchiveExtractionFilter(nullptr); + TVPSetXP3ArchiveContentFilter(nullptr); + } else { + TVPSetXP3ArchiveExtractionFilter(TVPXP3ArchiveExtractionFilterWrapper); + TVPSetXP3ArchiveContentFilter(TVPXP3ArchiveContentFilterWrapper); + } + sXP3FilterScript = content; } static void PostRegistCallback() @@ -429,7 +450,7 @@ static void PostRegistCallback() throw; } stream->Destruct(); - AddXP3Decoder(); + // AddXP3Decoder(); TVPSetXP3ArchiveExtractionFilter(TVPXP3ArchiveExtractionFilterWrapper); TVPSetXP3ArchiveContentFilter(TVPXP3ArchiveContentFilterWrapper); }