Merge branch 'dolphin-emu:master' into master

This commit is contained in:
Sandro Mocevic 2025-02-15 15:53:13 -05:00 committed by GitHub
commit 893d5136f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 192 additions and 86 deletions

@ -1 +1 @@
Subproject commit 05fe2cc910a68c9ba5dac07db46ef78573acee72
Subproject commit 39f924b810e561fd86b2558b6711ca68d4363f68

@ -1 +1 @@
Subproject commit 009ecd192c1289c7529bff248a16cfe896254816
Subproject commit 3bab6924988e5f19bf36586a496156cf72f70d9f

@ -1 +1 @@
Subproject commit 50b4d5389b6a06f86fb63a2848e1a7da6d9755ca
Subproject commit ebe2aa0cd80f5eb5cd8a605da604cacf72205f3b

@ -1 +1 @@
Subproject commit bbb27a5efb85b92a0486cf361a8635715a53f6ba
Subproject commit e626a72bc2321cd320e953a0ccf1584cad60f363

View file

@ -105,10 +105,10 @@
<string name="wii_sd_card_sync_description">Synchronizes the SD Card with the SD Sync Folder when starting and ending emulation.</string>
<string name="wii_sd_card_path">SD Card Path</string>
<string name="wii_sd_sync_folder">SD Sync Folder</string>
<string name="wii_sd_card_folder_to_file">Convert Folder to File Now</string>
<string name="wii_sd_card_folder_to_file_confirmation">You are about to convert the content of the SD sync folder into the SD card file. All current content of the file will be deleted. Are you sure you want to continue?</string>
<string name="wii_sd_card_file_to_folder">Convert File to Folder Now</string>
<string name="wii_sd_card_file_to_folder_confirmation">You are about to convert the content of the SD card file into the SD sync folder. All current content of the folder will be deleted. Are you sure you want to continue?</string>
<string name="wii_sd_card_folder_to_file">Pack SD Card Now</string>
<string name="wii_sd_card_folder_to_file_confirmation">You are about to pack the content of the SD sync folder into the SD card file. All current content of the file will be deleted. Are you sure you want to continue?</string>
<string name="wii_sd_card_file_to_folder">Unpack SD Card Now</string>
<string name="wii_sd_card_file_to_folder_confirmation">You are about to unpack the content of the SD card file into the SD sync folder. All current content of the folder will be deleted. Are you sure you want to continue?</string>
<string name="wii_converting">Converting…</string>
<string name="wii_convert_success">Conversion done.</string>
<string name="wii_convert_failure">Conversion failed.</string>

View file

@ -9,6 +9,9 @@
namespace Common
{
static constexpr auto SD_PACK_TEXT = "Pack SD Card Now";
static constexpr auto SD_UNPACK_TEXT = "Unpack SD Card Now";
bool SyncSDFolderToSDImage(const std::function<bool()>& cancelled, bool deterministic);
bool SyncSDImageToSDFolder(const std::function<bool()>& cancelled);

View file

@ -537,7 +537,8 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
PanicAlertFmtT(
"Failed to sync SD card with folder. All changes made this session will be "
"discarded on next boot if you do not manually re-issue a resync in Config > "
"Wii > SD Card Settings > Convert File to Folder Now!");
"Wii > SD Card Settings > {0}!",
Common::GetStringT(Common::SD_UNPACK_TEXT));
}
}
}};

View file

