Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ if(UNIX AND NOT APPLE AND BUILD_MINIMAL_MEDIA_BACKEND)
add_subdirectory(MinimalMediaBackend)
endif()

option(SHOTCUT_BUILD_TESTS "Build unit tests" OFF)
if(SHOTCUT_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

add_custom_target(codespell COMMAND
Expand Down
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ add_executable(shotcut WIN32 MACOSX_BUNDLE
docks/subtitlesdock.cpp docks/subtitlesdock.h
docks/timelinedock.cpp docks/timelinedock.h
FlatpakWrapperGenerator.cpp FlatpakWrapperGenerator.h
gpuinfo.cpp gpuinfo.h
htmlgenerator.h htmlgenerator.cpp
jobqueue.cpp jobqueue.h
jobs/abstractjob.cpp jobs/abstractjob.h
Expand Down Expand Up @@ -340,7 +341,7 @@ if(WIN32)
target_sources(shotcut PRIVATE windowstools.cpp windowstools.h)
target_sources(shotcut PRIVATE widgets/d3dvideowidget.h widgets/d3dvideowidget.cpp)
target_sources(shotcut PRIVATE widgets/openglvideowidget.h widgets/openglvideowidget.cpp)
target_link_libraries(shotcut PRIVATE d3d11 d3dcompiler ole32)
target_link_libraries(shotcut PRIVATE d3d11 d3dcompiler dxgi dxguid ole32)

# Runtime exception handler for debug only
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
Expand Down
21 changes: 12 additions & 9 deletions src/docks/encodedock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "dialogs/listselectiondialog.h"
#include "dialogs/multifileexportdialog.h"
#include "findanalysisfilterparser.h"
#include "gpuinfo.h"
#include "jobqueue.h"
#include "jobs/encodejob.h"
#include "mainwindow.h"
Expand Down Expand Up @@ -211,16 +212,18 @@ void EncodeDock::loadPresetFromProperties(Mlt::Properties &preset)
ui->metaLanguageLineEdit->clear();

if (ui->hwencodeCheckBox->isChecked()) {
// Prefer the hardware encoder family that matches the user-selected GPU vendor
// (NVIDIA -> *_nvenc, AMD -> *_amf, Intel -> *_qsv) so that selecting the
// discrete NVIDIA GPU drives export through NVENC even when an AMD (AMF) encoder
// also passed detection and happens to come first. 10-bit video skips H.264
// hardware encoders, which do not support it. See gpuinfo.cpp.
const bool is10bit = QString::fromLatin1(preset.get("pix_fmt")).contains("p10le");
for (const QString &hw : Settings.encodeHardware()) {
if ((vcodec == "libx264" && hw.startsWith("h264") && !is10bit)
|| (vcodec == "libx265" && hw.startsWith("hevc"))
|| (vcodec == "libvpx-vp9" && hw.startsWith("vp9"))
|| (vcodec == "libsvtav1" && hw.startsWith("av1"))) {
vcodec = hw;
break;
}
}
const QString chosen = preferredHardwareVcodec(Settings.encodeHardware(),
vcodec,
Settings.gpuAdapterVendorId(),
is10bit);
if (!chosen.isEmpty())
vcodec = chosen;
}

ui->disableAudioCheckbox->setChecked(preset.get_int("an"));
Expand Down
157 changes: 157 additions & 0 deletions src/gpuinfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (c) 2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "gpuinfo.h"

#include <QSet>
#include <QtGlobal>

// Platform-independent: pick the hardware encoder matching the GPU vendor, with a
// fallback to the first type-compatible encoder. Kept out of the Windows-only block
// so it builds and is unit-testable everywhere.
QString preferredHardwareVcodec(const QStringList &hardwareCodecs,
const QString &softwareVcodec,
uint vendorId,
bool is10bit)
{
auto matchesType = [&softwareVcodec, is10bit](const QString &hw) {
// No known H.264 hardware encoder supports 10-bit, so skip it in that case.
return (softwareVcodec == QLatin1String("libx264")
&& hw.startsWith(QLatin1String("h264")) && !is10bit)
|| (softwareVcodec == QLatin1String("libx265")
&& hw.startsWith(QLatin1String("hevc")))
|| (softwareVcodec == QLatin1String("libvpx-vp9")
&& hw.startsWith(QLatin1String("vp9")))
|| (softwareVcodec == QLatin1String("libsvtav1")
&& hw.startsWith(QLatin1String("av1")));
};
QString preferredSuffix;
switch (vendorId) {
case kGpuVendorNvidia:
preferredSuffix = QStringLiteral("_nvenc");
break;
case kGpuVendorAmd:
preferredSuffix = QStringLiteral("_amf");
break;
case kGpuVendorIntel:
preferredSuffix = QStringLiteral("_qsv");
break;
default:
break;
}
if (!preferredSuffix.isEmpty()) {
for (const QString &hw : hardwareCodecs) {
if (matchesType(hw) && hw.endsWith(preferredSuffix))
return hw;
}
}
for (const QString &hw : hardwareCodecs) {
if (matchesType(hw))
return hw;
}
return QString();
}

#ifdef Q_OS_WIN
#include <dxgi.h>

QList<GpuAdapterInfo> enumerateGpuAdapters()
{
QList<GpuAdapterInfo> result;
IDXGIFactory1 *factory = nullptr;
// IID_IDXGIFactory1 is provided by the dxguid import library (linked in CMake),
// which keeps this portable across MinGW/GCC and MSVC.
if (FAILED(CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast<void **>(&factory)))
|| !factory)
return result;

// Some drivers (notably AMD integrated graphics) enumerate the same physical GPU
// many times with identical hardware ids but different LUIDs. De-duplicate by a
// stable hardware key so each GPU appears once in the menu, while preserving the
// true DXGI adapter index (which is what QT_D3D_ADAPTER_INDEX expects).
QSet<QString> seen;
IDXGIAdapter1 *adapter = nullptr;
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) {
if (adapter) {
DXGI_ADAPTER_DESC1 desc;
if (SUCCEEDED(adapter->GetDesc1(&desc))
// Hide the Microsoft Basic Render Driver (software rasterizer).
&& !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) {
const QString key = QStringLiteral("%1:%2:%3:%4")
.arg(desc.VendorId)
.arg(desc.DeviceId)
.arg(desc.SubSysId)
.arg(desc.Revision);
if (!seen.contains(key)) {
seen.insert(key);
GpuAdapterInfo info;
info.index = static_cast<int>(i);
info.name = QString::fromWCharArray(desc.Description);
info.vendorId = desc.VendorId;
info.deviceId = desc.DeviceId;
result.append(info);
}
}
adapter->Release();
adapter = nullptr;
}
}
factory->Release();
return result;
}

int gpuAdapterIndexFor(uint vendorId, uint deviceId)
{
if (vendorId == 0)
return -1;
IDXGIFactory1 *factory = nullptr;
if (FAILED(CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast<void **>(&factory)))
|| !factory)
return -1;
int found = -1;
IDXGIAdapter1 *adapter = nullptr;
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) {
if (adapter) {
DXGI_ADAPTER_DESC1 desc;
if (found < 0 && SUCCEEDED(adapter->GetDesc1(&desc))
&& !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) && desc.VendorId == vendorId
&& desc.DeviceId == deviceId) {
found = static_cast<int>(i);
}
adapter->Release();
adapter = nullptr;
}
}
factory->Release();
return found;
}

