glrage: make .jpg screenshots

This commit is contained in:
rr- 2021-12-03 18:50:12 +01:00
parent 6402a325d6
commit b3d5703b92
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
18 changed files with 90 additions and 126 deletions

View file

@ -221,7 +221,6 @@ sources = [
'src/glrage/ContextImpl.cpp', 'src/glrage/ContextImpl.cpp',
'src/glrage/GLRage.cpp', 'src/glrage/GLRage.cpp',
'src/glrage/Interop.cpp', 'src/glrage/Interop.cpp',
'src/glrage/Screenshot.cpp',
'src/glrage_gl/Buffer.cpp', 'src/glrage_gl/Buffer.cpp',
'src/glrage_gl/Program.cpp', 'src/glrage_gl/Program.cpp',
'src/glrage_gl/Sampler.cpp', 'src/glrage_gl/Sampler.cpp',

View file

@ -83,7 +83,7 @@ HRESULT DirectDrawSurface::Blt(
gl::Screenshot::capture( gl::Screenshot::capture(
buffer, width, height, depth, GL_BGRA, buffer, width, height, depth, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV); GL_UNSIGNED_INT_8_8_8_8_REV, false);
Blitter::Rect srcRect { 0, height, width, 0 }; Blitter::Rect srcRect { 0, height, width, 0 };

View file

@ -856,3 +856,8 @@ void Output_ApplyWaterEffect(float *r, float *g, float *b)
*b *= m_WaterColor.b; *b *= m_WaterColor.b;
} }
} }
bool Output_MakeScreenshot(const char *path)
{
return S_Output_MakeScreenshot(path);
}

View file

@ -71,4 +71,6 @@ void Output_AnimateTextures(int32_t ticks);
void Output_ApplyWaterEffect(float *r, float *g, float *b); void Output_ApplyWaterEffect(float *r, float *g, float *b);
bool Output_MakeScreenshot(const char *path);
#endif #endif

View file

@ -2,6 +2,7 @@
#include "3dsystem/phd_math.h" #include "3dsystem/phd_math.h"
#include "config.h" #include "config.h"
#include "filesystem.h"
#include "game/clock.h" #include "game/clock.h"
#include "game/demo.h" #include "game/demo.h"
#include "game/fmv.h" #include "game/fmv.h"
@ -186,3 +187,16 @@ void Shell_Wait(int nticks)
Input_Update(); Input_Update();
} }
} }
bool Shell_MakeScreenshot()
{
char path[20];
for (int i = 0; i < 10000; i++) {
sprintf(path, "screenshot%04d.jpg", i);
if (File_Exists(path)) {
continue;
}
return Output_MakeScreenshot(path);
}
return false;
}

View file

@ -1,9 +1,12 @@
#ifndef T1M_GAME_SHELL_H #ifndef T1M_GAME_SHELL_H
#define T1M_GAME_SHELL_H #define T1M_GAME_SHELL_H
#include <stdbool.h>
void Shell_Main(); void Shell_Main();
void Shell_ExitSystem(const char *message); void Shell_ExitSystem(const char *message);
void Shell_ExitSystemFmt(const char *fmt, ...); void Shell_ExitSystemFmt(const char *fmt, ...);
void Shell_Wait(int nticks); void Shell_Wait(int nticks);
bool Shell_MakeScreenshot();
#endif #endif

View file

@ -28,6 +28,7 @@ public:
virtual bool isRendered() = 0; virtual bool isRendered() = 0;
virtual HWND getHWnd() = 0; virtual HWND getHWnd() = 0;
virtual std::string getBasePath() = 0; virtual std::string getBasePath() = 0;
virtual void scheduleScreenshot(const std::string &path) = 0;
virtual ~Context() = default; virtual ~Context() = default;
}; };

View file

