A while back I wanted to open this huge satellite image I downloaded from
NASA, and the Gimp required more than 2Gb of swap space. It didn't work, so I
wrote a new tile-swap.c which makes additional swap files once the current one
hits 1Gb. Also, the following diff needs to be aplpied:

--- gimp-1.3.7/app/base/tile-private.h  2001-12-03 05:44:50.000000000 -0800
+++ gimp-1.3.7-tiles/app/base/tile-private.h    2002-08-03 18:59:37.000000000 -0
700
@@ -76,7 +76,7 @@
                       * for swapping. swap_num 1 is always the global
                       * swap file.
                       */
-  off_t swap_offset;  /* the offset within the swap file of the tile data.
+  guint64 swap_offset;/* the offset within the swap file of the tile data.
                       * if the tile data is in memory this will be set to -1.
                       */
   TileLink *tlink;


This is against 1.3.7, but should trivially apply to 1.3.8 since it's just the
wholesale swapping out of a file.

Please direct any comments, flames, etc, my way.

Tim
-- 
Tim Newsome  [EMAIL PROTECTED]  http://www.wiw.org/~drz/
I'd like to hear some funky Dixieland / Pretty mama come and take me by the hand
/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <stdio.h> /* SEEK_SET */
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef USE_PTHREADS
#include <pthread.h>
#endif

#include <glib-object.h>

#ifdef G_OS_WIN32
#include <io.h>
#endif

#include "base-types.h"

#include "tile.h"
#include "tile-private.h"
#include "tile-swap.h"

/* The max size of a cache file. */
#define MAX_FILE_SIZE           (1024 * 1024 * 1024)
/* The blocksize we use to allocate tiles in. Tile sizes get rounded up to the
 * nearest multiple of this number. */
#define TILE_SIZE_INCREMENT     4096
/* Amount of files to keep open at one time. */
#define FILE_CACHE_SIZE         3
#undef SWAP_DEBUG

typedef struct _FreelistEntry {
    guint64 offset;
    guint64 size;
    struct _FreelistEntry *next;
} FreelistEntry;

typedef struct {
    guint file_number;
    gint fd;
    guint last_used;
} FileCacheEntry;

static FileCacheEntry file_cache[FILE_CACHE_SIZE];
static guint file_cache_hits = 0;
static guint file_cache_misses = 0;

typedef struct {
    gchar         *path;
    FreelistEntry *freelist;
    guint          max_file;
    guint          last_used;
} SwapDir;

static SwapDir swap_dir;

#ifdef SWAP_DEBUG
static void print_freelist(void)
{
    FreelistEntry *entry;

    entry = swap_dir.freelist;
    g_printerr("Freelist:\n");
    while (entry) {
        g_printerr("\t%llx -- %llx\n", entry->offset,
                entry->offset + entry->size);
        entry = entry->next;
    }
}
#endif

/* Remove the directory/tile files. */
void tile_swap_exit(void)
{
    FreelistEntry *entry, *next;
    guchar *filename;
    gint file;
    int e;

#ifdef SWAP_DEBUG
    g_printerr("tile_swap_exit()\n");
    g_printerr("file cache hits: %.2f%%\n",
            100. * file_cache_hits / (file_cache_hits + file_cache_misses));
#endif

    for (file = 0; file < FILE_CACHE_SIZE; file++)
        if (file_cache[file].fd >= 0) {
            close(file_cache[file].fd);
            file_cache[file].fd = -1;
        }

    /* free the freelist */
    entry = swap_dir.freelist;
    while (entry) {
        next = entry->next;
        g_free(entry);
        entry = next;
    }
    swap_dir.freelist = NULL;

    filename = g_new(guchar, strlen(swap_dir.path) + 50);
    for (file = 0; file <= swap_dir.max_file; file++) {
        strcpy(filename, swap_dir.path);
        sprintf(filename + strlen(filename), "/%08x.cache", file);
#ifdef SWAP_DEBUG
        g_printerr("unlink(%s)\n", filename);
#endif
        e = unlink(filename);
        if (e != 0) {
            g_printerr("%s: unlink(%s)\n", strerror(e), filename);
        }
    }
    g_free(filename);

    /* remove the directory */
    e = rmdir(swap_dir.path);
    if (e) {
        g_printerr("%s: rmdir(%s)\n", strerror(e), swap_dir.path);
    }
}