#else // !Q_OS_WIN

QList<GpuAdapterInfo> enumerateGpuAdapters()
{
// Index-based GPU selection is currently only implemented for the Windows
// Direct3D RHI backend. Returning empty hides the selection UI elsewhere.
return QList<GpuAdapterInfo>();
}

int gpuAdapterIndexFor(uint, uint)
{
return -1;
}

#endif
63 changes: 63 additions & 0 deletions src/gpuinfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef GPUINFO_H
#define GPUINFO_H

#include <QList>
#include <QString>
#include <QStringList>

// PCI vendor ids of the common GPU vendors.
enum GpuVendorId {
kGpuVendorNvidia = 0x10DE,
kGpuVendorAmd = 0x1002,
kGpuVendorIntel = 0x8086,
};

struct GpuAdapterInfo
{
int index = -1; // current DXGI adapter index; matches QT_D3D_ADAPTER_INDEX
QString name; // human-readable description, e.g. "NVIDIA GeForce RTX 3090"
uint vendorId = 0; // PCI vendor id (see GpuVendorId)
uint deviceId = 0; // PCI device id; together with vendorId identifies the GPU
};

// Enumerate the physical GPU adapters usable by the renderer.
// On Windows this uses DXGI (the same enumeration order Qt's D3D RHI backend uses).
// Returns an empty list on platforms/backends where index-based selection is not
// supported, in which case the caller should hide the selection UI.
QList<GpuAdapterInfo> enumerateGpuAdapters();