@ -1,5 +1,6 @@
#include "glrage/ContextImpl.hpp" #include "glrage/ContextImpl.hpp"
#include "glrage_gl/Screenshot.hpp"
#include "glrage_gl/gl_core_3_3.h" #include "glrage_gl/gl_core_3_3.h"
#include "glrage_gl/wgl_ext.h" #include "glrage_gl/wgl_ext.h"
#include "glrage_util/ErrorUtils.hpp" #include "glrage_util/ErrorUtils.hpp"
@ -109,17 +110,6 @@ LRESULT
ContextImpl::windowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) ContextImpl::windowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
switch (msg) { switch (msg) {
// Printscreen on Windows with OpenGL doesn't work in fullscreen, so
// hook the key and implement screenshot saving to files.
// For some reason, VK_SNAPSHOT doesn't generate WM_KEYDOWN events but
// only WM_KEYUP. Works just as well, though.
case WM_KEYUP:
if (wParam == VK_SNAPSHOT) {
m_screenshot.schedule(true);
return TRUE;
}
break;
// force default handling for some window messages when in windowed // force default handling for some window messages when in windowed
// mode, especially important for Tomb Raider // mode, especially important for Tomb Raider
case WM_MOVE: case WM_MOVE:
@ -223,11 +213,9 @@ void ContextImpl::swapBuffers()
{ {
glFinish(); glFinish();
try { if (!m_screenshotScheduledPath.empty()) {
m_screenshot.captureScheduled(); gl::Screenshot::capture(m_screenshotScheduledPath);
} catch (const std::exception &ex) { m_screenshotScheduledPath.clear();
ErrorUtils::warning("Can't capture screenshot", ex);
m_screenshot.schedule(false);
} }
SwapBuffers(m_hdc); SwapBuffers(m_hdc);
@ -270,4 +258,9 @@ std::string ContextImpl::getBasePath()
return path; return path;
} }
void ContextImpl::scheduleScreenshot(const std::string &path)
{
m_screenshotScheduledPath = path;
}
} }

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "glrage/Context.hpp" #include "glrage/Context.hpp"
#include "glrage/Screenshot.hpp"
namespace glrage { namespace glrage {
@ -30,6 +29,7 @@ public:
bool isRendered(); bool isRendered();
HWND getHWnd(); HWND getHWnd();
std::string getBasePath(); std::string getBasePath();
void scheduleScreenshot(const std::string &path);
private: private:
ContextImpl(); ContextImpl();
@ -66,9 +66,6 @@ private:
// rendering flag // rendering flag
bool m_render = false; bool m_render = false;
// screenshot object
Screenshot m_screenshot;
// DirectDraw display mode // DirectDraw display mode
int32_t m_displayWidth = 0; int32_t m_displayWidth = 0;
int32_t m_displayHeight = 0; int32_t m_displayHeight = 0;
@ -80,6 +77,9 @@ private:
// Window size, controlled by SDL // Window size, controlled by SDL
int32_t m_windowWidth = 0; int32_t m_windowWidth = 0;
int32_t m_windowHeight = 0; int32_t m_windowHeight = 0;
// whether to capture screenshot on next redraw
std::string m_screenshotScheduledPath;
}; };
} }

View file

@ -27,3 +27,10 @@ void GLRage_SetWindowSize(int width, int height)
auto &context = GLRage::getContext(); auto &context = GLRage::getContext();
context.setWindowSize(width, height); context.setWindowSize(width, height);
} }
bool GLRage_MakeScreenshot(const char *path)
{
auto &context = GLRage::getContext();
context.scheduleScreenshot(std::string(path));
return true;
}

View file

@ -14,6 +14,8 @@ void GLRage_Detach();
void GLRage_SetFullscreen(bool fullscreen); void GLRage_SetFullscreen(bool fullscreen);
void GLRage_SetWindowSize(int width, int height); void GLRage_SetWindowSize(int width, int height);
bool GLRage_MakeScreenshot(const char *path);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -1,51 +0,0 @@
#include "glrage/Screenshot.hpp"
#include "glrage/ContextImpl.hpp"
#include "glrage_gl/Screenshot.hpp"
#include "glrage_util/StringUtils.hpp"
#include <cstdint>
#include <exception>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace glrage {
void Screenshot::schedule(bool schedule)
{
m_schedule = schedule;
}
void Screenshot::captureScheduled()
{
if (m_schedule) {
capture();
m_schedule = false;
}
}
void Screenshot::capture()
{
// find unused screenshot file name
std::string basePath = ContextImpl::instance().getBasePath();
std::string path;
DWORD dwAttrib;
do {
std::string fileName =
StringUtils::format("screenshot%04d.tga", m_index++);
path = basePath + "\\" + fileName;
dwAttrib = GetFileAttributes(path.c_str());
} while (dwAttrib != INVALID_FILE_ATTRIBUTES && m_index < 9999);
// rather unlikely, but better safe than sorry
if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
throw std::runtime_error("All available screenshot slots are used up!");
}
// actual capture
gl::Screenshot::capture(path);
}
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <cstdint>
namespace glrage {
class Screenshot {
public:
void schedule(bool schedule);
void captureScheduled();
void capture();
private:
uint32_t m_index = 0;
bool m_schedule = false;
};
}

