Compare commits

...

8 Commits

Author SHA1 Message Date
Tilka
e099a9c180 Merge pull request #13838 from Tilka/videosw2
VideoSW: reuse Common::Vec2/3/4
2025-08-10 08:26:02 +01:00
Tilka
4fc3106761 Merge pull request #13859 from Tilka/root_path_assert
IOS/FS: add safety assert
2025-08-10 05:29:05 +01:00
Tillmann Karras
fa4127b145 VideoSW: reuse Common::Vec2/3/4 2025-08-10 05:17:26 +01:00
Tilka
a07974e2c3 Merge pull request #13846 from JoshuaVandaele/better-xcb
Qt: Better wayland detection to enforce xcb
2025-08-10 05:09:25 +01:00
Tilka
5a6c5e2639 Merge pull request #13787 from jordan-woyak/game-config-highlighter-fix
DolphinQt: Make GameConfigHighlighter better handle large files.
2025-08-10 05:02:21 +01:00
Tillmann Karras
ec8b8bdb8d IOS/FS: add safety assert
This is to prevent someone (me) from accidentally deleting their entire
/tmp directory just by not initializing the config system correctly.
2025-08-10 04:58:57 +01:00
Jordan Woyak
c5893093fc DolphinQt: Make GameConfigHighlighter better handle large files. 2025-08-08 23:49:24 -05:00
Joshua Vandaële
f8b85edd0c Qt: Better wayland detection to enforce xcb
In certain cases, the platform can be "wayland-egl", "wayland-xcomposite", and other values for which I haven't found a full list yet. Instead of matching only "wayland", we now look for "wayland" anywhere in the `QT_QPA_PLATFORM` string in a case-insensitive manner.

Acknowledgements:
`CaseInsensitiveContains`' implementation was heavily inspired by GNU's non-standard glibc `strcasestr` function, which can be found here licensed under GPLv2 or later: https://ftp.gnu.org/gnu/libc/
2025-08-04 19:34:31 +02:00
16 changed files with 144 additions and 152 deletions

View File

@@ -669,6 +669,22 @@ bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
a, b, [](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); });
}
bool CaseInsensitiveContains(std::string_view haystack, std::string_view needle)
{
if (needle.empty())
return true;
for (size_t i = 0; i + needle.size() <= haystack.size(); ++i)
{
if (std::ranges::equal(needle, haystack.substr(i, needle.size()),
[](char a, char b) { return Common::ToLower(a) == Common::ToLower(b); }))
{
return true;
}
}
return false;
}
bool CaseInsensitiveLess::operator()(std::string_view a, std::string_view b) const
{
return std::ranges::lexicographical_compare(

View File

@@ -311,6 +311,7 @@ std::string GetEscapedHtml(std::string html);
void ToLower(std::string* str);
void ToUpper(std::string* str);
bool CaseInsensitiveEquals(std::string_view a, std::string_view b);
bool CaseInsensitiveContains(std::string_view a, std::string_view b);
// 'std::less'-like comparison function object type for case-insensitive strings.
struct CaseInsensitiveLess

View File

@@ -43,6 +43,8 @@ HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wi
}
}
ASSERT(!m_root_path.empty());
if (wii_path.starts_with("/"))
return HostFilename{m_root_path + Common::EscapePath(wii_path), false};

View File

@@ -643,7 +643,6 @@
<ClInclude Include="VideoBackends\Software\TextureEncoder.h" />
<ClInclude Include="VideoBackends\Software\TextureSampler.h" />
<ClInclude Include="VideoBackends\Software\TransformUnit.h" />
<ClInclude Include="VideoBackends\Software\Vec3.h" />
<ClInclude Include="VideoBackends\Software\VideoBackend.h" />
<ClInclude Include="VideoBackends\Vulkan\CommandBufferManager.h" />
<ClInclude Include="VideoBackends\Vulkan\Constants.h" />

View File

@@ -7,6 +7,7 @@
#include <QCompleter>
#include <QDesktopServices>
#include <QFile>
#include <QKeyEvent>
#include <QMenu>
#include <QMenuBar>
#include <QPushButton>

