From 514aa4d750cd9e5b888e4204b7d8e30ac5df172d Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Sat, 10 Aug 2019 18:49:57 +0200 Subject: [PATCH] Screenshot function --- Utilities/date_time.h | 11 +++++- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 20 ++++++++++ rpcs3/Emu/RSX/GSRender.h | 3 ++ rpcs3/Emu/RSX/VK/VKGSRender.cpp | 37 ++++++++++++++++++ rpcs3/rpcs3qt/CMakeLists.txt | 3 +- rpcs3/rpcs3qt/gs_frame.cpp | 66 +++++++++++++++++++++++++++++++++ rpcs3/rpcs3qt/gs_frame.h | 2 + 7 files changed, 140 insertions(+), 2 deletions(-) diff --git a/Utilities/date_time.h b/Utilities/date_time.h index a61d26ca2f8e..18d19d82e022 100644 --- a/Utilities/date_time.h +++ b/Utilities/date_time.h @@ -25,11 +25,20 @@ namespace date_time return str; } + template 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; } } diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index ef8ea4cdfa2d..53bc6fbe797b 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -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 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)) diff --git a/rpcs3/Emu/RSX/GSRender.h b/rpcs3/Emu/RSX/GSRender.h index 8aec937fa41c..2979338ff2e2 100644 --- a/rpcs3/Emu/RSX/GSRender.h +++ b/rpcs3/Emu/RSX/GSRender.h @@ -85,6 +85,9 @@ using draw_context_t = void*; virtual int client_height() = 0; virtual display_handle_t handle() const = 0; + + std::atomic screenshot_toggle = false; + virtual void take_screenshot(const std::vector sshot_data, const u32 sshot_width, const u32 sshot_height) = 0; }; class GSRender : public rsx::thread diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 6b0f810cd951..84fc6f63729d 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -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 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) { diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 1ec25b5db9fa..19ab4325b6fe 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -78,4 +78,5 @@ target_link_libraries(rpcs3_ui 3rdparty::zlib 3rdparty::pugixml 3rdparty::discord-rpc 3rdparty::hidapi - 3rdparty::libusb) + 3rdparty::libusb + 3rdparty::libpng) diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index e354bbd8b7cc..e281b81955c9 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -2,6 +2,7 @@ #include "Utilities/Config.h" #include "Utilities/Timer.h" +#include "Utilities/date_time.h" #include "Emu/System.h" #include @@ -13,6 +14,8 @@ #include "rpcs3_version.h" +#include "png.h" + #ifdef _WIN32 #include #elif defined(__APPLE__) @@ -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; } } @@ -299,6 +303,68 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/) } } +void gs_frame::take_screenshot(const std::vector sshot_data, const u32 sshot_width, const u32 sshot_height) +{ + std::thread( + [sshot_width, sshot_height](const std::vector 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 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 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 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* p = (std::vector*)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; diff --git a/rpcs3/rpcs3qt/gs_frame.h b/rpcs3/rpcs3qt/gs_frame.h index 313d7ada8600..4b16b56e0d5e 100644 --- a/rpcs3/rpcs3qt/gs_frame.h +++ b/rpcs3/rpcs3qt/gs_frame.h @@ -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 sshot_data, const u32 sshot_width, const u32 sshot_height) override; + protected: virtual void paintEvent(QPaintEvent *event); virtual void showEvent(QShowEvent *event) override;