xref: /aosp_15_r20/external/angle/src/image_util/AstcDecompressor.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker //
2*8975f5c5SAndroid Build Coastguard Worker // Copyright 2022 The ANGLE Project Authors. All rights reserved.
3*8975f5c5SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
4*8975f5c5SAndroid Build Coastguard Worker // found in the LICENSE file.
5*8975f5c5SAndroid Build Coastguard Worker //
6*8975f5c5SAndroid Build Coastguard Worker 
7*8975f5c5SAndroid Build Coastguard Worker // AstcDecompressorImpl.cpp: Decodes ASTC-encoded textures.
8*8975f5c5SAndroid Build Coastguard Worker 
9*8975f5c5SAndroid Build Coastguard Worker #include <array>
10*8975f5c5SAndroid Build Coastguard Worker #include <future>
11*8975f5c5SAndroid Build Coastguard Worker #include <unordered_map>
12*8975f5c5SAndroid Build Coastguard Worker 
13*8975f5c5SAndroid Build Coastguard Worker #include "astcenc.h"
14*8975f5c5SAndroid Build Coastguard Worker #include "common/SimpleMutex.h"
15*8975f5c5SAndroid Build Coastguard Worker #include "common/WorkerThread.h"
16*8975f5c5SAndroid Build Coastguard Worker #include "image_util/AstcDecompressor.h"
17*8975f5c5SAndroid Build Coastguard Worker 
18*8975f5c5SAndroid Build Coastguard Worker namespace angle
19*8975f5c5SAndroid Build Coastguard Worker {
20*8975f5c5SAndroid Build Coastguard Worker namespace
21*8975f5c5SAndroid Build Coastguard Worker {
22*8975f5c5SAndroid Build Coastguard Worker 
23*8975f5c5SAndroid Build Coastguard Worker const astcenc_swizzle kSwizzle = {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
24*8975f5c5SAndroid Build Coastguard Worker 
25*8975f5c5SAndroid Build Coastguard Worker // Used by std::unique_ptr to release the context when the pointer is destroyed
26*8975f5c5SAndroid Build Coastguard Worker struct AstcencContextDeleter
27*8975f5c5SAndroid Build Coastguard Worker {
operator ()angle::__anon98277cd50111::AstcencContextDeleter28*8975f5c5SAndroid Build Coastguard Worker     void operator()(astcenc_context *c) { astcenc_context_free(c); }
29*8975f5c5SAndroid Build Coastguard Worker };
30*8975f5c5SAndroid Build Coastguard Worker 
31*8975f5c5SAndroid Build Coastguard Worker using AstcencContextUniquePtr = std::unique_ptr<astcenc_context, AstcencContextDeleter>;
32*8975f5c5SAndroid Build Coastguard Worker 
33*8975f5c5SAndroid Build Coastguard Worker // Returns the max number of threads to use when using multithreaded decompression
MaxThreads()34*8975f5c5SAndroid Build Coastguard Worker uint32_t MaxThreads()
35*8975f5c5SAndroid Build Coastguard Worker {
36*8975f5c5SAndroid Build Coastguard Worker     static const uint32_t numThreads = std::min(16u, std::thread::hardware_concurrency());
37*8975f5c5SAndroid Build Coastguard Worker     return numThreads;
38*8975f5c5SAndroid Build Coastguard Worker }
39*8975f5c5SAndroid Build Coastguard Worker 
40*8975f5c5SAndroid Build Coastguard Worker // Creates a new astcenc_context and wraps it in a smart pointer.
41*8975f5c5SAndroid Build Coastguard Worker // It is not needed to call astcenc_context_free() on the returned pointer.
42*8975f5c5SAndroid Build Coastguard Worker // blockWith, blockSize: ASTC block size for the context
43*8975f5c5SAndroid Build Coastguard Worker // Error: (output param) Where to put the error status. Must not be null.
44*8975f5c5SAndroid Build Coastguard Worker // Returns nullptr in case of error.
MakeDecoderContext(uint32_t blockWidth,uint32_t blockHeight,astcenc_error * error)45*8975f5c5SAndroid Build Coastguard Worker AstcencContextUniquePtr MakeDecoderContext(uint32_t blockWidth,
46*8975f5c5SAndroid Build Coastguard Worker                                            uint32_t blockHeight,
47*8975f5c5SAndroid Build Coastguard Worker                                            astcenc_error *error)
48*8975f5c5SAndroid Build Coastguard Worker {
49*8975f5c5SAndroid Build Coastguard Worker     astcenc_config config = {};
50*8975f5c5SAndroid Build Coastguard Worker     *error =
51*8975f5c5SAndroid Build Coastguard Worker         // TODO(gregschlom): Do we need a special case for sRGB images? (And pass
52*8975f5c5SAndroid Build Coastguard Worker         //                   ASTCENC_PRF_LDR_SRGB here?)
53*8975f5c5SAndroid Build Coastguard Worker         astcenc_config_init(ASTCENC_PRF_LDR, blockWidth, blockHeight, 1, ASTCENC_PRE_FASTEST,
54*8975f5c5SAndroid Build Coastguard Worker                             ASTCENC_FLG_DECOMPRESS_ONLY, &config);
55*8975f5c5SAndroid Build Coastguard Worker     if (*error != ASTCENC_SUCCESS)
56*8975f5c5SAndroid Build Coastguard Worker     {
57*8975f5c5SAndroid Build Coastguard Worker         return nullptr;
58*8975f5c5SAndroid Build Coastguard Worker     }
59*8975f5c5SAndroid Build Coastguard Worker 
60*8975f5c5SAndroid Build Coastguard Worker     astcenc_context *context;
61*8975f5c5SAndroid Build Coastguard Worker     *error = astcenc_context_alloc(&config, MaxThreads(), &context);
62*8975f5c5SAndroid Build Coastguard Worker     if (*error != ASTCENC_SUCCESS)
63*8975f5c5SAndroid Build Coastguard Worker     {
64*8975f5c5SAndroid Build Coastguard Worker         return nullptr;
65*8975f5c5SAndroid Build Coastguard Worker     }
66*8975f5c5SAndroid Build Coastguard Worker     return AstcencContextUniquePtr(context);
67*8975f5c5SAndroid Build Coastguard Worker }
68*8975f5c5SAndroid Build Coastguard Worker 
69*8975f5c5SAndroid Build Coastguard Worker // Returns whether the ASTC decompressor can be used on this machine. It might not be available if
70*8975f5c5SAndroid Build Coastguard Worker // the CPU doesn't support AVX2 instructions for example. Since this call is a bit expensive and
71*8975f5c5SAndroid Build Coastguard Worker // never changes, the result should be cached.
IsAstcDecompressorAvailable()72*8975f5c5SAndroid Build Coastguard Worker bool IsAstcDecompressorAvailable()
73*8975f5c5SAndroid Build Coastguard Worker {
74*8975f5c5SAndroid Build Coastguard Worker     astcenc_error error;
75*8975f5c5SAndroid Build Coastguard Worker     // Try getting an arbitrary context. If it works, the decompressor is available.
76*8975f5c5SAndroid Build Coastguard Worker     AstcencContextUniquePtr context = MakeDecoderContext(5, 5, &error);
77*8975f5c5SAndroid Build Coastguard Worker     return context != nullptr;
78*8975f5c5SAndroid Build Coastguard Worker }
79*8975f5c5SAndroid Build Coastguard Worker 
80*8975f5c5SAndroid Build Coastguard Worker // Caches and manages astcenc_context objects.
81*8975f5c5SAndroid Build Coastguard Worker //
82*8975f5c5SAndroid Build Coastguard Worker // Each context is fairly large (around 30 MB) and takes a while to construct, so it's important to
83*8975f5c5SAndroid Build Coastguard Worker // reuse them as much as possible.
84*8975f5c5SAndroid Build Coastguard Worker //
85*8975f5c5SAndroid Build Coastguard Worker // While context objects can be reused across multiple threads, they must be used sequentially. To
86*8975f5c5SAndroid Build Coastguard Worker // avoid having to lock and manage access between threads, we keep one cache per thread. This avoids
87*8975f5c5SAndroid Build Coastguard Worker // any concurrency issues, at the cost of extra memory.
88*8975f5c5SAndroid Build Coastguard Worker //
89*8975f5c5SAndroid Build Coastguard Worker // Currently, there is no eviction strategy. Each cache could grow to a maximum of ~400 MB in size
90*8975f5c5SAndroid Build Coastguard Worker // since they are 13 possible ASTC block sizes.
91*8975f5c5SAndroid Build Coastguard Worker //
92*8975f5c5SAndroid Build Coastguard Worker // Thread-safety: not thread safe.
93*8975f5c5SAndroid Build Coastguard Worker class AstcDecompressorContextCache
94*8975f5c5SAndroid Build Coastguard Worker {
95*8975f5c5SAndroid Build Coastguard Worker   public:
96*8975f5c5SAndroid Build Coastguard Worker     // Returns a context object for a given ASTC block size, along with the error code if the
97*8975f5c5SAndroid Build Coastguard Worker     // context initialization failed.
98*8975f5c5SAndroid Build Coastguard Worker     // In this case, the context will be null, and the status code will be non-zero.
get(uint32_t blockWidth,uint32_t blockHeight)99*8975f5c5SAndroid Build Coastguard Worker     std::pair<astcenc_context *, astcenc_error> get(uint32_t blockWidth, uint32_t blockHeight)
100*8975f5c5SAndroid Build Coastguard Worker     {
101*8975f5c5SAndroid Build Coastguard Worker         Value &value = mContexts[{blockWidth, blockHeight}];
102*8975f5c5SAndroid Build Coastguard Worker         if (value.context == nullptr)
103*8975f5c5SAndroid Build Coastguard Worker         {
104*8975f5c5SAndroid Build Coastguard Worker             value.context = MakeDecoderContext(blockWidth, blockHeight, &value.error);
105*8975f5c5SAndroid Build Coastguard Worker         }
106*8975f5c5SAndroid Build Coastguard Worker         return {value.context.get(), value.error};
107*8975f5c5SAndroid Build Coastguard Worker     }
108*8975f5c5SAndroid Build Coastguard Worker 
109*8975f5c5SAndroid Build Coastguard Worker   private:
110*8975f5c5SAndroid Build Coastguard Worker     // Holds the data we use as the cache key
111*8975f5c5SAndroid Build Coastguard Worker     struct Key
112*8975f5c5SAndroid Build Coastguard Worker     {
113*8975f5c5SAndroid Build Coastguard Worker         uint32_t blockWidth;
114*8975f5c5SAndroid Build Coastguard Worker         uint32_t blockHeight;
115*8975f5c5SAndroid Build Coastguard Worker 
operator ==angle::__anon98277cd50111::AstcDecompressorContextCache::Key116*8975f5c5SAndroid Build Coastguard Worker         bool operator==(const Key &other) const
117*8975f5c5SAndroid Build Coastguard Worker         {
118*8975f5c5SAndroid Build Coastguard Worker             return blockWidth == other.blockWidth && blockHeight == other.blockHeight;
119*8975f5c5SAndroid Build Coastguard Worker         }
120*8975f5c5SAndroid Build Coastguard Worker     };
121*8975f5c5SAndroid Build Coastguard Worker 
122*8975f5c5SAndroid Build Coastguard Worker     struct Value
123*8975f5c5SAndroid Build Coastguard Worker     {
124*8975f5c5SAndroid Build Coastguard Worker         AstcencContextUniquePtr context = nullptr;
125*8975f5c5SAndroid Build Coastguard Worker         astcenc_error error             = ASTCENC_SUCCESS;
126*8975f5c5SAndroid Build Coastguard Worker     };
127*8975f5c5SAndroid Build Coastguard Worker 
128*8975f5c5SAndroid Build Coastguard Worker     // Computes the hash of a Key
129*8975f5c5SAndroid Build Coastguard Worker     struct KeyHash
130*8975f5c5SAndroid Build Coastguard Worker     {
operator ()angle::__anon98277cd50111::AstcDecompressorContextCache::KeyHash131*8975f5c5SAndroid Build Coastguard Worker         std::size_t operator()(const Key &k) const
132*8975f5c5SAndroid Build Coastguard Worker         {
133*8975f5c5SAndroid Build Coastguard Worker             // blockWidth and blockHeight are < 256 (actually, < 16), so this is safe
134*8975f5c5SAndroid Build Coastguard Worker             return k.blockWidth << 8 | k.blockHeight;
135*8975f5c5SAndroid Build Coastguard Worker         }
136*8975f5c5SAndroid Build Coastguard Worker     };
137*8975f5c5SAndroid Build Coastguard Worker 
138*8975f5c5SAndroid Build Coastguard Worker     std::unordered_map<Key, Value, KeyHash> mContexts;
139*8975f5c5SAndroid Build Coastguard Worker };
140*8975f5c5SAndroid Build Coastguard Worker 
141*8975f5c5SAndroid Build Coastguard Worker struct DecompressTask : public Closure
142*8975f5c5SAndroid Build Coastguard Worker {
DecompressTaskangle::__anon98277cd50111::DecompressTask143*8975f5c5SAndroid Build Coastguard Worker     DecompressTask(astcenc_context *context,
144*8975f5c5SAndroid Build Coastguard Worker                    uint32_t threadIndex,
145*8975f5c5SAndroid Build Coastguard Worker                    const uint8_t *data,
146*8975f5c5SAndroid Build Coastguard Worker                    size_t dataLength,
147*8975f5c5SAndroid Build Coastguard Worker                    astcenc_image *image)
148*8975f5c5SAndroid Build Coastguard Worker         : context(context),
149*8975f5c5SAndroid Build Coastguard Worker           threadIndex(threadIndex),
150*8975f5c5SAndroid Build Coastguard Worker           data(data),
151*8975f5c5SAndroid Build Coastguard Worker           dataLength(dataLength),
152*8975f5c5SAndroid Build Coastguard Worker           image(image)
153*8975f5c5SAndroid Build Coastguard Worker     {}
154*8975f5c5SAndroid Build Coastguard Worker 
operator ()angle::__anon98277cd50111::DecompressTask155*8975f5c5SAndroid Build Coastguard Worker     void operator()() override
156*8975f5c5SAndroid Build Coastguard Worker     {
157*8975f5c5SAndroid Build Coastguard Worker         result = astcenc_decompress_image(context, data, dataLength, image, &kSwizzle, threadIndex);
158*8975f5c5SAndroid Build Coastguard Worker     }
159*8975f5c5SAndroid Build Coastguard Worker 
160*8975f5c5SAndroid Build Coastguard Worker     astcenc_context *context;
161*8975f5c5SAndroid Build Coastguard Worker     uint32_t threadIndex;
162*8975f5c5SAndroid Build Coastguard Worker     const uint8_t *data;
163*8975f5c5SAndroid Build Coastguard Worker     size_t dataLength;
164*8975f5c5SAndroid Build Coastguard Worker     astcenc_image *image;
165*8975f5c5SAndroid Build Coastguard Worker     astcenc_error result;
166*8975f5c5SAndroid Build Coastguard Worker };
167*8975f5c5SAndroid Build Coastguard Worker 
168*8975f5c5SAndroid Build Coastguard Worker // Performs ASTC decompression of an image on the CPU
169*8975f5c5SAndroid Build Coastguard Worker class AstcDecompressorImpl : public AstcDecompressor
170*8975f5c5SAndroid Build Coastguard Worker {
171*8975f5c5SAndroid Build Coastguard Worker   public:
AstcDecompressorImpl()172*8975f5c5SAndroid Build Coastguard Worker     AstcDecompressorImpl()
173*8975f5c5SAndroid Build Coastguard Worker         : AstcDecompressor(), mContextCache(std::make_unique<AstcDecompressorContextCache>())
174*8975f5c5SAndroid Build Coastguard Worker     {
175*8975f5c5SAndroid Build Coastguard Worker         mTasks.reserve(MaxThreads());
176*8975f5c5SAndroid Build Coastguard Worker         mWaitEvents.reserve(MaxThreads());
177*8975f5c5SAndroid Build Coastguard Worker     }
178*8975f5c5SAndroid Build Coastguard Worker 
179*8975f5c5SAndroid Build Coastguard Worker     ~AstcDecompressorImpl() override = default;
180*8975f5c5SAndroid Build Coastguard Worker 
available() const181*8975f5c5SAndroid Build Coastguard Worker     bool available() const override
182*8975f5c5SAndroid Build Coastguard Worker     {
183*8975f5c5SAndroid Build Coastguard Worker         static bool available = IsAstcDecompressorAvailable();
184*8975f5c5SAndroid Build Coastguard Worker         return available;
185*8975f5c5SAndroid Build Coastguard Worker     }
186*8975f5c5SAndroid Build Coastguard Worker 
decompress(std::shared_ptr<WorkerThreadPool> singleThreadPool,std::shared_ptr<WorkerThreadPool> multiThreadPool,const uint32_t imgWidth,const uint32_t imgHeight,const uint32_t blockWidth,const uint32_t blockHeight,const uint8_t * input,size_t inputLength,uint8_t * output)187*8975f5c5SAndroid Build Coastguard Worker     int32_t decompress(std::shared_ptr<WorkerThreadPool> singleThreadPool,
188*8975f5c5SAndroid Build Coastguard Worker                        std::shared_ptr<WorkerThreadPool> multiThreadPool,
189*8975f5c5SAndroid Build Coastguard Worker                        const uint32_t imgWidth,
190*8975f5c5SAndroid Build Coastguard Worker                        const uint32_t imgHeight,
191*8975f5c5SAndroid Build Coastguard Worker                        const uint32_t blockWidth,
192*8975f5c5SAndroid Build Coastguard Worker                        const uint32_t blockHeight,
193*8975f5c5SAndroid Build Coastguard Worker                        const uint8_t *input,
194*8975f5c5SAndroid Build Coastguard Worker                        size_t inputLength,
195*8975f5c5SAndroid Build Coastguard Worker                        uint8_t *output) override
196*8975f5c5SAndroid Build Coastguard Worker     {
197*8975f5c5SAndroid Build Coastguard Worker         // A given astcenc context can only decompress one image at a time, which we why we keep
198*8975f5c5SAndroid Build Coastguard Worker         // this mutex locked the whole time.
199*8975f5c5SAndroid Build Coastguard Worker         std::lock_guard global_lock(mMutex);
200*8975f5c5SAndroid Build Coastguard Worker 
201*8975f5c5SAndroid Build Coastguard Worker         auto [context, context_status] = mContextCache->get(blockWidth, blockHeight);
202*8975f5c5SAndroid Build Coastguard Worker         if (context_status != ASTCENC_SUCCESS)
203*8975f5c5SAndroid Build Coastguard Worker             return context_status;
204*8975f5c5SAndroid Build Coastguard Worker 
205*8975f5c5SAndroid Build Coastguard Worker         astcenc_image image;
206*8975f5c5SAndroid Build Coastguard Worker         image.dim_x     = imgWidth;
207*8975f5c5SAndroid Build Coastguard Worker         image.dim_y     = imgHeight;
208*8975f5c5SAndroid Build Coastguard Worker         image.dim_z     = 1;
209*8975f5c5SAndroid Build Coastguard Worker         image.data_type = ASTCENC_TYPE_U8;
210*8975f5c5SAndroid Build Coastguard Worker         image.data      = reinterpret_cast<void **>(&output);
211*8975f5c5SAndroid Build Coastguard Worker 
212*8975f5c5SAndroid Build Coastguard Worker         // For smaller images the overhead of multithreading exceeds the benefits.
213*8975f5c5SAndroid Build Coastguard Worker         const bool singleThreaded = (imgHeight <= 32 && imgWidth <= 32) || !multiThreadPool;
214*8975f5c5SAndroid Build Coastguard Worker 
215*8975f5c5SAndroid Build Coastguard Worker         std::shared_ptr<WorkerThreadPool> &threadPool =
216*8975f5c5SAndroid Build Coastguard Worker             singleThreaded ? singleThreadPool : multiThreadPool;
217*8975f5c5SAndroid Build Coastguard Worker         const uint32_t threadCount = singleThreaded ? 1 : MaxThreads();
218*8975f5c5SAndroid Build Coastguard Worker 
219*8975f5c5SAndroid Build Coastguard Worker         mTasks.clear();
220*8975f5c5SAndroid Build Coastguard Worker         mWaitEvents.clear();
221*8975f5c5SAndroid Build Coastguard Worker 
222*8975f5c5SAndroid Build Coastguard Worker         for (uint32_t i = 0; i < threadCount; ++i)
223*8975f5c5SAndroid Build Coastguard Worker         {
224*8975f5c5SAndroid Build Coastguard Worker             mTasks.push_back(
225*8975f5c5SAndroid Build Coastguard Worker                 std::make_shared<DecompressTask>(context, i, input, inputLength, &image));
226*8975f5c5SAndroid Build Coastguard Worker             mWaitEvents.push_back(threadPool->postWorkerTask(mTasks[i]));
227*8975f5c5SAndroid Build Coastguard Worker         }
228*8975f5c5SAndroid Build Coastguard Worker         WaitableEvent::WaitMany(&mWaitEvents);
229*8975f5c5SAndroid Build Coastguard Worker         astcenc_decompress_reset(context);
230*8975f5c5SAndroid Build Coastguard Worker 
231*8975f5c5SAndroid Build Coastguard Worker         for (auto &task : mTasks)
232*8975f5c5SAndroid Build Coastguard Worker         {
233*8975f5c5SAndroid Build Coastguard Worker             if (task->result != ASTCENC_SUCCESS)
234*8975f5c5SAndroid Build Coastguard Worker                 return task->result;
235*8975f5c5SAndroid Build Coastguard Worker         }
236*8975f5c5SAndroid Build Coastguard Worker         return ASTCENC_SUCCESS;
237*8975f5c5SAndroid Build Coastguard Worker     }
238*8975f5c5SAndroid Build Coastguard Worker 
getStatusString(int32_t statusCode) const239*8975f5c5SAndroid Build Coastguard Worker     const char *getStatusString(int32_t statusCode) const override
240*8975f5c5SAndroid Build Coastguard Worker     {
241*8975f5c5SAndroid Build Coastguard Worker         const char *msg = astcenc_get_error_string((astcenc_error)statusCode);
242*8975f5c5SAndroid Build Coastguard Worker         return msg ? msg : "ASTCENC_UNKNOWN_STATUS";
243*8975f5c5SAndroid Build Coastguard Worker     }
244*8975f5c5SAndroid Build Coastguard Worker 
245*8975f5c5SAndroid Build Coastguard Worker   private:
246*8975f5c5SAndroid Build Coastguard Worker     std::unique_ptr<AstcDecompressorContextCache> mContextCache;
247*8975f5c5SAndroid Build Coastguard Worker     angle::SimpleMutex mMutex;  // Locked while calling `decode()`
248*8975f5c5SAndroid Build Coastguard Worker     std::vector<std::shared_ptr<DecompressTask>> mTasks;
249*8975f5c5SAndroid Build Coastguard Worker     std::vector<std::shared_ptr<WaitableEvent>> mWaitEvents;
250*8975f5c5SAndroid Build Coastguard Worker };
251*8975f5c5SAndroid Build Coastguard Worker 
252*8975f5c5SAndroid Build Coastguard Worker }  // namespace
253*8975f5c5SAndroid Build Coastguard Worker 
get()254*8975f5c5SAndroid Build Coastguard Worker AstcDecompressor &AstcDecompressor::get()
255*8975f5c5SAndroid Build Coastguard Worker {
256*8975f5c5SAndroid Build Coastguard Worker     static auto *instance = new AstcDecompressorImpl();
257*8975f5c5SAndroid Build Coastguard Worker     return *instance;
258*8975f5c5SAndroid Build Coastguard Worker }
259*8975f5c5SAndroid Build Coastguard Worker 
260*8975f5c5SAndroid Build Coastguard Worker }  // namespace angle
261