Skip to content

Commit 2ebca92

Browse files
committed
cc/wallpaper: thumbnails and hero banner should be crisp
1 parent 4d1a081 commit 2ebca92

11 files changed

Lines changed: 343 additions & 102 deletions

File tree

src/app/application.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,7 @@ void Application::initUi() {
14261426
m_networkService.get(), m_networkSecretAgent.get(), m_bluetoothService.get(), m_bluetoothAgent.get(),
14271427
m_brightnessService.get(), m_systemMonitor.get(), &m_screenTimeService, &m_gammaService, &m_themeService,
14281428
&m_idleInhibitor, &m_dependencyService, &m_compositorPlatform, &m_ipcService, &m_wallpaper,
1429-
&m_calendarService, &m_scriptApi, &m_clipboardService, m_accountsService.get()
1429+
&m_calendarService, &m_scriptApi, &m_clipboardService, m_accountsService.get(), &m_thumbnailService
14301430
)
14311431
);
14321432
{

src/render/core/thumbnail_service.cpp

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
namespace {
2424

2525
constexpr Logger kLog("thumbnail");
26-
constexpr int kThumbnailTargetPx = 192;
2726
constexpr float kThumbnailWebPQuality = 82.0f;
2827
constexpr std::size_t kMinWorkers = 2;
2928
constexpr std::size_t kMaxWorkers = 4;
@@ -58,7 +57,7 @@ namespace {
5857
return out;
5958
}
6059

61-
std::optional<std::filesystem::path> cachePathForSource(const std::string& sourcePath) {
60+
std::optional<std::filesystem::path> cachePathForSource(const std::string& sourcePath, int targetPx) {
6261
namespace fs = std::filesystem;
6362
std::error_code ec;
6463
const auto size = fs::file_size(sourcePath, ec);
@@ -78,7 +77,7 @@ namespace {
7877
+ '\n'
7978
+ std::to_string(static_cast<long long>(ticks))
8079
+ '\n'
81-
+ std::to_string(kThumbnailTargetPx)
80+
+ std::to_string(targetPx)
8281
+ '\n'
8382
+ std::string(kThumbnailCacheVersion);
8483
return thumbnailCacheDir() / (hex64(fnv1a64(key)) + ".webp");
@@ -122,13 +121,13 @@ namespace {
122121
return rgba;
123122
}
124123

125-
bool resizeThumbnail(std::vector<std::uint8_t>& pixels, int& width, int& height) {
124+
bool resizeThumbnail(std::vector<std::uint8_t>& pixels, int& width, int& height, int targetPx) {
126125
const int maxDim = std::max(width, height);
127-
if (maxDim <= kThumbnailTargetPx || width <= 0 || height <= 0) {
126+
if (maxDim <= targetPx || width <= 0 || height <= 0) {
128127
return true;
129128
}
130129

131-
const float scale = static_cast<float>(kThumbnailTargetPx) / static_cast<float>(maxDim);
130+
const float scale = static_cast<float>(targetPx) / static_cast<float>(maxDim);
132131
const int resizedW = std::max(1, static_cast<int>(std::lround(static_cast<float>(width) * scale)));
133132
const int resizedH = std::max(1, static_cast<int>(std::lround(static_cast<float>(height) * scale)));
134133

@@ -147,6 +146,12 @@ namespace {
147146

148147
} // namespace
149148

149+
std::size_t ThumbnailService::RequestKeyHash::operator()(const RequestKey& key) const noexcept {
150+
const std::size_t pathHash = std::hash<std::string>{}(key.path);
151+
const std::size_t sizeHash = std::hash<int>{}(key.targetPx);
152+
return pathHash ^ (sizeHash + 0x9e3779b97f4a7c15ull + (pathHash << 6U) + (pathHash >> 2U));
153+
}
154+
150155
ThumbnailService::Subscription::Subscription(std::function<void()> disconnect) : m_disconnect(std::move(disconnect)) {}
151156

152157
ThumbnailService::Subscription::~Subscription() { disconnect(); }
@@ -240,19 +245,22 @@ ThumbnailService::Subscription ThumbnailService::subscribePendingUpload(PendingU
240245
});
241246
}
242247

243-
ThumbnailService::Subscription ThumbnailService::subscribeReady(const std::string& path, ReadyCallback callback) {
248+
ThumbnailService::Subscription
249+
ThumbnailService::subscribeReady(const std::string& path, ReadyCallback callback, int targetPx) {
244250
if (path.empty() || !callback) {
245251
return {};
246252
}
247253

248-
const TextureHandle current = peek(path);
254+
const TextureHandle current = peek(path, targetPx);
249255
if (current.id != 0) {
250256
callback(path, current);
251257
return {};
252258
}
253259

254260
const std::uint64_t id = m_nextListenerId++;
255-
m_readyListeners.emplace(id, ReadyListener{.path = path, .callback = std::move(callback)});
261+
m_readyListeners.emplace(
262+
id, ReadyListener{.key = RequestKey{.path = path, .targetPx = targetPx}, .callback = std::move(callback)}
263+
);
256264

257265
std::weak_ptr<bool> token = m_lifetimeToken;
258266
return Subscription([this, token, id]() {
@@ -264,31 +272,33 @@ ThumbnailService::Subscription ThumbnailService::subscribeReady(const std::strin
264272
});
265273
}
266274

267-
TextureHandle ThumbnailService::acquire(const std::string& path) {
275+
TextureHandle ThumbnailService::acquire(const std::string& path, int targetPx) {
268276
if (path.empty()) {
269277
return {};
270278
}
271279

272-
CacheEntry& entry = m_entries[path];
280+
const RequestKey key{.path = path, .targetPx = targetPx};
281+
CacheEntry& entry = m_entries[key];
273282
++entry.refCount;
274283
if (entry.handle.id != 0 || entry.failed) {
275284
return entry.handle;
276285
}
277286

278-
enqueueDecodeIfNeeded(path);
287+
enqueueDecodeIfNeeded(key);
279288
return {};
280289
}
281290

282-
TextureHandle ThumbnailService::peek(const std::string& path) const {
283-
const auto it = m_entries.find(path);
291+
TextureHandle ThumbnailService::peek(const std::string& path, int targetPx) const {
292+
const auto it = m_entries.find(RequestKey{.path = path, .targetPx = targetPx});
284293
if (it == m_entries.end()) {
285294
return {};
286295
}
287296
return it->second.handle;
288297
}
289298

290-
void ThumbnailService::release(const std::string& path) {
291-
const auto it = m_entries.find(path);
299+
void ThumbnailService::release(const std::string& path, int targetPx) {
300+
const RequestKey key{.path = path, .targetPx = targetPx};
301+
const auto it = m_entries.find(key);
292302
if (it == m_entries.end()) {
293303
return;
294304
}
@@ -308,19 +318,19 @@ void ThumbnailService::release(const std::string& path) {
308318
m_entries.erase(it);
309319

310320
std::lock_guard<std::mutex> lock(m_queueMutex);
311-
if (m_inFlight.contains(path)) {
312-
m_canceled.insert(path);
321+
if (m_inFlight.contains(key)) {
322+
m_canceled.insert(key);
313323
}
314324
}
315325

316-
void ThumbnailService::enqueueDecodeIfNeeded(const std::string& path) {
326+
void ThumbnailService::enqueueDecodeIfNeeded(const RequestKey& key) {
317327
std::lock_guard<std::mutex> lock(m_queueMutex);
318-
m_canceled.erase(path);
319-
if (m_inFlight.contains(path)) {
328+
m_canceled.erase(key);
329+
if (m_inFlight.contains(key)) {
320330
return;
321331
}
322-
m_inFlight.insert(path);
323-
m_jobQueue.push_back(path);
332+
m_inFlight.insert(key);
333+
m_jobQueue.push_back(key);
324334
m_queueCv.notify_one();
325335
}
326336

@@ -342,8 +352,8 @@ bool ThumbnailService::uploadPending(TextureManager& textures) {
342352
bool dropped = false;
343353
{
344354
std::lock_guard<std::mutex> lock(m_queueMutex);
345-
m_inFlight.erase(job.path);
346-
if (auto c = m_canceled.find(job.path); c != m_canceled.end()) {
355+
m_inFlight.erase(job.key);
356+
if (auto c = m_canceled.find(job.key); c != m_canceled.end()) {
347357
m_canceled.erase(c);
348358
dropped = true;
349359
}
@@ -352,7 +362,7 @@ bool ThumbnailService::uploadPending(TextureManager& textures) {
352362
continue;
353363
}
354364

355-
auto entryIt = m_entries.find(job.path);
365+
auto entryIt = m_entries.find(job.key);
356366
if (entryIt == m_entries.end() || entryIt->second.refCount == 0) {
357367
continue;
358368
}
@@ -364,7 +374,7 @@ bool ThumbnailService::uploadPending(TextureManager& textures) {
364374

365375
TextureHandle handle = textures.loadFromRgba(job.rgba.data(), job.width, job.height);
366376
if (handle.id == 0) {
367-
kLog.warn("failed to upload thumbnail texture for {}", job.path);
377+
kLog.warn("failed to upload thumbnail texture for {}", job.key.path);
368378
entryIt->second.failed = true;
369379
changed = true;
370380
continue;
@@ -376,29 +386,29 @@ bool ThumbnailService::uploadPending(TextureManager& textures) {
376386
entryIt->second.handle = handle;
377387
entryIt->second.failed = false;
378388
changed = true;
379-
notifyReady(job.path, handle);
389+
notifyReady(job.key, handle);
380390
}
381391
return changed;
382392
}
383393

384394
void ThumbnailService::invalidateGpuResources(TextureManager& textures) {
385395
m_textureManager = &textures;
386396

387-
std::vector<std::string> livePaths;
388-
livePaths.reserve(m_entries.size());
389-
for (auto& [path, entry] : m_entries) {
397+
std::vector<RequestKey> liveKeys;
398+
liveKeys.reserve(m_entries.size());
399+
for (auto& [key, entry] : m_entries) {
390400
if (entry.handle.id != 0) {
391401
m_textureManager->unload(entry.handle);
392402
}
393403
entry.handle = {};
394404
entry.failed = false;
395405
if (entry.refCount > 0) {
396-
livePaths.push_back(path);
406+
liveKeys.push_back(key);
397407
}
398408
}
399409

400-
for (const std::string& path : livePaths) {
401-
enqueueDecodeIfNeeded(path);
410+
for (const RequestKey& key : liveKeys) {
411+
enqueueDecodeIfNeeded(key);
402412
}
403413
}
404414

@@ -444,8 +454,8 @@ void ThumbnailService::pushResult(DecodedJob job) {
444454
}
445455

446456
void ThumbnailService::deleteAllTextures() {
447-
for (auto& [path, entry] : m_entries) {
448-
(void)path;
457+
for (auto& [key, entry] : m_entries) {
458+
(void)key;
449459
if (entry.handle.id != 0 && m_textureManager != nullptr) {
450460
m_textureManager->unload(entry.handle);
451461
}
@@ -468,10 +478,10 @@ void ThumbnailService::notifyPendingUpload() {
468478
}
469479
}
470480

471-
void ThumbnailService::notifyReady(const std::string& path, TextureHandle handle) {
481+
void ThumbnailService::notifyReady(const RequestKey& key, TextureHandle handle) {
472482
std::vector<std::pair<std::uint64_t, ReadyCallback>> callbacks;
473483
for (const auto& [id, listener] : m_readyListeners) {
474-
if (listener.path == path && listener.callback) {
484+
if (listener.key == key && listener.callback) {
475485
callbacks.emplace_back(id, listener.callback);
476486
}
477487
}
@@ -482,27 +492,28 @@ void ThumbnailService::notifyReady(const std::string& path, TextureHandle handle
482492
continue;
483493
}
484494
m_readyListeners.erase(it);
485-
callback(path, handle);
495+
callback(key.path, handle);
486496
}
487497
}
488498

489499
void ThumbnailService::workerLoop() {
490500
while (true) {
491-
std::string path;
501+
RequestKey key;
492502
{
493503
std::unique_lock<std::mutex> lock(m_queueMutex);
494504
m_queueCv.wait(lock, [this]() { return m_shutdown.load() || !m_jobQueue.empty(); });
495505
if (m_shutdown.load()) {
496506
return;
497507
}
498-
path = std::move(m_jobQueue.front());
508+
key = std::move(m_jobQueue.front());
499509
m_jobQueue.pop_front();
500510
}
501511

512+
const std::string& path = key.path;
502513
DecodedJob result;
503-
result.path = path;
514+
result.key = key;
504515

505-
if (const auto cachePath = cachePathForSource(path); cachePath.has_value()) {
516+
if (const auto cachePath = cachePathForSource(path, key.targetPx); cachePath.has_value()) {
506517
auto cachedBytes = FileUtils::readBinaryFile(cachePath->string());
507518
if (!cachedBytes.empty()) {
508519
if (auto cached = decodeRasterImage(cachedBytes.data(), cachedBytes.size())) {
@@ -536,13 +547,13 @@ void ThumbnailService::workerLoop() {
536547
int h = decoded->height;
537548
auto pixels = rgbaToRgb(decoded->pixels);
538549

539-
if (!resizeThumbnail(pixels, w, h)) {
550+
if (!resizeThumbnail(pixels, w, h, key.targetPx)) {
540551
result.failed = true;
541552
pushResult(std::move(result));
542553
continue;
543554
}
544555

545-
if (const auto cachePath = cachePathForSource(path); cachePath.has_value()) {
556+
if (const auto cachePath = cachePathForSource(path, key.targetPx); cachePath.has_value()) {
546557
std::uint8_t* encoded = nullptr;
547558
const std::size_t encodedSize = WebPEncodeRGB(pixels.data(), w, h, w * 3, kThumbnailWebPQuality, &encoded);
548559
if (encoded != nullptr && encodedSize > 0) {

0 commit comments

Comments
 (0)