View File

@@ -5,6 +5,10 @@
#include <QBrush>
#include <QColor>
#include <QRegularExpression>
#include <QTextBlock>
#include <QTextCharFormat>
#include <QTextDocument>
#include "DolphinQt/Settings.h"
@@ -16,7 +20,7 @@ struct HighlightingRule
GameConfigHighlighter::~GameConfigHighlighter() = default;
GameConfigHighlighter::GameConfigHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent)
GameConfigHighlighter::GameConfigHighlighter(QTextDocument* parent) : QObject(parent)
{
const bool is_dark_theme = Settings::Instance().IsThemeDark();
@@ -65,17 +69,42 @@ GameConfigHighlighter::GameConfigHighlighter(QTextDocument* parent) : QSyntaxHig
HighlightingRule{QRegularExpression(QStringLiteral("^\\$.*")), comment_format});
m_rules.emplace_back(
HighlightingRule{QRegularExpression(QStringLiteral("^\\*.*")), comment_format});
// Highlight block on change.
// We're manually highlighting blocks because QSyntaxHighlighter
// hangs with large (>2MB) files for some reason.
connect(parent, &QTextDocument::contentsChange, this,
[this, parent](const int pos, int /* removed */, const int added) {
QTextBlock block = parent->findBlock(pos);
const auto pos_end = pos + added;
while (block.isValid() && block.position() <= pos_end)
{
HighlightBlock(block);
block = block.next();
}
});
// Highlight all blocks right now.
for (QTextBlock block = parent->begin(); block.isValid(); block = block.next())
HighlightBlock(block);
}
void GameConfigHighlighter::highlightBlock(const QString& text)
void GameConfigHighlighter::HighlightBlock(const QTextBlock& block)
{
QList<QTextLayout::FormatRange> format_ranges;
for (const auto& rule : m_rules)
{
auto it = rule.pattern.globalMatch(text);
auto it = rule.pattern.globalMatch(block.text());
while (it.hasNext())
{
auto match = it.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
const auto match = it.next();
format_ranges.emplace_back(QTextLayout::FormatRange{.start = int(match.capturedStart()),
.length = int(match.capturedLength()),
.format = rule.format});
}
}
block.layout()->clearFormats();
block.layout()->setFormats(format_ranges);
}

View File

@@ -5,23 +5,23 @@
#include <vector>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#include <QObject>
struct HighlightingRule;
class GameConfigHighlighter : public QSyntaxHighlighter
class QTextBlock;
class QTextDocument;
class GameConfigHighlighter : public QObject
{
Q_OBJECT
public:
explicit GameConfigHighlighter(QTextDocument* parent = nullptr);
explicit GameConfigHighlighter(QTextDocument* parent);
~GameConfigHighlighter() override;
protected:
void highlightBlock(const QString& text) override;
private:
void HighlightBlock(const QTextBlock& block);
std::vector<HighlightingRule> m_rules;
};

View File

@@ -23,6 +23,7 @@
#include "Common/Config/Config.h"
#include "Common/MsgHandler.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Core/Boot/Boot.h"
#include "Core/Config/MainSettings.h"
@@ -152,8 +153,8 @@ int main(int argc, char* argv[])
// from happening.
// For more information: https://bugs.dolphin-emu.org/issues/11807
const char* current_qt_platform = getenv("QT_QPA_PLATFORM");
const bool replace_qt_platform =
(current_qt_platform && strcasecmp(current_qt_platform, "wayland") == 0);
const bool replace_qt_platform = current_qt_platform != nullptr &&
Common::CaseInsensitiveContains(current_qt_platform, "wayland");
setenv("QT_QPA_PLATFORM", "xcb", replace_qt_platform);
#endif

View File

@@ -30,7 +30,6 @@ add_library(videosoftware
TextureSampler.h
TransformUnit.cpp
TransformUnit.h
Vec3.h
VideoBackend.h
)

View File