// Resolve the CURRENT DXGI adapter index (as QT_D3D_ADAPTER_INDEX expects) of the GPU
// identified by vendorId+deviceId, or -1 if not found. Some drivers enumerate adapters
// in an unstable order across runs, so the index must be resolved live at startup from
// the GPU's stable identity rather than persisted from a previous session.
int gpuAdapterIndexFor(uint vendorId, uint deviceId);

// Choose the hardware video encoder for a given software codec, preferring the encoder
// family matching the selected GPU vendor (NVIDIA->*_nvenc, AMD->*_amf, Intel->*_qsv)
// and otherwise falling back to the first type-compatible encoder in hardwareCodecs.
// When is10bit is true, H.264 hardware encoders are skipped because they do not support
// 10-bit. Returns an empty string when no compatible hardware encoder is available.
// This is a pure, platform-independent function so it can be unit tested.
QString preferredHardwareVcodec(const QStringList &hardwareCodecs,
const QString &softwareVcodec,
uint vendorId,
bool is10bit);

#endif // GPUINFO_H
25 changes: 25 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ConsoleAppender.h"
#include "FileAppender.h"
#include "Logger.h"
#include "gpuinfo.h"
#include "mainwindow.h"
#include "settings.h"

Expand Down Expand Up @@ -432,6 +433,30 @@ int main(int argc, char **argv)

Application a(argc, argv);
int result = EXIT_SUCCESS;
#ifdef Q_OS_WIN
// Direct the user-selected physical GPU to be used by the Qt RHI (Direct3D)
// preview/UI via QT_D3D_ADAPTER_INDEX and by the FFmpeg hardware decoder via
// MLT_AVFORMAT_HWACCEL_DEVICE. These must be set before the RHI is initialized (the
// adapter index is read lazily when the first scene-graph window is created). In
// release builds the watchdog child process, and the melt export subprocess, inherit
// this environment. Values already set by the user take precedence. The adapter index
// is resolved live from the GPU's stable vendor+device identity because some drivers
// enumerate adapters in an unstable order across runs. See gpuinfo.cpp and Settings.
{
const uint gpuVendorId = Settings.gpuAdapterVendorId();
if (gpuVendorId != 0) {
const int gpuAdapterIndex = gpuAdapterIndexFor(gpuVendorId,
Settings.gpuAdapterDeviceId());
if (gpuAdapterIndex >= 0) {
const QByteArray index = QByteArray::number(gpuAdapterIndex);
if (!qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
::qputenv("QT_D3D_ADAPTER_INDEX", index);
if (!qEnvironmentVariableIsSet("MLT_AVFORMAT_HWACCEL_DEVICE"))
::qputenv("MLT_AVFORMAT_HWACCEL_DEVICE", index);
}
}
}
#endif
#ifdef QT_DEBUG
::qputenv(kWatchdogEnvVar, "1");
#endif
Expand Down
Loading