Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
  3.  *
  4.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  5.  *
  6.  * NetSurf is free software; you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation; version 2 of the License.
  9.  *
  10.  * NetSurf is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18.  
  19. #include <assert.h>
  20. #include <inttypes.h>
  21. #include <stdint.h>
  22. #include <stdbool.h>
  23. #include <string.h>
  24.  
  25. #include "utils/schedule.h"
  26. #include "utils/log.h"
  27. #include "content/content_protected.h"
  28.  
  29. #include "image/image_cache.h"
  30. #include "image/image.h"
  31.  
  32. /** Age of an entry within the cache
  33.  *
  34.  * type deffed away so it can be readily changed later perhaps to a
  35.  * wallclock time structure.
  36.  */
  37. typedef unsigned int cache_age;
  38.  
  39. /** Image cache entry
  40.  */
  41. struct image_cache_entry_s {
  42.         struct image_cache_entry_s *next; /* next cache entry in list */
  43.         struct image_cache_entry_s *prev; /* previous cache entry in list */
  44.  
  45.         struct content *content; /** content is used as a key */
  46.         struct bitmap *bitmap; /** associated bitmap entry */
  47.         /** Conversion routine */
  48.         image_cache_convert_fn *convert;
  49.  
  50.         /* Statistics for replacement algorithm */
  51.  
  52.         unsigned int redraw_count; /**< number of times object has been drawn */
  53.         cache_age redraw_age; /**< Age of last redraw */
  54.         size_t bitmap_size; /**< size if storage occupied by bitmap */
  55.         cache_age bitmap_age; /**< Age of last conversion to a bitmap by cache*/
  56.  
  57.         int conversion_count; /**< Number of times image has been converted */
  58. };
  59.  
  60. /** Current state of the cache.
  61.  *
  62.  * Global state of the cache. entries "age" is determined based on a
  63.  * monotonically incrementing operation count. This avoids issues with
  64.  * using wall clock time while allowing the LRU algorithm to work
  65.  * sensibly.
  66.  */
  67. struct image_cache_s {
  68.         /** Cache parameters */
  69.         struct image_cache_parameters params;
  70.  
  71.         /** The "age" of the current operation */
  72.         cache_age current_age;
  73.  
  74.         /* The objects the cache holds */
  75.         struct image_cache_entry_s *entries;
  76.  
  77.  
  78.         /* Statistics for management algorithm */
  79.  
  80.         /** total size of bitmaps currently allocated */
  81.         size_t total_bitmap_size;
  82.  
  83.         /** Total count of bitmaps currently allocated */
  84.         int bitmap_count;
  85.  
  86.         /** Maximum size of bitmaps allocated at any one time */
  87.         size_t max_bitmap_size;
  88.         /** The number of objects when maximum bitmap usage occoured */
  89.         int max_bitmap_size_count;
  90.  
  91.         /** Maximum count of bitmaps allocated at any one time */
  92.         int max_bitmap_count;
  93.         /** The size of the bitmaps when the max count occoured */
  94.         size_t max_bitmap_count_size;
  95.  
  96.         /** Bitmap was not available at plot time required conversion */
  97.         int miss_count;
  98.         uint64_t miss_size;
  99.         /** Bitmap was available at plot time required no conversion */
  100.         int hit_count;
  101.         uint64_t hit_size;
  102.         /** Bitmap was not available at plot time and required
  103.          * conversion which failed.
  104.          */
  105.         int fail_count;
  106.         uint64_t fail_size;
  107.  
  108.         /* Cache entry freed without ever being redrawn */
  109.         int total_unrendered;
  110.         /** Bitmap was available but never required - wasted conversions */
  111.         int specultive_miss_count;
  112.  
  113.         /** Total number of additional (after the first) conversions */
  114.         int total_extra_conversions;
  115.         /** counts total number of images with more than one conversion */
  116.         int total_extra_conversions_count;
  117.  
  118.         /** Bitmap with most conversions was converted this many times */
  119.         int peak_conversions;
  120.         /** Size of bitmap with most conversions */
  121.         unsigned int peak_conversions_size;
  122. };
  123.  
  124. /** image cache state */
  125. static struct image_cache_s *image_cache = NULL;
  126.  
  127.  
  128. /** Find the nth cache entry
  129.  */
  130. static struct image_cache_entry_s *image_cache__findn(int entryn)
  131. {
  132.         struct image_cache_entry_s *found;
  133.  
  134.         found = image_cache->entries;
  135.         while ((found != NULL) && (entryn > 0)) {
  136.                 entryn--;
  137.                 found = found->next;
  138.         }
  139.         return found;
  140. }
  141.  
  142. /** Find the cache entry for a content
  143.  */
  144. static struct image_cache_entry_s *image_cache__find(const struct content *c)
  145. {
  146.         struct image_cache_entry_s *found;
  147.  
  148.         found = image_cache->entries;
  149.         while ((found != NULL) && (found->content != c)) {
  150.                 found = found->next;
  151.         }
  152.         return found;
  153. }
  154.  
  155. static void image_cache_stats_bitmap_add(struct image_cache_entry_s *centry)
  156. {
  157.         centry->bitmap_age = image_cache->current_age;
  158.         centry->conversion_count++;
  159.  
  160.         image_cache->total_bitmap_size += centry->bitmap_size;
  161.         image_cache->bitmap_count++;
  162.  
  163.         if (image_cache->total_bitmap_size > image_cache->max_bitmap_size) {
  164.                 image_cache->max_bitmap_size = image_cache->total_bitmap_size;
  165.                 image_cache->max_bitmap_size_count = image_cache->bitmap_count;
  166.  
  167.         }
  168.  
  169.         if (image_cache->bitmap_count > image_cache->max_bitmap_count) {
  170.                 image_cache->max_bitmap_count = image_cache->bitmap_count;
  171.                 image_cache->max_bitmap_count_size = image_cache->total_bitmap_size;
  172.         }
  173.  
  174.         if (centry->conversion_count == 2) {
  175.                 image_cache->total_extra_conversions_count++;
  176.         }
  177.  
  178.         if (centry->conversion_count > 1) {
  179.                 image_cache->total_extra_conversions++;
  180.         }
  181.  
  182.         if ((centry->conversion_count > image_cache->peak_conversions) ||
  183.             (centry->conversion_count == image_cache->peak_conversions &&
  184.              centry->bitmap_size > image_cache->peak_conversions_size)) {
  185.                 image_cache->peak_conversions = centry->conversion_count;
  186.                 image_cache->peak_conversions_size = centry->bitmap_size;
  187.         }
  188. }
  189.  
  190. static void image_cache__link(struct image_cache_entry_s *centry)
  191. {
  192.         centry->next = image_cache->entries;
  193.         centry->prev = NULL;
  194.         if (centry->next != NULL) {
  195.                 centry->next->prev = centry;
  196.         }
  197.         image_cache->entries = centry;
  198. }
  199.  
  200. static void image_cache__unlink(struct image_cache_entry_s *centry)
  201. {
  202.         /* unlink entry */
  203.         if (centry->prev == NULL) {
  204.                 /* first in list */
  205.                 if (centry->next != NULL) {
  206.                         centry->next->prev = centry->prev;
  207.                         image_cache->entries = centry->next;
  208.                 } else {
  209.                         /* empty list */
  210.                         image_cache->entries = NULL;
  211.                 }
  212.         } else {
  213.                 centry->prev->next = centry->next;
  214.  
  215.                 if (centry->next != NULL) {
  216.                         centry->next->prev = centry->prev;
  217.                 }
  218.         }
  219. }
  220.  
  221. static void image_cache__free_bitmap(struct image_cache_entry_s *centry)
  222. {
  223.         if (centry->bitmap != NULL) {
  224. #ifdef IMAGE_CACHE_VERBOSE
  225.                 LOG(("Freeing bitmap %p size %d age %d redraw count %d",
  226.                      centry->bitmap,
  227.                      centry->bitmap_size,
  228.                      image_cache->current_age - centry->bitmap_age,
  229.                      centry->redraw_count));
  230. #endif
  231.                 bitmap_destroy(centry->bitmap);
  232.                 centry->bitmap = NULL;
  233.                 image_cache->total_bitmap_size -= centry->bitmap_size;
  234.                 image_cache->bitmap_count--;
  235.                 if (centry->redraw_count == 0) {
  236.                         image_cache->specultive_miss_count++;
  237.                 }
  238.         }
  239.  
  240. }
  241.  
  242. /* free cache entry */
  243. static void image_cache__free_entry(struct image_cache_entry_s *centry)
  244. {
  245. #ifdef IMAGE_CACHE_VERBOSE
  246.         LOG(("freeing %p ", centry));
  247. #endif
  248.  
  249.         if (centry->redraw_count == 0) {
  250.                 image_cache->total_unrendered++;
  251.         }
  252.  
  253.         image_cache__free_bitmap(centry);
  254.  
  255.         image_cache__unlink(centry);
  256.  
  257.         free(centry);
  258. }
  259.  
  260. /** Cache cleaner */
  261. static void image_cache__clean(struct image_cache_s *icache)
  262. {
  263.         struct image_cache_entry_s *centry = icache->entries;
  264.  
  265.         while (centry != NULL) {
  266.                 if ((icache->current_age - centry->redraw_age) >
  267.                     icache->params.bg_clean_time) {
  268.                         /* only consider older entries, avoids active entries */
  269.                         if ((icache->total_bitmap_size >
  270.                              (icache->params.limit - icache->params.hysteresis)) &&
  271.                             (rand() > (RAND_MAX / 2))) {
  272.                                 image_cache__free_bitmap(centry);
  273.                         }
  274.                 }
  275.                 centry=centry->next;
  276.         }
  277. }
  278.  
  279. /** Cache background scheduled callback. */
  280. static void image_cache__background_update(void *p)
  281. {
  282.         struct image_cache_s *icache = p;
  283.  
  284.         /* increment current cache age */
  285.         icache->current_age += icache->params.bg_clean_time;
  286.  
  287. #ifdef IMAGE_CACHE_VERBOSE
  288.         LOG(("Cache age %ds", icache->current_age / 1000));
  289. #endif
  290.  
  291.         image_cache__clean(icache);
  292.  
  293.         schedule((icache->params.bg_clean_time / 10),
  294.                  image_cache__background_update,
  295.                  icache);
  296. }
  297.  
  298. /* exported interface documented in image_cache.h */
  299. struct bitmap *image_cache_get_bitmap(const struct content *c)
  300. {
  301.         struct image_cache_entry_s *centry;
  302.  
  303.         centry = image_cache__find(c);
  304.         if (centry == NULL) {
  305.                 return NULL;
  306.         }
  307.  
  308.         if (centry->bitmap == NULL) {
  309.                 if (centry->convert != NULL) {
  310.                         centry->bitmap = centry->convert(centry->content);
  311.                 }
  312.  
  313.                 if (centry->bitmap != NULL) {
  314.                         image_cache_stats_bitmap_add(centry);
  315.                         image_cache->miss_count++;
  316.                         image_cache->miss_size += centry->bitmap_size;
  317.                 } else {
  318.                         image_cache->fail_count++;
  319.                         image_cache->fail_size += centry->bitmap_size;
  320.                 }
  321.         } else {
  322.                 image_cache->hit_count++;
  323.                 image_cache->hit_size += centry->bitmap_size;
  324.         }
  325.  
  326.         return centry->bitmap;
  327. }
  328.  
  329. /* exported interface documented in image_cache.h */
  330. bool image_cache_speculate(struct content *c)
  331. {
  332.         bool decision = false;
  333.  
  334.         /* If the cache is below its target usage and the bitmap is
  335.          * small enough speculate.
  336.          */
  337.         if ((image_cache->total_bitmap_size < image_cache->params.limit) &&
  338.             (c->size <= image_cache->params.speculative_small)) {
  339. #ifdef IMAGE_CACHE_VERBOSE
  340.                 LOG(("content size (%d) is smaller than minimum (%d)", c->size, SPECULATE_SMALL));
  341. #endif
  342.                 decision = true;
  343.         }
  344.  
  345. #ifdef IMAGE_CACHE_VERBOSE
  346.         LOG(("returning %d", decision));
  347. #endif
  348.         return decision;
  349. }
  350.  
  351. /* exported interface documented in image_cache.h */
  352. struct bitmap *image_cache_find_bitmap(struct content *c)
  353. {
  354.         struct image_cache_entry_s *centry;
  355.  
  356.         centry = image_cache__find(c);
  357.         if (centry == NULL) {
  358.                 return NULL;
  359.         }
  360.  
  361.         return centry->bitmap;
  362. }
  363.  
  364. /* exported interface documented in image_cache.h */
  365. nserror
  366. image_cache_init(const struct image_cache_parameters *image_cache_parameters)
  367. {
  368.         image_cache = calloc(1, sizeof(struct image_cache_s));
  369.         if (image_cache == NULL) {
  370.                 return NSERROR_NOMEM;
  371.         }
  372.  
  373.         image_cache->params = *image_cache_parameters;
  374.  
  375.         schedule((image_cache->params.bg_clean_time / 10),
  376.                  image_cache__background_update,
  377.                  image_cache);
  378.  
  379.         LOG(("Image cache initilised with a limit of %d hysteresis of %d",
  380.              image_cache->params.limit, image_cache->params.hysteresis));
  381.  
  382.         return NSERROR_OK;
  383. }
  384.  
  385. /* exported interface documented in image_cache.h */
  386. nserror image_cache_fini(void)
  387. {
  388.         unsigned int op_count;
  389.  
  390.         schedule_remove(image_cache__background_update, image_cache);
  391.  
  392.         LOG(("Size at finish %d (in %d)",
  393.              image_cache->total_bitmap_size,
  394.              image_cache->bitmap_count));
  395.  
  396.         while (image_cache->entries != NULL) {
  397.                 image_cache__free_entry(image_cache->entries);
  398.         }
  399.  
  400.         op_count = image_cache->hit_count +
  401.                 image_cache->miss_count +
  402.                 image_cache->fail_count;
  403.  
  404.         LOG(("Age %ds", image_cache->current_age / 1000));
  405.         LOG(("Peak size %d (in %d)",
  406.              image_cache->max_bitmap_size,
  407.              image_cache->max_bitmap_size_count ));
  408.         LOG(("Peak image count %d (size %d)",
  409.              image_cache->max_bitmap_count,
  410.              image_cache->max_bitmap_count_size));
  411.  
  412.         if (op_count > 0) {
  413.                 uint64_t op_size;
  414.  
  415.                 op_size = image_cache->hit_size +
  416.                         image_cache->miss_size +
  417.                         image_cache->fail_size;
  418.  
  419.                 LOG(("Cache total/hit/miss/fail (counts) %d/%d/%d/%d (100%%/%d%%/%d%%/%d%%)",
  420.                      op_count,
  421.                      image_cache->hit_count,
  422.                      image_cache->miss_count,
  423.                      image_cache->fail_count,
  424.                      (image_cache->hit_count * 100) / op_count,
  425.                      (image_cache->miss_count * 100) / op_count,
  426.                      (image_cache->fail_count * 100) / op_count));
  427.                 LOG(("Cache total/hit/miss/fail (size) %d/%d/%d/%d (100%%/%d%%/%d%%/%d%%)",
  428.                      op_size,
  429.                      image_cache->hit_size,
  430.                      image_cache->miss_size,
  431.                      image_cache->fail_size,
  432.                      (image_cache->hit_size * 100) / op_size,
  433.                      (image_cache->miss_size * 100) / op_size,
  434.                      (image_cache->fail_size * 100) / op_size));
  435.         }
  436.  
  437.         LOG(("Total images never rendered: %d (includes %d that were converted)",
  438.              image_cache->total_unrendered,
  439.              image_cache->specultive_miss_count));
  440.  
  441.         LOG(("Total number of excessive conversions: %d (from %d images converted more than once)",
  442.              image_cache->total_extra_conversions,
  443.              image_cache->total_extra_conversions_count));
  444.  
  445.         LOG(("Bitmap of size %d had most (%d) conversions",
  446.              image_cache->peak_conversions_size,
  447.              image_cache->peak_conversions));
  448.  
  449.         free(image_cache);
  450.  
  451.         return NSERROR_OK;
  452. }
  453.  
  454. /* exported interface documented in image_cache.h */
  455. nserror image_cache_add(struct content *content,
  456.                         struct bitmap *bitmap,
  457.                         image_cache_convert_fn *convert)
  458. {
  459.         struct image_cache_entry_s *centry;
  460.  
  461.         /* bump the cache age by a ms to ensure multiple items are not
  462.          * added at exactly the same time
  463.          */
  464.         image_cache->current_age++;
  465.  
  466.         centry = image_cache__find(content);
  467.         if (centry == NULL) {
  468.                 /* new cache entry, content not previously added */
  469.                 centry = calloc(1, sizeof(struct image_cache_entry_s));
  470.                 if (centry == NULL) {
  471.                         return NSERROR_NOMEM;
  472.                 }
  473.                 image_cache__link(centry);
  474.                 centry->content = content;
  475.  
  476.                 centry->bitmap_size = content->width * content->height * 4;
  477.         }
  478.  
  479.         LOG(("centry %p, content %p, bitmap %p", centry, content, bitmap));
  480.  
  481.         centry->convert = convert;
  482.  
  483.         /* set bitmap entry if one is passed, free extant one if present */
  484.         if (bitmap != NULL) {
  485.                 if (centry->bitmap != NULL) {
  486.                         bitmap_destroy(centry->bitmap);
  487.                 } else {
  488.                         image_cache_stats_bitmap_add(centry);
  489.                 }
  490.                 centry->bitmap = bitmap;
  491.         } else {
  492.                 /* no bitmap, check to see if we should speculatively convert */
  493.                 if ((centry->convert != NULL) &&
  494.                     (image_cache_speculate(content) == true)) {
  495.                         centry->bitmap = centry->convert(centry->content);
  496.  
  497.                         if (centry->bitmap != NULL) {
  498.                                 image_cache_stats_bitmap_add(centry);
  499.                         } else {
  500.                                 image_cache->fail_count++;
  501.                         }
  502.                 }
  503.         }
  504.  
  505.  
  506.  
  507.         return NSERROR_OK;
  508. }
  509.  
  510. /* exported interface documented in image_cache.h */
  511. nserror image_cache_remove(struct content *content)
  512. {
  513.         struct image_cache_entry_s *centry;
  514.  
  515.         /* get the cache entry */
  516.         centry = image_cache__find(content);
  517.         if (centry == NULL) {
  518.                 LOG(("Could not find cache entry for content (%p)", content));
  519.                 return NSERROR_NOT_FOUND;
  520.         }
  521.  
  522.         image_cache__free_entry(centry);
  523.  
  524.         return NSERROR_OK;
  525. }
  526.  
  527. /* exported interface documented in image_cache.h */
  528. int image_cache_snsummaryf(char *string, size_t size, const char *fmt)
  529. {
  530.         size_t slen = 0; /* current output string length */
  531.         int fmtc = 0; /* current index into format string */
  532.         bool pct;
  533.         unsigned int op_count;
  534.         uint64_t op_size;
  535.  
  536.         op_count = image_cache->hit_count +
  537.                 image_cache->miss_count +
  538.                 image_cache->fail_count;
  539.  
  540.         op_size = image_cache->hit_size +
  541.                 image_cache->miss_size +
  542.                 image_cache->fail_size;
  543.  
  544.         while((slen < size) && (fmt[fmtc] != 0)) {
  545.                 if (fmt[fmtc] == '%') {
  546.                         fmtc++;
  547.  
  548.                         /* check for percentage modifier */
  549.                         if (fmt[fmtc] == 'p') {
  550.                                 fmtc++;
  551.                                 pct = true;
  552.                         } else {
  553.                                 pct = false;
  554.                         }
  555.  
  556. #define FMTCHR(chr,fmt,var) case chr : \
  557. slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); break
  558.  
  559. #define FMTPCHR(chr,fmt,var,div) \
  560. case chr :                                      \
  561.         if (pct) {                                                      \
  562.                 if (div > 0) {                                          \
  563.                         slen += snprintf(string + slen, size - slen, "%"PRId64, (uint64_t)((image_cache->var * 100) / div)); \
  564.                 } else {                                                \
  565.                         slen += snprintf(string + slen, size - slen, "100"); \
  566.                 }                                                       \
  567.         } else {                                                        \
  568.                 slen += snprintf(string + slen, size - slen, "%"fmt, image_cache->var); \
  569.         } break
  570.  
  571.  
  572.                         switch (fmt[fmtc]) {
  573.                         case '%':
  574.                                 string[slen] = '%';
  575.                                 slen++;
  576.                                 break;
  577.  
  578.                         FMTCHR('a', SSIZET_FMT, params.limit);
  579.                         FMTCHR('b', SSIZET_FMT, params.hysteresis);
  580.                         FMTCHR('c', SSIZET_FMT, total_bitmap_size);
  581.                         FMTCHR('d', "d", bitmap_count);
  582.                         FMTCHR('e', "d", current_age / 1000);
  583.                         FMTCHR('f', SSIZET_FMT, max_bitmap_size);
  584.                         FMTCHR('g', "d", max_bitmap_size_count);
  585.                         FMTCHR('h', "d", max_bitmap_count);
  586.                         FMTCHR('i', SSIZET_FMT, max_bitmap_count_size);
  587.  
  588.  
  589.                         case 'j':
  590.                                 slen += snprintf(string + slen, size - slen,
  591.                                                  "%d", pct?100:op_count);
  592.                                 break;
  593.  
  594.                         FMTPCHR('k', "d", hit_count, op_count);
  595.                         FMTPCHR('l', "d", miss_count, op_count);
  596.                         FMTPCHR('m', "d", fail_count, op_count);
  597.  
  598.                         case 'n':
  599.                                 slen += snprintf(string + slen, size - slen,
  600.                                                  "%"PRId64, pct?100:op_size);
  601.                                 break;
  602.  
  603.                         FMTPCHR('o', PRId64, hit_size, op_size);
  604.                         FMTPCHR('q', PRId64, miss_size, op_size);
  605.                         FMTPCHR('r', PRId64, fail_size, op_size);
  606.  
  607.                         FMTCHR('s', "d", total_unrendered);
  608.                         FMTCHR('t', "d", specultive_miss_count);
  609.                         FMTCHR('u', "d", total_extra_conversions);
  610.                         FMTCHR('v', "d", total_extra_conversions_count);
  611.                         FMTCHR('w', "d", peak_conversions_size);
  612.                         FMTCHR('x', "d", peak_conversions);
  613.  
  614.  
  615.                         }
  616. #undef FMTCHR
  617. #undef FMTPCHR
  618.  
  619.                         fmtc++;
  620.                 } else {
  621.                         string[slen] = fmt[fmtc];
  622.                         slen++;
  623.                         fmtc++;
  624.                 }
  625.         }
  626.  
  627.         /* Ensure that we NUL-terminate the output */
  628.         string[min(slen, size - 1)] = '\0';
  629.  
  630.         return slen;
  631. }
  632.  
  633. /* exported interface documented in image_cache.h */
  634. int image_cache_snentryf(char *string, size_t size, unsigned int entryn,
  635.                 const char *fmt)
  636. {
  637.         struct image_cache_entry_s *centry;
  638.         size_t slen = 0; /* current output string length */
  639.         int fmtc = 0; /* current index into format string */
  640.         lwc_string *origin; /* current entry's origin */
  641.  
  642.         centry = image_cache__findn(entryn);
  643.         if (centry == NULL)
  644.                 return -1;
  645.  
  646.         while((slen < size) && (fmt[fmtc] != 0)) {
  647.                 if (fmt[fmtc] == '%') {
  648.                         fmtc++;
  649.                         switch (fmt[fmtc]) {
  650.                         case 'e':
  651.                                 slen += snprintf(string + slen, size - slen,
  652.                                                 "%d", entryn);
  653.                                 break;
  654.  
  655.                         case 'r':
  656.                                 slen += snprintf(string + slen, size - slen,
  657.                                                 "%u", centry->redraw_count);
  658.                                 break;
  659.  
  660.                         case 'a':
  661.                                 slen += snprintf(string + slen, size - slen,
  662.                                                  "%.2f", (float)((image_cache->current_age -  centry->redraw_age)) / 1000);
  663.                                 break;
  664.  
  665.  
  666.                         case 'c':
  667.                                 slen += snprintf(string + slen, size - slen,
  668.                                                 "%d", centry->conversion_count);
  669.                                 break;
  670.  
  671.                         case 'g':
  672.                                 slen += snprintf(string + slen, size - slen,
  673.                                                 "%.2f", (float)((image_cache->current_age -  centry->bitmap_age)) / 1000);
  674.                                 break;
  675.  
  676.                         case 'k':
  677.                                 slen += snprintf(string + slen, size - slen,
  678.                                                 "%p", centry->content);
  679.                                 break;
  680.  
  681.                         case 'U':
  682.                                 slen += snprintf(string + slen, size - slen,
  683.                                                 "%s", nsurl_access(llcache_handle_get_url(centry->content->llcache)));
  684.                                 break;
  685.  
  686.                         case 'o':
  687.                                 if (nsurl_has_component(llcache_handle_get_url(
  688.                                                 centry->content->llcache),
  689.                                                 NSURL_HOST)) {
  690.                                         origin = nsurl_get_component(
  691.                                                         llcache_handle_get_url(
  692.                                                         centry->content->
  693.                                                                 llcache),
  694.                                                         NSURL_HOST);
  695.                                        
  696.                                         slen += snprintf(string + slen,
  697.                                                         size - slen, "%s",
  698.                                                         lwc_string_data(
  699.                                                                 origin));
  700.  
  701.                                         lwc_string_unref(origin);
  702.                                 } else {
  703.                                         slen += snprintf(string + slen,
  704.                                                         size - slen, "%s",
  705.                                                         "localhost");
  706.                                 }
  707.                                 break;
  708.                        
  709.                         case 's':
  710.                                 if (centry->bitmap != NULL) {
  711.                                         slen += snprintf(string + slen,
  712.                                                          size - slen,
  713.                                                          "%"SSIZET_FMT,
  714.                                                          centry->bitmap_size);
  715.                                 } else {
  716.                                         slen += snprintf(string + slen,
  717.                                                          size - slen,
  718.                                                          "0");
  719.                                 }
  720.                                 break;
  721.                         }
  722.                         fmtc++;
  723.                 } else {
  724.                         string[slen] = fmt[fmtc];
  725.                         slen++;
  726.                         fmtc++;
  727.                 }
  728.         }
  729.  
  730.         /* Ensure that we NUL-terminate the output */
  731.         string[min(slen, size - 1)] = '\0';
  732.  
  733.         return slen;
  734. }
  735.  
  736.  
  737. /* exported interface documented in image_cache.h */
  738. bool image_cache_redraw(struct content *c,
  739.                         struct content_redraw_data *data,
  740.                         const struct rect *clip,
  741.                         const struct redraw_context *ctx)
  742. {
  743.         struct image_cache_entry_s *centry;
  744.  
  745.         /* get the cache entry */
  746.         centry = image_cache__find(c);
  747.         if (centry == NULL) {
  748.                 LOG(("Could not find cache entry for content (%p)", c));
  749.                 return false;
  750.         }
  751.  
  752.         if (centry->bitmap == NULL) {
  753.                 if (centry->convert != NULL) {
  754.                         centry->bitmap = centry->convert(centry->content);
  755.                 }
  756.  
  757.                 if (centry->bitmap != NULL) {
  758.                         image_cache_stats_bitmap_add(centry);
  759.                         image_cache->miss_count++;
  760.                         image_cache->miss_size += centry->bitmap_size;
  761.                 } else {
  762.                         image_cache->fail_count++;
  763.                         image_cache->fail_size += centry->bitmap_size;
  764.                         return false;
  765.                 }
  766.         } else {
  767.                 image_cache->hit_count++;
  768.                 image_cache->hit_size += centry->bitmap_size;
  769.         }
  770.  
  771.  
  772.         /* update statistics */
  773.         centry->redraw_count++;
  774.         centry->redraw_age = image_cache->current_age;
  775.  
  776.         return image_bitmap_plot(centry->bitmap, data, clip, ctx);
  777. }
  778.  
  779. void image_cache_destroy(struct content *content)
  780. {
  781.         struct image_cache_entry_s *centry;
  782.  
  783.         /* get the cache entry */
  784.         centry = image_cache__find(content);
  785.         if (centry == NULL) {
  786.                 LOG(("Could not find cache entry for content (%p)", content));
  787.         } else {
  788.                 image_cache__free_entry(centry);
  789.         }
  790. }
  791.  
  792. void *image_cache_get_internal(const struct content *c, void *context)
  793. {
  794.         return image_cache_get_bitmap(c);
  795. }
  796.  
  797. content_type image_cache_content_type(void)
  798. {
  799.         return CONTENT_IMAGE;
  800. }
  801.