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