@ -652,17 +652,58 @@ void JitArm64::cmp(UGeckoInstruction inst)
SXTW(CR, gpr.R(b));
MVN(CR, CR);
}
else if (gpr.IsImm(b) && !gpr.GetImm(b))
else if (gpr.IsImm(b) && (gpr.GetImm(b) & 0xFFF) == gpr.GetImm(b))
{
SXTW(CR, gpr.R(a));
if (const u32 imm = gpr.GetImm(b); imm != 0)
SUB(CR, CR, imm);
}
else if (gpr.IsImm(b) && (gpr.GetImm(b) & 0xFFF000) == gpr.GetImm(b))
{
SXTW(CR, gpr.R(a));
SUB(CR, CR, gpr.GetImm(b) >> 12, true);
}
else if (gpr.IsImm(b) && (((~gpr.GetImm(b) + 1) & 0xFFF) == (~gpr.GetImm(b) + 1)))
{
SXTW(CR, gpr.R(a));
ADD(CR, CR, ~gpr.GetImm(b) + 1);
}
else if (gpr.IsImm(b) && (((~gpr.GetImm(b) + 1) & 0xFFF000) == (~gpr.GetImm(b) + 1)))
{
SXTW(CR, gpr.R(a));
ADD(CR, CR, (~gpr.GetImm(b) + 1) >> 12, true);
}
else
{
// If we're dealing with immediates, check their most significant bit to
// see if we can skip sign extension.
const auto should_sign_extend = [&](u32 reg) -> bool {
return !gpr.IsImm(reg) || (gpr.GetImm(reg) & (1U << 31));
};
bool sign_extend_a = should_sign_extend(a);
bool sign_extend_b = should_sign_extend(b);
ARM64Reg RA = gpr.R(a);
ARM64Reg RB = gpr.R(b);
SXTW(CR, RA);
SUB(CR, CR, RB, ArithOption(RB, ExtendSpecifier::SXTW));
if (sign_extend_a)
{
SXTW(CR, RA);
RA = CR;
}
else
{
RA = EncodeRegTo64(RA);
}
auto opt = ArithOption(RB, ExtendSpecifier::SXTW);
if (!sign_extend_b)
{
opt = ArithOption(CR, ShiftType::LSL, 0);
RB = EncodeRegTo64(RB);
}
SUB(CR, RA, RB, opt);
}
}
@ -687,9 +728,17 @@ void JitArm64::cmpl(UGeckoInstruction inst)
{
NEG(CR, EncodeRegTo64(gpr.R(b)));
}
else if (gpr.IsImm(b) && !gpr.GetImm(b))
else if (gpr.IsImm(b) && (gpr.GetImm(b) & 0xFFF) == gpr.GetImm(b))
{
MOV(EncodeRegTo32(CR), gpr.R(a));
const u32 imm = gpr.GetImm(b);
if (imm == 0)
MOV(EncodeRegTo32(CR), gpr.R(a));
else
SUB(CR, EncodeRegTo64(gpr.R(a)), imm);
}
else if (gpr.IsImm(b) && (gpr.GetImm(b) & 0xFFF000) == gpr.GetImm(b))
{
SUB(CR, EncodeRegTo64(gpr.R(a)), gpr.GetImm(b) >> 12, true);
}
else
{

View file

@ -345,7 +345,7 @@ public:
// Gets the immediate that a register is set to. Only valid for guest GPRs.
u32 GetImm(size_t preg) const { return GetGuestGPROpArg(preg).GetImm(); }
bool IsImm(size_t preg, u32 imm) { return IsImm(preg) && GetImm(preg) == imm; }
bool IsImm(size_t preg, u32 imm) const { return IsImm(preg) && GetImm(preg) == imm; }
// Binds a guest GPR to a host register, optionally loading its value.
//

View file

@ -19,8 +19,10 @@
namespace MappingCommon
{
constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3);
constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(0);
constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(750);
constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5);
// Ignore the mouse-click when queuing more buttons with "alternate mappings" enabled.
constexpr auto INPUT_DETECT_ENDING_IGNORE_TIME = std::chrono::milliseconds(50);
class MappingProcessor : public QWidget
{
@ -50,7 +52,7 @@ public:
button->StartMapping();
std::vector device_strings{default_device.ToString()};
if (m_parent->IsMappingAllDevices())
if (m_parent->IsCreateOtherDeviceMappingsEnabled())
device_strings = g_controller_interface.GetAllDeviceStrings();
m_input_detector = std::make_unique<ciface::Core::InputDetector>();
@ -63,35 +65,41 @@ public:
if (!m_input_detector)
return;
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, INPUT_DETECT_CONFIRMATION_TIME,
const auto confirmation_time =
INPUT_DETECT_CONFIRMATION_TIME * (m_parent->IsWaitForAlternateMappingsEnabled() ? 1 : 0);
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, confirmation_time,
INPUT_DETECT_MAXIMUM_TIME);
if (m_input_detector->IsComplete())
{
auto detections = m_input_detector->TakeResults();
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
// No inputs detected. Cancel this and any other queued mappings.
if (detections.empty())
{
if (!FinalizeMapping(m_input_detector->TakeResults()))
CancelMapping();
return;
}
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
auto& button = m_clicked_mapping_buttons.front();
auto* const control_reference = button->GetControlReference();
control_reference->SetExpression(
BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On));
m_parent->Save();
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface,
control_reference);
UnQueueInputDetection(button);
}
}
bool FinalizeMapping(ciface::Core::InputDetector::Results detections)
{
if (!ciface::MappingCommon::ContainsCompleteDetection(detections))
return false;
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
auto& button = m_clicked_mapping_buttons.front();
auto* const control_reference = button->GetControlReference();
control_reference->SetExpression(
BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On));
m_parent->Save();
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface,
control_reference);
UnQueueInputDetection(button);
return true;
}
void UpdateInputDetectionStartTimer()
{
m_input_detector.reset();
@ -121,6 +129,15 @@ public:
button->setText(QStringLiteral("[ ... ]"));
m_clicked_mapping_buttons.push_back(button);
if (m_input_detector)
{
// Ignore the mouse-click that queued this new detection and finalize the current mapping.
auto results = m_input_detector->TakeResults();
ciface::MappingCommon::RemoveDetectionsAfterTimePoint(
&results, ciface::Core::DeviceContainer::Clock::now() - INPUT_DETECT_ENDING_IGNORE_TIME);
FinalizeMapping(std::move(results));
}
UpdateInputDetectionStartTimer();
}

