mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
GS: Add video dumping via ffmpeg
This commit is contained in:
committed by
refractionpcsx2
parent
e5009398e0
commit
699c3d80b7
@@ -0,0 +1,195 @@
|
||||
#[==[
|
||||
Provides the following variables:
|
||||
|
||||
* `FFMPEG_INCLUDE_DIRS`: Include directories necessary to use FFMPEG.
|
||||
* `FFMPEG_LIBRARIES`: Libraries necessary to use FFMPEG. Note that this only
|
||||
includes libraries for the components requested.
|
||||
* `FFMPEG_VERSION`: The version of FFMPEG found.
|
||||
|
||||
The following components are supported:
|
||||
|
||||
* `avcodec`
|
||||
* `avdevice`
|
||||
* `avfilter`
|
||||
* `avformat`
|
||||
* `avresample`
|
||||
* `avutil`
|
||||
* `swresample`
|
||||
* `swscale`
|
||||
|
||||
For each component, the following are provided:
|
||||
|
||||
* `FFMPEG_<component>_FOUND`: Libraries for the component.
|
||||
* `FFMPEG_<component>_INCLUDE_DIRS`: Include directories for
|
||||
the component.
|
||||
* `FFMPEG_<component>_LIBRARIES`: Libraries for the component.
|
||||
* `FFMPEG::<component>`: A target to use with `target_link_libraries`.
|
||||
|
||||
Note that only components requested with `COMPONENTS` or `OPTIONAL_COMPONENTS`
|
||||
are guaranteed to set these variables or provide targets.
|
||||
#]==]
|
||||
|
||||
function (_ffmpeg_find component headername)
|
||||
find_path("FFMPEG_${component}_INCLUDE_DIR"
|
||||
NAMES
|
||||
"lib${component}/${headername}"
|
||||
PATHS
|
||||
"${FFMPEG_ROOT}/include"
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/sw/include # Fink
|
||||
/opt/local/include # DarwinPorts
|
||||
/opt/csw/include # Blastwave
|
||||
/opt/include
|
||||
/usr/freeware/include
|
||||
PATH_SUFFIXES
|
||||
ffmpeg
|
||||
DOC "FFMPEG's ${component} include directory")
|
||||
mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR")
|
||||
|
||||
# On Windows, static FFMPEG is sometimes built as `lib<name>.a`.
|
||||
if (WIN32)
|
||||
list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
|
||||
list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
|
||||
endif ()
|
||||
|
||||
find_library("FFMPEG_${component}_LIBRARY"
|
||||
NAMES
|
||||
"${component}"
|
||||
PATHS
|
||||
"${FFMPEG_ROOT}/lib"
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/lib
|
||||
/usr/local/lib64
|
||||
/usr/lib
|
||||
/usr/lib64
|
||||
/sw/lib
|
||||
/opt/local/lib
|
||||
/opt/csw/lib
|
||||
/opt/lib
|
||||
/usr/freeware/lib64
|
||||
"${FFMPEG_ROOT}/bin"
|
||||
DOC "FFMPEG's ${component} library")
|
||||
mark_as_advanced("FFMPEG_${component}_LIBRARY")
|
||||
|
||||
if (FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR)
|
||||
set(_deps_found TRUE)
|
||||
set(_deps_link)
|
||||
foreach (_ffmpeg_dep IN LISTS ARGN)
|
||||
if (TARGET "FFMPEG::${_ffmpeg_dep}")
|
||||
list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}")
|
||||
else ()
|
||||
set(_deps_found FALSE)
|
||||
endif ()
|
||||
endforeach ()
|
||||
if (_deps_found)
|
||||
if (NOT TARGET "FFMPEG::${component}")
|
||||
add_library("FFMPEG::${component}" UNKNOWN IMPORTED)
|
||||
set_target_properties("FFMPEG::${component}" PROPERTIES
|
||||
IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}"
|
||||
IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}")
|
||||
endif ()
|
||||
set("FFMPEG_${component}_FOUND" 1
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h")
|
||||
if (EXISTS "${version_header_path}")
|
||||
string(TOUPPER "${component}" component_upper)
|
||||
file(STRINGS "${version_header_path}" version
|
||||
REGEX "#define *LIB${component_upper}_VERSION_(MAJOR|MINOR|MICRO) ")
|
||||
string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${version}")
|
||||
string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}")
|
||||
string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}")
|
||||
if (NOT major STREQUAL "" AND
|
||||
NOT minor STREQUAL "" AND
|
||||
NOT micro STREQUAL "")
|
||||
set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}"
|
||||
PARENT_SCOPE)
|
||||
endif ()
|
||||
endif ()
|
||||
else ()
|
||||
set("FFMPEG_${component}_FOUND" 0
|
||||
PARENT_SCOPE)
|
||||
set(what)
|
||||
if (NOT FFMPEG_${component}_LIBRARY)
|
||||
set(what "library")
|
||||
endif ()
|
||||
if (NOT FFMPEG_${component}_INCLUDE_DIR)
|
||||
if (what)
|
||||
string(APPEND what " or headers")
|
||||
else ()
|
||||
set(what "headers")
|
||||
endif ()
|
||||
endif ()
|
||||
set("FFMPEG_${component}_NOT_FOUND_MESSAGE"
|
||||
"Could not find the ${what} for ${component}."
|
||||
PARENT_SCOPE)
|
||||
endif ()
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
_ffmpeg_find(avutil avutil.h)
|
||||
_ffmpeg_find(avresample avresample.h
|
||||
avutil)
|
||||
_ffmpeg_find(swresample swresample.h
|
||||
avutil)
|
||||
_ffmpeg_find(swscale swscale.h
|
||||
avutil)
|
||||
_ffmpeg_find(avcodec avcodec.h
|
||||
avutil)
|
||||
_ffmpeg_find(avformat avformat.h
|
||||
avcodec avutil)
|
||||
_ffmpeg_find(avfilter avfilter.h
|
||||
avutil)
|
||||
_ffmpeg_find(avdevice avdevice.h
|
||||
avformat avutil)
|
||||
|
||||
if (TARGET FFMPEG::avutil)
|
||||
set(_ffmpeg_version_header_path "${FFMPEG_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
|
||||
if (EXISTS "${_ffmpeg_version_header_path}")
|
||||
file(STRINGS "${_ffmpeg_version_header_path}" _ffmpeg_version
|
||||
REGEX "FFMPEG_VERSION")
|
||||
string(REGEX REPLACE ".*\"n?\(.*\)\"" "\\1" FFMPEG_VERSION "${_ffmpeg_version}")
|
||||
unset(_ffmpeg_version)
|
||||
else ()
|
||||
set(FFMPEG_VERSION FFMPEG_VERSION-NOTFOUND)
|
||||
endif ()
|
||||
unset(_ffmpeg_version_header_path)
|
||||
endif ()
|
||||
|
||||
set(FFMPEG_INCLUDE_DIRS)
|
||||
set(FFMPEG_LIBRARIES)
|
||||
set(_ffmpeg_required_vars)
|
||||
foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS)
|
||||
if (TARGET "FFMPEG::${_ffmpeg_component}")
|
||||
set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
|
||||
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
|
||||
set(FFMPEG_${_ffmpeg_component}_LIBRARIES
|
||||
"${FFMPEG_${_ffmpeg_component}_LIBRARY}")
|
||||
list(APPEND FFMPEG_INCLUDE_DIRS
|
||||
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}")
|
||||
list(APPEND FFMPEG_LIBRARIES
|
||||
"${FFMPEG_${_ffmpeg_component}_LIBRARIES}")
|
||||
if (FFMEG_FIND_REQUIRED_${_ffmpeg_component})
|
||||
list(APPEND _ffmpeg_required_vars
|
||||
"FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS"
|
||||
"FFMPEG_${_ffmpeg_required_vars}_LIBRARIES")
|
||||
endif ()
|
||||
endif ()
|
||||
endforeach ()
|
||||
unset(_ffmpeg_component)
|
||||
|
||||
if (FFMPEG_INCLUDE_DIRS)
|
||||
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FFMPEG
|
||||
REQUIRED_VARS FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES ${_ffmpeg_required_vars}
|
||||
VERSION_VAR FFMPEG_VERSION
|
||||
HANDLE_COMPONENTS)
|
||||
unset(_ffmpeg_required_vars)
|
||||
@@ -18,6 +18,7 @@ if (WIN32)
|
||||
endif()
|
||||
add_subdirectory(3rdparty/xz EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(3rdparty/D3D12MemAlloc EXCLUDE_FROM_ALL)
|
||||
set(FFMPEG_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/3rdparty/ffmpeg/include")
|
||||
else()
|
||||
find_package(PCAP REQUIRED)
|
||||
find_package(Gettext) # translation tool
|
||||
@@ -38,6 +39,14 @@ else()
|
||||
set(CMAKE_FIND_FRAMEWORK ${FIND_FRAMEWORK_BACKUP})
|
||||
find_package(Vtune)
|
||||
|
||||
# Use bundled ffmpeg v4.x.x headers if we can't locate it in the system.
|
||||
# We'll try to load it dynamically at runtime.
|
||||
find_package(FFMPEG)
|
||||
if(NOT FFMPEG_VERSION)
|
||||
message(WARNING "FFmpeg not found, using bundled headers.")
|
||||
set(FFMPEG_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/3rdparty/ffmpeg/include")
|
||||
endif()
|
||||
|
||||
if(NOT PCSX2_CORE)
|
||||
# Does not require the module (allow to compile non-wx plugins)
|
||||
# Force the unicode build (the variable is only supported on cmake 2.8.3 and above)
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "pcsx2/CDVD/CDVDdiscReader.h"
|
||||
#include "pcsx2/Frontend/GameList.h"
|
||||
#include "pcsx2/Frontend/LogSink.h"
|
||||
#include "pcsx2/GS/GS.h"
|
||||
#include "pcsx2/GSDumpReplayer.h"
|
||||
#include "pcsx2/HostDisplay.h"
|
||||
#include "pcsx2/HostSettings.h"
|
||||
@@ -383,6 +384,7 @@ void MainWindow::connectSignals()
|
||||
connect(m_ui.actionSaveBlockDump, &QAction::toggled, this, &MainWindow::onBlockDumpActionToggled);
|
||||
connect(m_ui.actionShowAdvancedSettings, &QAction::toggled, this, &MainWindow::onShowAdvancedSettingsToggled);
|
||||
connect(m_ui.actionSaveGSDump, &QAction::triggered, this, &MainWindow::onSaveGSDumpActionTriggered);
|
||||
connect(m_ui.actionToolsVideoCapture, &QAction::toggled, this, &MainWindow::onToolsVideoCaptureToggled);
|
||||
|
||||
// Input Recording
|
||||
connect(m_ui.actionInputRecNew, &QAction::triggered, this, &MainWindow::onInputRecNewActionTriggered);
|
||||
@@ -877,6 +879,33 @@ void MainWindow::onShowAdvancedSettingsToggled(bool checked)
|
||||
recreateSettings();
|
||||
}
|
||||
|
||||
void MainWindow::onToolsVideoCaptureToggled(bool checked)
|
||||
{
|
||||
if (!s_vm_valid)
|
||||
return;
|
||||
|
||||
if (!checked)
|
||||
{
|
||||
g_emu_thread->endCapture();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString container(QString::fromStdString(
|
||||
Host::GetStringSettingValue("EmuCore/GS", "VideoCaptureContainer", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_CONTAINER)));
|
||||
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
|
||||
|
||||
QString path(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseSnapshotFilename())).arg(container));
|
||||
path = QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter);
|
||||
if (path.isEmpty())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionToolsVideoCapture);
|
||||
m_ui.actionToolsVideoCapture->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
g_emu_thread->beginCapture(path);
|
||||
}
|
||||
|
||||
void MainWindow::saveStateToConfig()
|
||||
{
|
||||
if (!isVisible())
|
||||
@@ -952,6 +981,10 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
|
||||
|
||||
m_ui.actionViewGameProperties->setEnabled(running);
|
||||
|
||||
m_ui.actionToolsVideoCapture->setEnabled(running);
|
||||
if (!running && m_ui.actionToolsVideoCapture->isChecked())
|
||||
m_ui.actionToolsVideoCapture->setChecked(false);
|
||||
|
||||
m_game_list_widget->setDisabled(starting && !running);
|
||||
|
||||
if (!starting && !running)
|
||||
|
||||
@@ -169,6 +169,7 @@ private Q_SLOTS:
|
||||
void onSaveGSDumpActionTriggered();
|
||||
void onBlockDumpActionToggled(bool checked);
|
||||
void onShowAdvancedSettingsToggled(bool checked);
|
||||
void onToolsVideoCaptureToggled(bool checked);
|
||||
|
||||
// Input Recording
|
||||
void onInputRecNewActionTriggered();
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
<addaction name="actionToggleSoftwareRendering"/>
|
||||
<addaction name="actionReloadPatches"/>
|
||||
<addaction name="menuInput_Recording"/>
|
||||
<addaction name="actionToolsVideoCapture"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEnableSystemConsole"/>
|
||||
<addaction name="actionEnableFileLogging"/>
|
||||
@@ -882,6 +883,14 @@
|
||||
<string>Recording Viewer</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolsVideoCapture">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Video Capture</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
|
||||
@@ -847,6 +847,36 @@ void EmuThread::queueSnapshot(quint32 gsdump_frames)
|
||||
GetMTGS().RunOnGSThread([gsdump_frames]() { GSQueueSnapshot(std::string(), gsdump_frames); });
|
||||
}
|
||||
|
||||
void EmuThread::beginCapture(const QString& path)
|
||||
{
|
||||
if (!isOnEmuThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "beginCapture", Qt::QueuedConnection, Q_ARG(const QString&, path));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
GetMTGS().RunOnGSThread([path = path.toStdString()]() {
|
||||
GSBeginCapture(std::move(path));
|
||||
});
|
||||
}
|
||||
|
||||
void EmuThread::endCapture()
|
||||
{
|
||||
if (!isOnEmuThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "endCapture", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
GetMTGS().RunOnGSThread(&GSEndCapture);
|
||||
}
|
||||
|
||||
void EmuThread::updateDisplay()
|
||||
{
|
||||
pxAssertRel(!isOnEmuThread(), "Not on emu thread");
|
||||
|
||||
@@ -112,6 +112,8 @@ public Q_SLOTS:
|
||||
void enumerateVibrationMotors();
|
||||
void runOnCPUThread(const std::function<void()>& func);
|
||||
void queueSnapshot(quint32 gsdump_frames);
|
||||
void beginCapture(const QString& path);
|
||||
void endCapture();
|
||||
|
||||
Q_SIGNALS:
|
||||
bool messageConfirmed(const QString& title, const QString& message);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "pcsx2/HostSettings.h"
|
||||
#include "pcsx2/GS/GS.h"
|
||||
#include "pcsx2/GS/GSCapture.h"
|
||||
#include "pcsx2/GS/GSUtil.h"
|
||||
|
||||
#ifdef ENABLE_VULKAN
|
||||
@@ -314,7 +315,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
if (!m_dialog->isPerGameSettings())
|
||||
{
|
||||
// Only allow disabling readbacks for per-game settings, it's too dangerous.
|
||||
m_ui.advancedDebugFormLayout->removeRow(2);
|
||||
m_ui.advancedOptionsFormLayout->removeRow(0);
|
||||
m_ui.gsDownloadMode = nullptr;
|
||||
|
||||
// Remove texture offset and skipdraw range for global settings.
|
||||
@@ -327,6 +328,23 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
}
|
||||
#endif
|
||||
|
||||
// Capture settings
|
||||
{
|
||||
for (const char** container = Pcsx2Config::GSOptions::VideoCaptureContainers; *container; container++)
|
||||
{
|
||||
const QString name(QString::fromUtf8(*container));
|
||||
m_ui.videoCaptureContainer->addItem(name.toUpper(), name);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.videoCaptureContainer, "EmuCore/GS", "VideoCaptureContainer");
|
||||
connect(m_ui.videoCaptureContainer, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onVideoCaptureContainerChanged);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||
sif, m_ui.videoCaptureBitrate, "EmuCore/GS", "VideoCaptureBitrate", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_BITRATE);
|
||||
|
||||
onVideoCaptureContainerChanged();
|
||||
}
|
||||
|
||||
// Display tab
|
||||
{
|
||||
|
||||
@@ -705,6 +723,26 @@ void GraphicsSettingsWidget::onShadeBoostChanged()
|
||||
m_ui.shadeBoostSaturation->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onVideoCaptureContainerChanged()
|
||||
{
|
||||
const std::string container(
|
||||
m_dialog->getEffectiveStringValue("EmuCore/GS", "VideoCaptureContainer", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_CONTAINER));
|
||||
|
||||
m_ui.videoCaptureCodec->disconnect();
|
||||
m_ui.videoCaptureCodec->clear();
|
||||
const std::vector<std::pair<std::string, std::string>> vcapture_formats(GSCapture::GetVideoCodecList(container.c_str()));
|
||||
m_ui.videoCaptureCodec->addItem(tr("Default"), QString());
|
||||
for (const auto& [format, name] : vcapture_formats)
|
||||
{
|
||||
const QString qformat(QString::fromStdString(format));
|
||||
const QString qname(QString::fromStdString(name));
|
||||
m_ui.videoCaptureCodec->addItem(QStringLiteral("%1 [%2]").arg(qformat).arg(qname), qformat);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(
|
||||
m_dialog->getSettingsInterface(), m_ui.videoCaptureCodec, "EmuCore/GS", "VideoCaptureCodec");
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onGpuPaletteConversionChanged(int state)
|
||||
{
|
||||
const bool enabled = state == Qt::CheckState::PartiallyChecked ? Host::GetBaseBoolSettingValue("EmuCore/GS", "paltex", false) : state;
|
||||
|
||||
@@ -44,6 +44,7 @@ private Q_SLOTS:
|
||||
void onGpuPaletteConversionChanged(int state);
|
||||
void onFullscreenModeChanged(int index);
|
||||
void onShadeBoostChanged();
|
||||
void onVideoCaptureContainerChanged();
|
||||
|
||||
private:
|
||||
GSRendererType getEffectiveRenderer() const;
|
||||
|
||||
@@ -1629,11 +1629,135 @@
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<widget class="QGroupBox" name="advancedOptions">
|
||||
<property name="title">
|
||||
<string>Debug Options</string>
|
||||
<string>Advanced Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="advancedDebugFormLayout">
|
||||
<layout class="QFormLayout" name="advancedOptionsFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_42">
|
||||
<property name="text">
|
||||
<string>Hardware Download Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="gsDownloadMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Accurate (Recommended)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disable Readbacks (Synchronize GS Thread)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unsynchronized (Non-Deterministic)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Ignore Transfers)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_34">
|
||||
<property name="text">
|
||||
<string>GS Dump Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="gsDumpCompression">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Uncompressed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>LZMA (xz)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Zstandard (zst)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useBlitSwapChain">
|
||||
<property name="text">
|
||||
<string>Use Blit Swap Chain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="skipPresentingDuplicateFrames">
|
||||
<property name="text">
|
||||
<string>Skip Presenting Duplicate Frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_45">
|
||||
<property name="text">
|
||||
<string>Video Capture Codec:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0,0,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="videoCaptureCodec"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="videoCaptureContainer"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_46">
|
||||
<property name="text">
|
||||
<string>Bitrate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="videoCaptureBitrate">
|
||||
<property name="suffix">
|
||||
<string> kbps</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="debuggingOptions">
|
||||
<property name="title">
|
||||
<string>Debugging Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="debuggingOptionsFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
@@ -1686,102 +1810,31 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="skipPresentingDuplicateFrames">
|
||||
<property name="text">
|
||||
<string>Skip Presenting Duplicate Frames</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useBlitSwapChain">
|
||||
<property name="text">
|
||||
<string>Use Blit Swap Chain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="disableFramebufferFetch">
|
||||
<property name="text">
|
||||
<string>Disable Framebuffer Fetch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="useDebugDevice">
|
||||
<property name="text">
|
||||
<string>Use Debug Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="disableDualSource">
|
||||
<property name="text">
|
||||
<string>Disable Dual Source Blending</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="disableFramebufferFetch">
|
||||
<property name="text">
|
||||
<string>Disable Framebuffer Fetch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_34">
|
||||
<property name="text">
|
||||
<string>GS Dump Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="gsDumpCompression">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Uncompressed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>LZMA (xz)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Zstandard (zst)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_42">
|
||||
<property name="text">
|
||||
<string>Hardware Download Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="gsDownloadMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Accurate (Recommended)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disable Readbacks (Synchronize GS Thread)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unsynchronized (Non-Deterministic)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Ignore Transfers)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -757,8 +757,6 @@ set(pcsx2GSHeaders
|
||||
GS/Renderers/SW/GSTextureCacheSW.h
|
||||
GS/Renderers/SW/GSTextureSW.h
|
||||
GS/Renderers/SW/GSVertexSW.h
|
||||
GS/Window/GSCaptureDlg.h
|
||||
GS/Window/GSDialog.h
|
||||
GS/Window/GSSetting.h
|
||||
)
|
||||
|
||||
@@ -884,8 +882,6 @@ if(WIN32)
|
||||
GS/Renderers/DX11/GSTextureFX11.cpp
|
||||
GS/Renderers/DX12/GSDevice12.cpp
|
||||
GS/Renderers/DX12/GSTexture12.cpp
|
||||
GS/Window/GSCaptureDlg.cpp
|
||||
GS/Window/GSDialog.cpp
|
||||
)
|
||||
list(APPEND pcsx2GSHeaders
|
||||
GS/Renderers/DX11/D3D.h
|
||||
@@ -1704,6 +1700,7 @@ target_include_directories(PCSX2_FLAGS INTERFACE
|
||||
${CMAKE_BINARY_DIR}/common/include/
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/jpgd/"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/xbyak/"
|
||||
"${FFMPEG_INCLUDE_DIRS}"
|
||||
)
|
||||
|
||||
if(COMMAND target_precompile_headers)
|
||||
|
||||
@@ -598,12 +598,16 @@ struct Pcsx2Config
|
||||
{
|
||||
static const char* AspectRatioNames[];
|
||||
static const char* FMVAspectRatioSwitchNames[];
|
||||
static const char* VideoCaptureContainers[];
|
||||
|
||||
static const char* GetRendererName(GSRendererType type);
|
||||
|
||||
static constexpr float DEFAULT_FRAME_RATE_NTSC = 59.94f;
|
||||
static constexpr float DEFAULT_FRAME_RATE_PAL = 50.00f;
|
||||
|
||||
static constexpr u32 DEFAULT_VIDEO_CAPTURE_BITRATE = 6000;
|
||||
static const char* DEFAULT_VIDEO_CAPTURE_CONTAINER;
|
||||
|
||||
union
|
||||
{
|
||||
u64 bitset;
|
||||
@@ -738,6 +742,10 @@ struct Pcsx2Config
|
||||
GSScreenshotFormat ScreenshotFormat{GSScreenshotFormat::PNG};
|
||||
int ScreenshotQuality{50};
|
||||
|
||||
std::string VideoCaptureContainer{DEFAULT_VIDEO_CAPTURE_CONTAINER};
|
||||
std::string VideoCaptureCodec;
|
||||
int VideoCaptureBitrate{DEFAULT_VIDEO_CAPTURE_BITRATE};
|
||||
|
||||
std::string Adapter;
|
||||
|
||||
GSOptions();
|
||||
|
||||
+32
-46
@@ -24,6 +24,7 @@
|
||||
#endif
|
||||
|
||||
#include "GS.h"
|
||||
#include "GSCapture.h"
|
||||
#include "GSGL.h"
|
||||
#include "GSUtil.h"
|
||||
#include "GSExtra.h"
|
||||
@@ -545,6 +546,20 @@ void GSStopGSDump()
|
||||
g_gs_renderer->StopGSDump();
|
||||
}
|
||||
|
||||
bool GSBeginCapture(std::string filename)
|
||||
{
|
||||
if (g_gs_renderer)
|
||||
return g_gs_renderer->BeginCapture(std::move(filename));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void GSEndCapture()
|
||||
{
|
||||
if (g_gs_renderer)
|
||||
g_gs_renderer->EndCapture();
|
||||
}
|
||||
|
||||
void GSPresentCurrentFrame()
|
||||
{
|
||||
g_gs_renderer->PresentCurrentFrame();
|
||||
@@ -613,6 +628,8 @@ void GSconfigure()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int GStest()
|
||||
{
|
||||
if (!GSUtil::CheckSSE())
|
||||
@@ -621,52 +638,6 @@ int GStest()
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pt(const char* str)
|
||||
{
|
||||
struct tm* current;
|
||||
time_t now;
|
||||
|
||||
time(&now);
|
||||
current = localtime(&now);
|
||||
|
||||
printf("%02i:%02i:%02i%s", current->tm_hour, current->tm_min, current->tm_sec, str);
|
||||
}
|
||||
|
||||
bool GSsetupRecording(std::string& filename)
|
||||
{
|
||||
if (g_gs_renderer == NULL)
|
||||
{
|
||||
printf("GS: no s_gs for recording\n");
|
||||
return false;
|
||||
}
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
if (!theApp.GetConfigB("capture_enabled"))
|
||||
{
|
||||
printf("GS: Recording is disabled\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
printf("GS: Recording start command\n");
|
||||
if (g_gs_renderer->BeginCapture(filename))
|
||||
{
|
||||
pt(" - Capture started\n");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pt(" - Capture cancelled\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GSendRecording()
|
||||
{
|
||||
printf("GS: Recording end command\n");
|
||||
g_gs_renderer->EndCapture();
|
||||
pt(" - Capture ended\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void GSsetGameCRC(u32 crc, int options)
|
||||
{
|
||||
g_gs_renderer->SetGameCRC(crc, options);
|
||||
@@ -1588,6 +1559,21 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
|
||||
});
|
||||
}
|
||||
}},
|
||||
{"ToggleVideoCapture", "Graphics", "Toggle Video Capture", [](s32 pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
GetMTGS().RunOnGSThread([]() {
|
||||
if (GSCapture::IsCapturing())
|
||||
{
|
||||
g_gs_renderer->EndCapture();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filename(fmt::format("{}.{}", GSGetBaseSnapshotFilename(), GSConfig.VideoCaptureContainer));
|
||||
g_gs_renderer->BeginCapture(std::move(filename));
|
||||
});
|
||||
}
|
||||
}},
|
||||
{"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](s32 pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
|
||||
+3
-2
@@ -70,16 +70,17 @@ void GSgifTransfer2(u8* mem, u32 size);
|
||||
void GSgifTransfer3(u8* mem, u32 size);
|
||||
void GSvsync(u32 field, bool registers_written);
|
||||
int GSfreeze(FreezeAction mode, freezeData* data);
|
||||
std::string GSGetBaseSnapshotFilename();
|
||||
void GSQueueSnapshot(const std::string& path, u32 gsdump_frames = 0);
|
||||
void GSStopGSDump();
|
||||
bool GSBeginCapture(std::string filename);
|
||||
void GSEndCapture();
|
||||
void GSPresentCurrentFrame();
|
||||
void GSThrottlePresentation();
|
||||
#ifndef PCSX2_CORE
|
||||
void GSkeyEvent(const HostKeyEvent& e);
|
||||
void GSconfigure();
|
||||
int GStest();
|
||||
bool GSsetupRecording(std::string& filename);
|
||||
void GSendRecording();
|
||||
#endif
|
||||
void GSsetGameCRC(u32 crc, int options);
|
||||
|
||||
|
||||
+446
-511
File diff suppressed because it is too large
Load Diff
+10
-34
@@ -14,44 +14,20 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "GSVector.h"
|
||||
#include "GSPng.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Window/GSCaptureDlg.h"
|
||||
#include <wil/com.h>
|
||||
#endif
|
||||
|
||||
class GSCapture
|
||||
namespace GSCapture
|
||||
{
|
||||
std::recursive_mutex m_lock;
|
||||
bool m_capturing;
|
||||
GSVector2i m_size;
|
||||
std::string m_out_dir;
|
||||
int m_threads;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
wil::com_ptr_failfast<IGraphBuilder> m_graph;
|
||||
wil::com_ptr_failfast<IBaseFilter> m_src;
|
||||
|
||||
#elif defined(__unix__)
|
||||
|
||||
u64 m_frame;
|
||||
std::vector<std::unique_ptr<GSPng::Worker>> m_workers;
|
||||
int m_compression_level;
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
GSCapture();
|
||||
virtual ~GSCapture();
|
||||
|
||||
bool BeginCapture(float fps, GSVector2i recommendedResolution, float aspect, std::string& filename);
|
||||
bool BeginCapture(float fps, GSVector2i recommendedResolution, float aspect, std::string filename);
|
||||
bool DeliverFrame(const void* bits, int pitch, bool rgba);
|
||||
bool EndCapture();
|
||||
|
||||
bool IsCapturing() { return m_capturing; }
|
||||
GSVector2i GetSize() { return m_size; }
|
||||
};
|
||||
bool IsCapturing();
|
||||
GSVector2i GetSize();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> GetVideoCodecList(const char* container);
|
||||
}; // namespace GSCapture
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GSRenderer.h"
|
||||
#include "GS/GSCapture.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "Host.h"
|
||||
#include "HostDisplay.h"
|
||||
@@ -95,6 +96,7 @@ void GSRenderer::Reset(bool hardware_reset)
|
||||
|
||||
void GSRenderer::Destroy()
|
||||
{
|
||||
GSCapture::EndCapture();
|
||||
}
|
||||
|
||||
bool GSRenderer::Merge(int field)
|
||||
@@ -824,13 +826,12 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
// capture
|
||||
if (m_capture.IsCapturing())
|
||||
if (GSCapture::IsCapturing())
|
||||
{
|
||||
if (GSTexture* current = g_gs_device->GetCurrent())
|
||||
{
|
||||
GSVector2i size = m_capture.GetSize();
|
||||
GSVector2i size = GSCapture::GetSize();
|
||||
|
||||
bool res;
|
||||
GSTexture::GSMap m;
|
||||
@@ -841,12 +842,11 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
|
||||
if (res)
|
||||
{
|
||||
m_capture.DeliverFrame(m.bits, m.pitch, !g_gs_device->IsRBSwapped());
|
||||
GSCapture::DeliverFrame(m.bits, m.pitch, !g_gs_device->IsRBSwapped());
|
||||
g_gs_device->DownloadTextureComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
||||
@@ -861,49 +861,7 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_snapshot = "";
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
{
|
||||
Path::SanitizeFileName(&name);
|
||||
if (name.length() > 219)
|
||||
name.resize(219);
|
||||
m_snapshot += name;
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
Path::SanitizeFileName(&serial);
|
||||
m_snapshot += '_';
|
||||
m_snapshot += serial;
|
||||
}
|
||||
|
||||
time_t cur_time = time(nullptr);
|
||||
char local_time[16];
|
||||
|
||||
if (strftime(local_time, sizeof(local_time), "%Y%m%d%H%M%S", localtime(&cur_time)))
|
||||
{
|
||||
static time_t prev_snap;
|
||||
// The variable 'n' is used for labelling the screenshots when multiple screenshots are taken in
|
||||
// a single second, we'll start using this variable for naming when a second screenshot request is detected
|
||||
// at the same time as the first one. Hence, we're initially setting this counter to 2 to imply that
|
||||
// the captured image is the 2nd image captured at this specific time.
|
||||
static int n = 2;
|
||||
|
||||
m_snapshot += '_';
|
||||
|
||||
if (cur_time == prev_snap)
|
||||
m_snapshot += fmt::format("{0}_({1})", local_time, n++);
|
||||
else
|
||||
{
|
||||
n = 2;
|
||||
m_snapshot += fmt::format("{}", local_time);
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// prepend snapshots directory
|
||||
m_snapshot = Path::Combine(EmuFolders::Snapshots, m_snapshot);
|
||||
m_snapshot = GSGetBaseSnapshotFilename();
|
||||
}
|
||||
|
||||
// this is really gross, but wx we get the snapshot request after shift...
|
||||
@@ -912,6 +870,53 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GSGetBaseSnapshotFilename()
|
||||
{
|
||||
std::string filename;
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
{
|
||||
Path::SanitizeFileName(&name);
|
||||
if (name.length() > 219)
|
||||
name.resize(219);
|
||||
filename += name;
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
Path::SanitizeFileName(&serial);
|
||||
filename += '_';
|
||||
filename += serial;
|
||||
}
|
||||
|
||||
time_t cur_time = time(nullptr);
|
||||
char local_time[16];
|
||||
|
||||
if (strftime(local_time, sizeof(local_time), "%Y%m%d%H%M%S", localtime(&cur_time)))
|
||||
{
|
||||
static time_t prev_snap;
|
||||
// The variable 'n' is used for labelling the screenshots when multiple screenshots are taken in
|
||||
// a single second, we'll start using this variable for naming when a second screenshot request is detected
|
||||
// at the same time as the first one. Hence, we're initially setting this counter to 2 to imply that
|
||||
// the captured image is the 2nd image captured at this specific time.
|
||||
static int n = 2;
|
||||
|
||||
filename += '_';
|
||||
|
||||
if (cur_time == prev_snap)
|
||||
filename += fmt::format("{0}_({1})", local_time, n++);
|
||||
else
|
||||
{
|
||||
n = 2;
|
||||
filename += fmt::format("{}", local_time);
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// prepend snapshots directory
|
||||
return Path::Combine(EmuFolders::Snapshots, filename);
|
||||
}
|
||||
|
||||
void GSRenderer::StopGSDump()
|
||||
{
|
||||
m_snapshot = {};
|
||||
@@ -962,18 +967,20 @@ void GSTranslateWindowToDisplayCoordinates(float window_x, float window_y, float
|
||||
*display_y = rel_y / draw_height;
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
|
||||
bool GSRenderer::BeginCapture(std::string& filename)
|
||||
bool GSRenderer::BeginCapture(std::string filename)
|
||||
{
|
||||
return m_capture.BeginCapture(GetTvRefreshRate(), GetInternalResolution(), GetCurrentAspectRatioFloat(GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets)), filename);
|
||||
return GSCapture::BeginCapture(GetTvRefreshRate(), GetInternalResolution(),
|
||||
GetCurrentAspectRatioFloat(GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets)),
|
||||
std::move(filename));
|
||||
}
|
||||
|
||||
void GSRenderer::EndCapture()
|
||||
{
|
||||
m_capture.EndCapture();
|
||||
GSCapture::EndCapture();
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
|
||||
void GSRenderer::KeyEvent(const HostKeyEvent& e)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "GS/GSState.h"
|
||||
#include "GS/GSCapture.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
#include <mutex>
|
||||
@@ -33,7 +33,6 @@ private:
|
||||
u64 m_shader_time_start = 0;
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
GSCapture m_capture;
|
||||
std::mutex m_snapshot_mutex;
|
||||
bool m_shift_key = false;
|
||||
bool m_control_key = false;
|
||||
@@ -73,9 +72,9 @@ public:
|
||||
void StopGSDump();
|
||||
void PresentCurrentFrame();
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
bool BeginCapture(std::string& filename);
|
||||
bool BeginCapture(std::string filename);
|
||||
void EndCapture();
|
||||
#ifndef PCSX2_CORE
|
||||
void KeyEvent(const HostKeyEvent& e);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2021 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GS.h"
|
||||
#include "GSCaptureDlg.h"
|
||||
#include "GS/GSExtra.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include <commdlg.h>
|
||||
|
||||
// Ideally this belongs in WIL, but CAUUID is used by a *single* COM function in WinAPI.
|
||||
// That's presumably why it's omitted and is unlikely to make it to upstream WIL.
|
||||
static void __stdcall CloseCAUUID(_Inout_ CAUUID* cauuid) WI_NOEXCEPT
|
||||
{
|
||||
::CoTaskMemFree(cauuid->pElems);
|
||||
}
|
||||
|
||||
using unique_cauuid = wil::unique_struct<CAUUID, decltype(&::CloseCAUUID), ::CloseCAUUID>;
|
||||
using unique_olestr = wil::unique_any<LPOLESTR, decltype(&::CoTaskMemFree), ::CoTaskMemFree>;
|
||||
|
||||
template<typename Func>
|
||||
static void EnumSysDev(const GUID& clsid, Func&& f)
|
||||
{
|
||||
if (auto devEnum = wil::CoCreateInstanceNoThrow<ICreateDevEnum>(CLSID_SystemDeviceEnum))
|
||||
{
|
||||
wil::com_ptr_nothrow<IEnumMoniker> classEnum;
|
||||
if (SUCCEEDED(devEnum->CreateClassEnumerator(clsid, classEnum.put(), 0)))
|
||||
{
|
||||
wil::com_ptr_nothrow<IMoniker> moniker;
|
||||
while (classEnum->Next(1, moniker.put(), nullptr) == S_OK)
|
||||
{
|
||||
std::forward<Func>(f)(moniker.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSCaptureDlg::InvalidFile()
|
||||
{
|
||||
const std::wstring message = L"GS couldn't open file for capturing: " + m_filename + L".\nCapture aborted.";
|
||||
MessageBox(GetActiveWindow(), message.c_str(), L"GS System Message", MB_OK | MB_SETFOREGROUND);
|
||||
}
|
||||
|
||||
GSCaptureDlg::GSCaptureDlg()
|
||||
: GSDialog(IDD_CAPTURE)
|
||||
{
|
||||
m_width = theApp.GetConfigI("CaptureWidth");
|
||||
m_height = theApp.GetConfigI("CaptureHeight");
|
||||
m_filename = StringUtil::UTF8StringToWideString(theApp.GetConfigS("CaptureFileName"));
|
||||
}
|
||||
|
||||
int GSCaptureDlg::GetSelCodec(Codec& c)
|
||||
{
|
||||
INT_PTR data = 0;
|
||||
|
||||
if (ComboBoxGetSelData(IDC_CODECS, data))
|
||||
{
|
||||
if (data == 0)
|
||||
return 2;
|
||||
|
||||
c = *(Codec*)data;
|
||||
|
||||
if (!c.filter)
|
||||
{
|
||||
c.moniker->BindToObject(NULL, NULL, IID_PPV_ARGS(c.filter.put()));
|
||||
|
||||
if (!c.filter)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GSCaptureDlg::UpdateConfigureButton()
|
||||
{
|
||||
Codec c;
|
||||
bool enable = false;
|
||||
|
||||
if (GetSelCodec(c) != 1)
|
||||
{
|
||||
EnableWindow(GetDlgItem(m_hWnd, IDC_CONFIGURE), false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto pSPP = c.filter.try_query<ISpecifyPropertyPages>())
|
||||
{
|
||||
unique_cauuid caGUID;
|
||||
enable = SUCCEEDED(pSPP->GetPages(&caGUID));
|
||||
}
|
||||
else if (auto pAMVfWCD = c.filter.try_query<IAMVfwCompressDialogs>())
|
||||
{
|
||||
enable = pAMVfWCD->ShowDialog(VfwCompressDialog_QueryConfig, nullptr) == S_OK;
|
||||
}
|
||||
EnableWindow(GetDlgItem(m_hWnd, IDC_CONFIGURE), enable);
|
||||
}
|
||||
|
||||
void GSCaptureDlg::OnInit()
|
||||
{
|
||||
__super::OnInit();
|
||||
|
||||
SetTextAsInt(IDC_WIDTH, m_width);
|
||||
SetTextAsInt(IDC_HEIGHT, m_height);
|
||||
SetText(IDC_FILENAME, m_filename.c_str());
|
||||
|
||||
m_codecs.clear();
|
||||
|
||||
const std::wstring selected = StringUtil::UTF8StringToWideString(theApp.GetConfigS("CaptureVideoCodecDisplayName"));
|
||||
|
||||
ComboBoxAppend(IDC_CODECS, "Uncompressed", 0, true);
|
||||
ComboBoxAppend(IDC_COLORSPACE, "YUY2", 0, true);
|
||||
ComboBoxAppend(IDC_COLORSPACE, "RGB32", 1, false);
|
||||
|
||||
CoInitialize(0); // this is obviously wrong here, each thread should call this on start, and where is CoUninitalize?
|
||||
|
||||
EnumSysDev(CLSID_VideoCompressorCategory, [&](IMoniker* moniker)
|
||||
{
|
||||
Codec c;
|
||||
|
||||
c.moniker = moniker;
|
||||
|
||||
unique_olestr str;
|
||||
if (FAILED(moniker->GetDisplayName(NULL, NULL, str.put())))
|
||||
return;
|
||||
|
||||
std::wstring prefix;
|
||||
if (wcsstr(str.get(), L"@device:dmo:")) prefix = L"(DMO) ";
|
||||
else if (wcsstr(str.get(), L"@device:sw:")) prefix = L"(DS) ";
|
||||
else if (wcsstr(str.get(), L"@device:cm:")) prefix = L"(VfW) ";
|
||||
|
||||
|
||||
c.DisplayName = str.get();
|
||||
|
||||
wil::com_ptr_nothrow<IPropertyBag> pPB;
|
||||
if (FAILED(moniker->BindToStorage(0, 0, IID_PPV_ARGS(pPB.put()))))
|
||||
return;
|
||||
|
||||
wil::unique_variant var;
|
||||
if (FAILED(pPB->Read(L"FriendlyName", &var, nullptr)))
|
||||
return;
|
||||
|
||||
c.FriendlyName = prefix + var.bstrVal;
|
||||
|
||||
m_codecs.push_back(c);
|
||||
|
||||
ComboBoxAppend(IDC_CODECS, c.FriendlyName.c_str(), (LPARAM)&m_codecs.back(), c.DisplayName == selected);
|
||||
});
|
||||
UpdateConfigureButton();
|
||||
}
|
||||
|
||||
bool GSCaptureDlg::OnCommand(HWND hWnd, UINT id, UINT code)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case IDC_FILENAME:
|
||||
{
|
||||
EnableWindow(GetDlgItem(m_hWnd, IDOK), GetText(IDC_FILENAME).length() != 0);
|
||||
return false;
|
||||
}
|
||||
case IDC_BROWSE:
|
||||
{
|
||||
if (code == BN_CLICKED)
|
||||
{
|
||||
wchar_t buff[MAX_PATH] = {0};
|
||||
|
||||
OPENFILENAME ofn;
|
||||
memset(&ofn, 0, sizeof(ofn));
|
||||
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.hwndOwner = m_hWnd;
|
||||
ofn.lpstrFile = buff;
|
||||
ofn.nMaxFile = std::size(buff);
|
||||
ofn.lpstrFilter = L"Avi files (*.avi)\0*.avi\0";
|
||||
ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
|
||||
|
||||
wcscpy(ofn.lpstrFile, m_filename.c_str());
|
||||
if (GetSaveFileName(&ofn))
|
||||
{
|
||||
m_filename = ofn.lpstrFile;
|
||||
SetText(IDC_FILENAME, m_filename.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IDC_CONFIGURE:
|
||||
{
|
||||
if (code == BN_CLICKED)
|
||||
{
|
||||
Codec c;
|
||||
if (GetSelCodec(c) == 1)
|
||||
{
|
||||
if (auto pSPP = c.filter.try_query<ISpecifyPropertyPages>())
|
||||
{
|
||||
unique_cauuid caGUID;
|
||||
if (SUCCEEDED(pSPP->GetPages(&caGUID)))
|
||||
{
|
||||
auto lpUnk = pSPP.try_query<IUnknown>();
|
||||
OleCreatePropertyFrame(m_hWnd, 0, 0, c.FriendlyName.c_str(), 1, lpUnk.addressof(), caGUID.cElems, caGUID.pElems, 0, 0, NULL);
|
||||
}
|
||||
}
|
||||
else if (auto pAMVfWCD = c.filter.try_query<IAMVfwCompressDialogs>())
|
||||
{
|
||||
if (pAMVfWCD->ShowDialog(VfwCompressDialog_QueryConfig, NULL) == S_OK)
|
||||
pAMVfWCD->ShowDialog(VfwCompressDialog_Config, m_hWnd);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IDC_CODECS:
|
||||
{
|
||||
UpdateConfigureButton();
|
||||
break;
|
||||
}
|
||||
case IDOK:
|
||||
{
|
||||
m_width = GetTextAsInt(IDC_WIDTH);
|
||||
m_height = GetTextAsInt(IDC_HEIGHT);
|
||||
m_filename = GetText(IDC_FILENAME);
|
||||
ComboBoxGetSelData(IDC_COLORSPACE, m_colorspace);
|
||||
|
||||
Codec c;
|
||||
int ris = GetSelCodec(c);
|
||||
if (ris == 0)
|
||||
return false;
|
||||
|
||||
m_enc = c.filter;
|
||||
|
||||
theApp.SetConfig("CaptureWidth", m_width);
|
||||
theApp.SetConfig("CaptureHeight", m_height);
|
||||
theApp.SetConfig("CaptureFileName", StringUtil::WideStringToUTF8String(m_filename).c_str());
|
||||
|
||||
if (ris != 2)
|
||||
theApp.SetConfig("CaptureVideoCodecDisplayName", StringUtil::WideStringToUTF8String(c.DisplayName).c_str());
|
||||
else
|
||||
theApp.SetConfig("CaptureVideoCodecDisplayName", "");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return __super::OnCommand(hWnd, id, code);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2021 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 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 PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GSDialog.h"
|
||||
#include "GS/resource.h"
|
||||
#include <streams.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
class GSCaptureDlg : public GSDialog
|
||||
{
|
||||
struct Codec
|
||||
{
|
||||
wil::com_ptr_nothrow<IMoniker> moniker;
|
||||
wil::com_ptr_nothrow<IBaseFilter> filter;
|
||||
std::wstring FriendlyName;
|
||||
std::wstring DisplayName;
|
||||
};
|
||||
|
||||
std::list<Codec> m_codecs;
|
||||
|
||||
int GetSelCodec(Codec& c);
|
||||
void UpdateConfigureButton();
|
||||
|
||||
protected:
|
||||
void OnInit();
|
||||
bool OnCommand(HWND hWnd, UINT id, UINT code);
|
||||
|
||||
public:
|
||||
GSCaptureDlg();
|
||||
void InvalidFile();
|
||||
|
||||
int m_width;
|
||||
int m_height;
|
||||
std::wstring m_filename;
|
||||
INT_PTR m_colorspace;
|
||||
wil::com_ptr_nothrow<IBaseFilter> m_enc;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user