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

Screenshot function #6334

Merged
merged 2 commits into from Aug 14, 2019
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
11 changes: 10 additions & 1 deletion Utilities/date_time.h
Expand Up @@ -25,11 +25,20 @@ namespace date_time
return str;
}

template<char separator = 0>
static inline std::string current_time_narrow()
{
char str[80];
tm now = get_time(0);
strftime(str, sizeof(str), "%Y%m%d%H%M%S", &now);

std::string parse_buf;

if constexpr(separator != 0)
parse_buf = std::string("%Y") + separator + "%m" + separator + "%d" + separator + "%H" + separator + "%M" + separator + "%S";
else
parse_buf = "%Y%m%d%H%M%S";

strftime(str, sizeof(str), parse_buf.c_str(), &now);
return str;
}
}
20 changes: 20 additions & 0 deletions rpcs3/Emu/RSX/GL/GLGSRender.cpp
Expand Up @@ -1681,6 +1681,26 @@ void GLGSRender::flip(int buffer, bool emu_flip)
image = m_flip_tex_color->id();
}

if (m_frame->screenshot_toggle == true)
{
m_frame->screenshot_toggle = false;

std::vector<u8> sshot_frame(buffer_height * buffer_width * 4);

gl::pixel_pack_settings pack_settings{};
pack_settings.apply();

if (gl::get_driver_caps().ARB_dsa_supported)
glGetTextureImage(image, 0, GL_BGRA, GL_UNSIGNED_BYTE, buffer_height * buffer_width * 4, sshot_frame.data());
else
glGetTextureImageEXT(image, GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, sshot_frame.data());

if (GLenum err; (err = glGetError()) != GL_NO_ERROR)
LOG_ERROR(GENERAL, "[Screenshot] Failed to capture image: 0x%x", err);
else
m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height);
}

areai screen_area = coordi({}, { (int)buffer_width, (int)buffer_height });

if (g_cfg.video.full_rgb_range_output && (!avconfig || avconfig->gamma == 1.f))
Expand Down
3 changes: 3 additions & 0 deletions rpcs3/Emu/RSX/GSRender.h
Expand Up @@ -85,6 +85,9 @@ using draw_context_t = void*;
virtual int client_height() = 0;

virtual display_handle_t handle() const = 0;

std::atomic<bool> screenshot_toggle = false;
virtual void take_screenshot(const std::vector<u8> sshot_data, const u32 sshot_width, const u32 sshot_height) = 0;
};

class GSRender : public rsx::thread
Expand Down
37 changes: 37 additions & 0 deletions rpcs3/Emu/RSX/VK/VKGSRender.cpp
Expand Up @@ -3373,6 +3373,43 @@ void VKGSRender::flip(int buffer, bool emu_flip)
vk::change_image_layout(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, present_layout, range);
}

if (m_frame->screenshot_toggle == true)
{
m_frame->screenshot_toggle = false;

const size_t sshot_size = buffer_height * buffer_width * 4;

vk::buffer sshot_vkbuf(*m_device, align(sshot_size, 0x100000), m_device->get_memory_mapping().host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0);

VkBufferImageCopy copy_info;
copy_info.bufferOffset = 0;
copy_info.bufferRowLength = 0;
copy_info.bufferImageHeight = 0;
copy_info.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_info.imageSubresource.baseArrayLayer = 0;
copy_info.imageSubresource.layerCount = 1;
copy_info.imageSubresource.mipLevel = 0;
copy_info.imageOffset.x = 0;
copy_info.imageOffset.y = 0;
copy_info.imageOffset.z = 0;
copy_info.imageExtent.width = buffer_width;
copy_info.imageExtent.height = buffer_height;
copy_info.imageExtent.depth = 1;

image_to_flip->push_layout(*m_current_command_buffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vk::copy_image_to_buffer(*m_current_command_buffer, image_to_flip, &sshot_vkbuf, copy_info);
image_to_flip->pop_layout(*m_current_command_buffer);

flush_command_queue(true);
auto src = sshot_vkbuf.map(0, sshot_size);
std::vector<u8> sshot_frame(sshot_size);
memcpy(sshot_frame.data(), src, sshot_size);
sshot_vkbuf.unmap();

m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height);
}