View file

@ -111,11 +111,15 @@ void MappingWindow::CreateDevicesLayout()
const auto refresh_action = new QAction(tr("Refresh"), options);
connect(refresh_action, &QAction::triggered, this, &MappingWindow::RefreshDevices);
m_all_devices_action = new QAction(tr("Create mappings for other devices"), options);
m_all_devices_action->setCheckable(true);
m_other_device_mappings = new QAction(tr("Create Mappings for Other Devices"), options);
m_other_device_mappings->setCheckable(true);
m_wait_for_alternate_mappings = new QAction(tr("Wait for Alternate Input Mappings"), options);
m_wait_for_alternate_mappings->setCheckable(true);
options->addAction(refresh_action);
options->addAction(m_all_devices_action);
options->addAction(m_other_device_mappings);
options->addAction(m_wait_for_alternate_mappings);
options->setDefaultAction(refresh_action);
m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
@ -354,9 +358,14 @@ void MappingWindow::OnSelectDevice(int)
m_controller->UpdateReferences(g_controller_interface);
}
bool MappingWindow::IsMappingAllDevices() const
bool MappingWindow::IsCreateOtherDeviceMappingsEnabled() const
{
return m_all_devices_action->isChecked();
return m_other_device_mappings->isChecked();
}
bool MappingWindow::IsWaitForAlternateMappingsEnabled() const
{
return m_wait_for_alternate_mappings->isChecked();
}
void MappingWindow::RefreshDevices()

View file

