early-access version 1962
This commit is contained in:
parent
beced16dd0
commit
c7e2b0984f
6 changed files with 88 additions and 149 deletions
|
@ -1,7 +1,7 @@
|
||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 1961.
|
This is the source code for early-access 1962.
|
||||||
|
|
||||||
## Legal Notice
|
## Legal Notice
|
||||||
|
|
||||||
|
|
|
@ -16,36 +16,14 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
void AVFrameDeleter(AVFrame* ptr) {
|
|
||||||
av_frame_free(&ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
|
||||||
: gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
|
|
||||||
vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
|
|
||||||
|
|
||||||
Codec::~Codec() {
|
|
||||||
if (!initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free libav memory
|
|
||||||
AVFrame* av_frame;
|
|
||||||
avcodec_send_packet(av_codec_ctx, nullptr);
|
|
||||||
av_frame = av_frame_alloc();
|
|
||||||
avcodec_receive_frame(av_codec_ctx, av_frame);
|
|
||||||
avcodec_flush_buffers(av_codec_ctx);
|
|
||||||
|
|
||||||
av_frame_unref(av_frame);
|
|
||||||
av_free(av_frame);
|
|
||||||
avcodec_close(av_codec_ctx);
|
|
||||||
av_buffer_unref(&av_hw_device);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
|
|
||||||
#if defined(LIBVA_FOUND)
|
#if defined(LIBVA_FOUND)
|
||||||
|
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
|
||||||
namespace {
|
namespace {
|
||||||
|
constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
|
||||||
|
"i915",
|
||||||
|
"amdgpu",
|
||||||
|
};
|
||||||
|
|
||||||
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
|
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
|
||||||
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
|
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
|
||||||
if (*p == AV_PIX_FMT_VAAPI) {
|
if (*p == AV_PIX_FMT_VAAPI) {
|
||||||
|
@ -56,15 +34,10 @@ AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
|
||||||
return *pix_fmts;
|
return *pix_fmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
|
|
||||||
"i915",
|
|
||||||
"amdgpu",
|
|
||||||
};
|
|
||||||
|
|
||||||
bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
|
bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
|
||||||
AVDictionary* hwdevice_options = nullptr;
|
AVDictionary* hwdevice_options = nullptr;
|
||||||
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
|
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
|
||||||
for (auto driver : VAAPI_DRIVERS) {
|
for (const auto& driver : VAAPI_DRIVERS) {
|
||||||
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
|
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
|
||||||
const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
|
const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
|
||||||
nullptr, hwdevice_options, 0);
|
nullptr, hwdevice_options, 0);
|
||||||
|
@ -82,7 +55,30 @@ bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void AVFrameDeleter(AVFrame* ptr) {
|
||||||
|
av_frame_free(&ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
||||||
|
: gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
|
||||||
|
vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
|
||||||
|
|
||||||
|
Codec::~Codec() {
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Free libav memory
|
||||||
|
avcodec_send_packet(av_codec_ctx, nullptr);
|
||||||
|
AVFrame* av_frame = av_frame_alloc();
|
||||||
|
avcodec_receive_frame(av_codec_ctx, av_frame);
|
||||||
|
avcodec_flush_buffers(av_codec_ctx);
|
||||||
|
av_frame_free(&av_frame);
|
||||||
|
avcodec_close(av_codec_ctx);
|
||||||
|
av_buffer_unref(&av_hw_device);
|
||||||
|
}
|
||||||
|
|
||||||
void Codec::InitializeHwdec() {
|
void Codec::InitializeHwdec() {
|
||||||
|
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
|
||||||
#if defined(LIBVA_FOUND)
|
#if defined(LIBVA_FOUND)
|
||||||
if (CreateVaapiHwdevice(&av_hw_device)) {
|
if (CreateVaapiHwdevice(&av_hw_device)) {
|
||||||
const auto hw_device_ctx = av_buffer_ref(av_hw_device);
|
const auto hw_device_ctx = av_buffer_ref(av_hw_device);
|
||||||
|
@ -92,65 +88,7 @@ void Codec::InitializeHwdec() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
// TODO more GPU accelerated decoders
|
||||||
// TODO NVDEC, but integrated GPU should be used first to avoid PCI
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] AVFrame* Codec::DecodeImpl(RawFrame& raw_frame) {
|
|
||||||
AVPacket packet{};
|
|
||||||
av_init_packet(&packet);
|
|
||||||
packet.data = raw_frame.frame_data.data();
|
|
||||||
packet.size = static_cast<s32>(raw_frame.frame_data.size());
|
|
||||||
|
|
||||||
if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
|
|
||||||
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only receive/store visible frames
|
|
||||||
if (raw_frame.vp9_hidden_frame) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
AVFrame* hw_frame = av_frame_alloc();
|
|
||||||
AVFrame* sw_frame = hw_frame;
|
|
||||||
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
|
|
||||||
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
|
|
||||||
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
|
|
||||||
av_frame_free(&hw_frame);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hw_frame->width || !hw_frame->height) {
|
|
||||||
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
|
|
||||||
av_frame_free(&hw_frame);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(LIBVA_FOUND)
|
|
||||||
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
|
|
||||||
if (hw_frame->format == AV_PIX_FMT_VAAPI) {
|
|
||||||
sw_frame = av_frame_alloc();
|
|
||||||
ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
|
|
||||||
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
|
|
||||||
// because Intel drivers crash unless using AV_PIX_FMT_NV12
|
|
||||||
sw_frame->format = AV_PIX_FMT_NV12;
|
|
||||||
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
|
|
||||||
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
|
|
||||||
av_frame_free(&hw_frame);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
switch (sw_frame->format) {
|
|
||||||
case AV_PIX_FMT_YUV420P:
|
|
||||||
case AV_PIX_FMT_NV12:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
|
|
||||||
av_frame_free(&sw_frame);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sw_frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Codec::Initialize() {
|
void Codec::Initialize() {
|
||||||
|
@ -169,12 +107,10 @@ void Codec::Initialize() {
|
||||||
av_codec = avcodec_find_decoder(codec);
|
av_codec = avcodec_find_decoder(codec);
|
||||||
av_codec_ctx = avcodec_alloc_context3(av_codec);
|
av_codec_ctx = avcodec_alloc_context3(av_codec);
|
||||||
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
|
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
|
||||||
|
|
||||||
InitializeHwdec();
|
InitializeHwdec();
|
||||||
if (!av_codec_ctx->hw_device_ctx) {
|
if (!av_codec_ctx->hw_device_ctx) {
|
||||||
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
|
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
|
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
|
||||||
if (av_error < 0) {
|
if (av_error < 0) {
|
||||||
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
|
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
|
||||||
|
@ -182,7 +118,6 @@ void Codec::Initialize() {
|
||||||
av_buffer_unref(&av_hw_device);
|
av_buffer_unref(&av_hw_device);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,30 +133,63 @@ void Codec::Decode() {
|
||||||
if (is_first_frame) {
|
if (is_first_frame) {
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool vp9_hidden_frame = false;
|
bool vp9_hidden_frame = false;
|
||||||
std::vector<u8> frame_data;
|
std::vector<u8> frame_data;
|
||||||
|
|
||||||
if (current_codec == NvdecCommon::VideoCodec::H264) {
|
if (current_codec == NvdecCommon::VideoCodec::H264) {
|
||||||
frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
|
frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
|
||||||
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
|
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
|
||||||
frame_data = vp9_decoder->ComposeFrameHeader(state);
|
frame_data = vp9_decoder->ComposeFrameHeader(state);
|
||||||
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
||||||
}
|
}
|
||||||
|
AVPacket packet{};
|
||||||
RawFrame raw_frame{
|
av_init_packet(&packet);
|
||||||
.frame_data = frame_data,
|
packet.data = frame_data.data();
|
||||||
.vp9_hidden_frame = vp9_hidden_frame,
|
packet.size = static_cast<s32>(frame_data.size());
|
||||||
};
|
if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
|
||||||
// TODO async
|
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
|
||||||
AVFrame* sw_frame = DecodeImpl(raw_frame);
|
return;
|
||||||
if (sw_frame) {
|
}
|
||||||
if (av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); av_frames.size() > 10) {
|
// Only receive/store visible frames
|
||||||
|
if (vp9_hidden_frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AVFrame* hw_frame = av_frame_alloc();
|
||||||
|
AVFrame* sw_frame = hw_frame;
|
||||||
|
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
|
||||||
|
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
|
||||||
|
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
|
||||||
|
av_frame_free(&hw_frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hw_frame->width || !hw_frame->height) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
|
||||||
|
av_frame_free(&hw_frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if defined(LIBVA_FOUND)
|
||||||
|
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
|
||||||
|
if (hw_frame->format == AV_PIX_FMT_VAAPI) {
|
||||||
|
sw_frame = av_frame_alloc();
|
||||||
|
ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
|
||||||
|
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
|
||||||
|
// because Intel drivers crash unless using AV_PIX_FMT_NV12
|
||||||
|
sw_frame->format = AV_PIX_FMT_NV12;
|
||||||
|
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
|
||||||
|
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
|
||||||
|
av_frame_free(&hw_frame);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) {
|
||||||
|
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
|
||||||
|
av_frame_free(&sw_frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter});
|
||||||
|
if (av_frames.size() > 10) {
|
||||||
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
|
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
|
||||||
av_frames.pop();
|
av_frames.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
AVFramePtr Codec::GetCurrentFrame() {
|
AVFramePtr Codec::GetCurrentFrame() {
|
||||||
// Sometimes VIC will request more frames than have been decoded.
|
// Sometimes VIC will request more frames than have been decoded.
|
||||||
|
@ -229,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() {
|
||||||
if (av_frames.empty()) {
|
if (av_frames.empty()) {
|
||||||
return AVFramePtr{nullptr, AVFrameDeleter};
|
return AVFramePtr{nullptr, AVFrameDeleter};
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFramePtr frame = std::move(av_frames.front());
|
AVFramePtr frame = std::move(av_frames.front());
|
||||||
av_frames.pop();
|
av_frames.pop();
|
||||||
return frame;
|
return frame;
|
||||||
|
@ -255,5 +222,4 @@ std::string_view Codec::GetCurrentCodecName() const {
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
|
@ -54,13 +54,7 @@ public:
|
||||||
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct RawFrame {
|
|
||||||
std::vector<u8> frame_data;
|
|
||||||
bool vp9_hidden_frame;
|
|
||||||
};
|
|
||||||
|
|
||||||
void InitializeHwdec();
|
void InitializeHwdec();
|
||||||
AVFrame* DecodeImpl(RawFrame&);
|
|
||||||
|
|
||||||
bool initialized{};
|
bool initialized{};
|
||||||
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
|
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
|
||||||
|
|
|
@ -88,7 +88,9 @@ void Vic::Execute() {
|
||||||
scaler_height = frame->height;
|
scaler_height = frame->height;
|
||||||
}
|
}
|
||||||
// Get Converted frame
|
// Get Converted frame
|
||||||
const std::size_t linear_size = static_cast<u32>(frame->width * frame->height * 4);
|
const u32 width = static_cast<u32>(frame->width);
|
||||||
|
const u32 height = static_cast<u32>(frame->height);
|
||||||
|
const std::size_t linear_size = width * height * 4;
|
||||||
|
|
||||||
// Only allocate frame_buffer once per stream, as the size is not expected to change
|
// Only allocate frame_buffer once per stream, as the size is not expected to change
|
||||||
if (!converted_frame_buffer) {
|
if (!converted_frame_buffer) {
|
||||||
|
@ -105,8 +107,6 @@ void Vic::Execute() {
|
||||||
if (blk_kind != 0) {
|
if (blk_kind != 0) {
|
||||||
// swizzle pitch linear to block linear
|
// swizzle pitch linear to block linear
|
||||||
const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
|
const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
|
||||||
const u32 width = static_cast<u32>(frame->width);
|
|
||||||
const u32 height = static_cast<u32>(frame->height);
|
|
||||||
const auto size =
|
const auto size =
|
||||||
Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
|
Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
|
||||||
luma_buffer.resize(size);
|
luma_buffer.resize(size);
|
||||||
|
|
|
@ -55,7 +55,7 @@ void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
||||||
|
|
||||||
QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());
|
QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());
|
||||||
|
|
||||||
if (str.isNull() || str.isEmpty()) {
|
if (str.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Tool Assisted Speedrun tool</string>
|
<string>TAS</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0" colspan="1">
|
<item row="0" column="0" colspan="1">
|
||||||
<widget class="QLabel" name="label_1">
|
<widget class="QLabel" name="label_1">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>This tool reads controller input from a script files under the same format as TAS-nx files. For more delailed explanation please consult the FAQ on the yuzu website.</string>
|
<string>Reads controller input from scripts in the same format as TAS-nx scripts. For a more detailed explanation please consult the FAQ on the yuzu website.</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -35,34 +35,13 @@
|
||||||
<item row="1" column="0" colspan="1">
|
<item row="1" column="0" colspan="1">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>How to use it:</string>
|
<string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="1">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Ctrl + F5: Start or stop TAS bot</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0" colspan="1">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>Ctrl + F6: Reload file or restart bot from the beggining</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0" colspan="1">
|
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="text">
|
|
||||||
<string>Ctrl + F7: Start or stop recording input from player 1</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -73,7 +52,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>TAS Settings</string>
|
<string>Settings</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0" colspan="4">
|
<item row="0" column="0" colspan="4">
|
||||||
|
@ -117,7 +96,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>TAS Directories</string>
|
<string>Script Directory</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
|
|
Loading…
Reference in a new issue