graphics: rewrite screenshot and api capture image processing (#870)
## Link to GitHub Issue or related Pull Request, if one exists #0 ## Description of change Significantly speeds up API screen capture and D3D9 screenshots saving. Two reasons for doing this: 1. We now have a 4K game (GITADORA) and existing capture code was taking multiple seconds. 2. Renewed user interest on streaming as we have a couple more companion apps in active development. **API screen capture (streaming), 1280x720:** 14.3ms -> 6.3ms per frame. Back buffer copies go to pooled `D3DPOOL_SYSTEMMEM` surfaces via `GetRenderTargetData` instead of allocating a lockable render target every frame, and TooJpeg is replaced with libjpeg-turbo (encode 9.8ms -> 3.0ms). MSAA remains unsupported **Screenshots for GITADORA arena model, across 4 screens with one of them 4K**: 4068ms -> 124ms. `D3DXSaveSurfaceToFileA` is replaced with fpng (encode 4043ms -> 76ms) and the screens encode in parallel. Dropping D3DX also removes the `d3dx9_43.dll` ... `d3dx9_24.dll` probing loop, so screenshots no longer fail outright on machines with no D3DX9 runtime installed. Screenshot surfaces are read on the present thread, so no D3D call reaches another thread for screenshots. This fixes a hang in DDR X2 introduced earlier in the branch: its device has no internal locking, and reading the surface on a pool thread while the present thread sat inside `GetRenderTargetData` left the game's own render thread deadlocked. ## Testing - **GITADORA** (arena model, D3D9Ex, 4K main plus three subscreens, windowed) with `-screenshotsub`: three sets of four screenshots, images verified correct. Completion order differs between sets, so the screens really are encoding in parallel. - **LovePlus** (KLP, plain D3D9, 768x1360): covers the inline path used by games whose image processing must not leave the present thread. - **API screen capture** through a companion app: live video correct throughout. - **Print Screen** bound as the screenshot key: the clipboard copy succeeded on every shot. - Quitting the game after capturing leaves no `IDirect3DDevice9` reference count warning, so the pooled readback surfaces are released along with the device.
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* jcicc.c
|
||||
*
|
||||
* Copyright (C) 1997-1998, Thomas G. Lane, Todd Newman.
|
||||
* Copyright (C) 2017, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file provides code to write International Color Consortium (ICC) device
|
||||
* profiles embedded in JFIF JPEG image files. The ICC has defined a standard
|
||||
* for including such data in JPEG "APP2" markers. The code given here does
|
||||
* not know anything about the internal structure of the ICC profile data; it
|
||||
* just knows how to embed the profile data in a JPEG file while writing it.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jerror.h"
|
||||
|
||||
|
||||
/*
|
||||
* Since an ICC profile can be larger than the maximum size of a JPEG marker
|
||||
* (64K), we need provisions to split it into multiple markers. The format
|
||||
* defined by the ICC specifies one or more APP2 markers containing the
|
||||
* following data:
|
||||
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
|
||||
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
|
||||
* Number of markers Total number of APP2's used (1 byte)
|
||||
* Profile data (remainder of APP2 data)
|
||||
* Decoders should use the marker sequence numbers to reassemble the profile,
|
||||
* rather than assuming that the APP2 markers appear in the correct sequence.
|
||||
*/
|
||||
|
||||
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
||||
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
||||
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
||||
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
||||
|
||||
|
||||
/*
|
||||
* This routine writes the given ICC profile data into a JPEG file. It *must*
|
||||
* be called AFTER calling jpeg_start_compress() and BEFORE the first call to
|
||||
* jpeg_write_scanlines(). (This ordering ensures that the APP2 marker(s) will
|
||||
* appear after the SOI and JFIF or Adobe markers, but before all else.)
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_icc_profile(j_compress_ptr cinfo, const JOCTET *icc_data_ptr,
|
||||
unsigned int icc_data_len)
|
||||
{
|
||||
unsigned int num_markers; /* total number of markers we'll write */
|
||||
int cur_marker = 1; /* per spec, counting starts at 1 */
|
||||
unsigned int length; /* number of bytes to write in this marker */
|
||||
|
||||
if (icc_data_ptr == NULL || icc_data_len == 0)
|
||||
ERREXIT(cinfo, JERR_BUFFER_SIZE);
|
||||
if (cinfo->global_state < CSTATE_SCANNING)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
/* Calculate the number of markers we'll need, rounding up of course */
|
||||
num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER;
|
||||
if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len)
|
||||
num_markers++;
|
||||
|
||||
while (icc_data_len > 0) {
|
||||
/* length of profile to put in this marker */
|
||||
length = icc_data_len;
|
||||
if (length > MAX_DATA_BYTES_IN_MARKER)
|
||||
length = MAX_DATA_BYTES_IN_MARKER;
|
||||
icc_data_len -= length;
|
||||
|
||||
/* Write the JPEG marker header (APP2 code and marker length) */
|
||||
jpeg_write_m_header(cinfo, ICC_MARKER,
|
||||
(unsigned int)(length + ICC_OVERHEAD_LEN));
|
||||
|
||||
/* Write the marker identifying string "ICC_PROFILE" (null-terminated). We
|
||||
* code it in this less-than-transparent way so that the code works even if
|
||||
* the local character set is not ASCII.
|
||||
*/
|
||||
jpeg_write_m_byte(cinfo, 0x49);
|
||||
jpeg_write_m_byte(cinfo, 0x43);
|
||||
jpeg_write_m_byte(cinfo, 0x43);
|
||||
jpeg_write_m_byte(cinfo, 0x5F);
|
||||
jpeg_write_m_byte(cinfo, 0x50);
|
||||
jpeg_write_m_byte(cinfo, 0x52);
|
||||
jpeg_write_m_byte(cinfo, 0x4F);
|
||||
jpeg_write_m_byte(cinfo, 0x46);
|
||||
jpeg_write_m_byte(cinfo, 0x49);
|
||||
jpeg_write_m_byte(cinfo, 0x4C);
|
||||
jpeg_write_m_byte(cinfo, 0x45);
|
||||
jpeg_write_m_byte(cinfo, 0x0);
|
||||
|
||||
/* Add the sequencing info */
|
||||
jpeg_write_m_byte(cinfo, cur_marker);
|
||||
jpeg_write_m_byte(cinfo, (int)num_markers);
|
||||
|
||||
/* Add the profile data */
|
||||
while (length--) {
|
||||
jpeg_write_m_byte(cinfo, *icc_data_ptr);
|
||||
icc_data_ptr++;
|
||||
}
|
||||
cur_marker++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user