mirror of https://github.com/PCSX2/pcsx2.git
GS: Add video dumping via ffmpeg
This commit is contained in:
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();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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,7 +861,18 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
|||
}
|
||||
else
|
||||
{
|
||||
m_snapshot = "";
|
||||
m_snapshot = GSGetBaseSnapshotFilename();
|
||||
}
|
||||
|
||||
// this is really gross, but wx we get the snapshot request after shift...
|
||||
#ifdef PCSX2_CORE
|
||||
m_dump_frames = gsdump_frames;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GSGetBaseSnapshotFilename()
|
||||
{
|
||||
std::string filename;
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
|
@ -869,13 +880,13 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
|||
Path::SanitizeFileName(&name);
|
||||
if (name.length() > 219)
|
||||
name.resize(219);
|
||||
m_snapshot += name;
|
||||
filename += name;
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
Path::SanitizeFileName(&serial);
|
||||
m_snapshot += '_';
|
||||
m_snapshot += serial;
|
||||
filename += '_';
|
||||
filename += serial;
|
||||
}
|
||||
|
||||
time_t cur_time = time(nullptr);
|
||||
|
@ -890,26 +901,20 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
|||
// the captured image is the 2nd image captured at this specific time.
|
||||
static int n = 2;
|
||||
|
||||
m_snapshot += '_';
|
||||
filename += '_';
|
||||
|
||||
if (cur_time == prev_snap)
|
||||
m_snapshot += fmt::format("{0}_({1})", local_time, n++);
|
||||
filename += fmt::format("{0}_({1})", local_time, n++);
|
||||
else
|
||||
{
|
||||
n = 2;
|
||||
m_snapshot += fmt::format("{}", local_time);
|
||||
filename += fmt::format("{}", local_time);
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// prepend snapshots directory
|
||||
m_snapshot = Path::Combine(EmuFolders::Snapshots, m_snapshot);
|
||||
}
|
||||
|
||||
// this is really gross, but wx we get the snapshot request after shift...
|
||||
#ifdef PCSX2_CORE
|
||||
m_dump_frames = gsdump_frames;
|
||||
#endif
|
||||
return Path::Combine(EmuFolders::Snapshots, filename);
|
||||
}
|
||||
|
||||
void GSRenderer::StopGSDump()
|
||||
|
@ -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;
|
||||
};
|
|
@ -1,350 +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 <Shlwapi.h>
|
||||
#include <CommCtrl.h>
|
||||
#include <commdlg.h>
|
||||
#include "GS.h"
|
||||
#include "GSDialog.h"
|
||||
#include "GS/GSVector.h"
|
||||
|
||||
GSDialog::GSDialog(UINT id)
|
||||
: m_id(id)
|
||||
, m_hWnd(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
INT_PTR GSDialog::DoModal()
|
||||
{
|
||||
return DialogBoxParam(GetModuleHandle(nullptr), MAKEINTRESOURCE(m_id), GetActiveWindow(), DialogProc, (LPARAM)this);
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK GSDialog::DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
GSDialog* dlg = NULL;
|
||||
|
||||
if (message == WM_INITDIALOG)
|
||||
{
|
||||
dlg = (GSDialog*)lParam;
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)dlg);
|
||||
dlg->m_hWnd = hWnd;
|
||||
|
||||
MONITORINFO mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &mi);
|
||||
|
||||
GSVector4i r;
|
||||
GetWindowRect(hWnd, reinterpret_cast<LPRECT>(&r));
|
||||
|
||||
int x = (mi.rcWork.left + mi.rcWork.right - r.width()) / 2;
|
||||
int y = (mi.rcWork.top + mi.rcWork.bottom - r.height()) / 2;
|
||||
|
||||
SetWindowPos(hWnd, NULL, x, y, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
dlg->OnInit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
dlg = (GSDialog*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
||||
|
||||
if (message == WM_NOTIFY)
|
||||
{
|
||||
if (((LPNMHDR)lParam)->code == TTN_GETDISPINFO)
|
||||
{
|
||||
LPNMTTDISPINFO pInfo = (LPNMTTDISPINFO)lParam;
|
||||
const UINT id = (UINT)GetWindowLongPtr((HWND)pInfo->hdr.idFrom, GWL_ID);
|
||||
|
||||
// lpszText is used only if hinst is NULL. Seems to be NULL already,
|
||||
// but it can't hurt to explicitly set it.
|
||||
pInfo->hinst = NULL;
|
||||
pInfo->lpszText = (LPTSTR)dialog_message(id);
|
||||
SendMessage(pInfo->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 500);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return dlg != NULL ? dlg->OnMessage(message, wParam, lParam) : FALSE;
|
||||
}
|
||||
|
||||
// Tooltips will only show if the TOOLINFO cbSize <= the struct size. If it's
|
||||
// smaller some functionality might be disabled. So let's try and use the
|
||||
// correct size.
|
||||
UINT GSDialog::GetTooltipStructSize()
|
||||
{
|
||||
DLLGETVERSIONPROC dllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(GetModuleHandle(L"ComCtl32.dll"), "DllGetVersion");
|
||||
if (dllGetVersion)
|
||||
{
|
||||
DLLVERSIONINFO2 dllversion = {0};
|
||||
dllversion.info1.cbSize = sizeof(DLLVERSIONINFO2);
|
||||
|
||||
if (dllGetVersion((DLLVERSIONINFO*)&dllversion) == S_OK)
|
||||
{
|
||||
// Minor, then major version.
|
||||
DWORD version = MAKELONG(dllversion.info1.dwMinorVersion, dllversion.info1.dwMajorVersion);
|
||||
DWORD tooltip_v3 = MAKELONG(0, 6);
|
||||
if (version >= tooltip_v3)
|
||||
return TTTOOLINFOA_V3_SIZE;
|
||||
}
|
||||
}
|
||||
// Should be fine for XP and onwards, comctl versions >= 4.7 should at least
|
||||
// be this size.
|
||||
return TTTOOLINFOA_V2_SIZE;
|
||||
}
|
||||
|
||||
bool GSDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return message == WM_COMMAND ? OnCommand((HWND)lParam, LOWORD(wParam), HIWORD(wParam)) : false;
|
||||
}
|
||||
|
||||
bool GSDialog::OnCommand(HWND hWnd, UINT id, UINT code)
|
||||
{
|
||||
if (id == IDOK || id == IDCANCEL)
|
||||
{
|
||||
EndDialog(m_hWnd, id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring GSDialog::GetText(UINT id)
|
||||
{
|
||||
std::wstring s;
|
||||
|
||||
wchar_t* buff = NULL;
|
||||
|
||||
for (int size = 256, limit = 65536; size < limit; size <<= 1)
|
||||
{
|
||||
buff = new wchar_t[size];
|
||||
|
||||
if (GetDlgItemText(m_hWnd, id, buff, size))
|
||||
{
|
||||
s = buff;
|
||||
size = limit;
|
||||
}
|
||||
|
||||
delete[] buff;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int GSDialog::GetTextAsInt(UINT id)
|
||||
{
|
||||
return _wtoi(GetText(id).c_str());
|
||||
}
|
||||
|
||||
void GSDialog::SetText(UINT id, const wchar_t* str)
|
||||
{
|
||||
SetDlgItemText(m_hWnd, id, str);
|
||||
}
|
||||
|
||||
void GSDialog::SetTextAsInt(UINT id, int i)
|
||||
{
|
||||
wchar_t buff[32] = {0};
|
||||
_itow(i, buff, 10);
|
||||
SetText(id, buff);
|
||||
}
|
||||
|
||||
void GSDialog::ComboBoxInit(UINT id, const std::vector<GSSetting>& settings, int32_t selectionValue, int32_t maxValue)
|
||||
{
|
||||
if (settings.empty())
|
||||
return;
|
||||
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
|
||||
SendMessage(hWnd, CB_RESETCONTENT, 0, 0);
|
||||
|
||||
const auto is_present = [=](const GSSetting& x) { return selectionValue == x.value; };
|
||||
if (std::none_of(settings.begin(), settings.end(), is_present))
|
||||
selectionValue = settings.front().value;
|
||||
|
||||
for (size_t i = 0; i < settings.size(); i++)
|
||||
{
|
||||
const GSSetting& s = settings[i];
|
||||
|
||||
if (s.value <= maxValue)
|
||||
{
|
||||
std::string str(s.name);
|
||||
|
||||
if (!s.note.empty())
|
||||
{
|
||||
str = str + " (" + s.note + ")";
|
||||
}
|
||||
|
||||
ComboBoxAppend(id, str.c_str(), (LPARAM)s.value, s.value == selectionValue);
|
||||
}
|
||||
}
|
||||
|
||||
ComboBoxFixDroppedWidth(id);
|
||||
}
|
||||
|
||||
int GSDialog::ComboBoxAppend(UINT id, const char* str, LPARAM data, bool select)
|
||||
{
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
int item = (int)SendMessageA(hWnd, CB_ADDSTRING, 0, (LPARAM)str);
|
||||
return BoxAppend(hWnd, item, data, select);
|
||||
}
|
||||
|
||||
int GSDialog::ComboBoxAppend(UINT id, const wchar_t* str, LPARAM data, bool select)
|
||||
{
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
int item = (int)SendMessageW(hWnd, CB_ADDSTRING, 0, (LPARAM)str);
|
||||
return BoxAppend(hWnd, item, data, select);
|
||||
}
|
||||
|
||||
int GSDialog::BoxAppend(HWND& hWnd, int item, LPARAM data, bool select)
|
||||
{
|
||||
SendMessage(hWnd, CB_SETITEMDATA, item, (LPARAM)data);
|
||||
|
||||
if (select)
|
||||
{
|
||||
SendMessage(hWnd, CB_SETCURSEL, item, 0);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
bool GSDialog::ComboBoxGetSelData(UINT id, INT_PTR& data)
|
||||
{
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
|
||||
const int item = (int)SendMessage(hWnd, CB_GETCURSEL, 0, 0);
|
||||
|
||||
if (item >= 0)
|
||||
{
|
||||
data = SendMessage(hWnd, CB_GETITEMDATA, item, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GSDialog::ComboBoxFixDroppedWidth(UINT id)
|
||||
{
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
|
||||
int count = (int)SendMessage(hWnd, CB_GETCOUNT, 0, 0);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
HDC hDC = GetDC(hWnd);
|
||||
|
||||
SelectObject(hDC, (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0));
|
||||
|
||||
int width = (int)SendMessage(hWnd, CB_GETDROPPEDWIDTH, 0, 0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int len = (int)SendMessage(hWnd, CB_GETLBTEXTLEN, i, 0);
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
wchar_t* buff = new wchar_t[len + 1];
|
||||
|
||||
SendMessage(hWnd, CB_GETLBTEXT, i, (LPARAM)buff);
|
||||
|
||||
SIZE size;
|
||||
|
||||
if (GetTextExtentPoint32(hDC, buff, wcslen(buff), &size))
|
||||
{
|
||||
size.cx += 10;
|
||||
|
||||
if (size.cx > width)
|
||||
width = size.cx;
|
||||
}
|
||||
|
||||
delete[] buff;
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseDC(hWnd, hDC);
|
||||
|
||||
if (width > 0)
|
||||
{
|
||||
SendMessage(hWnd, CB_SETDROPPEDWIDTH, width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSDialog::OpenFileDialog(UINT id, const wchar_t* title)
|
||||
{
|
||||
wchar_t filename[512];
|
||||
OPENFILENAME ofn = {0};
|
||||
ofn.lStructSize = sizeof(OPENFILENAME);
|
||||
ofn.hwndOwner = m_hWnd;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
|
||||
ofn.lpstrFile = filename;
|
||||
ofn.lpstrFile[0] = 0;
|
||||
ofn.nMaxFile = 512;
|
||||
ofn.lpstrTitle = title;
|
||||
|
||||
// GetOpenFileName changes the current directory, so we need to save and
|
||||
// restore the current directory or everything using relative paths will
|
||||
// break.
|
||||
wchar_t current_directory[512];
|
||||
GetCurrentDirectory(512, current_directory);
|
||||
|
||||
if (GetOpenFileName(&ofn))
|
||||
SendMessage(GetDlgItem(m_hWnd, id), WM_SETTEXT, 0, (LPARAM)filename);
|
||||
|
||||
SetCurrentDirectory(current_directory);
|
||||
}
|
||||
|
||||
void GSDialog::AddTooltip(UINT id)
|
||||
{
|
||||
static UINT tooltipStructSize = GetTooltipStructSize();
|
||||
bool hasTooltip;
|
||||
|
||||
dialog_message(id, &hasTooltip);
|
||||
if (!hasTooltip)
|
||||
return;
|
||||
|
||||
HWND hWnd = GetDlgItem(m_hWnd, id);
|
||||
if (hWnd == NULL)
|
||||
return;
|
||||
|
||||
// TTS_NOPREFIX allows tabs and '&' to be used.
|
||||
HWND hwndTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
|
||||
TTS_ALWAYSTIP | TTS_NOPREFIX,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
m_hWnd, NULL, GetModuleHandle(nullptr), NULL);
|
||||
if (hwndTip == NULL)
|
||||
return;
|
||||
|
||||
TOOLINFO toolInfo = {0};
|
||||
toolInfo.cbSize = tooltipStructSize;
|
||||
toolInfo.hwnd = m_hWnd;
|
||||
toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
|
||||
toolInfo.uId = (UINT_PTR)hWnd;
|
||||
// Can't directly add the tooltip string - it doesn't work for long messages
|
||||
toolInfo.lpszText = LPSTR_TEXTCALLBACK;
|
||||
SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
|
||||
// 32.767s is the max show time.
|
||||
SendMessage(hwndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
|
||||
}
|
||||
|
||||
void GSDialog::InitCommonControls()
|
||||
{
|
||||
INITCOMMONCONTROLSEX icex;
|
||||
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
|
||||
icex.dwICC = ICC_TAB_CLASSES;
|
||||
|
||||
InitCommonControlsEx(&icex);
|
||||
}
|
|
@ -1,63 +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 "GSSetting.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
||||
class GSDialog
|
||||
{
|
||||
int m_id;
|
||||
|
||||
static INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
static UINT GetTooltipStructSize();
|
||||
|
||||
protected:
|
||||
HWND m_hWnd;
|
||||
|
||||
virtual void OnInit() {}
|
||||
virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
virtual bool OnCommand(HWND hWnd, UINT id, UINT code);
|
||||
|
||||
public:
|
||||
GSDialog(UINT id);
|
||||
virtual ~GSDialog() {}
|
||||
|
||||
int GetId() const { return m_id; }
|
||||
|
||||
INT_PTR DoModal();
|
||||
|
||||
std::wstring GetText(UINT id);
|
||||
int GetTextAsInt(UINT id);
|
||||
|
||||
void SetText(UINT id, const wchar_t* str);
|
||||
void SetTextAsInt(UINT id, int i);
|
||||
|
||||
void ComboBoxInit(UINT id, const std::vector<GSSetting>& settings, int32_t selectionValue, int32_t maxValue = INT32_MAX);
|
||||
int ComboBoxAppend(UINT id, const char* str, LPARAM data = 0, bool select = false);
|
||||
int ComboBoxAppend(UINT id, const wchar_t* str, LPARAM data = 0, bool select = false);
|
||||
bool ComboBoxGetSelData(UINT id, INT_PTR& data);
|
||||
void ComboBoxFixDroppedWidth(UINT id);
|
||||
|
||||
void OpenFileDialog(UINT id, const wchar_t* title);
|
||||
|
||||
void AddTooltip(UINT id);
|
||||
|
||||
static void InitCommonControls();
|
||||
|
||||
private:
|
||||
int BoxAppend(HWND& hWnd, int item, LPARAM data = 0, bool select = false);
|
||||
};
|
|
@ -337,6 +337,13 @@ const char* Pcsx2Config::GSOptions::FMVAspectRatioSwitchNames[] = {
|
|||
"16:9",
|
||||
nullptr};
|
||||
|
||||
const char* Pcsx2Config::GSOptions::VideoCaptureContainers[] = {
|
||||
"mp4",
|
||||
"mkv",
|
||||
"avi",
|
||||
nullptr};
|
||||
const char* Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_CONTAINER = "mp4";
|
||||
|
||||
const char* Pcsx2Config::GSOptions::GetRendererName(GSRendererType type)
|
||||
{
|
||||
switch (type)
|
||||
|
@ -489,6 +496,10 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
|
|||
OpEqu(ScreenshotFormat) &&
|
||||
OpEqu(ScreenshotQuality) &&
|
||||
|
||||
OpEqu(VideoCaptureContainer) &&
|
||||
OpEqu(VideoCaptureCodec) &&
|
||||
OpEqu(VideoCaptureBitrate) &&
|
||||
|
||||
OpEqu(Adapter));
|
||||
}
|
||||
|
||||
|
@ -685,6 +696,10 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
|||
GSSettingIntEx(SaveN, "saven");
|
||||
GSSettingIntEx(SaveL, "savel");
|
||||
|
||||
GSSettingStringEx(VideoCaptureContainer, "VideoCaptureContainer");
|
||||
GSSettingStringEx(VideoCaptureCodec, "VideoCaptureCodec");
|
||||
GSSettingIntEx(VideoCaptureBitrate, "VideoCaptureBitrate");
|
||||
|
||||
GSSettingString(Adapter);
|
||||
|
||||
#undef GSSettingInt
|
||||
|
|
|
@ -278,50 +278,6 @@ namespace Implementations
|
|||
#endif
|
||||
}
|
||||
|
||||
void Sys_RecordingToggle()
|
||||
{
|
||||
ScopedCoreThreadPause paused_core;
|
||||
paused_core.AllowResume();
|
||||
|
||||
if (wxGetApp().HasGUI())
|
||||
{
|
||||
sMainFrame.VideoCaptureToggle();
|
||||
return;
|
||||
}
|
||||
|
||||
GetMTGS().WaitGS(); // make sure GS is in sync with the audio stream when we start.
|
||||
g_Pcsx2Recording = !g_Pcsx2Recording;
|
||||
if (g_Pcsx2Recording)
|
||||
{
|
||||
// start recording
|
||||
|
||||
// make the recording setup dialog[s] pseudo-modal also for the main PCSX2 window
|
||||
// (the GS dialog is already properly modal for the GS window)
|
||||
if (GetMainFramePtr() && GetMainFramePtr()->IsEnabled())
|
||||
GetMainFramePtr()->Disable();
|
||||
|
||||
// GSsetupRecording can be aborted/canceled by the user. Don't go on to record the audio if that happens.
|
||||
std::string filename;
|
||||
if (GSsetupRecording(filename))
|
||||
{
|
||||
if (g_Conf->AudioCapture.EnableAudio && !SPU2setupRecording(&filename))
|
||||
{
|
||||
GSendRecording();
|
||||
g_Pcsx2Recording = false;
|
||||
}
|
||||
}
|
||||
else // recording dialog canceled by the user. align our state
|
||||
g_Pcsx2Recording = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// stop recording
|
||||
GSendRecording();
|
||||
if (g_Conf->AudioCapture.EnableAudio)
|
||||
SPU2endRecording();
|
||||
}
|
||||
}
|
||||
|
||||
void Cpu_DumpRegisters()
|
||||
{
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
|
@ -567,14 +523,6 @@ static const GlobalCommandDescriptor CommandDeclarations[] =
|
|||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"Sys_RecordingToggle",
|
||||
Implementations::Sys_RecordingToggle,
|
||||
NULL,
|
||||
NULL,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"FullscreenToggle",
|
||||
Implementations::FullscreenToggle,
|
||||
|
|
|
@ -857,53 +857,6 @@ void MainEmuFrame::Menu_Capture_Video_IncludeAudio_Click(wxCommandEvent& event)
|
|||
|
||||
void MainEmuFrame::VideoCaptureToggle()
|
||||
{
|
||||
GetMTGS().WaitGS(); // make sure GS is in sync with the audio stream when we start.
|
||||
m_capturingVideo = !m_capturingVideo;
|
||||
if (m_capturingVideo)
|
||||
{
|
||||
// start recording
|
||||
|
||||
// make the recording setup dialog[s] pseudo-modal also for the main PCSX2 window
|
||||
// (the GS dialog is already properly modal for the GS window)
|
||||
bool needsMainFrameEnable = false;
|
||||
if (IsEnabled())
|
||||
{
|
||||
needsMainFrameEnable = true;
|
||||
Disable();
|
||||
}
|
||||
|
||||
// GSsetupRecording can be aborted/canceled by the user. Don't go on to record the audio if that happens
|
||||
std::string filename;
|
||||
if (GSsetupRecording(filename))
|
||||
{
|
||||
if (!g_Conf->AudioCapture.EnableAudio || SPU2setupRecording(&filename))
|
||||
{
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_Record, false);
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_Stop, true);
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_IncludeAudio, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSendRecording();
|
||||
m_capturingVideo = false;
|
||||
}
|
||||
}
|
||||
else // recording dialog canceled by the user. align our state
|
||||
m_capturingVideo = false;
|
||||
|
||||
if (needsMainFrameEnable)
|
||||
Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// stop recording
|
||||
GSendRecording();
|
||||
if (g_Conf->AudioCapture.EnableAudio)
|
||||
SPU2endRecording();
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_Record, true);
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_Stop, false);
|
||||
m_submenuVideoCapture.Enable(MenuId_Capture_Video_IncludeAudio, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click(wxCommandEvent& event)
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\ffmpeg\include</AdditionalIncludeDirectories>
|
||||
<ExceptionHandling>Async</ExceptionHandling>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
|
@ -384,7 +385,6 @@
|
|||
<ClCompile Include="GS\GSAlignedClass.cpp" />
|
||||
<ClCompile Include="GS\GSBlock.cpp" />
|
||||
<ClCompile Include="GS\GSCapture.cpp" />
|
||||
<ClCompile Include="GS\Window\GSCaptureDlg.cpp" />
|
||||
<ClCompile Include="GS\GSClut.cpp" />
|
||||
<ClCompile Include="GS\GSCodeBuffer.cpp" />
|
||||
<ClCompile Include="GS\GSCrc.cpp" />
|
||||
|
@ -392,7 +392,6 @@
|
|||
<ClCompile Include="GS\Renderers\DX11\GSDevice11.cpp" />
|
||||
<ClCompile Include="GS\Renderers\Null\GSDeviceNull.cpp" />
|
||||
<ClCompile Include="GS\Renderers\OpenGL\GSDeviceOGL.cpp" />
|
||||
<ClCompile Include="GS\Window\GSDialog.cpp" />
|
||||
<ClCompile Include="GS\Renderers\Common\GSDirtyRect.cpp" />
|
||||
<ClCompile Include="GS\GSDrawingContext.cpp" />
|
||||
<ClCompile Include="GS\Renderers\SW\GSDrawScanline.cpp" />
|
||||
|
@ -780,7 +779,6 @@
|
|||
<ClInclude Include="GS\GSAlignedClass.h" />
|
||||
<ClInclude Include="GS\GSBlock.h" />
|
||||
<ClInclude Include="GS\GSCapture.h" />
|
||||
<ClInclude Include="GS\Window\GSCaptureDlg.h" />
|
||||
<ClInclude Include="GS\GSClut.h" />
|
||||
<ClInclude Include="GS\GSCodeBuffer.h" />
|
||||
<ClInclude Include="GS\GSCrc.h" />
|
||||
|
@ -788,7 +786,6 @@
|
|||
<ClInclude Include="GS\Renderers\DX11\GSDevice11.h" />
|
||||
<ClInclude Include="GS\Renderers\Null\GSDeviceNull.h" />
|
||||
<ClInclude Include="GS\Renderers\OpenGL\GSDeviceOGL.h" />
|
||||
<ClInclude Include="GS\Window\GSDialog.h" />
|
||||
<ClInclude Include="GS\Renderers\Common\GSDirtyRect.h" />
|
||||
<ClInclude Include="GS\GSDrawingContext.h" />
|
||||
<ClInclude Include="GS\GSDrawingEnvironment.h" />
|
||||
|
|
|
@ -1472,12 +1472,6 @@
|
|||
<ClCompile Include="GS\GSCapture.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GS\Window\GSCaptureDlg.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GS\Window\GSDialog.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GS\Window\GSSetting.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
|
@ -2530,12 +2524,6 @@
|
|||
<ClInclude Include="GS\GSCapture.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GS\Window\GSCaptureDlg.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GS\Window\GSDialog.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GS\Window\GSSetting.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rcheevos\rcheevos\include;$(SolutionDir)3rdparty\rainterface</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\discord-rpc\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zydis\include;$(SolutionDir)3rdparty\zydis\dependencies\zycore\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\ffmpeg\include</AdditionalIncludeDirectories>
|
||||
<ExceptionHandling>Async</ExceptionHandling>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
|
@ -282,7 +283,6 @@
|
|||
<ClCompile Include="GS\GSAlignedClass.cpp" />
|
||||
<ClCompile Include="GS\GSBlock.cpp" />
|
||||
<ClCompile Include="GS\GSCapture.cpp" />
|
||||
<ClCompile Include="GS\Window\GSCaptureDlg.cpp" />
|
||||
<ClCompile Include="GS\GSClut.cpp" />
|
||||
<ClCompile Include="GS\GSCodeBuffer.cpp" />
|
||||
<ClCompile Include="GS\GSCrc.cpp" />
|
||||
|
@ -626,7 +626,6 @@
|
|||
<ClInclude Include="GS\GSAlignedClass.h" />
|
||||
<ClInclude Include="GS\GSBlock.h" />
|
||||
<ClInclude Include="GS\GSCapture.h" />
|
||||
<ClInclude Include="GS\Window\GSCaptureDlg.h" />
|
||||
<ClInclude Include="GS\GSClut.h" />
|
||||
<ClInclude Include="GS\GSCodeBuffer.h" />
|
||||
<ClInclude Include="GS\GSCrc.h" />
|
||||
|
|
|
@ -1175,9 +1175,6 @@
|
|||
<ClCompile Include="GS\GSCapture.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GS\Window\GSCaptureDlg.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GS\Window\GSSetting.cpp">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClCompile>
|
||||
|
@ -2126,9 +2123,6 @@
|
|||
<ClInclude Include="GS\GSCapture.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GS\Window\GSCaptureDlg.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GS\Window\GSSetting.h">
|
||||
<Filter>System\Ps2\GS\Window</Filter>
|
||||
</ClInclude>
|
||||
|
|
Loading…
Reference in New Issue