Changeset View
Changeset View
Standalone View
Standalone View
source/ps/Util.cpp
Show All 37 Lines | |||||
#include "i18n/L10n.h" | #include "i18n/L10n.h" | ||||
#include "lib/utf8.h" | #include "lib/utf8.h" | ||||
#include "ps/GameSetup/Config.h" | #include "ps/GameSetup/Config.h" | ||||
#include "ps/GameSetup/GameSetup.h" | #include "ps/GameSetup/GameSetup.h" | ||||
#include "ps/Game.h" | #include "ps/Game.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | ||||
#include "ps/ScreenshotWriter.h" | |||||
#include "ps/VideoMode.h" | #include "ps/VideoMode.h" | ||||
#include "renderer/Renderer.h" | #include "renderer/Renderer.h" | ||||
#include "maths/MathUtil.h" | #include "maths/MathUtil.h" | ||||
#include "graphics/GameView.h" | #include "graphics/GameView.h" | ||||
#include <iomanip> | #include <iomanip> | ||||
#include <sstream> | #include <sstream> | ||||
▲ Show 20 Lines • Show All 169 Lines • ▼ Show 20 Lines | do | ||||
if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK) | if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK) | ||||
break; | break; | ||||
} while(tries <= maxTries); | } while(tries <= maxTries); | ||||
return path; | return path; | ||||
} | } | ||||
static size_t s_nextScreenshotNumber; | |||||
// <extension> identifies the file format that is to be written | |||||
// (case-insensitive). examples: "bmp", "png", "jpg". | |||||
// BMP is good for quick output at the expense of large files. | |||||
void WriteScreenshot(const VfsPath& extension) | void WriteScreenshot(const VfsPath& extension) | ||||
{ | { | ||||
// get next available numbered filename | CScreenshotWriter screenshotWriter(extension); | ||||
// note: %04d -> always 4 digits, so sorting by filename works correctly. | screenshotWriter.WriteScreenshot(); | ||||
const VfsPath basenameFormat(L"screenshots/screenshot%04d"); | |||||
const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension); | |||||
VfsPath filename; | |||||
vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename); | |||||
const size_t w = (size_t)g_xres, h = (size_t)g_yres; | |||||
const size_t bpp = 24; | |||||
GLenum fmt = GL_RGB; | |||||
int flags = TEX_BOTTOM_UP; | |||||
// we want writing BMP to be as fast as possible, | |||||
// so read data from OpenGL in BMP format to obviate conversion. | |||||
if(extension == L".bmp") | |||||
{ | |||||
#if !CONFIG2_GLES // GLES doesn't support BGR | |||||
fmt = GL_BGR; | |||||
flags |= TEX_BGR; | |||||
#endif | |||||
} | |||||
// Hide log messages and re-render | |||||
RenderLogger(false); | |||||
Render(); | |||||
RenderLogger(true); | |||||
const size_t img_size = w * h * bpp/8; | |||||
const size_t hdr_size = tex_hdr_size(filename); | |||||
shared_ptr<u8> buf; | |||||
AllocateAligned(buf, hdr_size+img_size, maxSectorSize); | |||||
GLvoid* img = buf.get() + hdr_size; | |||||
Tex t; | |||||
if(t.wrap(w, h, bpp, flags, buf, hdr_size) < 0) | |||||
return; | |||||
glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img); | |||||
if (tex_write(&t, filename) == INFO::OK) | |||||
{ | |||||
OsPath realPath; | |||||
g_VFS->GetRealPath(filename, realPath); | |||||
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); | |||||
debug_printf( | |||||
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), | |||||
realPath.string8().c_str()); | |||||
} | |||||
else | |||||
LOGERROR("Error writing screenshot to '%s'", filename.string8()); | |||||
} | } | ||||
// Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles. | |||||
void WriteBigScreenshot(const VfsPath& extension, int tiles) | void WriteBigScreenshot(const VfsPath& extension, int tiles) | ||||
{ | { | ||||
// If the game hasn't started yet then use WriteScreenshot to generate the image. | CScreenshotWriter screenshotWriter(extension); | ||||
if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; } | screenshotWriter.WriteBigScreenshot(tiles); | ||||
// get next available numbered filename | |||||
// note: %04d -> always 4 digits, so sorting by filename works correctly. | |||||
const VfsPath basenameFormat(L"screenshots/screenshot%04d"); | |||||
const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension); | |||||
VfsPath filename; | |||||
vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename); | |||||
// Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and | |||||
// hope the screen is actually large enough for that. | |||||
const int tile_w = 640, tile_h = 480; | |||||
ENSURE(g_xres >= tile_w && g_yres >= tile_h); | |||||
const int img_w = tile_w*tiles, img_h = tile_h*tiles; | |||||
const int bpp = 24; | |||||
GLenum fmt = GL_RGB; | |||||
int flags = TEX_BOTTOM_UP; | |||||
// we want writing BMP to be as fast as possible, | |||||
// so read data from OpenGL in BMP format to obviate conversion. | |||||
if(extension == L".bmp") | |||||
{ | |||||
#if !CONFIG2_GLES // GLES doesn't support BGR | |||||
fmt = GL_BGR; | |||||
flags |= TEX_BGR; | |||||
#endif | |||||
} | |||||
const size_t img_size = img_w * img_h * bpp/8; | |||||
const size_t tile_size = tile_w * tile_h * bpp/8; | |||||
const size_t hdr_size = tex_hdr_size(filename); | |||||
void* tile_data = malloc(tile_size); | |||||
if(!tile_data) | |||||
{ | |||||
WARN_IF_ERR(ERR::NO_MEM); | |||||
return; | |||||
} | |||||
shared_ptr<u8> img_buf; | |||||
AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize); | |||||
Tex t; | |||||
GLvoid* img = img_buf.get() + hdr_size; | |||||
if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0) | |||||
{ | |||||
free(tile_data); | |||||
return; | |||||
} | |||||
ogl_WarnIfError(); | |||||
CCamera oldCamera = *g_Game->GetView()->GetCamera(); | |||||
// Resize various things so that the sizes and aspect ratios are correct | |||||
{ | |||||
g_Renderer.Resize(tile_w, tile_h); | |||||
SViewPort vp = { 0, 0, tile_w, tile_h }; | |||||
g_Game->GetView()->SetViewport(vp); | |||||
} | |||||
#if !CONFIG2_GLES | |||||
// Temporarily move everything onto the front buffer, so the user can | |||||
// see the exciting progress as it renders (and can tell when it's finished). | |||||
// (It doesn't just use SwapBuffers, because it doesn't know whether to | |||||
// call the SDL version or the Atlas version.) | |||||
GLint oldReadBuffer, oldDrawBuffer; | |||||
glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer); | |||||
glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer); | |||||
glDrawBuffer(GL_FRONT); | |||||
glReadBuffer(GL_FRONT); | |||||
#endif | |||||
// Hide the cursor | |||||
CStrW oldCursor = g_CursorName; | |||||
g_CursorName = L""; | |||||
// Render each tile | |||||
CMatrix3D projection; | |||||
projection.SetIdentity(); | |||||
for (int tile_y = 0; tile_y < tiles; ++tile_y) | |||||
{ | |||||
for (int tile_x = 0; tile_x < tiles; ++tile_x) | |||||
{ | |||||
// Adjust the camera to render the appropriate region | |||||
if (oldCamera.GetProjectionType() == CCamera::PERSPECTIVE) | |||||
{ | |||||
projection.SetPerspectiveTile(oldCamera.GetFOV(), oldCamera.GetAspectRatio(), oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tile_x, tile_y); | |||||
} | |||||
g_Game->GetView()->GetCamera()->SetProjection(projection); | |||||
RenderLogger(false); | |||||
RenderGui(false); | |||||
Render(); | |||||
RenderGui(true); | |||||
RenderLogger(true); | |||||
// Copy the tile pixels into the main image | |||||
glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data); | |||||
for (int y = 0; y < tile_h; ++y) | |||||
{ | |||||
void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8; | |||||
void* src = (char*)tile_data + y * tile_w * bpp/8; | |||||
memcpy(dest, src, tile_w * bpp/8); | |||||
} | |||||
} | |||||
} | |||||
// Restore the old cursor | |||||
g_CursorName = oldCursor; | |||||
#if !CONFIG2_GLES | |||||
// Restore the buffer settings | |||||
glDrawBuffer(oldDrawBuffer); | |||||
glReadBuffer(oldReadBuffer); | |||||
#endif | |||||
// Restore the viewport settings | |||||
{ | |||||
g_Renderer.Resize(g_xres, g_yres); | |||||
SViewPort vp = { 0, 0, g_xres, g_yres }; | |||||
g_Game->GetView()->SetViewport(vp); | |||||
g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera); | |||||
} | |||||
if (tex_write(&t, filename) == INFO::OK) | |||||
{ | |||||
OsPath realPath; | |||||
g_VFS->GetRealPath(filename, realPath); | |||||
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8()); | |||||
debug_printf( | |||||
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(), | |||||
realPath.string8().c_str()); | |||||
} | |||||
else | |||||
LOGERROR("Error writing screenshot to '%s'", filename.string8()); | |||||
free(tile_data); | |||||
} | } | ||||
std::string Hexify(const std::string& s) | std::string Hexify(const std::string& s) | ||||
{ | { | ||||
std::stringstream str; | std::stringstream str; | ||||
str << std::hex; | str << std::hex; | ||||
for (const char& c : s) | for (const char& c : s) | ||||
str << std::setfill('0') << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c)); | str << std::setfill('0') << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c)); | ||||
Show All 11 Lines |
Wildfire Games · Phabricator