View file

@ -3,6 +3,11 @@
#include "glrage_util/ErrorUtils.hpp" #include "glrage_util/ErrorUtils.hpp"
#include "glrage_util/StringUtils.hpp" #include "glrage_util/StringUtils.hpp"
extern "C" {
#include "memory.h"
#include "game/picture.h"
}
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <exception> #include <exception>
@ -14,46 +19,34 @@
namespace glrage { namespace glrage {
namespace gl { namespace gl {
// heavily simplified Targa header struct for raw BGR(A) data bool Screenshot::capture(const std::string &path)
struct TGAHeader {
uint8_t blank1[2];
uint8_t format;
uint8_t blank2[9];
uint16_t width;
uint16_t height;
uint8_t depth;
uint8_t blank3;
};
void Screenshot::capture(const std::string &path)
{ {
// open screenshot file bool ret = false;
std::ofstream file(path, std::ofstream::binary);
if (!file.good()) {
throw std::runtime_error(
"Can't open screenshot file '" + path
+ "': " + ErrorUtils::getSystemErrorString());
}
// copy framebuffer to local buffer
GLint width; GLint width;
GLint height; GLint height;
GLint depth = 3;
std::vector<uint8_t> buffer; std::vector<uint8_t> buffer;
capture(buffer, width, height, depth, GL_BGR, GL_UNSIGNED_BYTE, false);
// create Targa header Screenshot::capture(
TGAHeader tgaHeader = { { 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0, buffer, width, height, 3, GL_RGB, GL_UNSIGNED_BYTE, true);
0, 0 };
tgaHeader.format = 2;
tgaHeader.width = width;
tgaHeader.height = height;
tgaHeader.depth = depth * 8;
file.write(reinterpret_cast<char *>(&tgaHeader), sizeof(TGAHeader)); PICTURE *pic = Picture_Create();
file.write(reinterpret_cast<char *>(&buffer[0]), buffer.size()); if (!pic) {
file.close(); goto cleanup;
}
pic->width = width;
pic->height = height;
pic->data = static_cast<RGB888 *>(Memory_Alloc(width * height * 3));
std::copy(
buffer.begin(), buffer.end(), reinterpret_cast<uint8_t *>(pic->data));
ret = Picture_SaveToFile(pic, path.c_str());
cleanup:
if (pic) {
Picture_Free(pic);
}
return ret;
} }
void Screenshot::capture( void Screenshot::capture(

View file

@ -11,10 +11,10 @@ namespace gl {
class Screenshot { class Screenshot {
public: public:
static void capture(const std::string &path); static bool capture(const std::string &path);
static void capture( static void capture(
std::vector<uint8_t> &buffer, GLint &width, GLint &height, GLint depth, std::vector<uint8_t> &buffer, GLint &width, GLint &height, GLint depth,
GLenum format, GLenum type, bool vflip = false); GLenum format, GLenum type, bool vflip);
}; };
} }

View file

@ -1543,3 +1543,8 @@ void S_Output_DownloadTextures(int32_t pages)
m_SelectedTexture = -1; m_SelectedTexture = -1;
} }
bool S_Output_MakeScreenshot(const char *path)
{
return GLRage_MakeScreenshot(path);
}

View file

@ -54,4 +54,6 @@ void S_Output_DrawLightningSegment(
int x1, int y1, int z1, int thickness1, int x2, int y2, int z2, int x1, int y1, int z1, int thickness1, int x2, int y2, int z2,
int thickness2); int thickness2);
bool S_Output_MakeScreenshot(const char *path);
#endif #endif

View file

@ -87,6 +87,13 @@ void S_Shell_SpinMessageLoop()
break; break;
} }
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_PRINTSCREEN) {
Shell_MakeScreenshot();
break;
}
break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
switch (event.window.event) { switch (event.window.event) {
case SDL_WINDOWEVENT_FOCUS_GAINED: case SDL_WINDOWEVENT_FOCUS_GAINED: