summaryrefslogtreecommitdiffstatshomepage
path: root/src/tools/regrep.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/regrep.c')
-rw-r--r--src/tools/regrep.c1181
1 files changed, 1181 insertions, 0 deletions
diff --git a/src/tools/regrep.c b/src/tools/regrep.c
new file mode 100644
index 00000000000..97bd2b9c17b
--- /dev/null
+++ b/src/tools/regrep.c
@@ -0,0 +1,1181 @@
+/***************************************************************************
+
+ Regression test report generator
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "osdcore.h"
+#include "png.h"
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define MAX_COMPARES 16
+#define BITMAP_SPACE 4
+
+enum
+{
+ STATUS_NOT_PRESENT = 0,
+ STATUS_SUCCESS,
+ STATUS_SUCCESS_DIFFERENT,
+ STATUS_MISSING_FILES,
+ STATUS_EXCEPTION,
+ STATUS_FATAL_ERROR,
+ STATUS_FAILED_VALIDITY,
+ STATUS_OTHER,
+ STATUS_COUNT
+};
+
+enum
+{
+ BUCKET_UNKNOWN = 0,
+ BUCKET_IMPROVED,
+ BUCKET_REGRESSED,
+ BUCKET_CHANGED,
+ BUCKET_MULTI_ERROR,
+ BUCKET_CONSISTENT_ERROR,
+ BUCKET_GOOD,
+ BUCKET_GOOD_BUT_CHANGED,
+ BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
+ BUCKET_COUNT
+};
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+typedef struct _summary_file summary_file;
+struct _summary_file
+{
+ summary_file * next;
+ char name[10];
+ char source[100];
+ UINT8 status[MAX_COMPARES];
+ UINT8 matchbitmap[MAX_COMPARES];
+ char * text[MAX_COMPARES];
+ UINT32 textsize[MAX_COMPARES];
+ UINT32 textalloc[MAX_COMPARES];
+};
+
+
+typedef struct _summary_list summary_list;
+struct _summary_list
+{
+ summary_list * next;
+ summary_file * files;
+ char * dir;
+ char version[40];
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static summary_file *filehash[128][128];
+static summary_list lists[MAX_COMPARES];
+static int list_count;
+
+static const char *bucket_name[] =
+{
+ "Unknown",
+ "Games That Have Improved",
+ "Games That Have Regressed",
+ "Games With Changed Screenshots",
+ "Games With Multiple Errors",
+ "Games With Consistent Errors",
+ "Games That Are Consistently Good",
+ "Games That Regressed But Improved",
+ "Games With Changed Screenshots",
+};
+
+static const int bucket_output_order[] =
+{
+ BUCKET_REGRESSED,
+ BUCKET_IMPROVED,
+ BUCKET_CHANGED,
+ BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
+ BUCKET_GOOD_BUT_CHANGED,
+ BUCKET_MULTI_ERROR,
+ BUCKET_CONSISTENT_ERROR
+};
+
+static const char *status_text[] =
+{
+ "",
+ "Success",
+ "Changed",
+ "Missing Files",
+ "Exception",
+ "Fatal Error",
+ "Failed Validity Check",
+ "Other Unknown Error"
+};
+
+static const char *status_color[] =
+{
+ "",
+ "bgcolor=\"#00A000\"",
+ "bgcolor=\"#E0E000\"",
+ "bgcolor=\"#8000C0\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C06000\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C00000\"",
+};
+
+
+
+/***************************************************************************
+ PROTOTYPES
+***************************************************************************/
+
+/* summary parsing */
+static int read_summary_log(const char *filename, int index);
+static summary_file *parse_driver_tag(char *linestart, int index);
+static summary_file *get_file(const char *filename);
+static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr);
+static summary_file *sort_file_list(void);
+
+/* HTML helpers */
+static FILE *create_file_and_output_header(const char *filename, const char *title, const char *subtitle);
+static void output_footer_and_close_file(FILE *file);
+
+/* report generators */
+static void output_report(const char *dirname, summary_file *filelist);
+static int compare_screenshots(summary_file *curfile);
+static int generate_png_diff(const summary_file *curfile, const char *destdir, const char *destname);
+static void create_linked_file(const char *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile);
+static void append_driver_list_table(const char *header, const char *dirname, FILE *indexfile, const summary_file *listhead);
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ alloc_filename - combine directory name
+ and remainder into full path, living in
+ allocated memory
+-------------------------------------------------*/
+
+INLINE const char *alloc_filename(const char *dirname, const char *remainder)
+{
+ int count = strlen(dirname) + 1 + strlen(remainder) + 1;
+ char *result;
+
+ result = malloc(count);
+ if (result == NULL)
+ return NULL;
+ sprintf(result, "%s" PATH_SEPARATOR "%s", dirname, remainder);
+ return result;
+}
+
+
+/*-------------------------------------------------
+ trim_string - trim leading/trailing spaces
+ from a string
+-------------------------------------------------*/
+
+INLINE char *trim_string(char *string)
+{
+ int length;
+
+ /* trim leading spaces */
+ while (*string != 0 && isspace(*string))
+ string++;
+
+ /* trim trailing spaces */
+ length = strlen(string);
+ while (length > 0 && isspace(string[length - 1]))
+ string[--length] = 0;
+
+ return string;
+}
+
+
+/*-------------------------------------------------
+ get_unique_index - get the unique bitmap
+ index for a given entry
+-------------------------------------------------*/
+
+INLINE int get_unique_index(const summary_file *curfile, int index)
+{
+ int listnum, curindex = 0;
+
+ /* if we're invalid, just return that */
+ if (curfile->matchbitmap[index] == 0xff)
+ return -1;
+
+ /* count unique elements up to us */
+ for (listnum = 0; listnum < curfile->matchbitmap[index]; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ curindex++;
+ return curindex;
+}
+
+
+
+/***************************************************************************
+ MAIN
+***************************************************************************/
+
+/*-------------------------------------------------
+ main - main entry point
+-------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ const char *dirname;
+ int listnum;
+ int result;
+
+ /* first argument is the directory */
+ if (argc < 3)
+ {
+ fprintf(stderr, "Usage:\nsummcomp <outputdir> <summary1> [<summary2> [<summary3> ...]]\n");
+ return 1;
+ }
+ dirname = argv[1];
+ list_count = argc - 2;
+
+ /* loop over arguments and read the files */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ result = read_summary_log(argv[listnum + 2], listnum);
+ if (result != 0)
+ return result;
+ }
+
+ /* output the summary */
+ output_report(dirname, sort_file_list());
+ return 0;
+}
+
+
+
+/***************************************************************************
+ SUMMARY PARSING
+***************************************************************************/
+
+/*-------------------------------------------------
+ get_file - lookup a driver name in the hash
+ table and return a pointer to it; if none
+ found, allocate a new entry
+-------------------------------------------------*/
+
+static summary_file *get_file(const char *filename)
+{
+ summary_file *file;
+
+ /* use the first two characters as a lookup */
+ for (file = filehash[filename[0] & 0x7f][filename[1] & 0x7f]; file != NULL; file = file->next)
+ if (strcmp(filename, file->name) == 0)
+ return file;
+
+ /* didn't find one -- allocate */
+ file = malloc(sizeof(*file));
+ if (file == NULL)
+ return NULL;
+ memset(file, 0, sizeof(*file));
+
+ /* set the name so we find it in the future */
+ strcpy(file->name, filename);
+
+ /* add to the head of the list */
+ file->next = filehash[filename[0] & 0x7f][filename[1] & 0x7f];
+ filehash[filename[0] & 0x7f][filename[1] & 0x7f] = file;
+ return file;
+}
+
+
+/*-------------------------------------------------
+ read_summary_log - read a summary.log file
+ and build entries for its data
+-------------------------------------------------*/
+
+static int read_summary_log(const char *filename, int index)
+{
+ summary_file *curfile = NULL;
+ char linebuffer[1024];
+ char *linestart;
+ int drivers = 0;
+ FILE *file;
+
+ /* open the logfile */
+ file = fopen(filename, "r");
+ if (file == NULL)
+ {
+ fprintf(stderr, "Error: file '%s' not found\n", filename);
+ return 1;
+ }
+
+ /* parse it */
+ while (fgets(linebuffer, sizeof(linebuffer), file) != NULL)
+ {
+ /* trim the leading/trailing spaces */
+ linestart = trim_string(linebuffer);
+
+ /* is this one of our specials? */
+ if (strncmp(linestart, "@@@@@", 5) == 0)
+ {
+ /* advance past the signature */
+ linestart += 5;
+
+ /* look for the driver= tag */
+ if (strncmp(linestart, "driver=", 7) == 0)
+ {
+ curfile = parse_driver_tag(linestart + 7, index);
+ if (curfile == NULL)
+ goto error;
+ drivers++;
+ }
+
+ /* look for the source= tag */
+ else if (strncmp(linestart, "source=", 7) == 0)
+ {
+ /* error if no driver yet */
+ if (curfile == NULL)
+ {
+ fprintf(stderr, "Unexpected @@@@@source= tag\n");
+ goto error;
+ }
+
+ /* copy the string */
+ strcpy(curfile->source, trim_string(linestart + 7));
+ }
+
+ /* look for the dir= tag */
+ else if (strncmp(linestart, "dir=", 4) == 0)
+ {
+ char *dirname = trim_string(linestart + 4);
+
+ /* allocate a copy of the string */
+ lists[index].dir = malloc(strlen(dirname) + 1);
+ if (lists[index].dir == NULL)
+ goto error;
+ strcpy(lists[index].dir, dirname);
+ fprintf(stderr, "Directory %s\n", lists[index].dir);
+ }
+ }
+
+ /* if not, consider other options */
+ else if (curfile != NULL)
+ {
+ int foundchars = 0;
+ char *curptr;
+
+ /* look for the pngcrc= tag */
+ if (strncmp(linestart, "pngcrc: ", 7) == 0)
+ {
+ }
+
+ /* otherwise, accumulate the text */
+ else
+ {
+ /* find the end of the line and normalize it with a CR */
+ for (curptr = linestart; *curptr != 0 && *curptr != '\n' && *curptr != '\r'; curptr++)
+ if (!isspace(*curptr))
+ foundchars = 1;
+ *curptr++ = '\n';
+ *curptr = 0;
+
+ /* ignore blank lines */
+ if (!foundchars)
+ continue;
+
+ /* see if we have enough room */
+ if (curfile->textsize[index] + (curptr - linestart) + 1 >= curfile->textalloc[index])
+ {
+ curfile->textalloc[index] = curfile->textsize[index] + (curptr - linestart) + 256;
+ curfile->text[index] = realloc(curfile->text[index], curfile->textalloc[index]);
+ if (curfile->text[index] == NULL)
+ {
+ fprintf(stderr, "Unable to allocate memory for text\n");
+ goto error;
+ }
+ }
+
+ /* append our text */
+ strcpy(curfile->text[index] + curfile->textsize[index], linestart);
+ curfile->textsize[index] += curptr - linestart;
+ }
+ }
+
+ /* look for the M.A.M.E. header */
+ else if (strncmp(linestart, "M.A.M.E. v", 10) == 0)
+ {
+ char *start = linestart + 10;
+ char *end;
+
+ /* find the end */
+ for (end = start; !isspace(*end); end++) ;
+ *end = 0;
+ strcpy(lists[index].version, start);
+ fprintf(stderr, "Parsing results from version %s\n", lists[index].version);
+ }
+ }
+
+ fclose(file);
+ fprintf(stderr, "Parsed %d drivers\n", drivers);
+ return 0;
+
+error:
+ fclose(file);
+ return 1;
+}
+
+
+/*-------------------------------------------------
+ parse_driver_tag - parse the status info
+ from a driver tag
+-------------------------------------------------*/
+
+static summary_file *parse_driver_tag(char *linestart, int index)
+{
+ summary_file *curfile;
+ char *colon;
+
+ /* find the colon separating name from status */
+ colon = strchr(linestart, ':');
+ if (colon == NULL)
+ {
+ fprintf(stderr, "Unexpected text after @@@@@driver=\n");
+ return NULL;
+ }
+
+ /* NULL terminate at the colon and look up the file */
+ *colon = 0;
+ curfile = get_file(trim_string(linestart));
+ if (curfile == NULL)
+ {
+ fprintf(stderr, "Unable to allocate memory for driver\n");
+ return NULL;
+ }
+
+ /* clear out any old status for this file */
+ curfile->status[index] = STATUS_NOT_PRESENT;
+ if (curfile->text[index] != NULL)
+ free(curfile->text[index]);
+ curfile->text[index] = NULL;
+ curfile->textsize[index] = 0;
+ curfile->textalloc[index] = 0;
+
+ /* strip leading/trailing spaces from the status */
+ colon = trim_string(colon + 1);
+
+ /* convert status into statistics */
+ if (strcmp(colon, "Success") == 0)
+ curfile->status[index] = STATUS_SUCCESS;
+ else if (strcmp(colon, "Missing files") == 0)
+ curfile->status[index] = STATUS_MISSING_FILES;
+ else if (strcmp(colon, "Exception") == 0)
+ curfile->status[index] = STATUS_EXCEPTION;
+ else if (strcmp(colon, "Fatal error") == 0)
+ curfile->status[index] = STATUS_FATAL_ERROR;
+ else if (strcmp(colon, "Failed validity check") == 0)
+ curfile->status[index] = STATUS_FAILED_VALIDITY;
+ else
+ curfile->status[index] = STATUS_OTHER;
+
+ return curfile;
+}
+
+
+/*-------------------------------------------------
+ compare_file - compare two files, sorting
+ first by source filename, then by driver name
+-------------------------------------------------*/
+
+static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr)
+{
+ summary_file *file0 = *(summary_file **)file0ptr;
+ summary_file *file1 = *(summary_file **)file1ptr;
+ int result = strcmp(file0->source, file1->source);
+ if (result == 0)
+ result = strcmp(file0->name, file1->name);
+ return result;
+}
+
+
+/*-------------------------------------------------
+ sort_file_list - convert the hashed lists
+ into a single, sorted list
+-------------------------------------------------*/
+
+static summary_file *sort_file_list(void)
+{
+ summary_file *listhead, **tailptr, *curfile, **filearray;
+ int numfiles, filenum;
+ int c0, c1;
+
+ /* count the total number of files */
+ numfiles = 0;
+ for (c0 = 0; c0 < 128; c0++)
+ for (c1 = 0; c1 < 128; c1++)
+ for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
+ numfiles++;
+
+ /* allocate an array of files */
+ filearray = malloc(numfiles * sizeof(*filearray));
+ if (filearray == NULL)
+ {
+ fprintf(stderr, "Out of memory!\n");
+ return NULL;
+ }
+
+ /* populate the array */
+ numfiles = 0;
+ for (c0 = 0; c0 < 128; c0++)
+ for (c1 = 0; c1 < 128; c1++)
+ for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
+ filearray[numfiles++] = curfile;
+
+ /* sort the array */
+ qsort(filearray, numfiles, sizeof(filearray[0]), compare_file);
+
+ /* now regenerate a single list */
+ listhead = NULL;
+ tailptr = &listhead;
+ for (filenum = 0; filenum < numfiles; filenum++)
+ {
+ *tailptr = filearray[filenum];
+ tailptr = &(*tailptr)->next;
+ }
+ *tailptr = NULL;
+ free(filearray);
+
+ return listhead;
+}
+
+
+
+/***************************************************************************
+ HTML OUTPUT HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ create_file_and_output_header - create a new
+ HTML file with a standard header
+-------------------------------------------------*/
+
+static FILE *create_file_and_output_header(const char *filename, const char *title, const char *subtitle)
+{
+ FILE *file;
+
+ /* create the indexfile */
+ file = fopen(filename, "w");
+ if (file == NULL)
+ return NULL;
+
+ /* print a header */
+ fprintf(file,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ "<head>\n"
+ "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
+ "\t<title>%s</title>\n"
+ "\t<link rel=\"stylesheet\" href=\"http://mamedev.org/styles-site.css\" type=\"text/css\" />\n"
+ "</head>\n"
+ "\n"
+ "<body>\n"
+ "\t<div id=\"outer\">\n"
+ "\n"
+ "\t<div id=\"banner\">\n"
+ "\t<h1>%s</h1>\n"
+ "\t<h2>%s</h2>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"left\">\n"
+ "\t<div class=\"sidebar\">\n"
+ "\t<!--#include virtual=\"/links.txt\" -->\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"center\">\n"
+ "\t<div class=\"content\">\n"
+ "\n",
+ title, title, (subtitle == NULL) ? "&nbsp;" : subtitle
+ );
+
+ /* return the file */
+ return file;
+}
+
+
+/*-------------------------------------------------
+ output_footer_and_close_file - write a
+ standard footer to an HTML file and close it
+-------------------------------------------------*/
+
+static void output_footer_and_close_file(FILE *file)
+{
+ fprintf(file,
+ "\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "</body>"
+ "\n"
+ "</html>\n"
+ );
+ fclose(file);
+}
+
+
+
+/***************************************************************************
+ REPORT GENERATORS
+***************************************************************************/
+
+/*-------------------------------------------------
+ output_report - generate the summary
+ report HTML files
+-------------------------------------------------*/
+
+static void output_report(const char *dirname, summary_file *filelist)
+{
+ summary_file *buckethead[BUCKET_COUNT], **buckettailptr[BUCKET_COUNT];
+ summary_file *curfile;
+ const char *indexname;
+ int listnum, bucknum;
+ FILE *indexfile;
+ int count = 0, total;
+
+ /* initialize the lists */
+ for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
+ {
+ buckethead[bucknum] = NULL;
+ buckettailptr[bucknum] = &buckethead[bucknum];
+ }
+
+ /* compute the total number of files */
+ total = 0;
+ for (curfile = filelist; curfile != NULL; curfile = curfile->next)
+ total++;
+
+ /* first bucketize the games */
+ for (curfile = filelist; curfile != NULL; curfile = curfile->next)
+ {
+ int statcount[STATUS_COUNT] = { 0 };
+ int bucket = BUCKET_UNKNOWN;
+ int unique_codes = 0;
+ int first_valid;
+
+ /* print status */
+ if (++count % 100 == 0)
+ fprintf(stderr, "Processing file %d/%d\n", count, total);
+
+ /* find the first valid entry */
+ for (first_valid = 0; curfile->status[first_valid] == STATUS_NOT_PRESENT; first_valid++) ;
+
+ /* do we need to output anything? */
+ for (listnum = first_valid; listnum < list_count; listnum++)
+ if (statcount[curfile->status[listnum]]++ == 0)
+ unique_codes++;
+
+ /* were we consistent? */
+ if (unique_codes == 1)
+ {
+ /* were we consistently ok? */
+ if (curfile->status[first_valid] == STATUS_SUCCESS)
+ bucket = compare_screenshots(curfile);
+
+ /* must have been consistently erroring */
+ else
+ bucket = BUCKET_CONSISTENT_ERROR;
+ }
+
+ /* ok, we're not consistent; could be a number of things */
+ else
+ {
+ /* were we ok at the start and end but not in the middle? */
+ if (curfile->status[first_valid] == STATUS_SUCCESS && curfile->status[list_count - 1] == STATUS_SUCCESS)
+ bucket = BUCKET_GOOD_BUT_CHANGED;
+
+ /* did we go from good to bad? */
+ else if (curfile->status[first_valid] == STATUS_SUCCESS)
+ bucket = BUCKET_REGRESSED;
+
+ /* did we go from bad to good? */
+ else if (curfile->status[list_count - 1] == STATUS_SUCCESS)
+ bucket = BUCKET_IMPROVED;
+
+ /* must have had multiple errors */
+ else
+ bucket = BUCKET_MULTI_ERROR;
+ }
+
+ /* add us to the appropriate list */
+ *buckettailptr[bucket] = curfile;
+ buckettailptr[bucket] = &curfile->next;
+ }
+
+ /* terminate all the lists */
+ for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
+ *buckettailptr[bucknum] = NULL;
+
+ /* output header */
+ indexname = alloc_filename(dirname, "index.html");
+ indexfile = create_file_and_output_header(indexname, "MAME&trade; Regressions", NULL);
+ free((void *)indexname);
+ if (indexfile == NULL)
+ {
+ fprintf(stderr, "Error creating file '%s'\n", indexname);
+ return;
+ }
+
+ /* iterate over buckets and output them */
+ for (bucknum = 0; bucknum < ARRAY_LENGTH(bucket_output_order); bucknum++)
+ {
+ int curbucket = bucket_output_order[bucknum];
+
+ if (buckethead[curbucket] != NULL)
+ {
+ fprintf(stderr, "Outputting bucket: %s\n", bucket_name[curbucket]);
+ append_driver_list_table(bucket_name[curbucket], dirname, indexfile, buckethead[curbucket]);
+ }
+ }
+
+ /* output footer */
+ output_footer_and_close_file(indexfile);
+}
+
+
+/*-------------------------------------------------
+ compare_screenshots - compare the screenshots
+ for all the games in a file
+-------------------------------------------------*/
+
+static int compare_screenshots(summary_file *curfile)
+{
+ bitmap_t *bitmaps[MAX_COMPARES];
+ int unique[MAX_COMPARES];
+ int numunique = 0;
+ int listnum;
+
+ /* iterate over all files and load their bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ bitmaps[listnum] = NULL;
+ if (curfile->status[listnum] == STATUS_SUCCESS)
+ {
+ file_error filerr;
+ const char *fullname;
+ char imgname[100];
+ core_file *file;
+
+ /* get the filename for the image */
+ sprintf(imgname, "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", curfile->name);
+ fullname = alloc_filename(lists[listnum].dir, imgname);
+
+ /* open the file */
+ filerr = core_fopen(fullname, OPEN_FLAG_READ, &file);
+ free((void *)fullname);
+
+ /* if that failed, look in the old location */
+ if (filerr != FILERR_NONE)
+ {
+ /* get the filename for the image */
+ sprintf(imgname, "snap" PATH_SEPARATOR "_%s.png", curfile->name);
+ fullname = alloc_filename(lists[listnum].dir, imgname);
+
+ /* open the file */
+ filerr = core_fopen(fullname, OPEN_FLAG_READ, &file);
+ free((void *)fullname);
+ }
+
+ /* if that worked, load the file */
+ if (filerr == FILERR_NONE)
+ {
+ png_read_bitmap(file, &bitmaps[listnum]);
+ core_fclose(file);
+ }
+ }
+ }
+
+ /* now find all the different bitmap types */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ curfile->matchbitmap[listnum] = 0xff;
+ if (bitmaps[listnum] != NULL)
+ {
+ bitmap_t *this_bitmap = bitmaps[listnum];
+ int compnum;
+
+ /* compare against all unique bitmaps */
+ for (compnum = 0; compnum < numunique; compnum++)
+ {
+ bitmap_t *base_bitmap = bitmaps[unique[compnum]];
+ int bitmaps_differ;
+ int x, y;
+
+ /* if the sizes are different, we differ; otherwise start off assuming we are the same */
+ bitmaps_differ = (this_bitmap->width != base_bitmap->width || this_bitmap->height != base_bitmap->height);
+
+ /* compare scanline by scanline */
+ for (y = 0; y < this_bitmap->height && !bitmaps_differ; y++)
+ {
+ UINT32 *base = BITMAP_ADDR32(base_bitmap, y, 0);
+ UINT32 *curr = BITMAP_ADDR32(this_bitmap, y, 0);
+
+ /* scan the scanline */
+ for (x = 0; x < this_bitmap->width; x++)
+ if (*base++ != *curr++)
+ break;
+ bitmaps_differ = (x != this_bitmap->width);
+ }
+
+ /* if we matched, remember which listnum index we matched, and stop */
+ if (!bitmaps_differ)
+ {
+ curfile->matchbitmap[listnum] = unique[compnum];
+ break;
+ }
+
+ /* if different from the first unique entry, adjust the status */
+ if (bitmaps_differ && compnum == 0)
+ curfile->status[listnum] = STATUS_SUCCESS_DIFFERENT;
+ }
+
+ /* if we're unique, add ourselves to the list */
+ if (compnum >= numunique)
+ {
+ unique[numunique++] = listnum;
+ curfile->matchbitmap[listnum] = listnum;
+ continue;
+ }
+ }
+ }
+
+ /* free the bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (bitmaps[listnum] != NULL)
+ bitmap_free(bitmaps[listnum]);
+
+ /* if all screenshots matched, we're good */
+ if (numunique == 1)
+ return BUCKET_GOOD;
+
+ /* if the last screenshot matched the first unique one, we're good but changed */
+ if (curfile->matchbitmap[listnum - 1] == unique[0])
+ return BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS;
+
+ /* otherwise we're just changed */
+ return BUCKET_CHANGED;
+}
+
+
+/*-------------------------------------------------
+ generate_png_diff - create a new PNG file
+ that shows multiple differing PNGs side by
+ side with a third set of differences
+-------------------------------------------------*/
+
+static int generate_png_diff(const summary_file *curfile, const char *destdir, const char *destname)
+{
+ const char *dstfilename = alloc_filename(destdir, destname);
+ bitmap_t *bitmaps[MAX_COMPARES] = { NULL };
+ bitmap_t *finalbitmap = NULL;
+ int width, height, maxwidth;
+ int bitmapcount = 0;
+ int listnum, bmnum;
+ core_file *file = NULL;
+ char srcimgname[50];
+ file_error filerr;
+ png_error pngerr;
+ int error = -1;
+ int starty;
+
+ /* generate the common source filename */
+ sprintf(srcimgname, "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", curfile->name);
+
+ /* open and load all unique bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ {
+ const char *filename = alloc_filename(lists[listnum].dir, srcimgname);
+
+ /* open the source image */
+ filerr = core_fopen(filename, OPEN_FLAG_READ, &file);
+ free((void *)filename);
+ if (filerr != FILERR_NONE)
+ goto error;
+
+ /* load the source image */
+ pngerr = png_read_bitmap(file, &bitmaps[bitmapcount++]);
+ core_fclose(file);
+ if (pngerr != FILERR_NONE)
+ goto error;
+ }
+
+ /* if there's only one unique bitmap, skip it */
+ if (bitmapcount <= 1)
+ goto error;
+
+ /* determine the size of the final bitmap */
+ height = width = 0;
+ maxwidth = bitmaps[0]->width;
+ for (bmnum = 1; bmnum < bitmapcount; bmnum++)
+ {
+ int curwidth;
+
+ /* determine the maximal width */
+ maxwidth = MAX(maxwidth, bitmaps[bmnum]->width);
+ curwidth = bitmaps[0]->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE + maxwidth;
+ width = MAX(width, curwidth);
+
+ /* add to the height */
+ height += MAX(bitmaps[0]->height, bitmaps[bmnum]->height);
+ if (bmnum != 1)
+ height += BITMAP_SPACE;
+ }
+
+ /* allocate the final bitmap */
+ finalbitmap = bitmap_alloc(width, height, BITMAP_FORMAT_ARGB32);
+ if (finalbitmap == NULL)
+ goto error;
+
+ /* now copy and compare each set of bitmaps */
+ starty = 0;
+ for (bmnum = 1; bmnum < bitmapcount; bmnum++)
+ {
+ bitmap_t *bitmap1 = bitmaps[0];
+ bitmap_t *bitmap2 = bitmaps[bmnum];
+ int curheight = MAX(bitmap1->height, bitmap2->height);
+ int x, y;
+
+ /* iterate over rows in these bitmaps */
+ for (y = 0; y < curheight; y++)
+ {
+ UINT32 *src1 = (y < bitmap1->height) ? BITMAP_ADDR32(bitmap1, y, 0) : NULL;
+ UINT32 *src2 = (y < bitmap2->height) ? BITMAP_ADDR32(bitmap2, y, 0) : NULL;
+ UINT32 *dst1 = BITMAP_ADDR32(finalbitmap, starty + y, 0);
+ UINT32 *dst2 = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE);
+ UINT32 *dstdiff = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE);
+
+ /* now iterate over columns */
+ for (x = 0; x < maxwidth; x++)
+ {
+ int pix1 = -1, pix2 = -2;
+
+ if (src1 != NULL && x < bitmap1->width)
+ pix1 = dst1[x] = src1[x];
+ if (src2 != NULL && x < bitmap2->width)
+ pix2 = dst2[x] = src2[x];
+ dstdiff[x] = (pix1 != pix2) ? 0xffffffff : 0xff000000;
+ }
+ }
+
+ /* update the starting Y position */
+ starty += BITMAP_SPACE + MAX(bitmap1->height, bitmap2->height);
+ }
+
+ /* write the final PNG */
+ filerr = core_fopen(dstfilename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
+ if (filerr != FILERR_NONE)
+ goto error;
+ pngerr = png_write_bitmap(file, NULL, finalbitmap, 0, NULL);
+ core_fclose(file);
+ if (pngerr != FILERR_NONE)
+ goto error;
+
+ /* if we get here, we are error free */
+ error = 0;
+
+error:
+ if (dstfilename != NULL)
+ free((void *)dstfilename);
+ if (finalbitmap != NULL)
+ bitmap_free(finalbitmap);
+ for (bmnum = 0; bmnum < bitmapcount; bmnum++)
+ if (bitmaps[bmnum] != NULL)
+ bitmap_free(bitmaps[bmnum]);
+ if (error)
+ osd_rmfile(dstfilename);
+ return error;
+}
+
+
+/*-------------------------------------------------
+ create_linked_file - create a comparison
+ file between differing versions
+-------------------------------------------------*/
+
+static void create_linked_file(const char *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile)
+{
+ const char *linkname;
+ char filename[100];
+ char title[100];
+ FILE *linkfile;
+ int listnum;
+
+ /* create the filename */
+ sprintf(filename, "%s.html", curfile->name);
+
+ /* output header */
+ sprintf(title, "%s Regressions", curfile->name);
+ linkname = alloc_filename(dirname, filename);
+ linkfile = create_file_and_output_header(linkname, title, NULL);
+ free((void *)linkname);
+ if (linkfile == NULL)
+ {
+ fprintf(stderr, "Error creating file '%s'\n", filename);
+ return;
+ }
+
+ /* link to the previous/next entries */
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<table width=\"100%%\">\n");
+ fprintf(linkfile, "\t\t<td align=\"left\" width=\"40%%\" style=\"border:none\">");
+ if (prevfile != NULL)
+ fprintf(linkfile, "<a href=\"%s.html\"><< %s (%s)</a>", prevfile->name, prevfile->name, prevfile->source);
+ fprintf(linkfile, "</td>\n");
+ fprintf(linkfile, "\t\t<td align=\"center\" width=\"20%%\" style=\"border:none\"><a href=\"index.html\">Home</a></td>\n");
+ fprintf(linkfile, "\t\t<td align=\"right\" width=\"40%%\" style=\"border:none\">");
+ if (nextfile != NULL)
+ fprintf(linkfile, "<a href=\"%s.html\">%s (%s) >></a>", nextfile->name, nextfile->name, nextfile->source);
+ fprintf(linkfile, "</td>\n");
+ fprintf(linkfile, "\t</table>\n");
+ fprintf(linkfile, "\t</p>\n");
+
+ /* output data for each one */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ int imageindex = -1;
+
+ /* generate the HTML */
+ fprintf(linkfile, "\n\t<h2>%s</h2>\n", lists[listnum].version);
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<b>Status:</b> %s\n", status_text[curfile->status[listnum]]);
+ if (pngfile != NULL)
+ imageindex = get_unique_index(curfile, listnum);
+ if (imageindex != -1)
+ fprintf(linkfile, " [%d]", imageindex);
+ fprintf(linkfile, "\t</p>\n");
+ if (curfile->text[listnum] != NULL)
+ {
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<b>Errors:</b>\n");
+ fprintf(linkfile, "\t<pre>%s</pre>\n", curfile->text[listnum]);
+ fprintf(linkfile, "\t</p>\n");
+ }
+ }
+
+ /* output link to the image */
+ if (pngfile != NULL)
+ {
+ fprintf(linkfile, "\n\t<h2>Screenshot Comparisons</h2>\n");
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<img src=\"%s\" />\n", pngfile);
+ fprintf(linkfile, "\t</p>\n");
+ }
+
+ /* output footer */
+ output_footer_and_close_file(linkfile);
+}
+
+
+/*-------------------------------------------------
+ append_driver_list_table - append a table
+ of drivers from a list to an HTML file
+-------------------------------------------------*/
+
+static void append_driver_list_table(const char *header, const char *dirname, FILE *indexfile, const summary_file *listhead)
+{
+ const summary_file *curfile, *prevfile;
+ int width = 100 / (2 + list_count);
+ int listnum;
+
+ /* output a header */
+ fprintf(indexfile, "\t<h2>%s</h2>\n", header);
+ fprintf(indexfile, "\t<div style=\"padding-left:20px;\">\n");
+
+ /* start the table */
+ fprintf(indexfile, "\t<p><table width=\"90%%\">\n");
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t<th width=\"%d%%\">Source</th><th width=\"%d%%\">Driver</th>", width, width);
+ for (listnum = 0; listnum < list_count; listnum++)
+ fprintf(indexfile, "<th width=\"%d%%\">%s</th>", width, lists[listnum].version);
+ fprintf(indexfile, "\n\t\t</tr>\n");
+
+ /* if nothing, print a default message */
+ if (listhead == NULL)
+ {
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t");
+ fprintf(indexfile, "<td colspan=\"%d\" align=\"center\">(No regressions detected)</td>", list_count + 2);
+ fprintf(indexfile, "\n\t\t</tr>\n");
+ }
+
+ /* iterate over files */
+ for (prevfile = NULL, curfile = listhead; curfile != NULL; prevfile = curfile, curfile = curfile->next)
+ {
+ int rowspan = 0, uniqueshots = 0;
+ char pngdiffname[40];
+
+ /* if this is the first entry in this source file, count how many rows we need to span */
+ if (prevfile == NULL || strcmp(prevfile->source, curfile->source) != 0)
+ {
+ const summary_file *cur;
+ for (cur = curfile; cur != NULL; cur = cur->next)
+ if (strcmp(cur->source, curfile->source) == 0)
+ rowspan++;
+ else
+ break;
+ }
+
+ /* create screenshots if necessary */
+ pngdiffname[0] = 0;
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ uniqueshots++;
+ if (uniqueshots > 1)
+ {
+ sprintf(pngdiffname, "compare_%s.png", curfile->name);
+ if (generate_png_diff(curfile, dirname, pngdiffname) != 0)
+ pngdiffname[0] = 0;
+ }
+
+ /* create a linked file */
+ create_linked_file(dirname, curfile, prevfile, curfile->next, (pngdiffname[0] == 0) ? NULL : pngdiffname);
+
+ /* create a row */
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t");
+ if (rowspan > 0)
+ fprintf(indexfile, "<td rowspan=\"%d\">%s</td>", rowspan, curfile->source);
+ fprintf(indexfile, "<td><a href=\"%s.html\">%s</a></td>", curfile->name, curfile->name);
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ int unique_index = -1;
+
+ if (pngdiffname[0] != 0)
+ unique_index = get_unique_index(curfile, listnum);
+ if (unique_index != -1)
+ fprintf(indexfile, "<td %s>%s [<a href=\"%s\" target=\"blank\">%d</a>]</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]], pngdiffname, unique_index);
+ else
+ fprintf(indexfile, "<td %s>%s</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]]);
+ }
+ fprintf(indexfile, "\n\t\t</tr>\n");
+
+ /* also print the name and source file */
+ printf("%s %s\n", curfile->name, curfile->source);
+ }
+
+ /* end of table */
+ fprintf(indexfile, "</table></p>\n");
+ fprintf(indexfile, "</div>\n");
+}