@ -51,7 +51,8 @@ public:
int GetPort() const;
ControllerEmu::EmulatedController* GetController() const;
bool IsMappingAllDevices() const;
bool IsCreateOtherDeviceMappingsEnabled() const;
bool IsWaitForAlternateMappingsEnabled() const;
void ShowExtensionMotionTabs(bool show);
void ActivateExtensionTab();
@ -103,7 +104,8 @@ private:
QGroupBox* m_devices_box;
QHBoxLayout* m_devices_layout;
QComboBox* m_devices_combo;
QAction* m_all_devices_action;
QAction* m_other_device_mappings;
QAction* m_wait_for_alternate_mappings;
// Profiles
QGroupBox* m_profiles_box;

View file

@ -265,12 +265,12 @@ void WiiPane::CreateSDCard()
sd_settings_group_layout->addWidget(m_sd_card_size_combo, row, 1);
++row;
m_sd_pack_button = new NonDefaultQPushButton(tr("Convert Folder to File Now"));
m_sd_unpack_button = new NonDefaultQPushButton(tr("Convert File to Folder Now"));
m_sd_pack_button = new NonDefaultQPushButton(tr(Common::SD_PACK_TEXT));
m_sd_unpack_button = new NonDefaultQPushButton(tr(Common::SD_UNPACK_TEXT));
connect(m_sd_pack_button, &QPushButton::clicked, [this] {
auto result = ModalMessageBox::warning(
this, tr("Convert Folder to File Now"),
tr("You are about to convert the content of the folder at %1 into the file at %2. All "
this, tr(Common::SD_PACK_TEXT),
tr("You are about to pack the content of the folder at %1 into the file at %2. All "
"current content of the file will be deleted. Are you sure you want to continue?")
.arg(QString::fromStdString(File::GetUserPath(D_WIISDCARDSYNCFOLDER_IDX)))
.arg(QString::fromStdString(File::GetUserPath(F_WIISDCARDIMAGE_IDX))),
@ -289,13 +289,13 @@ void WiiPane::CreateSDCard()
SetQWidgetWindowDecorations(progress_dialog.GetRaw());
progress_dialog.GetRaw()->exec();
if (!success.get())
ModalMessageBox::warning(this, tr("Convert Folder to File Now"), tr("Conversion failed."));
ModalMessageBox::warning(this, tr(Common::SD_PACK_TEXT), tr("Conversion failed."));
}
});
connect(m_sd_unpack_button, &QPushButton::clicked, [this] {
auto result = ModalMessageBox::warning(
this, tr("Convert File to Folder Now"),
tr("You are about to convert the content of the file at %2 into the folder at %1. All "
this, tr(Common::SD_UNPACK_TEXT),
tr("You are about to unpack the content of the file at %2 into the folder at %1. All "
"current content of the folder will be deleted. Are you sure you want to continue?")
.arg(QString::fromStdString(File::GetUserPath(D_WIISDCARDSYNCFOLDER_IDX)))
.arg(QString::fromStdString(File::GetUserPath(F_WIISDCARDIMAGE_IDX))),
@ -314,7 +314,7 @@ void WiiPane::CreateSDCard()
SetQWidgetWindowDecorations(progress_dialog.GetRaw());
progress_dialog.GetRaw()->exec();
if (!success.get())
ModalMessageBox::warning(this, tr("Convert File to Folder Now"), tr("Conversion failed."));
ModalMessageBox::warning(this, tr(Common::SD_UNPACK_TEXT), tr("Conversion failed."));
}
});
sd_settings_group_layout->addWidget(m_sd_pack_button, row, 0, 1, 1);

View file

@ -491,7 +491,7 @@ void InputDetector::Update(std::chrono::milliseconds initial_wait,
Detection new_detection;
new_detection.device = device_state.device;
new_detection.input = input_state.input;
new_detection.press_time = Clock::now();
new_detection.press_time = now;
new_detection.smoothness = smoothness;
// We found an input. Add it to our detections.
@ -516,12 +516,12 @@ bool InputDetector::IsComplete() const
return !m_state;
}
auto InputDetector::GetResults() const -> const std::vector<Detection>&
auto InputDetector::GetResults() const -> const Results&
{
return m_detections;
}
auto InputDetector::TakeResults() -> std::vector<Detection>
auto InputDetector::TakeResults() -> Results
{
return std::move(m_detections);
}

View file

@ -250,6 +250,7 @@ class InputDetector
{
public:
using Detection = DeviceContainer::InputDetection;
using Results = std::vector<Detection>;
InputDetector();
~InputDetector();
@ -259,16 +260,16 @@ public:
std::chrono::milliseconds maximum_wait);
bool IsComplete() const;
const std::vector<Detection>& GetResults() const;
const Results& GetResults() const;
// move-return'd to prevent copying.
std::vector<Detection> TakeResults();
Results TakeResults();
private:
struct Impl;
Clock::time_point m_start_time;
std::vector<Detection> m_detections;
Results m_detections;
std::unique_ptr<Impl> m_state;
};

View file

@ -51,11 +51,10 @@ std::string GetExpressionForControl(const std::string& control_name,
return expr;
}
std::string
BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>& detections,
const ciface::Core::DeviceQualifier& default_device, Quote quote)
std::string BuildExpression(const Core::InputDetector::Results& detections,
const ciface::Core::DeviceQualifier& default_device, Quote quote)
{
std::vector<const ciface::Core::DeviceContainer::InputDetection*> pressed_inputs;
std::vector<const Core::InputDetector::Detection*> pressed_inputs;
std::vector<std::string> alternations;
@ -135,8 +134,7 @@ BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>
return fmt::to_string(fmt::join(alternations, "|"));
}
void RemoveSpuriousTriggerCombinations(
std::vector<ciface::Core::DeviceContainer::InputDetection>* detections)
void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results* detections)
{
const auto is_spurious = [&](const auto& detection) {
return std::ranges::any_of(*detections, [&](const auto& d) {
@ -149,4 +147,21 @@ void RemoveSpuriousTriggerCombinations(
std::erase_if(*detections, is_spurious);
}
void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results* results,
Core::DeviceContainer::Clock::time_point after)
{
const auto is_after_time = [&](const Core::InputDetector::Detection& detection) {
return detection.release_time.value_or(after) >= after;
};
std::erase_if(*results, is_after_time);
}
bool ContainsCompleteDetection(const Core::InputDetector::Results& results)
{
return std::ranges::any_of(results, [](const Core::InputDetector::Detection& detection) {
return detection.release_time.has_value();
});
}
} // namespace ciface::MappingCommon

View file

@ -17,13 +17,16 @@ enum class Quote
};
std::string GetExpressionForControl(const std::string& control_name,
const ciface::Core::DeviceQualifier& control_device,
const ciface::Core::DeviceQualifier& default_device,
const Core::DeviceQualifier& control_device,
const Core::DeviceQualifier& default_device,
Quote quote = Quote::On);
std::string BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>&,
const ciface::Core::DeviceQualifier& default_device, Quote quote);
std::string BuildExpression(const Core::InputDetector::Results&,
const Core::DeviceQualifier& default_device, Quote quote);
void RemoveSpuriousTriggerCombinations(std::vector<ciface::Core::DeviceContainer::InputDetection>*);
void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results*);
void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results*,
Core::DeviceContainer::Clock::time_point after);
bool ContainsCompleteDetection(const Core::InputDetector::Results&);
} // namespace ciface::MappingCommon

