You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
877 lines
22 KiB
877 lines
22 KiB
/*
|
|
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
* Cache implementation for bitmap images decoded into frontend format.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "netsurf/inttypes.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/log.h"
|
|
#include "netsurf/misc.h"
|
|
#include "netsurf/bitmap.h"
|
|
#include "content/llcache.h"
|
|
#include "content/content_protected.h"
|
|
#include "desktop/gui_internal.h"
|
|
|
|
#include "image/image_cache.h"
|
|
#include "image/image.h"
|
|
|
|
/**
|
|
* Age of an entry within the cache
|
|
*
|
|
* type deffed away so it can be readily changed later perhaps to a
|
|
* wallclock time structure.
|
|
*/
|
|
typedef unsigned int cache_age;
|
|
|
|
/**
|
|
* Image cache entry
|
|
*/
|
|
struct image_cache_entry_s {
|
|
struct image_cache_entry_s *next; /**< next cache entry in list */
|
|
struct image_cache_entry_s *prev; /**< previous cache entry in list */
|
|
|
|
/** content is used as a key */
|
|
struct content *content;
|
|
/** associated bitmap entry */
|
|
struct bitmap *bitmap;
|
|
/** routine to convert content into bitmap */
|
|
image_cache_convert_fn *convert;
|
|
|
|
/* Statistics for replacement algorithm */
|
|
|
|
unsigned int redraw_count; /**< number of times object has been drawn */
|
|
cache_age redraw_age; /**< Age of last redraw */
|
|
size_t bitmap_size; /**< size if storage occupied by bitmap */
|
|
cache_age bitmap_age; /**< Age of last conversion to a bitmap by cache*/
|
|
|
|
int conversion_count; /**< Number of times image has been converted */
|
|
};
|
|
|
|
/**
|
|
* Current state of the cache.
|
|
*
|
|
* Global state of the cache. entries "age" is determined based on a
|
|
* monotonically incrementing operation count. This avoids issues with
|
|
* using wall clock time while allowing the LRU algorithm to work
|
|
* sensibly.
|
|
*/
|
|
struct image_cache_s {
|
|
/** Cache parameters */
|
|
struct image_cache_parameters params;
|
|
|
|
/** The "age" of the current operation */
|
|
cache_age current_age;
|
|
|
|
/* The objects the cache holds */
|
|
struct image_cache_entry_s *entries;
|
|
|
|
|
|
/* Statistics for management algorithm */
|
|
|
|
/** total size of bitmaps currently allocated */
|
|
size_t total_bitmap_size;
|
|
|
|
/** Total count of bitmaps currently allocated */
|
|
int bitmap_count;
|
|
|
|
/** Maximum size of bitmaps allocated at any one time */
|
|
size_t max_bitmap_size;
|
|
/** The number of objects when maximum bitmap usage occurred */
|
|
int max_bitmap_size_count;
|
|
|
|
/** Maximum count of bitmaps allocated at any one time */
|
|
int max_bitmap_count;
|
|
/** The size of the bitmaps when the max count occurred */
|
|
size_t max_bitmap_count_size;
|
|
|
|
/** Bitmap was not available at plot time required conversion */
|
|
int miss_count;
|
|
uint64_t miss_size;
|
|
/** Bitmap was available at plot time required no conversion */
|
|
int hit_count;
|
|
uint64_t hit_size;
|
|
/** Bitmap was not available at plot time and required
|
|
* conversion which failed.
|
|
*/
|
|
int fail_count;
|
|
uint64_t fail_size;
|
|
|
|
/* Cache entry freed without ever being redrawn */
|
|
int total_unrendered;
|
|
/** Bitmap was available but never required - wasted conversions */
|
|
int specultive_miss_count;
|
|
|
|
/** Total number of additional (after the first) conversions */
|
|
int total_extra_conversions;
|
|
/** counts total number of images with more than one conversion */
|
|
int total_extra_conversions_count;
|
|
|
|
/** Bitmap with most conversions was converted this many times */
|
|
int peak_conversions;
|
|
/** Size of bitmap with most conversions */
|
|
unsigned int peak_conversions_size;
|
|
};
|
|
|
|
/** image cache state */
|
|
static struct image_cache_s *image_cache = NULL;
|
|
|
|
|
|
/**
|
|
* Find a cache entry by index.
|
|
*
|
|
* \param entryn index of cache entry
|
|
* \return cache entry at index or NULL if not found.
|
|
*/
|
|
static struct image_cache_entry_s *image_cache__findn(int entryn)
|
|
{
|
|
struct image_cache_entry_s *found;
|
|
|
|
found = image_cache->entries;
|
|
while ((found != NULL) && (entryn > 0)) {
|
|
entryn--;
|
|
found = found->next;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the cache entry for a content
|
|
*
|
|
* \param c The content to get an entry for
|
|
* \return The image cache entry or NULL if not found.
|
|
*/
|
|
static struct image_cache_entry_s *image_cache__find(const struct content *c)
|
|
{
|
|
struct image_cache_entry_s *found;
|
|
|
|
found = image_cache->entries;
|
|
while ((found != NULL) && (found->content != c)) {
|
|
found = found->next;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Update the image cache statistics with an entry.
|
|
*
|
|
* \param centry The image cache entry to update the stats with.
|
|
*/
|
|
static void image_cache_stats_bitmap_add(struct image_cache_entry_s *centry)
|
|
{
|
|
centry->bitmap_age = image_cache->current_age;
|
|
centry->conversion_count++;
|
|
|
|
image_cache->total_bitmap_size += centry->bitmap_size;
|
|
image_cache->bitmap_count++;
|
|
|
|
if (image_cache->total_bitmap_size > image_cache->max_bitmap_size) {
|
|
image_cache->max_bitmap_size = image_cache->total_bitmap_size;
|
|
image_cache->max_bitmap_size_count = image_cache->bitmap_count;
|
|
|
|
}
|
|
|
|
if (image_cache->bitmap_count > image_cache->max_bitmap_count) {
|
|
image_cache->max_bitmap_count = image_cache->bitmap_count;
|
|
image_cache->max_bitmap_count_size = image_cache->total_bitmap_size;
|
|
}
|
|
|
|
if (centry->conversion_count == 2) {
|
|
image_cache->total_extra_conversions_count++;
|
|
}
|
|
|
|
if (centry->conversion_count > 1) {
|
|
image_cache->total_extra_conversions++;
|
|
}
|
|
|
|
if ((centry->conversion_count > image_cache->peak_conversions) ||
|
|
(centry->conversion_count == image_cache->peak_conversions &&
|
|
centry->bitmap_size > image_cache->peak_conversions_size)) {
|
|
image_cache->peak_conversions = centry->conversion_count;
|
|
image_cache->peak_conversions_size = centry->bitmap_size;
|
|
}
|
|
}
|
|
|
|
static void image_cache__link(struct image_cache_entry_s *centry)
|
|
{
|
|
centry->next = image_cache->entries;
|
|
centry->prev = NULL;
|
|
if (centry->next != NULL) {
|
|
centry->next->prev = centry;
|
|
}
|
|
image_cache->entries = centry;
|
|
}
|
|
|
|
static void image_cache__unlink(struct image_cache_entry_s *centry)
|
|
{
|
|
/* unlink entry */
|
|
if (centry->prev == NULL) {
|
|
/* first in list */
|
|
if (centry->next != NULL) {
|
|
centry->next->prev = centry->prev;
|
|
image_cache->entries = centry->next;
|
|
} else {
|
|
/* empty list */
|
|
image_cache->entries = NULL;
|
|
}
|
|
} else {
|
|
centry->prev->next = centry->next;
|
|
|
|
if (centry->next != NULL) {
|
|
centry->next->prev = centry->prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* free bitmap from an image cache entry
|
|
*
|
|
* \param centry The image cache entry to free bitmap from.
|
|
*/
|
|
static void image_cache__free_bitmap(struct image_cache_entry_s *centry)
|
|
{
|
|
if (centry->bitmap != NULL) {
|
|
#ifdef IMAGE_CACHE_VERBOSE
|
|
NSLOG(netsurf, INFO,
|
|
"Freeing bitmap %p size %d age %d redraw count %d",
|
|
centry->bitmap,
|
|
centry->bitmap_size,
|
|
image_cache->current_age - centry->bitmap_age,
|
|
centry->redraw_count);
|
|
#endif
|
|
guit->bitmap->destroy(centry->bitmap);
|
|
centry->bitmap = NULL;
|
|
image_cache->total_bitmap_size -= centry->bitmap_size;
|
|
image_cache->bitmap_count--;
|
|
if (centry->redraw_count == 0) {
|
|
image_cache->specultive_miss_count++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* free image cache entry
|
|
*
|
|
* \param centry The image cache entry to free.
|
|
*/
|
|
static void image_cache__free_entry(struct image_cache_entry_s *centry)
|
|
{
|
|
#ifdef IMAGE_CACHE_VERBOSE
|
|
NSLOG(netsurf, INFO, "freeing %p ", centry);
|
|
#endif
|
|
|
|
if (centry->redraw_count == 0) {
|
|
image_cache->total_unrendered++;
|
|
}
|
|
|
|
image_cache__free_bitmap(centry);
|
|
|
|
image_cache__unlink(centry);
|
|
|
|
free(centry);
|
|
}
|
|
|
|
/**
|
|
* Image cache cleaner
|
|
*
|
|
* \param icache The image cache context.
|
|
*/
|
|
static void image_cache__clean(struct image_cache_s *icache)
|
|
{
|
|
struct image_cache_entry_s *centry = icache->entries;
|
|
|
|
while (centry != NULL) {
|
|
if ((icache->current_age - centry->redraw_age) >
|
|
icache->params.bg_clean_time) {
|
|
/* only consider older entries, avoids active entries */
|
|
if ((icache->total_bitmap_size >
|
|
(icache->params.limit - icache->params.hysteresis)) &&
|
|
(rand() > (RAND_MAX / 2))) {
|
|
image_cache__free_bitmap(centry);
|
|
}
|
|
}
|
|
centry=centry->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache background scheduled callback.
|
|
*
|
|
* \param p The image cache context.
|
|
*/
|
|
static void image_cache__background_update(void *p)
|
|
{
|
|
struct image_cache_s *icache = p;
|
|
|
|
/* increment current cache age */
|
|
icache->current_age += icache->params.bg_clean_time;
|
|
|
|
#ifdef IMAGE_CACHE_VERBOSE
|
|
NSLOG(netsurf, INFO, "Cache age %ds", icache->current_age / 1000);
|
|
#endif
|
|
|
|
image_cache__clean(icache);
|
|
|
|
guit->misc->schedule(icache->params.bg_clean_time,
|
|
image_cache__background_update,
|
|
icache);
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
struct bitmap *image_cache_get_bitmap(const struct content *c)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
centry = image_cache__find(c);
|
|
if (centry == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (centry->bitmap == NULL) {
|
|
if (centry->convert != NULL) {
|
|
centry->bitmap = centry->convert(centry->content);
|
|
}
|
|
|
|
if (centry->bitmap != NULL) {
|
|
image_cache_stats_bitmap_add(centry);
|
|
image_cache->miss_count++;
|
|
image_cache->miss_size += centry->bitmap_size;
|
|
} else {
|
|
image_cache->fail_count++;
|
|
image_cache->fail_size += centry->bitmap_size;
|
|
}
|
|
} else {
|
|
image_cache->hit_count++;
|
|
image_cache->hit_size += centry->bitmap_size;
|
|
}
|
|
|
|
return centry->bitmap;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
bool image_cache_speculate(struct content *c)
|
|
{
|
|
bool decision = false;
|
|
|
|
/* If the cache is below its target usage and the bitmap is
|
|
* small enough speculate.
|
|
*/
|
|
if ((image_cache->total_bitmap_size < image_cache->params.limit) &&
|
|
(c->size <= image_cache->params.speculative_small)) {
|
|
#ifdef IMAGE_CACHE_VERBOSE
|
|
NSLOG(netsurf, INFO,
|
|
"content size (%d) is smaller than minimum (%d)",
|
|
c->size,
|
|
SPECULATE_SMALL);
|
|
#endif
|
|
decision = true;
|
|
}
|
|
|
|
#ifdef IMAGE_CACHE_VERBOSE
|
|
NSLOG(netsurf, INFO, "returning %d", decision);
|
|
#endif
|
|
return decision;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
struct bitmap *image_cache_find_bitmap(struct content *c)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
centry = image_cache__find(c);
|
|
if (centry == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return centry->bitmap;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
nserror
|
|
image_cache_init(const struct image_cache_parameters *image_cache_parameters)
|
|
{
|
|
image_cache = calloc(1, sizeof(struct image_cache_s));
|
|
if (image_cache == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
image_cache->params = *image_cache_parameters;
|
|
|
|
guit->misc->schedule(image_cache->params.bg_clean_time,
|
|
image_cache__background_update,
|
|
image_cache);
|
|
|
|
NSLOG(netsurf, INFO,
|
|
"Image cache initialised with a limit of %"PRIsizet" hysteresis of %"PRIsizet,
|
|
image_cache->params.limit,
|
|
image_cache->params.hysteresis);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
nserror image_cache_fini(void)
|
|
{
|
|
unsigned int op_count;
|
|
uint64_t op_size;
|
|
|
|
guit->misc->schedule(-1, image_cache__background_update, image_cache);
|
|
|
|
NSLOG(netsurf, INFO, "Size at finish %"PRIsizet" (in %d)",
|
|
image_cache->total_bitmap_size, image_cache->bitmap_count);
|
|
|
|
while (image_cache->entries != NULL) {
|
|
image_cache__free_entry(image_cache->entries);
|
|
}
|
|
|
|
op_count = image_cache->hit_count +
|
|
image_cache->miss_count +
|
|
image_cache->fail_count;
|
|
|
|
op_size = image_cache->hit_size +
|
|
image_cache->miss_size +
|
|
image_cache->fail_size;
|
|
|
|
NSLOG(netsurf, INFO, "Age %ds", image_cache->current_age / 1000);
|
|
NSLOG(netsurf, INFO, "Peak size %"PRIsizet" (in %d)",
|
|
image_cache->max_bitmap_size,
|
|
image_cache->max_bitmap_size_count);
|
|
NSLOG(netsurf, INFO, "Peak image count %d (size %"PRIsizet")",
|
|
image_cache->max_bitmap_count,
|
|
image_cache->max_bitmap_count_size);
|
|
|
|
if ((op_count > 0) && (op_size >0)) {
|
|
|
|
NSLOG(netsurf, INFO,
|
|
"Cache total/hit/miss/fail (counts) %d/%d/%d/%d (100%%/%d%%/%d%%/%d%%)",
|
|
op_count,
|
|
image_cache->hit_count,
|
|
image_cache->miss_count,
|
|
image_cache->fail_count,
|
|
(image_cache->hit_count * 100) / op_count,
|
|
(image_cache->miss_count * 100) / op_count,
|
|
(image_cache->fail_count * 100) / op_count);
|
|
NSLOG(netsurf, INFO,
|
|
"Cache total/hit/miss/fail (size) %"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64" (100%%/%"PRId64"%%/%"PRId64"%%/%"PRId64"%%)",
|
|
op_size,
|
|
image_cache->hit_size,
|
|
image_cache->miss_size,
|
|
image_cache->fail_size,
|
|
(image_cache->hit_size * 100) / op_size,
|
|
(image_cache->miss_size * 100) / op_size,
|
|
(image_cache->fail_size * 100) / op_size);
|
|
}
|
|
|
|
NSLOG(netsurf, INFO,
|
|
"Total images never rendered: %d (includes %d that were converted)",
|
|
image_cache->total_unrendered,
|
|
image_cache->specultive_miss_count);
|
|
|
|
NSLOG(netsurf, INFO,
|
|
"Total number of excessive conversions: %d (from %d images converted more than once)",
|
|
image_cache->total_extra_conversions,
|
|
image_cache->total_extra_conversions_count);
|
|
|
|
NSLOG(netsurf, INFO, "Bitmap of size %d had most (%d) conversions",
|
|
image_cache->peak_conversions_size,
|
|
image_cache->peak_conversions);
|
|
|
|
free(image_cache);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
nserror image_cache_add(struct content *content,
|
|
struct bitmap *bitmap,
|
|
image_cache_convert_fn *convert)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
/* bump the cache age by a ms to ensure multiple items are not
|
|
* added at exactly the same time
|
|
*/
|
|
image_cache->current_age++;
|
|
|
|
centry = image_cache__find(content);
|
|
if (centry == NULL) {
|
|
/* new cache entry, content not previously added */
|
|
centry = calloc(1, sizeof(struct image_cache_entry_s));
|
|
if (centry == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
image_cache__link(centry);
|
|
centry->content = content;
|
|
|
|
centry->bitmap_size = content->width * content->height * 4llu;
|
|
}
|
|
|
|
NSLOG(netsurf, INFO, "centry %p, content %p, bitmap %p", centry,
|
|
content, bitmap);
|
|
|
|
centry->convert = convert;
|
|
|
|
/* set bitmap entry if one is passed, free extant one if present */
|
|
if (bitmap != NULL) {
|
|
if (centry->bitmap != NULL) {
|
|
guit->bitmap->destroy(centry->bitmap);
|
|
} else {
|
|
image_cache_stats_bitmap_add(centry);
|
|
}
|
|
centry->bitmap = bitmap;
|
|
} else {
|
|
/* no bitmap, check to see if we should speculatively convert */
|
|
if ((centry->convert != NULL) &&
|
|
(image_cache_speculate(content) == true)) {
|
|
centry->bitmap = centry->convert(centry->content);
|
|
|
|
if (centry->bitmap != NULL) {
|
|
image_cache_stats_bitmap_add(centry);
|
|
} else {
|
|
image_cache->fail_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
nserror image_cache_remove(struct content *content)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
/* get the cache entry */
|
|
centry = image_cache__find(content);
|
|
if (centry == NULL) {
|
|
NSLOG(netsurf, INFO,
|
|
"Could not find cache entry for content (%p)", content);
|
|
return NSERROR_NOT_FOUND;
|
|
}
|
|
|
|
image_cache__free_entry(centry);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
int image_cache_snsummaryf(char *string, size_t size, const char *fmt)
|
|
{
|
|
size_t slen = 0; /* current output string length */
|
|
int fmtc = 0; /* current index into format string */
|
|
bool pct;
|
|
unsigned int op_count;
|
|
uint64_t op_size;
|
|
|
|
op_count = image_cache->hit_count +
|
|
image_cache->miss_count +
|
|
image_cache->fail_count;
|
|
|
|
op_size = image_cache->hit_size +
|
|
image_cache->miss_size +
|
|
image_cache->fail_size;
|
|
|
|
while((slen < size) && (fmt[fmtc] != 0)) {
|
|
if (fmt[fmtc] == '%') {
|
|
fmtc++;
|
|
|
|
/* check for percentage modifier */
|
|
if (fmt[fmtc] == 'p') {
|
|
fmtc++;
|
|
pct = true;
|
|
} else {
|
|
pct = false;
|
|
}
|
|
|
|
#define FMTCHR(chr,fmt,var) case chr : \
|
|
slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); break
|
|
|
|
#define FMTPCHR(chr,fmt,var,div) \
|
|
case chr : \
|
|
if (pct) { \
|
|
if (div > 0) { \
|
|
slen += snprintf(string + slen, size - slen, "%"PRId64, (uint64_t)((image_cache->var * 100) / div)); \
|
|
} else { \
|
|
slen += snprintf(string + slen, size - slen, "100"); \
|
|
} \
|
|
} else { \
|
|
slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); \
|
|
} break
|
|
|
|
|
|
switch (fmt[fmtc]) {
|
|
case '%':
|
|
string[slen] = '%';
|
|
slen++;
|
|
break;
|
|
|
|
FMTCHR('a', PRIsizet, params.limit);
|
|
FMTCHR('b', PRIsizet, params.hysteresis);
|
|
FMTCHR('c', PRIsizet, total_bitmap_size);
|
|
FMTCHR('d', "d", bitmap_count);
|
|
FMTCHR('e', "u", current_age / 1000);
|
|
FMTCHR('f', PRIsizet, max_bitmap_size);
|
|
FMTCHR('g', "d", max_bitmap_size_count);
|
|
FMTCHR('h', "d", max_bitmap_count);
|
|
FMTCHR('i', PRIsizet, max_bitmap_count_size);
|
|
|
|
|
|
case 'j':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%u", pct?100:op_count);
|
|
break;
|
|
|
|
FMTPCHR('k', "d", hit_count, op_count);
|
|
FMTPCHR('l', "d", miss_count, op_count);
|
|
FMTPCHR('m', "d", fail_count, op_count);
|
|
|
|
case 'n':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%"PRId64, pct?100:op_size);
|
|
break;
|
|
|
|
FMTPCHR('o', PRId64, hit_size, op_size);
|
|
FMTPCHR('q', PRId64, miss_size, op_size);
|
|
FMTPCHR('r', PRId64, fail_size, op_size);
|
|
|
|
FMTCHR('s', "d", total_unrendered);
|
|
FMTCHR('t', "d", specultive_miss_count);
|
|
FMTCHR('u', "d", total_extra_conversions);
|
|
FMTCHR('v', "d", total_extra_conversions_count);
|
|
FMTCHR('w', "u", peak_conversions_size);
|
|
FMTCHR('x', "d", peak_conversions);
|
|
|
|
|
|
}
|
|
#undef FMTCHR
|
|
#undef FMTPCHR
|
|
|
|
fmtc++;
|
|
} else {
|
|
string[slen] = fmt[fmtc];
|
|
slen++;
|
|
fmtc++;
|
|
}
|
|
}
|
|
|
|
/* Ensure that we NUL-terminate the output */
|
|
string[min(slen, size - 1)] = '\0';
|
|
|
|
return slen;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
int
|
|
image_cache_snentryf(char *string,
|
|
size_t size,
|
|
unsigned int entryn,
|
|
const char *fmt)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
size_t slen = 0; /* current output string length */
|
|
int fmtc = 0; /* current index into format string */
|
|
lwc_string *origin; /* current entry's origin */
|
|
|
|
centry = image_cache__findn(entryn);
|
|
if (centry == NULL)
|
|
return -1;
|
|
|
|
while((slen < size) && (fmt[fmtc] != 0)) {
|
|
if (fmt[fmtc] == '%') {
|
|
fmtc++;
|
|
switch (fmt[fmtc]) {
|
|
case 'e':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%u", entryn);
|
|
break;
|
|
|
|
case 'r':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%u", centry->redraw_count);
|
|
break;
|
|
|
|
case 'a':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%.2f", (float)((image_cache->current_age - centry->redraw_age)) / 1000);
|
|
break;
|
|
|
|
|
|
case 'c':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%d", centry->conversion_count);
|
|
break;
|
|
|
|
case 'g':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%.2f", (float)((image_cache->current_age - centry->bitmap_age)) / 1000);
|
|
break;
|
|
|
|
case 'k':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%p", centry->content);
|
|
break;
|
|
|
|
case 'U':
|
|
slen += snprintf(string + slen, size - slen,
|
|
"%s", nsurl_access(llcache_handle_get_url(centry->content->llcache)));
|
|
break;
|
|
|
|
case 'o':
|
|
if (nsurl_has_component(llcache_handle_get_url(
|
|
centry->content->llcache),
|
|
NSURL_HOST)) {
|
|
origin = nsurl_get_component(
|
|
llcache_handle_get_url(
|
|
centry->content->
|
|
llcache),
|
|
NSURL_HOST);
|
|
|
|
slen += snprintf(string + slen,
|
|
size - slen, "%s",
|
|
lwc_string_data(
|
|
origin));
|
|
|
|
lwc_string_unref(origin);
|
|
} else {
|
|
slen += snprintf(string + slen,
|
|
size - slen, "%s",
|
|
"localhost");
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if (centry->bitmap != NULL) {
|
|
slen += snprintf(string + slen,
|
|
size - slen,
|
|
"%" PRIsizet,
|
|
centry->bitmap_size);
|
|
} else {
|
|
slen += snprintf(string + slen,
|
|
size - slen,
|
|
"0");
|
|
}
|
|
break;
|
|
}
|
|
fmtc++;
|
|
} else {
|
|
string[slen] = fmt[fmtc];
|
|
slen++;
|
|
fmtc++;
|
|
}
|
|
}
|
|
|
|
/* Ensure that we NUL-terminate the output */
|
|
string[min(slen, size - 1)] = '\0';
|
|
|
|
return slen;
|
|
}
|
|
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
bool image_cache_redraw(struct content *c,
|
|
struct content_redraw_data *data,
|
|
const struct rect *clip,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
/* get the cache entry */
|
|
centry = image_cache__find(c);
|
|
if (centry == NULL) {
|
|
NSLOG(netsurf, INFO,
|
|
"Could not find cache entry for content (%p)", c);
|
|
return false;
|
|
}
|
|
|
|
if (centry->bitmap == NULL) {
|
|
if (centry->convert != NULL) {
|
|
centry->bitmap = centry->convert(centry->content);
|
|
}
|
|
|
|
if (centry->bitmap != NULL) {
|
|
image_cache_stats_bitmap_add(centry);
|
|
image_cache->miss_count++;
|
|
image_cache->miss_size += centry->bitmap_size;
|
|
} else {
|
|
image_cache->fail_count++;
|
|
image_cache->fail_size += centry->bitmap_size;
|
|
return false;
|
|
}
|
|
} else {
|
|
image_cache->hit_count++;
|
|
image_cache->hit_size += centry->bitmap_size;
|
|
}
|
|
|
|
|
|
/* update statistics */
|
|
centry->redraw_count++;
|
|
centry->redraw_age = image_cache->current_age;
|
|
|
|
return image_bitmap_plot(centry->bitmap, data, clip, ctx);
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
void image_cache_destroy(struct content *content)
|
|
{
|
|
struct image_cache_entry_s *centry;
|
|
|
|
/* get the cache entry */
|
|
centry = image_cache__find(content);
|
|
if (centry == NULL) {
|
|
NSLOG(netsurf, INFO,
|
|
"Could not find cache entry for content (%p)", content);
|
|
} else {
|
|
image_cache__free_entry(centry);
|
|
}
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
void *image_cache_get_internal(const struct content *c, void *context)
|
|
{
|
|
return image_cache_get_bitmap(c);
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
bool image_cache_is_opaque(struct content *c)
|
|
{
|
|
struct bitmap *bmp;
|
|
bmp = image_cache_get_bitmap(c);
|
|
if (bmp != NULL) {
|
|
return guit->bitmap->get_opaque(bmp);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* exported interface documented in image_cache.h */
|
|
content_type image_cache_content_type(void)
|
|
{
|
|
return CONTENT_IMAGE;
|
|
}
|