@@ -76,7 +76,7 @@ enum
static inline int CalcClipMask(const OutputVertexData* v)
{
int cmask = 0;
Vec4 pos = v->projectedPosition;
Common::Vec4 pos = v->projectedPosition;
if (pos.w - pos.x < 0)
cmask |= CLIP_POS_X_BIT;
@@ -546,8 +546,8 @@ bool IsBackface(const OutputVertexData* v0, const OutputVertexData* v1, const Ou
void PerspectiveDivide(OutputVertexData* vertex)
{
Vec4& projected = vertex->projectedPosition;
Vec3& screen = vertex->screenPosition;
Common::Vec4& projected = vertex->projectedPosition;
Common::Vec3& screen = vertex->screenPosition;
float wInverse = 1.0f / projected.w;
screen.x = projected.x * wInverse * xfmem.viewport.wd + xfmem.viewport.xOrig;

View File

@@ -7,25 +7,17 @@
#include <cstddef>
#include "Common/CommonTypes.h"
#include "VideoBackends/Software/Vec3.h"
struct Vec4
{
float x;
float y;
float z;
float w;
};
#include "Common/Matrix.h"
struct InputVertexData
{
u8 posMtx;
std::array<u8, 8> texMtx;
Vec3 position;
std::array<Vec3, 3> normal;
Common::Vec3 position;
std::array<Common::Vec3, 3> normal;
std::array<std::array<u8, 4>, 2> color;
std::array<std::array<float, 2>, 8> texCoords;
std::array<Common::Vec2, 8> texCoords;
};
struct OutputVertexData
@@ -39,12 +31,12 @@ struct OutputVertexData
ALP_C
};
Vec3 mvPosition = {};
Vec4 projectedPosition = {};
Vec3 screenPosition = {};
std::array<Vec3, 3> normal{};
Common::Vec3 mvPosition = {};
Common::Vec4 projectedPosition = {};
Common::Vec3 screenPosition = {};
std::array<Common::Vec3, 3> normal{};
std::array<std::array<u8, 4>, 2> color{};
std::array<Vec3, 8> texCoords{};
std::array<Common::Vec3, 8> texCoords{};
void Lerp(float t, const OutputVertexData* a, const OutputVertexData* b)
{

View File

@@ -392,11 +392,12 @@ static void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertex
for (unsigned int i = 0; i < bpmem.genMode.numtexgens; i++)
{
for (int comp = 0; comp < 3; comp++)
{
TexSlopes[i][comp] = Slope(v0->texCoords[i][comp] * w[0], v1->texCoords[i][comp] * w[1],
v2->texCoords[i][comp] * w[2], ctx);
}
TexSlopes[i][0] =
Slope(v0->texCoords[i].x * w[0], v1->texCoords[i].x * w[1], v2->texCoords[i].x * w[2], ctx);
TexSlopes[i][1] =
Slope(v0->texCoords[i].y * w[0], v1->texCoords[i].y * w[1], v2->texCoords[i].y * w[2], ctx);
TexSlopes[i][2] =
Slope(v0->texCoords[i].z * w[0], v1->texCoords[i].z * w[1], v2->texCoords[i].z * w[2], ctx);
}
// Half-edge constants

View File

@@ -200,42 +200,42 @@ void SWVertexLoader::ParseVertex(const PortableVertexDeclaration& vdec, int inde
m_cpu_vertex_buffer.data() + m_cpu_vertex_buffer.size());
src.Skip(index * vdec.stride);
ReadVertexAttribute<float>(&m_vertex.position[0], src, vdec.position, 0, 3, false);
ReadVertexAttribute<float>(&m_vertex.position.x, src, vdec.position, 0, 3, false);
for (std::size_t i = 0; i < m_vertex.normal.size(); i++)
{
ReadVertexAttribute<float>(&m_vertex.normal[i][0], src, vdec.normals[i], 0, 3, false);
ReadVertexAttribute<float>(&m_vertex.normal[i].x, src, vdec.normals[i], 0, 3, false);
}
if (!vdec.normals[0].enable)
{
auto& system = Core::System::GetInstance();
auto& vertex_shader_manager = system.GetVertexShaderManager();
m_vertex.normal[0][0] = vertex_shader_manager.constants.cached_normal[0];
m_vertex.normal[0][1] = vertex_shader_manager.constants.cached_normal[1];
m_vertex.normal[0][2] = vertex_shader_manager.constants.cached_normal[2];
m_vertex.normal[0].x = vertex_shader_manager.constants.cached_normal[0];
m_vertex.normal[0].y = vertex_shader_manager.constants.cached_normal[1];
m_vertex.normal[0].z = vertex_shader_manager.constants.cached_normal[2];
}
if (!vdec.normals[1].enable)
{
auto& system = Core::System::GetInstance();
auto& vertex_shader_manager = system.GetVertexShaderManager();
m_vertex.normal[1][0] = vertex_shader_manager.constants.cached_tangent[0];
m_vertex.normal[1][1] = vertex_shader_manager.constants.cached_tangent[1];
m_vertex.normal[1][2] = vertex_shader_manager.constants.cached_tangent[2];
m_vertex.normal[1].x = vertex_shader_manager.constants.cached_tangent[0];
m_vertex.normal[1].y = vertex_shader_manager.constants.cached_tangent[1];
m_vertex.normal[1].z = vertex_shader_manager.constants.cached_tangent[2];
}
if (!vdec.normals[2].enable)
{
auto& system = Core::System::GetInstance();
auto& vertex_shader_manager = system.GetVertexShaderManager();
m_vertex.normal[2][0] = vertex_shader_manager.constants.cached_binormal[0];
m_vertex.normal[2][1] = vertex_shader_manager.constants.cached_binormal[1];
m_vertex.normal[2][2] = vertex_shader_manager.constants.cached_binormal[2];
m_vertex.normal[2].x = vertex_shader_manager.constants.cached_binormal[0];
m_vertex.normal[2].y = vertex_shader_manager.constants.cached_binormal[1];
m_vertex.normal[2].z = vertex_shader_manager.constants.cached_binormal[2];
}
ParseColorAttributes(&m_vertex, src, vdec);
for (std::size_t i = 0; i < m_vertex.texCoords.size(); i++)
{
ReadVertexAttribute<float>(m_vertex.texCoords[i].data(), src, vdec.texcoords[i], 0, 2, false);
ReadVertexAttribute<float>(&m_vertex.texCoords[i].x, src, vdec.texcoords[i], 0, 2, false);
// the texmtr is stored as third component of the texCoord
if (vdec.texcoords[i].components >= 3)

View File

@@ -11,17 +11,21 @@
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Matrix.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"
#include "VideoBackends/Software/NativeVertexFormat.h"
#include "VideoBackends/Software/Vec3.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/XFMemory.h"
namespace TransformUnit
{
using Vec3 = Common::Vec3;
using Vec4 = Common::Vec4;
static void MultiplyVec2Mat24(const Vec3& vec, const float* mat, Vec3& result)
{
result.x = mat[0] * vec.x + mat[1] * vec.y + mat[2] + mat[3];
@@ -102,7 +106,7 @@ void TransformNormal(const InputVertexData* src, OutputVertexData* dst)
// By normalising the first transformed normal (which is used by lighting calculations and needs
// to be unit length), the same transform matrix can do double duty, scaling for emboss mapping,
// and not scaling for lighting.
dst->normal[0].Normalize();
dst->normal[0] = dst->normal[0].Normalized();
}
static void TransformTexCoordRegular(const TexMtxInfo& texinfo, int coordNum,
@@ -127,8 +131,8 @@ static void TransformTexCoordRegular(const TexMtxInfo& texinfo, int coordNum,
{
ASSERT(texinfo.sourcerow >= SourceRow::Tex0 && texinfo.sourcerow <= SourceRow::Tex7);
u32 texnum = static_cast<u32>(texinfo.sourcerow.Value()) - static_cast<u32>(SourceRow::Tex0);
src.x = srcVertex->texCoords[texnum][0];
src.y = srcVertex->texCoords[texnum][1];
src.x = srcVertex->texCoords[texnum].x;
src.y = srcVertex->texCoords[texnum].y;
src.z = 1.0f;
break;
}
@@ -228,22 +232,22 @@ static float CalculateLightAttn(const LightPointer* light, Vec3* _ldir, const Ve
case AttenuationFunc::Spec:
{
ldir = ldir.Normalized();
attn = (ldir * normal) >= 0.0 ? std::max(0.0f, light->dir * normal) : 0;
attn = ldir.Dot(normal) >= 0.0 ? std::max(0.0f, light->dir.Dot(normal)) : 0;
Vec3 attLen = Vec3(1.0, attn, attn * attn);
Vec3 cosAttn = light->cosatt;
Vec3 distAttn = light->distatt;
if (chan.diffusefunc != DiffuseFunc::None)
distAttn = distAttn.Normalized();
attn = SafeDivide(std::max(0.0f, attLen * cosAttn), attLen * distAttn);
attn = SafeDivide(std::max(0.0f, attLen.Dot(cosAttn)), attLen.Dot(distAttn));
break;
}
case AttenuationFunc::Spot:
{
float dist2 = ldir.Length2();
float dist2 = ldir.LengthSquared();
float dist = sqrtf(dist2);
ldir = ldir / dist;
attn = std::max(0.0f, ldir * light->dir);
attn = std::max(0.0f, ldir.Dot(light->dir));
float cosAtt = light->cosatt.x + (light->cosatt.y * attn) + (light->cosatt.z * attn * attn);
float distAtt = light->distatt.x + (light->distatt.y * dist) + (light->distatt.z * dist2);
@@ -265,7 +269,7 @@ static void LightColor(const Vec3& pos, const Vec3& normal, u8 lightNum, const L
Vec3 ldir = light->pos - pos;
float attn = CalculateLightAttn(light, &ldir, normal, chan);
float difAttn = ldir * normal;
float difAttn = ldir.Dot(normal);
switch (chan.diffusefunc)
{
case DiffuseFunc::None:
@@ -291,7 +295,7 @@ static void LightAlpha(const Vec3& pos, const Vec3& normal, u8 lightNum, const L
Vec3 ldir = light->pos - pos;
float attn = CalculateLightAttn(light, &ldir, normal, chan);
float difAttn = ldir * normal;
float difAttn = ldir.Dot(normal);
switch (chan.diffusefunc)
{
case DiffuseFunc::None:
@@ -412,8 +416,8 @@ void TransformTexCoord(const InputVertexData* src, OutputVertexData* dst)
const LightPointer* light = (const LightPointer*)&xfmem.lights[texinfo.embosslightshift];
Vec3 ldir = (light->pos - dst->mvPosition).Normalized();
float d1 = ldir * dst->normal[1];
float d2 = ldir * dst->normal[2];
float d1 = ldir.Dot(dst->normal[1]);
float d2 = ldir.Dot(dst->normal[2]);
dst->texCoords[coordNum].x = dst->texCoords[texinfo.embosssourceshift].x + d1;
dst->texCoords[coordNum].y = dst->texCoords[texinfo.embosssourceshift].y + d2;
@@ -440,8 +444,8 @@ void TransformTexCoord(const InputVertexData* src, OutputVertexData* dst)
for (u32 coordNum = 0; coordNum < xfmem.numTexGen.numTexGens; coordNum++)
{
dst->texCoords[coordNum][0] *= (bpmem.texcoords[coordNum].s.scale_minus_1 + 1);
dst->texCoords[coordNum][1] *= (bpmem.texcoords[coordNum].t.scale_minus_1 + 1);
dst->texCoords[coordNum].x *= bpmem.texcoords[coordNum].s.scale_minus_1 + 1;
dst->texCoords[coordNum].y *= bpmem.texcoords[coordNum].t.scale_minus_1 + 1;
}
}
} // namespace TransformUnit

View File

@@ -1,84 +0,0 @@
// Copyright 2010 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cmath>
#include <cstdlib>
class Vec3
{
public:
float x, y, z;
Vec3() = default;
explicit Vec3(float f) { x = y = z = f; }
explicit Vec3(const float* f)
{
x = f[0];
y = f[1];
z = f[2];
}
Vec3(const float _x, const float _y, const float _z)
{
x = _x;
y = _y;
z = _z;
}
void set(const float _x, const float _y, const float _z)
{
x = _x;
y = _y;
z = _z;
}
Vec3 operator+(const Vec3& other) const { return Vec3(x + other.x, y + other.y, z + other.z); }
void operator+=(const Vec3& other)
{
x += other.x;
y += other.y;
z += other.z;
}
Vec3 operator-(const Vec3& v) const { return Vec3(x - v.x, y - v.y, z - v.z); }
void operator-=(const Vec3& other)
{
x -= other.x;
y -= other.y;
z -= other.z;
}
Vec3 operator-() const { return Vec3(-x, -y, -z); }
Vec3 operator*(const float f) const { return Vec3(x * f, y * f, z * f); }
Vec3 operator/(const float f) const
{
float invf = (1.0f / f);
return Vec3(x * invf, y * invf, z * invf);
}
void operator/=(const float f) { *this = *this / f; }
float operator*(const Vec3& other) const { return (x * other.x) + (y * other.y) + (z * other.z); }
void operator*=(const float f) { *this = *this * f; }
Vec3 ScaledBy(const Vec3& other) const { return Vec3(x * other.x, y * other.y, z * other.z); }
Vec3 operator%(const Vec3& v) const
{
return Vec3((y * v.z) - (z * v.y), (z * v.x) - (x * v.z), (x * v.y) - (y * v.x));
}
float Length2() const { return (x * x) + (y * y) + (z * z); }
float Length() const { return sqrtf(Length2()); }
float Distance2To(Vec3& other) { return (other - (*this)).Length2(); }
Vec3 Normalized() const { return (*this) / Length(); }
void Normalize() { (*this) /= Length(); }
float& operator[](int i) { return *((&x) + i); }
float operator[](const int i) const { return *((&x) + i); }
bool operator==(const Vec3& other) const { return x == other.x && y == other.y && z == other.z; }
void SetZero()
{
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
};

View File

@@ -225,3 +225,34 @@ TEST(StringUtil, SplitPathWindowsPathWithDriveLetter)
EXPECT_EQ(extension, "");
}
#endif
TEST(StringUtil, CaseInsensitiveContains_BasicMatches)
{
EXPECT_TRUE(Common::CaseInsensitiveContains("hello world", "hello"));
EXPECT_TRUE(Common::CaseInsensitiveContains("hello world", "world"));
EXPECT_TRUE(Common::CaseInsensitiveContains("HELLO WORLD", "hello"));
EXPECT_TRUE(Common::CaseInsensitiveContains("HeLLo WoRLd", "WORLD"));
}
TEST(StringUtil, CaseInsensitiveContains_SubstringNotFound)
{
EXPECT_FALSE(Common::CaseInsensitiveContains("hello world", "hey"));
}
TEST(StringUtil, CaseInsensitiveContains_EmptyStrings)
{
EXPECT_TRUE(Common::CaseInsensitiveContains("", ""));
EXPECT_TRUE(Common::CaseInsensitiveContains("hello", ""));
EXPECT_FALSE(Common::CaseInsensitiveContains("", "world"));
}
TEST(StringUtil, CaseInsensitiveContains_EntireStringMatch)
{
EXPECT_TRUE(Common::CaseInsensitiveContains("Test", "TEST"));
}
TEST(StringUtil, CaseInsensitiveContains_OverlappingMatches)
{
EXPECT_TRUE(Common::CaseInsensitiveContains("aaaaaa", "aa"));
EXPECT_TRUE(Common::CaseInsensitiveContains("ababababa", "bABa"));
}