View file

@ -3,10 +3,9 @@
#pragma once
#include <d3d11.h>
#include <d3d11_1.h>
#include <d3d11_4.h>
#include <d3dcompiler.h>
#include <dxgi1_5.h>
#include <dxgi1_6.h>
#include <fmt/format.h>
#include <vector>
#include <wrl/client.h>

View file

@ -3,8 +3,9 @@
#pragma once
#include <d3d11.h>
#include <d3d11_4.h>
#include <string_view>
#include "VideoBackends/D3D/D3DState.h"
#include "VideoCommon/AbstractGfx.h"

View file

@ -3,8 +3,7 @@
#pragma once
#include <d3d11.h>
#include <dxgi.h>
#include <d3d11_4.h>
#include <memory>
#include <vector>

View file

@ -3,7 +3,7 @@
#include "VideoBackends/D3D/D3DVertexManager.h"
#include <d3d11.h>
#include <d3d11_4.h>
#include "Common/Align.h"
#include "Common/Assert.h"

View file

@ -3,7 +3,7 @@
#pragma once
#include <d3d11.h>
#include <d3d11_4.h>
#include <memory>
#include "VideoBackends/D3D/D3DBase.h"

View file

@ -3,11 +3,12 @@
#pragma once
#include <d3d11.h>
#include <d3d11_4.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "Common/CommonTypes.h"
#include "VideoBackends/D3D/D3DBase.h"