const bool has_overlay = (m_overlay_manager && m_overlay_manager->has_visible());
if (g_cfg.video.overlay || has_overlay)
{
Expand Down
3 changes: 2 additions & 1 deletion rpcs3/rpcs3qt/CMakeLists.txt
Expand Up @@ -78,4 +78,5 @@ target_link_libraries(rpcs3_ui
3rdparty::zlib 3rdparty::pugixml
3rdparty::discord-rpc
3rdparty::hidapi
3rdparty::libusb)
3rdparty::libusb
3rdparty::libpng)
66 changes: 66 additions & 0 deletions rpcs3/rpcs3qt/gs_frame.cpp
Expand Up @@ -2,6 +2,7 @@

#include "Utilities/Config.h"
#include "Utilities/Timer.h"
#include "Utilities/date_time.h"
#include "Emu/System.h"

#include <QKeyEvent>
Expand All @@ -13,6 +14,8 @@

#include "rpcs3_version.h"

#include "png.h"

#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
Expand Down Expand Up @@ -151,6 +154,7 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent)
else if (Emu.IsPaused()) { Emu.Resume(); return; }
}
break;
case Qt::Key_F12: screenshot_toggle = true; break;
}
}

Expand Down Expand Up @@ -299,6 +303,68 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/)
}
}

void gs_frame::take_screenshot(const std::vector<u8> sshot_data, const u32 sshot_width, const u32 sshot_height)
{
std::thread(
[sshot_width, sshot_height](const std::vector<u8> sshot_data) {
std::string screen_path = fs::get_config_dir() + "/screenshots/";

if (!fs::create_dir(screen_path) && fs::g_tls_error != fs::error::exist)
{
LOG_ERROR(GENERAL, "Failed to create screenshot path \"%s\" : %s", screen_path, fs::g_tls_error);
return;
}

std::string filename = screen_path + "screenshot-" + date_time::current_time_narrow<'_'>() + ".png";

fs::file sshot_file(filename, fs::open_mode::create + fs::open_mode::write + fs::open_mode::excl);
if (!sshot_file)
{
LOG_ERROR(GENERAL, "[Screenshot] Failed to save screenshot \"%s\" : %s", filename, fs::g_tls_error);
return;
}

std::vector<u8> sshot_data_alpha(sshot_data.size());
const u32* sshot_ptr = (const u32*)sshot_data.data();
u32* alpha_ptr = (u32*)sshot_data_alpha.data();

for (size_t index = 0; index < sshot_data.size() / sizeof(u32); index++)
{
alpha_ptr[index] = ((sshot_ptr[index] & 0xFF) << 16) | (sshot_ptr[index] & 0xFF00) | ((sshot_ptr[index] & 0xFF0000) >> 16) | 0xFF000000;
}

std::vector<u8> encoded_png;

png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = png_create_info_struct(write_ptr);
png_set_IHDR(write_ptr, info_ptr, sshot_width, sshot_height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

std::vector<u8*> rows(sshot_height);
for (size_t y = 0; y < sshot_height; y++)
rows[y] = (u8*)sshot_data_alpha.data() + y * sshot_width * 4;

png_set_rows(write_ptr, info_ptr, &rows[0]);
png_set_write_fn(write_ptr, &encoded_png,
[](png_structp png_ptr, png_bytep data, png_size_t length) {
std::vector<u8>* p = (std::vector<u8>*)png_get_io_ptr(png_ptr);
p->insert(p->end(), data, data + length);
},
nullptr);

png_write_png(write_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);

png_free_data(write_ptr, info_ptr, PNG_FREE_ALL, -1);
png_destroy_write_struct(&write_ptr, nullptr);

sshot_file.write(encoded_png.data(), encoded_png.size());

LOG_SUCCESS(GENERAL, "[Screenshot] Successfully saved screenshot to %s", filename);
return;
},
std::move(sshot_data))
.detach();
}

void gs_frame::mouseDoubleClickEvent(QMouseEvent* ev)
{
if (m_disable_mouse) return;
Expand Down
2 changes: 2 additions & 0 deletions rpcs3/rpcs3qt/gs_frame.h
Expand Up @@ -54,6 +54,8 @@ class gs_frame : public QWindow, public GSFrameBase
void progress_increment(int delta);
void progress_set_limit(int limit);

void take_screenshot(const std::vector<u8> sshot_data, const u32 sshot_width, const u32 sshot_height) override;

protected:
virtual void paintEvent(QPaintEvent *event);
virtual void showEvent(QShowEvent *event) override;
Expand Down