gint tile_swap_add(gchar *filename, SwapFunc swap_func, gpointer user_data)
{
    int e, i;

#ifdef SWAP_DEBUG
    g_printerr("multiple-tiles-per-file swap algorithm\n");
    g_printerr ("tile_swap_add(filename=%s, swap_func=%p, user_data=%p)\n",
            filename, swap_func, user_data);
#endif

    swap_dir.path = g_strdup (filename);
    swap_dir.freelist = g_new(FreelistEntry, 1);
    swap_dir.freelist->offset = 0;
    swap_dir.freelist->size = ~(guint64) 0;
    swap_dir.freelist->next = NULL;
    swap_dir.max_file = 0;
    swap_dir.last_used = 0;

    for (i = 0; i < FILE_CACHE_SIZE; i++) {
        file_cache[i].fd = -1;
        file_cache[i].last_used = 0;
    }

    /* now create the directory */
    e = mkdir (swap_dir.path, 0777);
    if (e) {
        g_printerr ("%s: mkdir(%s, 0777)\n", strerror(e), swap_dir.path);
        return 0;
    }

    return 1;
}

void tile_swap_remove(gint swap_num)
{
#ifdef SWAP_DEBUG
    g_printerr("tile_swap_remove(swap_num=%d)\n", swap_num);
#endif
}

void tile_swap_in_async(Tile *tile)
{
#ifdef SWAP_DEBUG
    g_printerr("tile_swap_in_async(tile=%p)\n", tile);
#endif
}

static guchar *tile_filename(Tile *tile)
{
    guchar *filename;

    /* RAM is cheap. */
    filename = g_new(guchar, strlen(swap_dir.path) + 50);
    strcpy(filename, swap_dir.path);
    sprintf(filename + strlen(filename), "/%08x.cache",
            (unsigned int) (tile->swap_offset / MAX_FILE_SIZE));
    swap_dir.max_file = MAX(swap_dir.max_file, 
            (unsigned int) (tile->swap_offset / MAX_FILE_SIZE));

    return filename;
}

static gint tile_offset(Tile *tile)
{
    return tile->swap_offset % MAX_FILE_SIZE;
}

static guint allocate_size(Tile *tile)
{
    return tile_size(tile);
}

static void allocate_tile(Tile *tile)
{
    FreelistEntry *entry, *previous, *new_entry;
    guint bytes, first_chunk, second_chunk;

    if (tile->swap_offset == -1) {
        bytes = allocate_size(tile);

        /* Find space on the freelist. There has to be space, and we can't
         * cross a file boundary. */
        previous = NULL;
        entry = swap_dir.freelist;
        while (entry) {
            first_chunk = MAX_FILE_SIZE - (entry->offset % MAX_FILE_SIZE);
            if (first_chunk >= entry->size) {
                first_chunk = entry->size;
                second_chunk = 0;
            } else {
                second_chunk = entry->size - first_chunk;
            }
            /*
            g_printerr("first_chunk=%x, second_chunk=%x\n",
                    first_chunk, second_chunk);
                    */

            if (first_chunk >= bytes) {
                tile->swap_offset = entry->offset;
                if (entry->size == bytes) {
                    /* remove this entry */
                    if (previous)
                        previous->next = entry->next;
                    else
                        swap_dir.freelist = entry->next;
                    g_free(entry);
                } else {
                    entry->offset += bytes;
                    entry->size -= bytes;
                }
                break;

            } else if (second_chunk >= bytes) {
                /* need to break the current entry up */
                tile->swap_offset = entry->offset + first_chunk;
                if (second_chunk == bytes) {
                    /* actually, just make the entry smaller */
                    entry->size -= bytes;
                } else {
                    new_entry = g_new(FreelistEntry, 1);
                    new_entry->offset = tile->swap_offset + bytes;
                    new_entry->size = second_chunk - bytes;
                    new_entry->next = NULL;
                    entry->size = first_chunk;
                    entry->next = new_entry;
                }
                break;
            }

            previous = entry;
            entry = entry->next;
        }

#ifdef SWAP_DEBUG
        print_freelist();
#endif

        if (tile->swap_offset == -1) {
            g_printerr("BAD ERROR: Couldn't allocate swap space for a tile.\n");
        }
    }
}