View file

@ -4,6 +4,7 @@
#pragma once
#include <d3d12.h>
#include "VideoBackends/D3D12/DescriptorAllocator.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoCommon/AbstractGfx.h"

View file

@ -4,7 +4,6 @@
#pragma once
#include <d3d12.h>
#include <dxgi.h>
#include <memory>
#include <vector>

View file

@ -4,6 +4,7 @@
#pragma once
#include <memory>
#include "VideoBackends/D3D12/D3D12StreamBuffer.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoCommon/VertexManagerBase.h"

View file

@ -5,7 +5,7 @@
#include <algorithm>
#include <array>
#include <dxgi1_2.h>
#include <dxgi1_6.h>
#include <queue>
#include <vector>

View file

@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <string_view>
#include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3DCommon/Shader.h"

View file

@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <string_view>
#include "Common/CommonTypes.h"
#include "VideoBackends/D3D12/Common.h"
#include "VideoBackends/D3D12/DescriptorHeapManager.h"

View file

@ -4,6 +4,7 @@
#pragma once
#include <map>
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
#include "VideoCommon/Constants.h"

View file

@ -5,6 +5,7 @@
#include <bitset>
#include <unordered_map>
#include "VideoBackends/D3D12/Common.h"
#include "VideoCommon/RenderState.h"

View file

@ -4,6 +4,7 @@
#pragma once
#include <string>
#include "VideoCommon/VideoBackendBase.h"
namespace DX12

View file

@ -3,9 +3,9 @@
#include "VideoBackends/D3DCommon/D3DCommon.h"
#include <d3d11.h>
#include <d3d11_4.h>
#include <d3d12.h>
#include <dxgi1_3.h>
#include <dxgi1_6.h>
#include <wrl/client.h>
#include "Common/Assert.h"

View file

@ -1308,9 +1308,9 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
if (uid_data->dither)
{
// Flipper uses a standard 2x2 Bayer Matrix for 6 bit dithering
// Here the matrix is encoded into the two factor constants
out.Write("\tint2 dither = int2(rawpos.xy) & 1;\n");
out.Write("\tprev.rgb = (prev.rgb - (prev.rgb >> 6)) + abs(dither.y * 3 - dither.x * 2);\n");
out.Write(
"\tprev.rgb = (prev.rgb - (prev.rgb >> 6)) + (dither.x ^ dither.y) * 2 + dither.y;\n");
}
WriteFog(out, uid_data);

View file

@ -1434,8 +1434,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
" // Flipper uses a standard 2x2 Bayer Matrix for 6 bit dithering\n"
" // Here the matrix is encoded into the two factor constants\n"
" int2 dither = int2(rawpos.xy) & 1;\n"
" TevResult.rgb = (TevResult.rgb - (TevResult.rgb >> 6)) + abs(dither.y * 3 - "
"dither.x * 2);\n"
" TevResult.rgb = (TevResult.rgb - (TevResult.rgb >> 6)) + (dither.x ^ dither.y) * "
"2 + dither.y;\n"
" }}\n\n");
// =========