Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make screenshots copy to clipboard #2739

Merged
merged 3 commits into from Jun 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 119 additions & 6 deletions game/graphics/opengl_renderer/OpenGLRenderer.cpp
Expand Up @@ -32,6 +32,10 @@
#include "game/kernel/common/kmachine.h"
#include "game/runtime.h"

#ifdef _WIN32
#include <Windows.h>
#endif

namespace {
std::string g_current_render;
}
Expand Down Expand Up @@ -761,7 +765,7 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
read_buffer = GL_FRONT;
}
finish_screenshot(settings.screenshot_path, screenshot_src->width, screenshot_src->height, 0, 0,
screenshot_src->fbo_id, read_buffer);
screenshot_src->fbo_id, read_buffer, settings.quick_screenshot);
}
if (settings.gpu_sync) {
glFinish();
Expand Down Expand Up @@ -1053,6 +1057,105 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma,
}
}

#ifdef _WIN32
void win_print_last_error(const std::string& msg) {
LPSTR lpMsgBuf;

FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL);

lg::error("[OpenGLRenderer] {} Win Err: {}", msg, lpMsgBuf);
}

HGLOBAL hClipboardData;
void copy_texture_to_clipboard(int width, int height, const std::vector<u32>& texture_data) {
std::vector<u32> data(texture_data);

// BGR -> RGB
for (auto& px : data) {
u8 r = px >> 0;
u8 g = px >> 8;
u8 b = px >> 16;
u8 a = px >> 24;
px = (a << 24) | (r << 16) | (g << 8) | (b << 0);
}

// Calculate the total size of the image data
size_t image_size = data.size() * sizeof(u32);

// BMP/DIB file header
BITMAPINFOHEADER header;
header.biSize = sizeof(header);
header.biWidth = width;
header.biHeight = height;
header.biPlanes = 1;
header.biBitCount = 32;
header.biCompression = BI_RGB;
header.biSizeImage = 0;
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biClrUsed = 0;
header.biClrImportant = 0;

// Open the clipboard
if (!OpenClipboard(NULL)) {
win_print_last_error("Failed to open the clipboard.");
return;
}

// Empty the clipboard
if (!EmptyClipboard()) {
win_print_last_error("Failed to empty the clipboard.");
CloseClipboard();
return;
}

// Create a global memory object to hold the image data
hClipboardData = GlobalAlloc(GMEM_MOVEABLE, sizeof(header) + image_size);
if (hClipboardData == NULL) {
win_print_last_error("Failed to allocate memory for clipboard data.");
CloseClipboard();
return;
}

// Get a pointer to the global memory object
void* pData = GlobalLock(hClipboardData);
if (pData == NULL) {
win_print_last_error("Failed to lock clipboard memory.");
CloseClipboard();
GlobalFree(hClipboardData);
return;
}

// Copy the image data into the global memory object
memcpy(pData, &header, sizeof(header));
memcpy((char*)pData + sizeof(header), data.data(), image_size);

// Unlock the global memory object
if (!GlobalUnlock(hClipboardData) && GetLastError() != NO_ERROR) {
win_print_last_error("Failed to unlock memory.");
CloseClipboard();
GlobalFree(hClipboardData);
return;
}

// Set the image data to clipboard
if (!SetClipboardData(CF_DIB, hClipboardData)) {
win_print_last_error("Failed to set clipboard data.");
CloseClipboard();
GlobalFree(hClipboardData);
return;
}

// Close the clipboard
CloseClipboard();
GlobalFree(hClipboardData);

lg::info("Image data copied to clipboard successfully!");
}
#endif

/*!
* Take a screenshot!
*/
Expand All @@ -1062,25 +1165,35 @@ void OpenGLRenderer::finish_screenshot(const std::string& output_name,
int x,
int y,
GLuint fbo,
int read_buffer) {
int read_buffer,
bool quick_screenshot) {
std::vector<u32> buffer(width * height);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
GLint oldbuf;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldbuf);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glReadBuffer(read_buffer);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());

// set alpha. For some reason, image viewers do weird stuff with alpha.
for (auto& px : buffer) {
px |= 0xff000000;
}

#ifdef _WIN32
if (quick_screenshot) {
// copy to clipboard (windows only)
copy_texture_to_clipboard(width, height, buffer);
}
#endif

// flip upside down in place
for (int h = 0; h < height / 2; h++) {
for (int w = 0; w < width; w++) {
std::swap(buffer[h * width + w], buffer[(height - h - 1) * width + w]);
}
}

// set alpha. For some reason, image viewers do weird stuff with alpha.
for (auto& px : buffer) {
px |= 0xff000000;
}
file_util::write_rgba_png(output_name, buffer.data(), width, height);
glBindFramebuffer(GL_READ_FRAMEBUFFER, oldbuf);
}
Expand Down
4 changes: 3 additions & 1 deletion game/graphics/opengl_renderer/OpenGLRenderer.h
Expand Up @@ -42,6 +42,7 @@ struct RenderOptions {
int draw_region_width = 0;

bool save_screenshot = false;
bool quick_screenshot = false;
std::string screenshot_path;

float pmode_alp_register = 0.f;
Expand Down Expand Up @@ -131,7 +132,8 @@ class OpenGLRenderer {
int x,
int y,
GLuint fbo,
int read_buffer);
int read_buffer,
bool quick_screenshot);
template <typename T, typename U, class... Args>
T* init_bucket_renderer(const std::string& name, BucketCategory cat, U id, Args&&... args) {
auto renderer = std::make_unique<T>(name, (int)id, std::forward<Args>(args)...);
Expand Down
2 changes: 2 additions & 0 deletions game/graphics/pipelines/opengl.cpp
Expand Up @@ -363,10 +363,12 @@ void render_game_frame(int game_width,
options.draw_subtitle2_editor_window = g_gfx_data->debug_gui.should_draw_subtitle2_editor();
options.draw_filters_window = g_gfx_data->debug_gui.should_draw_filters_menu();
options.save_screenshot = false;
options.quick_screenshot = false;
options.gpu_sync = g_gfx_data->debug_gui.should_gl_finish();

if (take_screenshot) {
options.save_screenshot = true;
options.quick_screenshot = true;
options.screenshot_path = file_util::make_screenshot_filepath(g_game_version);
}
if (g_gfx_data->debug_gui.get_screenshot_flag()) {
Expand Down