Index: source/ps/ScreenshotWriter.h =================================================================== --- source/ps/ScreenshotWriter.h +++ source/ps/ScreenshotWriter.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_SCREENSHOTWRITER +#define INCLUDED_SCREENSHOTWRITER + +#include "lib/file/vfs/vfs.h" +#include "lib/ogl.h" + +struct Tex; + +class CScreenshotWriter +{ +public: + /** + * 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. + */ + explicit CScreenshotWriter(const VfsPath& extension); + + ~CScreenshotWriter() = default; + + void WriteScreenshot(); + + /** + * Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles. + */ + void WriteBigScreenshot(int tiles); + +private: + void WriteTextureToFile(Tex* texture); + + VfsPath m_Path; + + GLenum m_GLFormat; + size_t m_TexFlags; + size_t m_Bpp; +}; + +#endif // INCLUDED_SCREENSHOTWRITER Index: source/ps/ScreenshotWriter.cpp =================================================================== --- source/ps/ScreenshotWriter.cpp +++ source/ps/ScreenshotWriter.cpp @@ -0,0 +1,216 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ps/ScreenshotWriter.h" + +#include "graphics/Camera.h" +#include "graphics/GameView.h" +#include "maths/MathUtil.h" +#include "i18n/L10n.h" +#include "lib/allocators/shared_ptr.h" +#include "lib/tex/tex.h" +#include "lib/utf8.h" +#include "ps/GameSetup/Config.h" +#include "ps/GameSetup/GameSetup.h" +#include "ps/Game.h" +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/Util.h" +#include "ps/VideoMode.h" +#include "renderer/Renderer.h" + +static size_t s_nextScreenshotNumber; + +CScreenshotWriter::CScreenshotWriter(const VfsPath& extension) + : m_Bpp(24) +{ + // 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); + vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, m_Path); + + m_GLFormat = GL_RGB; + m_TexFlags = 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 + m_GLFormat = GL_BGR; + m_TexFlags |= TEX_BGR; +#endif + } +} + +void CScreenshotWriter::WriteTextureToFile(Tex* texture) +{ + if (tex_write(texture, m_Path) == INFO::OK) + { + OsPath realPath; + g_VFS->GetRealPath(m_Path, 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'", m_Path.string8()); +} + +void CScreenshotWriter::WriteScreenshot() +{ + // Hide log messages and re-render + RenderLogger(false); + Render(); + RenderLogger(true); + + const size_t w = static_cast(g_xres); + const size_t h = static_cast(g_yres); + const size_t img_size = w * h * m_Bpp / 8; + const size_t hdr_size = tex_hdr_size(m_Path); + + shared_ptr buf; + AllocateAligned(buf, hdr_size + img_size, maxSectorSize); + + GLvoid* img = buf.get() + hdr_size; + Tex t; + if (t.wrap(w, h, m_Bpp, m_TexFlags, buf, hdr_size) < 0) + return; + glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, m_GLFormat, GL_UNSIGNED_BYTE, img); + + WriteTextureToFile(&t); +} + +void CScreenshotWriter::WriteBigScreenshot(int tiles) +{ + // If the game hasn't started yet then use WriteScreenshot to generate the image. + if(g_Game == nullptr) + { + WriteScreenshot(); + return; + } + + // 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 size_t tile_size = tile_w * tile_h * m_Bpp / 8; + void* tile_data = malloc(tile_size); + if(!tile_data) + { + WARN_IF_ERR(ERR::NO_MEM); + return; + } + + const int img_w = tile_w*tiles, img_h = tile_h*tiles; + const size_t img_size = img_w * img_h * m_Bpp / 8; + const size_t hdr_size = tex_hdr_size(m_Path); + shared_ptr 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, m_Bpp, m_TexFlags, 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, m_GLFormat, GL_UNSIGNED_BYTE, tile_data); + for (int y = 0; y < tile_h; ++y) + { + void* dest = static_cast(img) + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * m_Bpp / 8; + void* src = static_cast(tile_data) + y * tile_w * m_Bpp / 8; + memcpy(dest, src, tile_w * m_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); + } + + WriteTextureToFile(&t); + + free(tile_data); +} Index: source/ps/Util.cpp =================================================================== --- source/ps/Util.cpp +++ source/ps/Util.cpp @@ -43,6 +43,7 @@ #include "ps/Game.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/ScreenshotWriter.h" #include "ps/VideoMode.h" #include "renderer/Renderer.h" #include "maths/MathUtil.h" @@ -228,208 +229,16 @@ return path; } -static size_t s_nextScreenshotNumber; - -// 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) { - // 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); - - 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 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()); + CScreenshotWriter screenshotWriter(extension); + screenshotWriter.WriteScreenshot(); } - - -// Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles. void WriteBigScreenshot(const VfsPath& extension, int tiles) { - // If the game hasn't started yet then use WriteScreenshot to generate the image. - if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; } - - // 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 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); + CScreenshotWriter screenshotWriter(extension); + screenshotWriter.WriteBigScreenshot(tiles); } std::string Hexify(const std::string& s)