From 9902d73a24fac30d6dbe2fcaf62f10395550a62f Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 12 Jan 2008 06:27:13 +0000 Subject: [PATCH] - Added support for 24-bit screenshots, so now accelerated 2D screenshots can work. - Tweaked the box splitting algorithm for packed textures to hopefully produce less wasted space. SVN r696 (trunk) --- docs/rh-log.txt | 8 +- src/g_game.cpp | 2 +- src/m_misc.cpp | 145 +++++++++++------- src/m_png.cpp | 290 ++++++++++++++++++++++++++++------- src/m_png.h | 5 +- src/v_video.cpp | 301 ++++++++++++++++++++++++++++++++++++- src/v_video.h | 19 ++- src/win32/fb_d3d9.cpp | 222 +++++++++++++++++++++++---- src/win32/fb_d3d9_wipe.cpp | 95 ++---------- src/win32/win32iface.h | 5 + 10 files changed, 862 insertions(+), 230 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 352586608c..6efebff72b 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,11 +1,15 @@ January 11, 2008 +- Added support for 24-bit screenshots, so now accelerated 2D screenshots + can work. +- Tweaked the box splitting algorithm for packed textures to hopefully + produce less wasted space. - For compatibility with the software renderer, D3DFB::DrawTextureV needs to truncate the coordinates to integers before sending them to the hardware. Otherwise, there can be one pixel gaps compared to the software renderer, because the hardware is rounding to nearest but the software renderer is simply truncating the fractional part of the coordinate. This is the real - cause of the gap at the top of the status bar at 1152x864 (and another gap - to the left of the status bar at 800x500). + cause of the gap above the status bar at 1152x864 (and another gap to the + left of the status bar at 800x500). - Fixed: When D3DFB::DrawTextureV had to clip a tile, it adjusted the texture coordinates erroneously, still using the old calculations from before texture packing was implemented. diff --git a/src/g_game.cpp b/src/g_game.cpp index b3bb79ace8..0581ca5827 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1977,7 +1977,7 @@ static void PutSavePic (FILE *file, int width, int height) P_CheckPlayerSprites(); R_RenderViewToCanvas (players[consoleplayer].mo, pic, 0, 0, width, height); screen->GetFlashedPalette (palette); - M_CreatePNG (file, pic, palette); + M_CreatePNG (file, pic->GetBuffer(), palette, SS_PAL, width, height, pic->GetPitch()); pic->Unlock (); delete pic; } diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 7478844028..6c146b3483 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -432,24 +432,21 @@ typedef struct // // WritePCXfile // -void WritePCXfile (FILE *file, const DCanvas *canvas, const PalEntry *palette) +void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette, + ESSType color_type, int width, int height, int pitch) { + BYTE temprow[MAXWIDTH * 3]; + const BYTE *data; int x, y; int runlen; + int bytes_per_row_minus_one; BYTE color; pcx_t pcx; - BYTE *data; - int width, height, pitch; - - data = canvas->GetBuffer (); - width = canvas->GetWidth (); - height = canvas->GetHeight (); - pitch = canvas->GetPitch (); pcx.manufacturer = 10; // PCX id - pcx.version = 5; // 256 color + pcx.version = 5; // 256 (or more) colors pcx.encoding = 1; - pcx.bits_per_pixel = 8; // 256 color + pcx.bits_per_pixel = 8; // 256 (or more) colors pcx.xmin = 0; pcx.ymin = 0; pcx.xmax = LittleShort(width-1); @@ -458,20 +455,52 @@ void WritePCXfile (FILE *file, const DCanvas *canvas, const PalEntry *palette) pcx.vdpi = LittleShort(75); memset (pcx.palette, 0, sizeof(pcx.palette)); pcx.reserved = 0; - pcx.color_planes = 1; // chunky image + pcx.color_planes = (color_type == SS_PAL) ? 1 : 3; // chunky image pcx.bytes_per_line = width + (width & 1); pcx.palette_type = 1; // not a grey scale memset (pcx.filler, 0, sizeof(pcx.filler)); fwrite (&pcx, 128, 1, file); + bytes_per_row_minus_one = ((color_type == SS_PAL) ? width : width * 3) - 1; + // pack the image for (y = height; y > 0; y--) { + switch (color_type) + { + case SS_PAL: + data = buffer; + break; + + case SS_RGB: + // Unpack RGB into separate planes. + for (int i = 0; i < width; ++i) + { + temprow[i ] = buffer[i*3]; + temprow[i + width ] = buffer[i*3 + 1]; + temprow[i + width * 2] = buffer[i*3 + 2]; + } + data = temprow; + break; + + case SS_BGRA: + // Unpack RGB into separate planes, discarding A. + for (int i = 0; i < width; ++i) + { + temprow[i ] = buffer[i*4 + 2]; + temprow[i + width ] = buffer[i*4 + 1]; + temprow[i + width * 2] = buffer[i*4]; + } + data = temprow; + break; + } + buffer += pitch; + color = *data++; runlen = 1; - for (x = width - 1; x > 0; x--) + for (x = bytes_per_row_minus_one; x > 0; x--) { if (*data == color) { @@ -522,26 +551,28 @@ void WritePCXfile (FILE *file, const DCanvas *canvas, const PalEntry *palette) if (width & 1) putc (0, file); - - data += pitch - width; } // write the palette - putc (12, file); // palette ID byte - for (x = 0; x < 256; x++, palette++) + if (color_type == SS_PAL) { - putc (palette->r, file); - putc (palette->g, file); - putc (palette->b, file); + putc (12, file); // palette ID byte + for (x = 0; x < 256; x++, palette++) + { + putc (palette->r, file); + putc (palette->g, file); + putc (palette->b, file); + } } } // // WritePNGfile // -void WritePNGfile (FILE *file, const DCanvas *canvas, const PalEntry *palette) +void WritePNGfile (FILE *file, const BYTE *buffer, const PalEntry *palette, + ESSType color_type, int width, int height, int pitch) { - if (!M_CreatePNG (file, canvas, palette) || + if (!M_CreatePNG (file, buffer, palette, color_type, width, height, pitch) || !M_AppendPNGText (file, "Software", GAMENAME DOTVERSIONSTR) || !M_FinishPNG (file)) { @@ -572,8 +603,9 @@ static bool FindFreeName (FString &fullname, const char *extension) void M_ScreenShot (const char *filename) { + FILE *file; FString autoname; - bool writepcx = screen->CanWritePCX() && (stricmp (screenshot_type, "pcx") == 0); // PNG is the default + bool writepcx = (stricmp (screenshot_type, "pcx") == 0); // PNG is the default // find a file name to save it to if (filename == NULL || filename[0] == '\0') @@ -614,46 +646,51 @@ void M_ScreenShot (const char *filename) CreatePath(screenshot_dir); // save the screenshot - screen->Save(autoname, writepcx); + const BYTE *buffer; + int pitch; + ESSType color_type; - if (!screenshot_quiet) + screen->GetScreenshotBuffer(buffer, pitch, color_type); + if (buffer != NULL) { - Printf ("Captured %s\n", autoname.GetChars()); - } -} + PalEntry palette[256]; -bool DCanvas::CanWritePCX() -{ - return true; -} + if (color_type == SS_PAL) + { + screen->GetFlashedPalette(palette); + } + file = fopen (autoname, "wb"); + if (file == NULL) + { + Printf ("Could not open %s\n", autoname.GetChars()); + screen->ReleaseScreenshotBuffer(); + return; + } + if (writepcx) + { + WritePCXfile(file, buffer, palette, color_type, + screen->GetWidth(), screen->GetHeight(), pitch); + } + else + { + WritePNGfile(file, buffer, palette, color_type, + screen->GetWidth(), screen->GetHeight(), pitch); + } + fclose(file); + screen->ReleaseScreenshotBuffer(); -void DCanvas::Save(const char *filename, bool writepcx) -{ - FILE *file; - - Lock (true); - - PalEntry palette[256]; - screen->GetFlashedPalette (palette); - - file = fopen (filename, "wb"); - if (file == NULL) - { - Printf ("Could not open %s\n", filename); - Unlock (); - return; - } - - if (writepcx) - { - WritePCXfile (file, this, palette); + if (!screenshot_quiet) + { + Printf ("Captured %s\n", autoname.GetChars()); + } } else { - WritePNGfile (file, this, palette); + if (!screenshot_quiet) + { + Printf ("Could not create screenshot.\n"); + } } - fclose (file); - Unlock (); } CCMD (screenshot) diff --git a/src/m_png.cpp b/src/m_png.cpp index 0b10b3383e..055fe1d7d2 100644 --- a/src/m_png.cpp +++ b/src/m_png.cpp @@ -94,7 +94,6 @@ PNGHandle::~PNGHandle () static inline void MakeChunk (void *where, DWORD type, size_t len); static inline void StuffPalette (const PalEntry *from, BYTE *to); -static bool StuffBitmap (const DCanvas *canvas, FILE *file); static bool WriteIDAT (FILE *file, const BYTE *data, int len); static void UnfilterRow (int width, BYTE *dest, BYTE *stream, BYTE *prev, int bpp); static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout); @@ -125,7 +124,8 @@ CVAR(Float, png_gamma, 0.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // //========================================================================== -bool M_CreatePNG (FILE *file, const DCanvas *canvas, const PalEntry *palette) +bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *palette, + ESSType color_type, int width, int height, int pitch) { BYTE work[8 + // signature 12+2*4+5 + // IHDR @@ -135,14 +135,15 @@ bool M_CreatePNG (FILE *file, const DCanvas *canvas, const PalEntry *palette) IHDR *const ihdr = (IHDR *)&work[8 + 8]; DWORD *const gama = (DWORD *)((BYTE *)ihdr + 2*4+5 + 12); BYTE *const plte = (BYTE *)gama + 4 + 12; + size_t work_len; sig[0] = MAKE_ID(137,'P','N','G'); sig[1] = MAKE_ID(13,10,26,10); - ihdr->Width = BigLong (canvas->GetWidth ()); - ihdr->Height = BigLong (canvas->GetHeight ()); + ihdr->Width = BigLong(width); + ihdr->Height = BigLong(height); ihdr->BitDepth = 8; - ihdr->ColorType = 3; + ihdr->ColorType = color_type == SS_PAL ? 3 : 2; ihdr->Compression = 0; ihdr->Filter = 0; ihdr->Interlace = 0; @@ -152,13 +153,21 @@ bool M_CreatePNG (FILE *file, const DCanvas *canvas, const PalEntry *palette) *gama = BigLong (int (45454.5f * (png_gamma == 0.f ? Gamma : png_gamma))); MakeChunk (gama, MAKE_ID('g','A','M','A'), 4); - StuffPalette (palette, plte); - MakeChunk (plte, MAKE_ID('P','L','T','E'), 256*3); + if (color_type == SS_PAL) + { + StuffPalette (palette, plte); + MakeChunk (plte, MAKE_ID('P','L','T','E'), 256*3); + work_len = sizeof(work); + } + else + { + work_len = sizeof(work) - (12+256*3); + } - if (fwrite (work, 1, sizeof(work), file) != sizeof(work)) + if (fwrite (work, 1, work_len, file) != work_len) return false; - return StuffBitmap (canvas, file); + return M_SaveBitmap (buffer, color_type, width, height, pitch, file); } //========================================================================== @@ -707,7 +716,7 @@ static inline void MakeChunk (void *where, DWORD type, size_t len) // //========================================================================== -static inline void StuffPalette (const PalEntry *from, BYTE *to) +static void StuffPalette (const PalEntry *from, BYTE *to) { for (int i = 256; i > 0; --i) { @@ -721,27 +730,173 @@ static inline void StuffPalette (const PalEntry *from, BYTE *to) //========================================================================== // -// StuffBitmap +// CalcSum +// +// +//========================================================================== + +DWORD CalcSum(Byte *row, int len) +{ + DWORD sum = 0; + + while (len-- != 0) + { + sum += (char)*row++; + } + return sum; +} + +//========================================================================== +// +// SelectFilter +// +// Performs the heuristic recommended by the PNG spec to decide the +// (hopefully) best filter to use for this row. To quate: +// +// Select the filter that gives the smallest sum of absolute values of +// outputs. (Consider the output bytes as signed differences for this +// test.) +// +//========================================================================== + +static int SelectFilter(Byte row[5][1 + MAXWIDTH*3], Byte prior[MAXWIDTH], int width) +{ +#if 1 + // As it turns out, it seems no filtering is the best for Doom screenshots, + // no matter what the heuristic might determine. + return 0; +#else + DWORD sum; + DWORD bestsum; + int bestfilter; + int x; + + width *= 3; + + // The first byte of each row holds the filter type, filled in by the caller. + // However, the prior row does not contain a filter type, since it's always 0. + + bestsum = 0; + bestfilter = 0; + + // None + for (x = 1; x <= width; ++x) + { + bestsum += abs((char)row[0][x]); + } + + // Sub + row[1][1] = row[0][1]; + row[1][2] = row[0][2]; + row[1][3] = row[0][3]; + sum = abs((char)row[0][1]) + abs((char)row[0][2]) + abs((char)row[0][3]); + for (x = 4; x <= width; ++x) + { + row[1][x] = row[0][x] - row[0][x - 3]; + sum += abs((char)row[1][x]); + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 1; + } + + // Up + sum = 0; + for (x = 1; x <= width; ++x) + { + row[2][x] = row[0][x] - prior[x - 1]; + sum += abs((char)row[2][x]); + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 2; + } + + // Average + row[3][1] = row[0][1] - prior[0] / 2; + row[3][2] = row[0][2] - prior[1] / 2; + row[3][3] = row[0][3] - prior[2] / 2; + sum = abs((char)row[3][1]) + abs((char)row[3][2]) + abs((char)row[3][3]); + for (x = 4; x <= width; ++x) + { + row[3][x] = row[0][x] - (row[0][x - 3] + prior[x - 1]) / 2; + sum += (char)row[3][x]; + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 3; + } + + // Paeth + row[4][1] = row[0][1] - prior[0]; + row[4][2] = row[0][2] - prior[1]; + row[4][3] = row[0][3] - prior[2]; + sum = abs((char)row[4][1]) + abs((char)row[4][2]) + abs((char)row[4][3]); + for (x = 4; x <= width; ++x) + { + Byte a = row[0][x - 3]; + Byte b = prior[x - 1]; + Byte c = prior[x - 4]; + int p = a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + if (pa <= pb && pa <= pc) + { + row[4][x] = row[0][x] - a; + } + else if (pb <= pc) + { + row[4][x] = row[0][x] - b; + } + else + { + row[4][x] = row[0][x] - c; + } + sum += (char)row[4][x]; + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestfilter = 4; + } + + return bestfilter; +#endif +} + +//========================================================================== +// +// M_SaveBitmap // // Given a bitmap, creates one or more IDAT chunks in the given file. // Returns true on success. // //========================================================================== -static bool StuffBitmap (const DCanvas *canvas, FILE *file) -{ - const int pitch = canvas->GetPitch(); - const int width = canvas->GetWidth(); - const int height = canvas->GetHeight(); - BYTE *from = canvas->GetBuffer(); - - return M_SaveBitmap(from, width, height, pitch, file); -} - -bool M_SaveBitmap(BYTE * from, int width, int height, int pitch, FILE *file) +bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FILE *file) { + Byte prior[MAXWIDTH]; Byte buffer[PNG_WRITE_SIZE]; - Byte zero = 0; + Byte temprow[5][1 + MAXWIDTH*3]; z_stream stream; int err; int y; @@ -761,43 +916,74 @@ bool M_SaveBitmap(BYTE * from, int width, int height, int pitch, FILE *file) stream.next_out = buffer; stream.avail_out = sizeof(buffer); - while (y > 0 && err == Z_OK) + temprow[0][0] = 0; + temprow[1][0] = 1; + temprow[2][0] = 2; + temprow[3][0] = 3; + temprow[4][0] = 4; + + // Fill the prior row to 0 for RGB images. Paletted is always filter 0, + // so it doesn't need this. + if (color_type != SS_PAL) { - y--; - for (int i = 2; i && err == Z_OK; --i) + memset(prior, 0, width * 3); + } + + while (y-- > 0 && err == Z_OK) + { + switch (color_type) { - const int flushiness = (y == 0 && i == 1) ? Z_FINISH : 0; - if (i == 2) - { // always use filter type 0 - stream.next_in = &zero; - stream.avail_in = 1; - } - else + case SS_PAL: + memcpy(&temprow[0][1], from, width); + // always use filter type 0 for paletted images + stream.next_in = temprow[0]; + stream.avail_in = width + 1; + break; + + case SS_RGB: + memcpy(&temprow[0][1], from, width*3); + stream.next_in = temprow[SelectFilter(temprow, prior, width)]; + stream.avail_in = width * 3 + 1; + break; + + case SS_BGRA: + for (int x = 0; x < width; ++x) { - stream.next_in = from; - stream.avail_in = width; - from += pitch; + temprow[0][x*3 + 1] = from[x*4 + 2]; + temprow[0][x*3 + 2] = from[x*4 + 1]; + temprow[0][x*3 + 3] = from[x*4]; } - err = deflate (&stream, flushiness); - if (err != Z_OK) + stream.next_in = temprow[SelectFilter(temprow, prior, width)]; + stream.avail_in = width * 3 + 1; + break; + } + if (color_type != SS_PAL) + { + // Save this row for filter calculations on the next row. + memcpy (prior, &temprow[0][1], stream.avail_in - 1); + } + + from += pitch; + + err = deflate (&stream, (y == 0) ? Z_FINISH : 0); + if (err != Z_OK) + { + break; + } + while (stream.avail_out == 0) + { + if (!WriteIDAT (file, buffer, sizeof(buffer))) { - break; + return false; } - while (stream.avail_out == 0) + stream.next_out = buffer; + stream.avail_out = sizeof(buffer); + if (stream.avail_in != 0) { - if (!WriteIDAT (file, buffer, sizeof(buffer))) + err = deflate (&stream, (y == 0) ? Z_FINISH : 0); + if (err != Z_OK) { - return false; - } - stream.next_out = buffer; - stream.avail_out = sizeof(buffer); - if (stream.avail_in != 0) - { - err = deflate (&stream, flushiness); - if (err != Z_OK) - { - break; - } + break; } } } diff --git a/src/m_png.h b/src/m_png.h index e37eecc2b2..f49583ed6c 100644 --- a/src/m_png.h +++ b/src/m_png.h @@ -39,7 +39,8 @@ // The passed file should be a newly created file. // This function writes the PNG signature and the IHDR, gAMA, PLTE, and IDAT // chunks. -bool M_CreatePNG (FILE *file, const DCanvas *canvas, const PalEntry *pal); +bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *pal, + ESSType color_type, int width, int height, int pitch); // Creates a grayscale 1x1 PNG file. Used for savegames without savepics. bool M_CreateDummyPNG (FILE *file); @@ -53,7 +54,7 @@ bool M_AppendPNGText (FILE *file, const char *keyword, const char *text); // Appends the IEND chunk to a PNG file. bool M_FinishPNG (FILE *file); -bool M_SaveBitmap(BYTE * from, int width, int height, int pitch, FILE *file); +bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FILE *file); // PNG Reading -------------------------------------------------------------- diff --git a/src/v_video.cpp b/src/v_video.cpp index fc71eaddb0..0117051406 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -184,6 +184,12 @@ void V_MarkRect (int x, int y, int width, int height) DCanvas *DCanvas::CanvasChain = NULL; +//========================================================================== +// +// DCanvas Constructor +// +//========================================================================== + DCanvas::DCanvas (int _width, int _height) { // Init member vars @@ -198,6 +204,12 @@ DCanvas::DCanvas (int _width, int _height) CanvasChain = this; } +//========================================================================== +// +// DCanvas Destructor +// +//========================================================================== + DCanvas::~DCanvas () { // Remove from list of active canvases @@ -218,6 +230,12 @@ DCanvas::~DCanvas () } } +//========================================================================== +// +// DCanvas :: IsValid +// +//========================================================================== + bool DCanvas::IsValid () { // A nun-subclassed DCanvas is never valid @@ -255,8 +273,14 @@ void DCanvas::FlatFill (int left, int top, int right, int bottom, FTexture *src, } } +//========================================================================== +// +// DCanvas :: Clear +// +// Set an area to a specified color. +// +//========================================================================== -// [RH] Set an area to a specified color void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color) { int x, y; @@ -298,6 +322,15 @@ void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uin } } +//========================================================================== +// +// DCanvas :: Dim +// +// Applies a colored overlay to the entire screen, with the opacity +// determined by the dimamount cvar. +// +//========================================================================== + void DCanvas::Dim (PalEntry color) { PalEntry dimmer; @@ -320,6 +353,14 @@ void DCanvas::Dim (PalEntry color) Dim (dimmer, amount, 0, 0, Width, Height); } +//========================================================================== +// +// DCanvas :: Dim +// +// Applies a colored overlay to an area of the screen. +// +//========================================================================== + void DCanvas::Dim (PalEntry color, float damount, int x1, int y1, int w, int h) { if (damount == 0.f) @@ -359,11 +400,59 @@ void DCanvas::Dim (PalEntry color, float damount, int x1, int y1, int w, int h) } } +//========================================================================== +// +// DCanvas :: UsesColormap +// +//========================================================================== + bool DCanvas::UsesColormap() const { return true; } +//========================================================================== +// +// DCanvas :: GetScreenshotBuffer +// +// Returns a buffer containing the most recently displayed frame. The +// width and height of this buffer are the same as the canvas. +// +//========================================================================== + +void DCanvas::GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type) +{ + Lock(true); + buffer = GetBuffer(); + pitch = GetPitch(); + color_type = SS_PAL; +} + +//========================================================================== +// +// DCanvas :: ReleaseScreenshotBuffer +// +// Releases the buffer obtained through GetScreenshotBuffer. These calls +// must not be nested. +// +//========================================================================== + +void DCanvas::ReleaseScreenshotBuffer() +{ + Unlock(); +} + +//========================================================================== +// +// V_GetColorFromString +// +// Passed a string of the form "#RGB", "#RRGGBB", "R G B", or "RR GG BB", +// returns a number representing that color. If palette is non-NULL, the +// index of the best match in the palette is returned, otherwise the +// RRGGBB value is returned directly. +// +//========================================================================== + int V_GetColorFromString (const DWORD *palette, const char *cstr) { int c[3], i, p; @@ -434,11 +523,20 @@ int V_GetColorFromString (const DWORD *palette, const char *cstr) } } if (palette) - return ColorMatcher.Pick (c[0]>>8, c[1]>>8, c[2]>>8); + return ColorMatcher.Pick (c[0], c[1], c[2]); else return MAKERGB(c[0], c[1], c[2]); } +//========================================================================== +// +// V_GetColorStringByName +// +// Searches for the given color name in x11r6rgb.txt and returns an +// HTML-ish "#RRGGBB" string for it if found or the empty string if not. +// +//========================================================================== + FString V_GetColorStringByName (const char *name) { FMemLump rgbNames; @@ -520,6 +618,14 @@ FString V_GetColorStringByName (const char *name) return FString(); } +//========================================================================== +// +// V_GetColor +// +// Works like V_GetColorFromString(), but also understands X11 color names. +// +//========================================================================== + int V_GetColor (const DWORD *palette, const char *str) { FString string = V_GetColorStringByName (str); @@ -536,7 +642,14 @@ int V_GetColor (const DWORD *palette, const char *str) return res; } +//========================================================================== +// +// BuildTransTable +// // Build the tables necessary for blending +// +//========================================================================== + static void BuildTransTable (const PalEntry *palette) { int r, g, b; @@ -570,6 +683,12 @@ static void BuildTransTable (const PalEntry *palette) Col2RGB8_LessPrecision[64] = Col2RGB8[64]; } +//========================================================================== +// +// DCanvas :: CalcGamma +// +//========================================================================== + void DCanvas::CalcGamma (float gamma, BYTE gammalookup[256]) { // I found this formula on the web at @@ -585,6 +704,14 @@ void DCanvas::CalcGamma (float gamma, BYTE gammalookup[256]) } } +//========================================================================== +// +// DSimpleCanvas Constructor +// +// A simple canvas just holds a buffer in main memory. +// +//========================================================================== + DSimpleCanvas::DSimpleCanvas (int width, int height) : DCanvas (width, height) { @@ -627,6 +754,12 @@ DSimpleCanvas::DSimpleCanvas (int width, int height) memset (MemBuffer, 0, Pitch * height); } +//========================================================================== +// +// DSimpleCanvas Destructor +// +//========================================================================== + DSimpleCanvas::~DSimpleCanvas () { if (MemBuffer != NULL) @@ -636,11 +769,23 @@ DSimpleCanvas::~DSimpleCanvas () } } +//========================================================================== +// +// DSimpleCanvas :: IsValid +// +//========================================================================== + bool DSimpleCanvas::IsValid () { return (MemBuffer != NULL); } +//========================================================================== +// +// DSimpleCanvas :: Lock +// +//========================================================================== + bool DSimpleCanvas::Lock () { if (LockCount == 0) @@ -651,6 +796,12 @@ bool DSimpleCanvas::Lock () return false; // System surfaces are never lost } +//========================================================================== +// +// DSimpleCanvas :: Unlock +// +//========================================================================== + void DSimpleCanvas::Unlock () { if (--LockCount <= 0) @@ -660,6 +811,15 @@ void DSimpleCanvas::Unlock () } } +//========================================================================== +// +// DFrameBuffer Constructor +// +// A frame buffer canvas is the most common and represents the image that +// gets drawn to the screen. +// +//========================================================================== + DFrameBuffer::DFrameBuffer (int width, int height) : DSimpleCanvas (width, height) { @@ -667,6 +827,14 @@ DFrameBuffer::DFrameBuffer (int width, int height) Accel2D = false; } +//========================================================================== +// +// DFrameBuffer :: DrawRateStuff +// +// Draws the fps counter, dot ticker, and palette debug. +// +//========================================================================== + void DFrameBuffer::DrawRateStuff () { // Draws frame time and cumulative fps @@ -742,6 +910,14 @@ void DFrameBuffer::DrawRateStuff () } } +//========================================================================== +// +// FPaleteTester Constructor +// +// This is just a 16x16 image with every possible color value. +// +//========================================================================== + FPaletteTester::FPaletteTester() { Width = 16; @@ -754,11 +930,23 @@ FPaletteTester::FPaletteTester() MakeTexture(); } +//========================================================================== +// +// FPaletteTester :: CheckModified +// +//========================================================================== + bool FPaletteTester::CheckModified() { return CurTranslation != WantTranslation; } +//========================================================================== +// +// FPaletteTester :: SetTranslation +// +//========================================================================== + void FPaletteTester::SetTranslation(int num) { if (num >= 1 && num <= 9) @@ -767,10 +955,22 @@ void FPaletteTester::SetTranslation(int num) } } +//========================================================================== +// +// FPaletteTester :: Unload +// +//========================================================================== + void FPaletteTester::Unload() { } +//========================================================================== +// +// FPaletteTester :: GetColumn +// +//========================================================================== + const BYTE *FPaletteTester::GetColumn (unsigned int column, const Span **spans_out) { if (CurTranslation != WantTranslation) @@ -785,6 +985,12 @@ const BYTE *FPaletteTester::GetColumn (unsigned int column, const Span **spans_o return Pixels + column*16; } +//========================================================================== +// +// FPaletteTester :: GetPixels +// +//========================================================================== + const BYTE *FPaletteTester::GetPixels () { if (CurTranslation != WantTranslation) @@ -794,6 +1000,12 @@ const BYTE *FPaletteTester::GetPixels () return Pixels; } +//========================================================================== +// +// FPaletteTester :: MakeTexture +// +//========================================================================== + void FPaletteTester::MakeTexture() { int i, j, k, t; @@ -814,6 +1026,15 @@ void FPaletteTester::MakeTexture() CurTranslation = t; } +//========================================================================== +// +// DFrameBuffer :: CopyFromBuff +// +// Copies pixels from main memory to video memory. This is only used by +// DDrawFB. +// +//========================================================================== + void DFrameBuffer::CopyFromBuff (BYTE *src, int srcPitch, int width, int height, BYTE *dest) { if (Pitch == width && Pitch == Width && srcPitch == width) @@ -831,46 +1052,122 @@ void DFrameBuffer::CopyFromBuff (BYTE *src, int srcPitch, int width, int height, } } +//========================================================================== +// +// DFrameBuffer :: SetVSync +// +// Turns vertical sync on and off, if supported. +// +//========================================================================== + void DFrameBuffer::SetVSync (bool vsync) { } +//========================================================================== +// +// DFrameBuffer :: SetBlendingRect +// +// Defines the area of the screen containing the 3D view. +// +//========================================================================== + void DFrameBuffer::SetBlendingRect (int x1, int y1, int x2, int y2) { } +//========================================================================== +// +// DFrameBuffer :: Begin2D +// +// Signal that 3D rendering is complete, and the rest of the operations on +// the canvas until Unlock() will be 2D ones. +// +//========================================================================== + bool DFrameBuffer::Begin2D (bool copy3d) { return false; } +//========================================================================== +// +// DFrameBuffer :: CreateTexture +// +// Creates a native texture for a game texture, if supported. +// +//========================================================================== + FNativeTexture *DFrameBuffer::CreateTexture(FTexture *gametex, bool wrapping) { return NULL; } +//========================================================================== +// +// DFrameBuffer :: CreatePalette +// +// Creates a native palette from a remap table, if supported. +// +//========================================================================== + FNativePalette *DFrameBuffer::CreatePalette(FRemapTable *remap) { return NULL; } +//========================================================================== +// +// DFrameBuffer :: WipeStartScreen +// +// Grabs a copy of the screen currently displayed to serve as the initial +// frame of a screen wipe. Also determines which screenwipe will be +// performed. +// +//========================================================================== + bool DFrameBuffer::WipeStartScreen(int type) { return wipe_StartScreen(type); } +//========================================================================== +// +// DFrameBuffer :: WipeEndScreen +// +// Grabs a copy of the most-recently drawn, but not yet displayed, screen +// to serve as the final frame of a screen wipe. +// +//========================================================================== + void DFrameBuffer::WipeEndScreen() { wipe_EndScreen(); Unlock(); } +//========================================================================== +// +// DFrameBuffer :: WipeDo +// +// Draws one frame of a screenwipe. Should be called no more than 35 +// times per second. If called less than that, ticks indicates how many +// ticks have passed since the last call. +// +//========================================================================== + bool DFrameBuffer::WipeDo(int ticks) { Lock(true); return wipe_ScreenWipe(ticks); } +//========================================================================== +// +// DFrameBuffer :: WipeCleanup +// +//========================================================================== + void DFrameBuffer::WipeCleanup() { wipe_Cleanup(); diff --git a/src/v_video.h b/src/v_video.h index 5762408d6e..70051d824b 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -117,13 +117,18 @@ enum HUD_HorizCenter }; +// Screenshot buffer image data types +enum ESSType +{ + SS_PAL, + SS_RGB, + SS_BGRA +}; // // VIDEO // // [RH] Made screens more implementation-independant: -// This layer isn't really necessary, and it would be nice to remove it, I think. -// But ZDoom is now built around it so much, I'll probably just leave it. // class DCanvas : public DObject { @@ -178,11 +183,13 @@ public: // Can be overridden so that the colormaps for sector color/fade won't be built. virtual bool UsesColormap() const; - // software renderer always returns true but other renderers may not want to implement PCX. - bool CanWritePCX(); + // Retrieves a buffer containing image data for a screenshot. + // Hint: Pitch can be negative for upside-down images, in which case buffer + // points to the last row in the buffer, which will be the first row output. + virtual void GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type); - // Saves canvas to a file - void Save(const char *filename, bool writepcx); + // Releases the screenshot buffer. + virtual void ReleaseScreenshotBuffer(); // Text drawing functions ----------------------------------------------- diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index 9fa13cb0f9..52a073138b 100644 --- a/src/win32/fb_d3d9.cpp +++ b/src/win32/fb_d3d9.cpp @@ -227,6 +227,8 @@ D3DFB::D3DFB (int width, int height, bool fullscreen) FBTexture = NULL; TempRenderTexture = NULL; InitialWipeScreen = NULL; + ScreenshotTexture = NULL; + ScreenshotSurface = NULL; FinalWipeScreen = NULL; PaletteTexture = NULL; StencilPaletteTexture = NULL; @@ -439,6 +441,8 @@ void D3DFB::ReleaseResources () KillNativeTexs(); KillNativePals(); ReleaseDefaultPoolItems(); + SAFE_RELEASE( ScreenshotSurface ); + SAFE_RELEASE( ScreenshotTexture ); SAFE_RELEASE( PaletteTexture ); SAFE_RELEASE( StencilPaletteTexture ); SAFE_RELEASE( ShadedPaletteTexture ); @@ -1087,6 +1091,161 @@ void D3DFB::SetBlendingRect(int x1, int y1, int x2, int y2) BlendingRect.bottom = y2; } +//========================================================================== +// +// D3DFB :: GetScreenshotBuffer +// +// Returns a pointer into a surface holding the current screen data. +// +//========================================================================== + +void D3DFB::GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type) +{ + D3DLOCKED_RECT lrect; + + if (!test2d) + { + Super::GetScreenshotBuffer(buffer, pitch, color_type); + return; + } + buffer = NULL; + if ((ScreenshotTexture = GetCurrentScreen()) != NULL) + { + if (FAILED(ScreenshotTexture->GetSurfaceLevel(0, &ScreenshotSurface))) + { + ScreenshotTexture->Release(); + ScreenshotTexture = NULL; + } + else if (FAILED(ScreenshotSurface->LockRect(&lrect, NULL, D3DLOCK_READONLY | D3DLOCK_NOSYSLOCK))) + { + ScreenshotSurface->Release(); + ScreenshotSurface = NULL; + ScreenshotTexture->Release(); + ScreenshotTexture = NULL; + } + else + { + buffer = (const BYTE *)lrect.pBits + lrect.Pitch * LBOffsetI; + pitch = lrect.Pitch; + color_type = SS_BGRA; + } + } +} + +//========================================================================== +// +// D3DFB :: ReleaseScreenshotBuffer +// +//========================================================================== + +void D3DFB::ReleaseScreenshotBuffer() +{ + if (LockCount > 0) + { + Super::ReleaseScreenshotBuffer(); + } + if (ScreenshotSurface != NULL) + { + ScreenshotSurface->UnlockRect(); + ScreenshotSurface->Release(); + ScreenshotSurface = NULL; + } + SAFE_RELEASE( ScreenshotTexture ); +} + +//========================================================================== +// +// D3DFB :: GetCurrentScreen +// +// Returns a texture containing the pixels currently visible on-screen. +// +//========================================================================== + +IDirect3DTexture9 *D3DFB::GetCurrentScreen() +{ + IDirect3DTexture9 *tex; + IDirect3DSurface9 *tsurf, *surf; + D3DSURFACE_DESC desc; + + if (Windowed) + { + // The texture we read into must have the same pixel format as + // the TempRenderTexture. + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) + { + if (FAILED(tsurf->GetDesc(&desc))) + { + tsurf->Release(); + return NULL; + } + tsurf->Release(); + } + else + { + return NULL; + } + } + else + { + if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &tsurf))) + { + if (FAILED(tsurf->GetDesc(&desc))) + { + tsurf->Release(); + return NULL; + } + tsurf->Release(); + } + else + { + return NULL; + } + // GetFrontBufferData works only with this format + desc.Format = D3DFMT_A8R8G8B8; + } + + if (FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, 1, 0, + desc.Format, D3DPOOL_SYSTEMMEM, &tex, NULL))) + { + return NULL; + } + if (FAILED(tex->GetSurfaceLevel(0, &surf))) + { + tex->Release(); + return NULL; + } + + if (!Windowed) + { + if (FAILED(D3DDevice->GetFrontBufferData(0, surf))) + { + surf->Release(); + tex->Release(); + return NULL; + } + } + else + { + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) + { + if (FAILED(D3DDevice->GetRenderTargetData(tsurf, surf))) + { + tsurf->Release(); + tex->Release(); + return NULL; + } + tsurf->Release(); + } + else + { + tex->Release(); + return NULL; + } + } + surf->Release(); + return tex; +} + /**************************************************************************/ /* 2D Stuff */ /**************************************************************************/ @@ -1435,40 +1594,43 @@ void D3DFB::PackingTexture::AllocateImage(D3DFB::PackedTexture *box, int w, int box->Prev = &UsedList; // If we didn't use the whole box, split the remainder into the empty list. -#if 1 - // Split like this: - // +---+------+ - // |###| | - // +---+------+ - // | | - // | | - // +----------+ - // Empirical evidence indicates that this gives the best utilization. - if (box->Area.bottom < start.bottom) + + if (box->Area.bottom + 7 < start.bottom && box->Area.right + 7 < start.right) { - AddEmptyBox(start.left, box->Area.bottom, start.right, start.bottom); + // Split like this: + // +---+------+ + // |###| | + // +---+------+ + // | | + // | | + // +----------+ + if (box->Area.bottom < start.bottom) + { + AddEmptyBox(start.left, box->Area.bottom, start.right, start.bottom); + } + if (box->Area.right < start.right) + { + AddEmptyBox(box->Area.right, start.top, start.right, box->Area.bottom); + } } - if (box->Area.right < start.right) + else { - AddEmptyBox(box->Area.right, start.top, start.right, box->Area.bottom); + // Split like this: + // +---+------+ + // |###| | + // +---+ | + // | | | + // | | | + // +---+------+ + if (box->Area.bottom < start.bottom) + { + AddEmptyBox(start.left, box->Area.bottom, box->Area.right, start.bottom); + } + if (box->Area.right < start.right) + { + AddEmptyBox(box->Area.right, start.top, start.right, start.bottom); + } } -#else - // Split like this: - // +---+------+ - // |###| | - // +---+ | - // | | | - // | | | - // +---+------+ - if (box->Area.bottom < start.bottom) - { - AddEmptyBox(start.left, box->Area.bottom, box->Area.right, start.bottom); - } - if (box->Area.right < start.right) - { - AddEmptyBox(box->Area.right, start.top, start.right, start.bottom); - } -#endif } //========================================================================== diff --git a/src/win32/fb_d3d9_wipe.cpp b/src/win32/fb_d3d9_wipe.cpp index f48545a266..ee27f9444c 100644 --- a/src/win32/fb_d3d9_wipe.cpp +++ b/src/win32/fb_d3d9_wipe.cpp @@ -135,7 +135,7 @@ EXTERN_CVAR(Bool, test2d) bool D3DFB::WipeStartScreen(int type) { - IDirect3DSurface9 *surf, *tsurf; + IDirect3DSurface9 *tsurf; D3DSURFACE_DESC desc; if (!test2d) @@ -161,106 +161,39 @@ bool D3DFB::WipeStartScreen(int type) return false; } - if (Windowed) - { - // The InitialWipeScreen must have the same pixel format as - // the TempRenderTexture. - if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) - { - if (FAILED(tsurf->GetDesc(&desc))) - { - tsurf->Release(); - return false; - } - tsurf->Release(); - } - else - { - return false; - } - } - else - { - if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &tsurf))) - { - if (FAILED(tsurf->GetDesc(&desc))) - { - tsurf->Release(); - return false; - } - tsurf->Release(); - } - else - { - return false; - } - // GetFrontBufferData works only with this format - desc.Format = D3DFMT_A8R8G8B8; - } + InitialWipeScreen = GetCurrentScreen(); - if (FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, 1, 0, - desc.Format, D3DPOOL_SYSTEMMEM, &InitialWipeScreen, NULL))) - { - InitialWipeScreen = NULL; - return false; - } - if (FAILED(InitialWipeScreen->GetSurfaceLevel(0, &surf))) - { - InitialWipeScreen->Release(); - InitialWipeScreen = NULL; - return false; - } if (!Windowed) { - if (FAILED(D3DDevice->GetFrontBufferData(0, surf))) - { - surf->Release(); - InitialWipeScreen->Release(); - InitialWipeScreen = NULL; - return false; - } FinalWipeScreen = TempRenderTexture; } else { - if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) - { - if (FAILED(D3DDevice->GetRenderTargetData(tsurf, surf))) - { - tsurf->Release(); - InitialWipeScreen->Release(); - InitialWipeScreen = NULL; - return false; - } - } - else - { - InitialWipeScreen->Release(); - InitialWipeScreen = NULL; - return false; - } // Create another texture to copy the final wipe screen to so // we can still gamma correct the wipe. Since this is just for // gamma correction, it's okay to fail (though not desirable.) if (GammaFixerShader != NULL && Gamma != 1) { - if (FAILED(tsurf->GetDesc(&desc)) || - FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, - 1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT, - &FinalWipeScreen, NULL))) + if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf))) { - FinalWipeScreen = TempRenderTexture; + if (FAILED(tsurf->GetDesc(&desc)) || + FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, + 1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT, + &FinalWipeScreen, NULL))) + { + FinalWipeScreen = TempRenderTexture; + } + tsurf->Release(); } } else { FinalWipeScreen = TempRenderTexture; } - tsurf->Release(); } - surf->Release(); - // Even fullscreen will render to the TempRenderTexture, so we can have - // a copy of the new screen readily available. + + // Make even fullscreen model render to the TempRenderTexture, so + // we can have a copy of the new screen readily available. GatheringWipeScreen = true; return true; } diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h index 5c7fdde00f..291eff4e48 100644 --- a/src/win32/win32iface.h +++ b/src/win32/win32iface.h @@ -237,6 +237,8 @@ public: void Blank (); bool PaintToWindow (); void SetVSync (bool vsync); + void GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type); + void ReleaseScreenshotBuffer(); void SetBlendingRect (int x1, int y1, int x2, int y2); bool Begin2D (bool copy3d); FNativeTexture *CreateTexture (FTexture *gametex, bool wrapping); @@ -297,6 +299,7 @@ private: void FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, bool vsync); void CalcFullscreenCoords (FBVERTEX verts[4], bool viewarea_only, D3DCOLOR color0, D3DCOLOR color1) const; bool Reset(); + IDirect3DTexture9 *GetCurrentScreen(); void ReleaseDefaultPoolItems(); void KillNativePals(); void KillNativeTexs(); @@ -360,6 +363,8 @@ private: IDirect3DTexture9 *PaletteTexture; IDirect3DTexture9 *StencilPaletteTexture; IDirect3DTexture9 *ShadedPaletteTexture; + IDirect3DTexture9 *ScreenshotTexture; + IDirect3DSurface9 *ScreenshotSurface; IDirect3DVertexBuffer9 *VertexBuffer; FBVERTEX *VertexData;