static gint tile_open_file(Tile *tile)
{
    char *filename = NULL;
    gint i, lru = 0;

    allocate_tile(tile);

    if (swap_dir.last_used > (1<<30)) {
        swap_dir.last_used /= 16;
        for (i = 0; i < FILE_CACHE_SIZE; i++)
            file_cache[i].last_used /= 16;
    }

    /* see if we've got this file open */
    for (i = 0; i < FILE_CACHE_SIZE; i++) {
        if (file_cache[i].fd >= 0 &&
                file_cache[i].file_number ==
                (guint) (tile->swap_offset / MAX_FILE_SIZE)) {
            file_cache[i].last_used = swap_dir.last_used++;
            lseek(file_cache[i].fd, tile_offset(tile), SEEK_SET);
            file_cache_hits++;
            return file_cache[i].fd;
        }
        if (file_cache[i].last_used < file_cache[lru].last_used)
            lru = i;
    }

    file_cache_misses++;
    if (file_cache[lru].fd >= 0)
        close(file_cache[lru].fd);

    filename = tile_filename(tile);

#ifdef SWAP_DEBUG
    g_printerr("Opening %s for cache slot %d.\n", filename, lru);
#endif

    file_cache[lru].last_used = swap_dir.last_used++;
    file_cache[lru].file_number = (tile->swap_offset / MAX_FILE_SIZE);
    file_cache[lru].fd = open(filename, O_RDWR);

    if (file_cache[lru].fd == -1) {
        /* not yet in cache. create it. */
        file_cache[lru].fd =
            open(filename, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
        if (file_cache[lru].fd == -1)
            g_printerr("%s: open(%s, O_CREAT ...)\n", strerror(errno),
                    filename);
    }

    g_free(filename);

    lseek(file_cache[lru].fd, tile_offset(tile), SEEK_SET);

    return file_cache[lru].fd;
}

void tile_swap_in(Tile *tile)
{
    gint fd;
    gint bytes_read = 0, bytes, ret;

#ifdef SWAP_DEBUG
    g_printerr("tile_swap_in(swap_num=%d, swap_offset=0x%llx, size=0x%x)\n",
            tile->swap_num, tile->swap_offset, tile_size(tile));
#endif

    if (tile->data) {
        g_printerr("tile_swap_in(): tile already has data allocated\n");
        return;
    }

    tile_alloc(tile);
    if (tile->swap_offset == -1)
        /* It's not in cache. Just return some uninitialized data. */
        return;

    fd = tile_open_file(tile);
    if (fd == -1)
        /* This is an error. */
        return;

    bytes = tile_size(tile);

    while (bytes_read < bytes) {
        ret = read(fd, tile->data + bytes_read, bytes - bytes_read);
        if (ret == -1) {
            g_printerr("%s: reading %d bytes from tile cache\n",
                    strerror(errno), bytes - bytes_read);
            break;
        }
        bytes_read += ret;
    }
    /* tile_close_file(tile, fd); */
}

void tile_swap_out (Tile *tile)
{
    gint bytes;
    gint written = 0;
    gint ret;
    gint fd;

#ifdef SWAP_DEBUG
    g_printerr("tile_swap_out(swap_num=%d, swap_offset=0x%llx, size=0x%x)\n",
            tile->swap_num, tile->swap_offset, tile_size(tile));
#endif

    fd = tile_open_file(tile);
    if (fd == -1)
        /* This is an error. */
        return;

    bytes = tile_size(tile);

    while (written < bytes) {
        ret = write(fd, tile->data + written, bytes - written);
        if (ret == -1) {
            g_printerr("%s: writing %d bytes to tile cache\n",
                    strerror(errno), bytes - written);
            break;
        }
        written += ret;
    }
    /* tile_close_file(tile, fd); */

    tile->dirty = FALSE;
}

void tile_swap_delete(Tile *tile)
{
    FreelistEntry *entry = NULL, *left = NULL, *right = NULL;
    gint bytes;

#ifdef SWAP_DEBUG
    g_printerr("tile_swap_delete(swap_num=%d, swap_offset=0x%llx, size=0x%x)\n",
            tile->swap_num, tile->swap_offset, tile_size(tile));
#endif

    bytes = allocate_size(tile);

    /* add it to the freelist */

    /* find out where it should be added */
    left = NULL;
    right = swap_dir.freelist;
    while (right && right->offset < tile->swap_offset) {
        left = right;
        right = right->next;
    }

    if (left && left->offset + left->size == tile->swap_offset) {
        /* new fits snug next to left */
        if (right && tile->swap_offset + bytes == right->offset) {
            /* new fits snug next to right */
            left->size += bytes + right->size;
            left->next = right->next;
            g_free(right);

        } else {
            /* new doesn't fit snug next to right */
            left->size += bytes;
        }

    } else {
        /* new doesn't fit snug next to left */
        if (right && tile->swap_offset + bytes == right->offset) {
            /* new fits snug next to right */
            right->offset -= bytes;
            right->size += bytes;

        } else {
            /* new doesn't fit snug next to right */
            entry = g_new(FreelistEntry, 1);
            entry->offset = tile->swap_offset;
            entry->size = bytes;
            entry->next = right;
            if (left)
                left->next = entry;
            else
                swap_dir.freelist = entry;
        }
    }

#ifdef SWAP_DEBUG
    print_freelist();
#endif
}

void tile_swap_compress (gint swap_num)
{
#ifdef SWAP_DEBUG
    g_printerr("tile_swap_compress(swap_num=%d)\n", swap_num);
#endif
}

Reply via email to