Compare commits

...

10 Commits

Author SHA1 Message Date
TJnotJT bbfb9b0848
Merge 6ca075497c into 20411aa8d6 2025-08-28 15:40:01 +07:00
PCSX2 Bot 20411aa8d6 [ci skip] Qt: Update Base Translation. 2025-08-28 09:29:18 +02:00
NightFyre be94aa97db
Core: Fix vumem export and offsets 2025-08-27 16:55:11 -04:00
chaoticgd f65c1dd5bc Debugger: Split debugger event actions strings for easier translation 2025-08-27 16:53:41 -04:00
chaoticgd a92297ceec Debugger: Fix some untranslatable strings 2025-08-27 16:53:41 -04:00
chaoticgd a8ea4e55ef Debugger: Fix Automatically Select Symbols To Clear checkbox 2025-08-27 16:51:45 -04:00
dependabot[bot] 3195befab1 Bump baptiste0928/cargo-install from 3.3.1 to 3.3.2 in the ci-deps group
Bumps the ci-deps group with 1 update: [baptiste0928/cargo-install](https://github.com/baptiste0928/cargo-install).


Updates `baptiste0928/cargo-install` from 3.3.1 to 3.3.2
- [Release notes](https://github.com/baptiste0928/cargo-install/releases)
- [Changelog](https://github.com/baptiste0928/cargo-install/blob/main/CHANGELOG.md)
- [Commits](e38323ef01...b687c656bd)

---
updated-dependencies:
- dependency-name: baptiste0928/cargo-install
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-27 16:50:51 -04:00
TJnotJT 6ca075497c GS: Refactor GSVertexTrace. 2025-08-08 14:35:38 -04:00
TJnotJT 0e6f790ef2 GS: Add optimized right triangle check.
Co-authored-by: TellowKrinkle
2025-08-07 13:53:51 -04:00
TJnotJT a0dabccad3 GS: Refactor GSState::GetTextureMinMax() and GSDrawingContext::GetSizeFixedTex0(). 2025-08-07 13:51:09 -04:00
25 changed files with 886 additions and 691 deletions

View File

@ -168,7 +168,7 @@ jobs:
!./bin/**/*.lib
- name: Install the Breakpad Symbol Generator
uses: baptiste0928/cargo-install@e38323ef017552d7f7af73a3f4db467f278310ed
uses: baptiste0928/cargo-install@b687c656bda5733207e629b50a22bf68974a0305
with:
crate: dump_syms

View File

@ -38,7 +38,8 @@ namespace DebuggerEvents
bool switch_to_tab = true;
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in");
static constexpr const char* ACTION_STRING = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in %1");
static constexpr const char* ACTION_OVERFLOW_STRING = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in...");
};
// The state of the VM has changed and views should be updated to reflect
@ -53,6 +54,7 @@ namespace DebuggerEvents
u32 address = 0;
bool switch_to_tab = true;
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to");
static constexpr const char* ACTION_STRING = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to %1");
static constexpr const char* ACTION_OVERFLOW_STRING = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to...");
};
} // namespace DebuggerEvents

View File

@ -257,7 +257,8 @@ std::vector<QAction*> DebuggerView::createEventActionsImplementation(
u32 max_top_level_actions,
bool skip_self,
const char* event_type,
const char* action_prefix,
const char* action_string,
const char* action_overflow_string,
std::function<const DebuggerEvents::Event*()> event_func)
{
if (!g_debugger_window)
@ -278,8 +279,7 @@ std::vector<QAction*> DebuggerView::createEventActionsImplementation(
QMenu* submenu = nullptr;
if (receivers.size() > max_top_level_actions)
{
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1...");
submenu = new QMenu(title_format.arg(QCoreApplication::translate("DebuggerEvent", action_prefix)), menu);
submenu = new QMenu(QCoreApplication::translate("DebuggerEvents", action_overflow_string), menu);
}
std::vector<QAction*> actions;
@ -290,9 +290,8 @@ std::vector<QAction*> DebuggerView::createEventActionsImplementation(
QAction* action;
if (!submenu || i + 1 < max_top_level_actions)
{
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1 %2");
QString event_title = QCoreApplication::translate("DebuggerEvent", action_prefix);
QString title = title_format.arg(event_title).arg(receiver->displayName());
QString title_format = QCoreApplication::translate("DebuggerEvents", action_string);
QString title = title_format.arg(receiver->displayName());
action = new QAction(title, menu);
menu->addAction(action);
}

View File

@ -127,7 +127,8 @@ public:
u32 max_top_level_actions = 5)
{
return createEventActionsImplementation(
menu, max_top_level_actions, skip_self, typeid(Event).name(), Event::ACTION_PREFIX,
menu, max_top_level_actions, skip_self, typeid(Event).name(),
Event::ACTION_STRING, Event::ACTION_OVERFLOW_STRING,
[event_func]() -> DebuggerEvents::Event* {
static std::optional<Event> event;
event = event_func();
@ -176,7 +177,8 @@ private:
u32 max_top_level_actions,
bool skip_self,
const char* event_type,
const char* action_prefix,
const char* action_string,
const char* action_overflow_string,
std::function<const DebuggerEvents::Event*()> event_func);
// Used for sorting debugger views that have the same display name. Unique

View File

@ -393,7 +393,10 @@ void DockManager::createWindowsMenu(QMenu* menu)
const auto description_iterator = DockTables::DEBUGGER_VIEWS.find(type);
pxAssert(description_iterator != DockTables::DEBUGGER_VIEWS.end());
QAction* action = add_another_menu->addAction(description_iterator->second.display_name);
QString display_name = QCoreApplication::translate(
"DebuggerView", description_iterator->second.display_name);
QAction* action = add_another_menu->addAction(display_name);
connect(action, &QAction::triggered, this, [this, type]() {
if (m_current_layout == DockLayout::INVALID_INDEX)
return;

View File

@ -158,7 +158,7 @@ Qt::ItemFlags SavedAddressesModel::flags(const QModelIndex& index) const
void SavedAddressesModel::addRow()
{
const SavedAddress defaultNewAddress = {0, "Name", "Description"};
const SavedAddress defaultNewAddress = {0, tr("Name"), tr("Description")};
addRow(defaultNewAddress);
}

View File

@ -245,11 +245,21 @@ void DebugAnalysisSettingsWidget::setupSymbolSourceGrid()
int i = 0;
for (auto& [name, temp] : m_symbol_sources)
{
temp.check_box = new QCheckBox(QString::fromStdString(name));
QString display_name = SymbolGuardian::TranslateSymbolSourceName(name.c_str());
temp.check_box = new QCheckBox(display_name);
temp.check_box->setChecked(temp.previous_value);
layout->addWidget(temp.check_box, i / 2, i % 2);
connect(temp.check_box, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged);
connect(temp.check_box, &QCheckBox::checkStateChanged, this, [this, name]() {
auto temp = m_symbol_sources.find(name);
if (temp == m_symbol_sources.end())
return;
temp->second.modified_by_user = true;
saveSymbolSources();
});
i++;
}
@ -264,21 +274,6 @@ void DebugAnalysisSettingsWidget::setupSymbolSourceGrid()
m_ui.symbolSourceErrorMessage->hide();
}
void DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged()
{
QCheckBox* check_box = qobject_cast<QCheckBox*>(sender());
if (!check_box)
return;
auto temp = m_symbol_sources.find(check_box->text().toStdString());
if (temp == m_symbol_sources.end())
return;
temp->second.modified_by_user = true;
saveSymbolSources();
}
void DebugAnalysisSettingsWidget::saveSymbolSources()
{
if (!m_dialog)

View File

@ -30,7 +30,6 @@ public:
protected:
void setupSymbolSourceGrid();
void symbolSourceCheckStateChanged();
void saveSymbolSources();
void setupSymbolFileList();

View File

@ -4253,27 +4253,27 @@ Do you want to overwrite?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="259"/>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="269"/>
<source>&lt;i&gt;Start this game to modify the symbol sources list.&lt;/i&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="334"/>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="329"/>
<source>Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="335"/>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="330"/>
<source>Base Address</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="336"/>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="331"/>
<source>Condition</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="383"/>
<location filename="../Settings/DebugAnalysisSettingsWidget.cpp" line="378"/>
<source>Add Symbol File</source>
<translation type="unfinished"></translation>
</message>
@ -5038,29 +5038,26 @@ Do you want to overwrite?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DebuggerEvent</name>
<message>
<location filename="../Debugger/DebuggerView.cpp" line="281"/>
<source>%1...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/DebuggerView.cpp" line="293"/>
<source>%1 %2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DebuggerEvents</name>
<message>
<location filename="../Debugger/DebuggerEvents.h" line="41"/>
<source>Go to in</source>
<source>Go to in %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/DebuggerEvents.h" line="56"/>
<source>Add to</source>
<location filename="../Debugger/DebuggerEvents.h" line="42"/>
<source>Go to in...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/DebuggerEvents.h" line="57"/>
<source>Add to %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/DebuggerEvents.h" line="58"/>
<source>Add to...</source>
<translation type="unfinished"></translation>
</message>
</context>
@ -5553,33 +5550,33 @@ Do you want to overwrite?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="581"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="584"/>
<source>Edit Layout</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="586"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="589"/>
<source>Reset Layout</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="636"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="639"/>
<source>Are you sure you want to reset layout &apos;%1&apos;?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="637"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="660"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="640"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="663"/>
<source>Confirmation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="592"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="595"/>
<source>Delete Layout</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Docking/DockManager.cpp" line="659"/>
<location filename="../Debugger/Docking/DockManager.cpp" line="662"/>
<source>Are you sure you want to delete layout &apos;%1&apos;?</source>
<translation type="unfinished"></translation>
</message>
@ -20559,6 +20556,16 @@ If you have any unsaved progress on this save state, you can download the compat
<source>DESCRIPTION</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Memory/SavedAddressesModel.cpp" line="161"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../Debugger/Memory/SavedAddressesModel.cpp" line="161"/>
<source>Description</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SavedAddressesView</name>
@ -21260,6 +21267,49 @@ Scanning recursively takes more time, but will identify files in subdirectories.
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SymbolGuardian</name>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="224"/>
<source>DWARF Symbol Table</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="225"/>
<source>ELF Section Headers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="226"/>
<source>ELF Symbol Table</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="227"/>
<source>Function Scanner</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="228"/>
<source>Nocash Symbols</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="229"/>
<source>SNDLL Symbol Table</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="230"/>
<source>Symbol Table Importer</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/DebugTools/SymbolGuardian.cpp" line="231"/>
<source>User-Defined</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SymbolTreeModel</name>
<message>

View File

@ -4,6 +4,7 @@
#include "SymbolGuardian.h"
#include "DebugInterface.h"
#include "Host.h"
SymbolGuardian R5900SymbolGuardian;
SymbolGuardian R3000SymbolGuardian;
@ -216,3 +217,23 @@ void SymbolGuardian::ClearIrxModules()
m_database.destroy_symbols_from_module(module, false);
});
}
const char* SymbolGuardian::TranslateSymbolSourceName(const char* name)
{
static constexpr std::array<const char*, 8> names = {
TRANSLATE_NOOP("SymbolGuardian", "DWARF Symbol Table"),
TRANSLATE_NOOP("SymbolGuardian", "ELF Section Headers"),
TRANSLATE_NOOP("SymbolGuardian", "ELF Symbol Table"),
TRANSLATE_NOOP("SymbolGuardian", "Function Scanner"),
TRANSLATE_NOOP("SymbolGuardian", "Nocash Symbols"),
TRANSLATE_NOOP("SymbolGuardian", "SNDLL Symbol Table"),
TRANSLATE_NOOP("SymbolGuardian", "Symbol Table Importer"),
TRANSLATE_NOOP("SymbolGuardian", "User-Defined"),
};
for (const char* test_name : names)
if (strcmp(test_name, name) == 0)
return TRANSLATE("SymbolGuardian", name);
return name;
}

View File

@ -88,6 +88,10 @@ public:
// Delete all symbols from modules that have the "is_irx" flag set.
void ClearIrxModules();
// Translate the name of a ccc::SymbolSource object, or return it as is if
// no translation is available.
static const char* TranslateSymbolSourceName(const char* name);
protected:
ccc::SymbolDatabase m_database;
mutable std::shared_mutex m_big_symbol_lock;

View File

@ -311,9 +311,10 @@ void SymbolImporter::ClearExistingSymbols(ccc::SymbolDatabase& database, const P
{
bool should_destroy = ShouldClearSymbolsFromSourceByDefault(source.name());
for (const DebugSymbolSource& source_config : options.SymbolSources)
if (source_config.Name == source.name())
should_destroy = source_config.ClearDuringAnalysis;
if (!options.AutomaticallySelectSymbolsToClear)
for (const DebugSymbolSource& source_config : options.SymbolSources)
if (source_config.Name == source.name())
should_destroy = source_config.ClearDuringAnalysis;
if (should_destroy)
sources_to_destroy.emplace_back(source.handle());
@ -326,9 +327,9 @@ void SymbolImporter::ClearExistingSymbols(ccc::SymbolDatabase& database, const P
bool SymbolImporter::ShouldClearSymbolsFromSourceByDefault(const std::string& source_name)
{
return source_name.find("Symbol Table") != std::string::npos ||
source_name == "ELF Section Headers" ||
source_name == "Function Scanner" ||
source_name == "Nocash Symbols";
source_name == "ELF Section Headers" ||
source_name == "Function Scanner" ||
source_name == "Nocash Symbols";
}
void SymbolImporter::ImportSymbols(

View File

@ -5,66 +5,24 @@
#include "GS/GSGL.h"
#include "GS/GS.h"
#include "GS/GSUtil.h"
#include "GS/GSState.h"
static int findmax(int tl, int br, int limit, int wm, int minuv, int maxuv)
// SIZE: TW or TW
// WM, MIN, MAX : Correspondng field of TEX0
// min, max: Range that U or V coordintes take on.
static int GetMaxUV(int SIZE, int WM, int MIN, int MAX, int min, int max)
{
// return max possible texcoord.
int uv = br;
// Confirmed on hardware if SIZE > 10 (or pixel size > 1024),
// it basically gets masked so you end up with a 1x1 pixel (Except Region Clamp).
if (SIZE > 10 && (WM != CLAMP_REGION_CLAMP))
return 0;
// Confirmed on hardware if the size exceeds 1024, it basically gets masked so you end up with a 1x1 pixel (Except Region Clamp).
if (limit > 1024)
limit = 0;
int min_out, max_out; // ignore min_out
bool min_boundary, max_boundary; // ignore both
GSState::GetClampWrapMinMaxUV(SIZE, WM, MIN, MAX, min, max, &min_out, &max_out, &min_boundary, &max_boundary);
if (wm == CLAMP_CLAMP)
{
if (uv > limit)
uv = limit;
}
else if (wm == CLAMP_REPEAT)
{
if (tl < 0)
uv = limit; // wrap around
else if (uv > limit)
uv = limit;
}
else if (wm == CLAMP_REGION_CLAMP)
{
if (uv < minuv)
uv = minuv;
if (uv > maxuv)
uv = maxuv;
}
else if (wm == CLAMP_REGION_REPEAT)
{
// REGION_REPEAT adhears to the original texture size, even if offset outside the texture (with MAXUV).
minuv &= limit;
if (tl < 0)
uv = minuv | maxuv; // wrap around, just use (any & mask) | fix.
else
uv = std::min(uv, minuv) | maxuv; // (any & mask) cannot be larger than mask, select br if that is smaller (not br & mask because there might be a larger value between tl and br when &'ed with the mask).
}
return uv;
}
static int reduce(int uv, int size)
{
while (size > 3 && (1 << (size - 1)) >= uv)
{
size--;
}
return size;
}
static int extend(int uv, int size)
{
while (size < 10 && (1 << size) < uv)
{
size++;
}
return size;
return max_out;
}
void GSDrawingContext::Reset()
@ -98,65 +56,69 @@ void GSDrawingContext::UpdateScissor()
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy().sub32(GSVector4i::cxpr(0, 0, 15, 15));
}
GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap) const
// Find the optimal value for TW/TH by analyzing vertex trace and clamping values,
// extending only for region modes where uv may be outside.
// uv_rect has rectangle bounding effecive UV coordinate (u0, v0, u1, v1) (u1 v1 endpoints exclusive)
GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(GSVector4i uv_rect, bool linear, bool mipmap) const
{
if (mipmap)
return TEX0; // no mipmaping allowed
// find the optimal value for TW/TH by analyzing vertex trace and clamping values, extending only for region modes where uv may be outside
const int WMS = (int)CLAMP.WMS;
const int WMT = (int)CLAMP.WMT;
int tw = TEX0.TW;
int th = TEX0.TH;
const int MINU = (int)CLAMP.MINU;
const int MINV = (int)CLAMP.MINV;
const int MAXU = (int)CLAMP.MAXU;
const int MAXV = (int)CLAMP.MAXV;
int wms = (int)CLAMP.WMS;
int wmt = (int)CLAMP.WMT;
int TW = TEX0.TW;
int TH = TEX0.TH;
int minu = (int)CLAMP.MINU;
int minv = (int)CLAMP.MINV;
int maxu = (int)CLAMP.MAXU;
int maxv = (int)CLAMP.MAXV;
const int min_width = uv_rect.right;
const int min_height = uv_rect.bottom;
GSVector4 uvf = st;
auto ExtendLog2Size = [](int min_size, int log2_size) {
while (log2_size < 10 && (1 << log2_size) < min_size)
log2_size++;
return log2_size;
};
if (linear)
auto ReduceLog2Size = [](int min_size, int log2_size) {
while (log2_size > 3 && (1 << (log2_size - 1)) >= min_size)
log2_size--;
return log2_size;
};
if (TW + TH >= 19) // smaller sizes aren't worth, they just create multiple entries in the textue cache and the saved memory is less
{
uvf += GSVector4(-0.5f, 0.5f).xxyy();
TW = ReduceLog2Size(min_width, TW);
TH = ReduceLog2Size(min_height, TH);
}
GSVector4i uv = GSVector4i(uvf.floor().xyzw(uvf.ceil()));
uv.x = findmax(uv.x, uv.z, (1 << tw) - 1, wms, minu, maxu);
uv.y = findmax(uv.y, uv.w, (1 << th) - 1, wmt, minv, maxv);
if (tw + th >= 19) // smaller sizes aren't worth, they just create multiple entries in the textue cache and the saved memory is less
if (WMS == CLAMP_REGION_CLAMP || WMS == CLAMP_REGION_REPEAT)
{
tw = reduce(uv.x, tw);
th = reduce(uv.y, th);
TW = ExtendLog2Size(min_width, TW);
}
if (wms == CLAMP_REGION_CLAMP || wms == CLAMP_REGION_REPEAT)
if (WMT == CLAMP_REGION_CLAMP || WMT == CLAMP_REGION_REPEAT)
{
tw = extend(uv.x, tw);
}
if (wmt == CLAMP_REGION_CLAMP || wmt == CLAMP_REGION_REPEAT)
{
th = extend(uv.y, th);
TH = ExtendLog2Size(min_height, TH);
}
GIFRegTEX0 res = TEX0;
res.TW = tw > 10 ? 0 : tw;
res.TH = th > 10 ? 0 : th;
res.TW = TW > 10 ? 0 : TW;
res.TH = TH > 10 ? 0 : TH;
if (TEX0.TW != res.TW || TEX0.TH != res.TH)
{
GL_DBG("FixedTEX0 %05x %d %d tw %d=>%d th %d=>%d st (%.0f,%.0f,%.0f,%.0f) uvmax %d,%d wm %d,%d (%d,%d,%d,%d)",
GL_DBG("FixedTEX0 %05x %d %d tw %d=>%d th %d=>%d uv (%d,%d,%d,%d) uvmax %d,%d wm %d,%d (%d,%d,%d,%d)",
(int)TEX0.TBP0, (int)TEX0.TBW, (int)TEX0.PSM,
(int)TEX0.TW, tw, (int)TEX0.TH, th,
uvf.x, uvf.y, uvf.z, uvf.w,
uv.x, uv.y,
wms, wmt, minu, maxu, minv, maxv);
(int)TEX0.TW, TW, (int)TEX0.TH, TH,
uv_rect.left, uv_rect.top, uv_rect.right, uv_rect.bottom,
min_width - 1, min_height - 1,
WMS, WMT, MINU, MAXU, MINV, MAXV);
}
return res;

View File

@ -44,7 +44,7 @@ public:
void UpdateScissor();
GIFRegTEX0 GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap = false) const;
GIFRegTEX0 GetSizeFixedTEX0(GSVector4i uv_rect, bool linear, bool mipmap = false) const;
void Dump(const std::string& filename);
};

View File

@ -3957,329 +3957,340 @@ __forceinline void GSState::VertexKick(u32 skip)
Flush(VERTEXCOUNT);
}
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
/// Also calculates the real min and max values seen after applying the region repeat to all values in min...max
static bool UsesRegionRepeat(int fix, int msk, int min, int max, int* min_out, int* max_out)
// Maps the range min .. max under the region repeat function determined by MSK and FIX.
// The region repeat function is f(x) = (x & MSK) | FIX.
// min_boundary and max_boundary return the same value: if any values are modified.
// i.e., does the region repeat have any effect on the values in min .. max?
void GSState::GetClampWrapMinMaxUVRegionRepeat(int MSK, int FIX, int min, int max, int* min_out, int* max_out, bool* min_boundary, bool* max_boundary)
{
if ((min < 0) != (max < 0))
if (min < 0 && 0 <= max)
{
// Algorithm doesn't work properly if bits overflow when incrementing (happens on the -1 → 0 crossing)
// Conveniently, crossing zero guarantees you use the full range
*min_out = fix;
*max_out = (fix | msk) + 1;
return true;
// If we cross from -1 to 0 combine the negative and positive parts separately
// as the below algorithm only works if min <= max as unsigned integers.
int min_out_1, max_out_1, min_out_2, max_out_2;
bool min_boundary_1, max_boundary_1, min_boundary_2, max_boundary_2;
GetClampWrapMinMaxUVRegionRepeat(MSK, FIX, min, -1, &min_out_1, &max_out_1, &min_boundary_1, &max_boundary_1);
GetClampWrapMinMaxUVRegionRepeat(MSK, FIX, 0, max, &min_out_2, &max_out_2, &min_boundary_2, &max_boundary_2);
*min_out = std::min(min_out_1, min_out_2);
*max_out = std::max(max_out_1, max_out_2);
*min_boundary = min_boundary_1 || min_boundary_2;
*max_boundary = max_boundary_1 || max_boundary_2;
}
else
{
// min, max both negative or non-negative
const int cleared_bits = ~msk & ~fix; // Bits that are always cleared by applying msk and fix
const int set_bits = fix; // Bits that are always set by applying msk and fix
unsigned long msb;
int variable_bits = min ^ max;
if (_BitScanReverse(&msb, variable_bits))
variable_bits |= (1 << msb) - 1; // Fill in all lower bits
const int cleared_bits = ~MSK & ~FIX; // Bits that are always cleared by applying msk and fix
const int set_bits = FIX; // Bits that are always set by applying msk and fix
unsigned long msb;
int variable_bits = min ^ max;
if (_BitScanReverse(&msb, variable_bits))
variable_bits |= (1 << msb) - 1; // Fill in all lower bits
const int always_set = min & ~variable_bits; // Bits that are set in every value in min...max
const int sometimes_set = min | variable_bits; // Bits that are set in at least one value in min...max
const int always_set = min & ~variable_bits; // Bits that are set in every value in min...max
const int sometimes_set = min | variable_bits; // Bits that are set in at least one value in min...max
const bool sets_bits = (set_bits | always_set) != always_set; // At least one bit in min...max is set by applying msk and fix
const bool clears_bits = (cleared_bits & sometimes_set) != 0; // At least one bit in min...max is cleared by applying msk and fix
const bool sets_bits = (set_bits | always_set) != always_set; // At least one bit in min...max is set by applying msk and fix
const bool clears_bits = (cleared_bits & sometimes_set) != 0; // At least one bit in min...max is cleared by applying msk and fix
const int overwritten_variable_bits = (cleared_bits | set_bits) & variable_bits;
// A variable bit that's `0` in `min` will at some point switch to a `1` (because it's variable)
// When it does, all bits below it will switch to a `0` (that's how incrementing works)
// If the 0 to 1 switch is reflected in the final output (not masked and not replaced by a fixed value),
// the final value would be larger than the previous. Otherwise, the final value will be less.
// The true minimum value is `min` with all bits below the most significant replaced variable `0` bit cleared
const int min_overwritten_variable_zeros = ~min & overwritten_variable_bits;
if (_BitScanReverse(&msb, min_overwritten_variable_zeros))
min &= (~0u << msb);
// Similar thing for max, but the first masked `1` bit
const int max_overwritten_variable_ones = max & overwritten_variable_bits;
if (_BitScanReverse(&msb, max_overwritten_variable_ones))
max |= (1 << msb) - 1;
const int overwritten_variable_bits = (cleared_bits | set_bits) & variable_bits;
// A variable bit that's `0` in `min` will at some point switch to a `1` (because it's variable)
// When it does, all bits below it will switch to a `0` (that's how incrementing works)
// If the 0 to 1 switch is reflected in the final output (not masked and not replaced by a fixed value),
// the final value would be larger than the previous. Otherwise, the final value will be less.
// The true minimum value is `min` with all bits below the most significant replaced variable `0` bit cleared
const int min_overwritten_variable_zeros = ~min & overwritten_variable_bits;
if (_BitScanReverse(&msb, min_overwritten_variable_zeros))
min &= (~0u << msb);
// Similar thing for max, but the first masked `1` bit
const int max_overwritten_variable_ones = max & overwritten_variable_bits;
if (_BitScanReverse(&msb, max_overwritten_variable_ones))
max |= (1 << msb) - 1;
*min_out = (msk & min) | fix;
*max_out = ((msk & max) | fix) + 1;
*min_out = (min & MSK) | FIX;
*max_out = (max & MSK) | FIX;
return sets_bits || clears_bits;
// Assume both boundaries are used if any wrapping occurs
*max_boundary = *min_boundary = sets_bits || clears_bits;
}
}
GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, bool linear, bool clamp_to_tsize)
// Get the min/max texel coordinate (U or V) assuming it takes the values min .. max and is then
// wrapped/clamped according to the mode WM.
// SIZE: Log2 width or height of texture (TH or TW)
// MIN/MAX: Either the clamping range (in REGION_CLAMP mode) or the MKS/FIX parameters (in REGION_REPEAT mode)
// Returns true if any of the values are changed. I.e., if f(x) is the mapping function for clamp/wrap mode,
// return true if f(x) != x for some x in [ min .. max ]
void GSState::GetClampWrapMinMaxUV(int SIZE, int WM, int MIN, int MAX, int min, int max, int* min_out, int* max_out, bool* min_boundary, bool* max_boundary)
{
// TODO: some of the +1s can be removed if linear == false
const int size = 1 << SIZE;
const int MSK = MIN;
const int FIX = MAX;
const int tw = TEX0.TW;
const int th = TEX0.TH;
const int w = 1 << tw;
const int h = 1 << th;
const int tw_mask = (1 << tw) - 1;
const int th_mask = (1 << th) - 1;
GSVector4i tr(0, 0, w, h);
const int wms = CLAMP.WMS;
const int wmt = CLAMP.WMT;
const int minu = (int)CLAMP.MINU;
const int minv = (int)CLAMP.MINV;
const int maxu = (int)CLAMP.MAXU;
const int maxv = (int)CLAMP.MAXV;
GSVector4i vr = tr;
switch (wms)
if (WM == CLAMP_REPEAT)
{
case CLAMP_REPEAT:
break;
case CLAMP_CLAMP:
break;
case CLAMP_REGION_CLAMP:
vr.x = minu;
vr.z = maxu + 1;
break;
case CLAMP_REGION_REPEAT:
vr.x = maxu;
vr.z = (maxu | minu) + 1;
break;
default:
ASSUME(0);
// If we cross the size boundary then we always get the largest/smallest possible wrapped value
if ((min & ~(size - 1)) != (max & ~(size - 1)))
{
*min_out = 0;
*max_out = size - 1;
*min_boundary = *max_boundary = true;
}
else
{
*min_out = min & (size - 1);
*max_out = max & (size - 1);
*min_boundary = *max_boundary = false;
}
}
switch (wmt)
else if (WM == CLAMP_CLAMP)
{
case CLAMP_REPEAT:
break;
case CLAMP_CLAMP:
break;
case CLAMP_REGION_CLAMP:
vr.y = minv;
vr.w = maxv + 1;
break;
case CLAMP_REGION_REPEAT:
vr.y = maxv;
vr.w = (maxv | minv) + 1;
break;
default:
ASSUME(0);
*min_out = std::max(0, std::min(size - 1, min));
*max_out = std::max(0, std::min(size - 1, max));
*min_boundary = min < 0;
*max_boundary = max > size - 1;
}
else if (WM == CLAMP_REGION_CLAMP)
{
*min_out = std::max(MIN, std::min(MAX, min));
*max_out = std::max(MIN, std::min(MAX, max));
*min_boundary = min < MIN;
*max_boundary = max > MAX;
}
else if (WM == CLAMP_REGION_REPEAT)
{
GetClampWrapMinMaxUVRegionRepeat(MSK, FIX, min, max, min_out, max_out, min_boundary, max_boundary);
}
// Software renderer fixes TEX0 so that TW/TH contain MAXU/MAXV.
// Hardware renderer doesn't, and handles it in the texture cache, so don't clamp here.
if (clamp_to_tsize)
vr = vr.rintersect(tr);
else
tr = tr.runion(vr);
{
pxAssertMsg(false, "Invalid clamp/wrap mode");
}
}
u8 uses_border = 0;
GSState::TextureMinMaxResult GSState::GetTextureMinMaxApprox(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, bool linear, bool no_wrap0)
{
const int TW = TEX0.TW;
const int TH = TEX0.TH;
if (m_vt.m_max.t.x >= FLT_MAX || m_vt.m_min.t.x <= -FLT_MAX ||
m_vt.m_max.t.y >= FLT_MAX || m_vt.m_min.t.y <= -FLT_MAX)
const int width = 1 << TW;
const int height = 1 << TH;
const int WMS = CLAMP.WMS;
const int WMT = CLAMP.WMT;
const int MINU = (int)CLAMP.MINU;
const int MINV = (int)CLAMP.MINV;
const int MAXU = (int)CLAMP.MAXU;
const int MAXV = (int)CLAMP.MAXV;
GSVector4i uvi; // Rectangle computed from UV bounds of vertices
u8 uses_border = 0; // Bit mask of TextureMinMaxResult::USES_BOUNDARY_* flags
GSVector4 uvf = m_vt.m_min.t.xyxy(m_vt.m_max.t); // Contains min, max of texture coordinates (Umin, Vmin, Umax, Vmax)
// FIXME: Replace this with a flag that says "huge or nans" detected in texture coords.
if (uvf.right >= FLT_MAX || uvf.left <= -FLT_MAX || uvf.bottom >= FLT_MAX || uvf.top <= -FLT_MAX)
{
// If any of the min/max values are +-FLT_MAX we can't rely on them
// so just assume full texture.
// so just assume full texture and all borders.
uvi = GSVector4i(0, 0, width - 1, height - 1);
uses_border = 0xF;
}
else
{
// Optimisation aims to reduce the amount of texture loaded to only the bit which will be read
GSVector4 st = m_vt.m_min.t.xyxy(m_vt.m_max.t);
if (linear)
uvf += GSVector4(-0.5f, -0.5f, 0.5f, 0.5f);
uvi = GSVector4i(uvf.floor());
// This is a hack to prevent coordinates from wrapping in certain effects.
// Games sometimes draw a sprite or triangle while sampling a rectangle of form (0, 0, u, v) of the
// current texture (where u < 2**TW, v < 2**TH). If linear filtering is enabled with repeat or region repeat,
// the left/top 0s can become -1 and wrap to 2**TW and 2**TH. While this is technically
// PS2 accurate, it causes problems with the texture caches, which may have allocated
// less for the actual texture, and will need to resize texture to an unwanted
// larger size. This can lead to garbage data in the borders of the texture and break
// graphics later on. To prevent this, we force the left/top coordinates to 0.
if (linear && no_wrap0)
{
st += GSVector4(-0.5f, 0.5f).xxyy();
if (uvi.left == -1 && uvf.left >= 0 && (CLAMP.WMS == CLAMP_REPEAT || CLAMP.WMS == CLAMP_REGION_REPEAT))
uvi.left = 0;
if (uvi.top == -1 && uvf.top >= 0 && (CLAMP.WMT == CLAMP_REPEAT || CLAMP.WMT == CLAMP_REGION_REPEAT))
uvi.top = 0;
}
bool min_boundary_u, min_boundary_v, max_boundary_u, max_boundary_v;
GetClampWrapMinMaxUV(TW, WMS, MINU, MAXU, uvi.left, uvi.right, &uvi.left, &uvi.right, &min_boundary_u, &max_boundary_u);
GetClampWrapMinMaxUV(TH, WMT, MINV, MAXV, uvi.top, uvi.bottom, &uvi.top, &uvi.bottom, &min_boundary_v, &max_boundary_v);
if (min_boundary_u)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_LEFT;
if (max_boundary_u)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_RIGHT;
if (min_boundary_v)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_TOP;
if (max_boundary_v)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_BOTTOM;
}
uvi += GSVector4i(0, 0, 1, 1); // Rectangles have exclusive endpoints by convention
return {uvi, uses_border};
}
GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, GSVector4i scissor, bool linear, bool nowrap0)
{
// Calculate the range of UV coordinates of the texture that are used for the draw.
// First try to do exact UV calculation in the case of axis-aligned triangles or sprites.
// If this fails, do a rough UV calculation.
TextureMinMaxResult tmm_result;
if (GetTextureMinMaxAxisAligned(TEX0, CLAMP, scissor, linear, nowrap0, &tmm_result))
return tmm_result;
else
return GetTextureMinMaxApprox(TEX0, CLAMP, linear, nowrap0);
}
void GSState::GetTextureMinMaxAxisAlignedHelper(
GIFRegTEX0 TEX0, GSVector4i scissor, u32 fst, const GSVertex* vertex, u16 index0, u16 index1, GSVector4* out)
{
const u16 index[2] = { index0, index1 };
const int ix0 = (vertex[index[0]].XYZ.X <= vertex[index[1]].XYZ.X) ? 0 : 1;
const int iy0 = (vertex[index[0]].XYZ.Y <= vertex[index[1]].XYZ.Y) ? 0 : 1;
const int ix1 = 1 - ix0;
const int iy1 = 1 - iy0;
float x0 = static_cast<float>(vertex[index[ix0]].XYZ.X - m_xyof.x) / 16.0f;
float y0 = static_cast<float>(vertex[index[iy0]].XYZ.Y - m_xyof.y) / 16.0f;
float x1 = static_cast<float>(vertex[index[ix1]].XYZ.X - m_xyof.x) / 16.0f;
float y1 = static_cast<float>(vertex[index[iy1]].XYZ.Y - m_xyof.y) / 16.0f;
float u0, u1, v0, v1;
if (fst)
{
u0 = static_cast<float>(vertex[index[ix0]].U) / 16.0f;
v0 = static_cast<float>(vertex[index[iy0]].V) / 16.0f;
u1 = static_cast<float>(vertex[index[ix1]].U) / 16.0f;
v1 = static_cast<float>(vertex[index[iy1]].V) / 16.0f;
}
else
{
u0 = vertex[index[ix0]].ST.S / vertex[index[ix0]].RGBAQ.Q * (1 << TEX0.TW);
v0 = vertex[index[iy0]].ST.T / vertex[index[iy0]].RGBAQ.Q * (1 << TEX0.TH);
u1 = vertex[index[ix1]].ST.S / vertex[index[ix1]].RGBAQ.Q * (1 << TEX0.TW);
v1 = vertex[index[iy1]].ST.T / vertex[index[iy1]].RGBAQ.Q * (1 << TEX0.TH);
}
// Pixel center coordinates.
float px0 = std::ceil(x0);
float py0 = std::ceil(y0);
float px1 = std::floor(x1);
float py1 = std::floor(y1);
// Exclude right/bottom edges
if (px1 == x1)
px1 = std::max(px0, px1 - 1);
if (py1 == y1)
py1 = std::max(py0, py1 - 1);
// Scissoring.
px0 = std::clamp(px0, static_cast<float>(scissor.x), static_cast<float>(scissor.z));
py0 = std::clamp(py0, static_cast<float>(scissor.y), static_cast<float>(scissor.w));
px1 = std::clamp(px1, static_cast<float>(scissor.x), static_cast<float>(scissor.z));
py1 = std::clamp(py1, static_cast<float>(scissor.y), static_cast<float>(scissor.w));
px1 = std::max(px0, px1);
py1 = std::max(py0, py1);
const float u0_interp = ((x1 - px0) * u0 + (px0 - x0) * u1) / (x1 - x0);
const float v0_interp = ((y1 - py0) * v0 + (py0 - y0) * v1) / (y1 - y0);
const float u1_interp = ((x1 - px1) * u0 + (px1 - x0) * u1) / (x1 - x0);
const float v1_interp = ((y1 - py1) * v0 + (py1 - y0) * v1) / (y1 - y0);
const float u_min = std::min(u0_interp, u1_interp);
const float v_min = std::min(v0_interp, v1_interp);
const float u_max = std::max(u0_interp, u1_interp);
const float v_max = std::max(v0_interp, v1_interp);
out->x = std::min(out->x, u_min);
out->y = std::min(out->y, v_min);
out->z = std::max(out->z, u_max);
out->w = std::max(out->w, v_max);
}
bool GSState::GetTextureMinMaxAxisAligned(
GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, GSVector4i scissor, bool linear, bool nowrap0, GSState::TextureMinMaxResult* result)
{
const GS_PRIM_CLASS primclass = GSUtil::GetPrimClass(PRIM->PRIM);
GSVector4 uvf(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
const GSVertex* const vertex = m_vertex.buff;
const u16* const index = m_index.buff;
const u32 fst = PRIM->FST;
if (primclass == GS_PRIM_CLASS::GS_TRIANGLE_CLASS)
{
const auto IsTriangleRight = fst ? GSUtil::IsTriangleRight<1, 1> : GSUtil::IsTriangleRight<1, 0>;
for (u32 i = 0; i < m_index.tail; i += 3)
{
const u16* const idx = &index[i];
// If it's the start of the texture and our little adjustment is all that pushed it over, clamp it to 0.
// This stops the border check failing when using repeat but needed less than the full texture
// since this was making it take the full texture even though it wasn't needed.
if (!clamp_to_tsize)
{
const u32 mask = (m_vt.m_min.t.floor() == GSVector4::zero()).mask();
if (mask & 1) // X == 0
st.x = st.max(GSVector4::zero()).x;
if (mask & 2) // Y == 0
st.y = st.max(GSVector4::zero()).y;
}
GSUtil::TriangleOrdering order;
if (!IsTriangleRight(vertex, &idx[0], &order))
return false;
// We ignore the right angle corner of the triangle (order.b) and just pass the acute angle corners,
// (order.a and order.c) since this is all we need to infer the limits of the UV coordinates.
GetTextureMinMaxAxisAlignedHelper(TEX0, scissor, fst, vertex, idx[order.a], idx[order.c], &uvf);
}
}
else if (primclass == GS_PRIM_CLASS::GS_SPRITE_CLASS)
{
for (u32 i = 0; i < m_index.tail; i += 2)
GetTextureMinMaxAxisAlignedHelper(TEX0, scissor, fst, vertex, index[i], index[i + 1], &uvf);
}
else
{
return false;
}
// draw will get scissored, adjust UVs to suit
const GSVector2 pos_range(std::max(m_vt.m_max.p.x - m_vt.m_min.p.x, 1.0f), std::max(m_vt.m_max.p.y - m_vt.m_min.p.y, 1.0f));
const GSVector2 uv_range(m_vt.m_max.t.x - m_vt.m_min.t.x, m_vt.m_max.t.y - m_vt.m_min.t.y);
const GSVector2 grad(uv_range / pos_range);
// Adjust texture range when sprites get scissor clipped. Since we linearly interpolate, this
// optimization doesn't work when perspective correction is enabled.
// Allowing for quads when the gradiant is 1. It's not guaranteed (would need to check the grandient on each vector), but should be close enough.
if (m_primitive_covers_without_gaps != NoGapsType::GapsFound && (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && grad.x == 1.0f && grad.y == 1.0f && TrianglesAreQuads(false))))
{
// When coordinates are fractional, GS appears to draw to the right/bottom (effectively
// taking the ceiling), not to the top/left (taking the floor).
const GSVector4i int_rc(m_vt.m_min.p.ceil().xyxy(m_vt.m_max.p.floor()));
const GSVector4i scissored_rc(int_rc.rintersect(m_context->scissor.in));
if (!int_rc.eq(scissored_rc))
{
// Convert to integer coordinates.
const GSVector4 round_offsets = linear ? GSVector4(-0.5f, -0.5f, 0.5f, 0.5f) : GSVector4::cxpr(0.0f);
GSVector4i uvi = GSVector4i((uvf + round_offsets).floor());
const GSVertex* vert_first = &m_vertex.buff[m_index.buff[0]];
const GSVertex* vert_second = &m_vertex.buff[m_index.buff[1]];
const GSVertex* vert_third = &m_vertex.buff[m_index.buff[2]];
if (linear && nowrap0)
{
// This is a hack to prevent coordinates from wrapping in certain effects.
// Games sometimes draw a sprite or triangle while sampling a rectangle of form (0, 0, u, v) of the
// current texture (where u < 2**TW, v < 2**TH). If linear filtering is enabled with repeat or region repeat,
// the left/top 0s can become -1 and wrap to 2**TW and 2**TH. While this is technically
// PS2 accurate, it causes problems with the texture caches, which may have allocated
// less for the actual texture, and will need to resize texture to an unwanted
// larger size. This can lead to garbage data in the borders of the texture and break
// graphics later on. To prevent this, we force the left/top coordinates to 0.
if (uvi.x == -1 && uvf.x >= 0 && (CLAMP.WMS == CLAMP_REPEAT || CLAMP.WMS == CLAMP_REGION_REPEAT))
uvi.x = 0;
if (uvi.y == -1 && uvf.y >= 0 && (CLAMP.WMT == CLAMP_REPEAT || CLAMP.WMT == CLAMP_REGION_REPEAT))
uvi.y = 0;
}
GSVector4 new_st = st;
bool u_forward_check = false;
bool x_forward_check = false;
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
u_forward_check = PRIM->FST ? ((vert_first->U < vert_second->U) || (vert_first->U < vert_third->U)) : (((vert_first->ST.S / vert_first->RGBAQ.Q) < (vert_second->ST.S / vert_second->RGBAQ.Q)) || ((vert_first->ST.S / vert_first->RGBAQ.Q) < (vert_third->ST.S / vert_third->RGBAQ.Q)));
x_forward_check = (vert_first->XYZ.X < vert_second->XYZ.X) || (vert_first->XYZ.X < vert_third->XYZ.X);
}
else
{
u_forward_check = PRIM->FST ? (vert_first->U < vert_second->U) : ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_first->RGBAQ.Q));
x_forward_check = vert_first->XYZ.Y < vert_second->XYZ.Y;
}
// Check if the UV coords are going in a different direction to the verts, if they match direction, no need to swap
const bool u_forward = u_forward_check;
const bool x_forward = x_forward_check;
const bool swap_x = u_forward != x_forward;
if (int_rc.left < scissored_rc.left)
{
if (!swap_x)
new_st.x += floor(static_cast<float>(scissored_rc.left - int_rc.left) * grad.x);
else
new_st.z -= floor(static_cast<float>(scissored_rc.left - int_rc.left) * grad.x);
}
if (int_rc.right > scissored_rc.right)
{
if (!swap_x)
new_st.z -= floor(static_cast<float>(int_rc.right - scissored_rc.right) * grad.x);
else
new_st.x += floor(static_cast<float>(int_rc.right - scissored_rc.right) * grad.x);
}
// we need to check that it's not going to repeat over the non-clipped part
if (wms != CLAMP_REGION_REPEAT && (wms != CLAMP_REPEAT || (static_cast<int>(new_st.x) & ~tw_mask) == (static_cast<int>(new_st.z - 1) & ~tw_mask)))
{
st.x = new_st.x;
st.z = new_st.z;
}
bool v_forward_check = false;
bool y_forward_check = false;
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
v_forward_check = PRIM->FST ? ((vert_first->V < vert_second->V) || (vert_first->V < vert_third->V)) : (((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_second->RGBAQ.Q)) || ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_third->ST.T / vert_third->RGBAQ.Q)));
y_forward_check = (vert_first->XYZ.Y < vert_second->XYZ.Y) || (vert_first->XYZ.Y < vert_third->XYZ.Y);
}
else
{
v_forward_check = PRIM->FST ? (vert_first->V < vert_second->V) : ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_first->RGBAQ.Q));
y_forward_check = vert_first->XYZ.Y < vert_second->XYZ.Y;
}
const bool v_forward = v_forward_check;
const bool y_forward = y_forward_check;
const bool swap_y = v_forward != y_forward;
if (int_rc.top < scissored_rc.top)
{
if (!swap_y)
new_st.y += floor(static_cast<float>(scissored_rc.top - int_rc.top) * grad.y);
else
new_st.w -= floor(static_cast<float>(scissored_rc.top - int_rc.top) * grad.y);
}
if (int_rc.bottom > scissored_rc.bottom)
{
if (!swap_y)
new_st.w -= floor(static_cast<float>(int_rc.bottom - scissored_rc.bottom) * grad.y);
else
new_st.y += floor(static_cast<float>(int_rc.bottom - scissored_rc.bottom) * grad.y);
}
if (wmt != CLAMP_REGION_REPEAT && (wmt != CLAMP_REPEAT || (static_cast<int>(new_st.y) & ~th_mask) == (static_cast<int>(new_st.w - 1) & ~th_mask)))
{
st.y = new_st.y;
st.w = new_st.w;
}
}
}
const GSVector4i uv = GSVector4i(st.floor());
uses_border = GSVector4::cast((uv < vr).blend32<0xc>(uv >= vr)).mask();
// Need to make sure we don't oversample, this can cause trouble in grabbing textures.
// This may be inaccurate depending on the draw, but adding 1 all the time is wrong too.
// FIXME: It breaks sw renderer so let's still use 1 for SW mode for now.
const int inclusive_x_req = GSIsHardwareRenderer() ? (((m_vt.m_primclass < GS_TRIANGLE_CLASS) || (grad.x < 1.0f || (grad.x == 1.0f && m_vt.m_max.p.x != floor(m_vt.m_max.p.x)))) ? 1 : 0) : 1;
const int inclusive_y_req = GSIsHardwareRenderer() ? (((m_vt.m_primclass < GS_TRIANGLE_CLASS) || (grad.y < 1.0f || (grad.y == 1.0f && m_vt.m_max.p.y != floor(m_vt.m_max.p.y)))) ? 1 : 0) : 1;
bool left_boundary, top_boundary, right_boundary, bottom_boundary;
GetClampWrapMinMaxUV(TEX0.TW, CLAMP.WMS, CLAMP.MINU, CLAMP.MAXU, uvi.x, uvi.z,
&uvi.x, &uvi.z, &left_boundary, &right_boundary);
GetClampWrapMinMaxUV(TEX0.TH, CLAMP.WMT, CLAMP.MINV, CLAMP.MAXV, uvi.y, uvi.w,
&uvi.y, &uvi.w, &top_boundary, &bottom_boundary);
// Roughly cut out the min/max of the read (Clamp)
switch (wms)
{
case CLAMP_REPEAT:
if ((uv.x & ~tw_mask) == (uv.z & ~tw_mask))
{
vr.x = std::max(vr.x, uv.x & tw_mask);
vr.z = std::min(vr.z, (uv.z & tw_mask) + inclusive_x_req);
}
break;
case CLAMP_CLAMP:
case CLAMP_REGION_CLAMP:
if (vr.x < uv.x)
vr.x = std::min(uv.x, vr.z - 1);
if (vr.z > (uv.z + 1))
vr.z = std::max(uv.z, vr.x) + inclusive_x_req;
break;
case CLAMP_REGION_REPEAT:
if (UsesRegionRepeat(maxu, minu, uv.x, uv.z, &vr.x, &vr.z) || maxu >= tw)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_U;
break;
}
switch (wmt)
{
case CLAMP_REPEAT:
if ((uv.y & ~th_mask) == (uv.w & ~th_mask))
{
vr.y = std::max(vr.y, uv.y & th_mask);
vr.w = std::min(vr.w, (uv.w & th_mask) + inclusive_y_req);
}
break;
case CLAMP_CLAMP:
case CLAMP_REGION_CLAMP:
if (vr.y < uv.y)
vr.y = std::min(uv.y, vr.w - 1);
if (vr.w > (uv.w + 1))
vr.w = std::max(uv.w, vr.y) + inclusive_y_req;
break;
case CLAMP_REGION_REPEAT:
if (UsesRegionRepeat(maxv, minv, uv.y, uv.w, &vr.y, &vr.w) || maxv >= th)
uses_border |= TextureMinMaxResult::USES_BOUNDARY_V;
break;
}
}
vr = vr.rintersect(tr);
// This really shouldn't happen now except with the clamping region set entirely outside the texture,
// special handling should be written for that case.
if (vr.rempty())
{
// NOTE: this can happen when texcoords are all outside the texture or clamping area is zero, but we can't
// let the texture cache update nothing, the sampler will still need a single texel from the border somewhere
// examples:
// - THPS (no visible problems)
// - NFSMW (strange rectangles on screen, might be unrelated)
// - Lupin 3rd (huge problems, textures sizes seem to be randomly specified)
const bool inc_x = vr.x < tr.z;
const bool inc_y = vr.y < tr.w;
vr = (vr + GSVector4i(inc_x ? 0 : -1, inc_y ? 0 : -1, inc_x ? 1 : 0, inc_y ? 1 : 0)).rintersect(tr);
}
else if (vr.xxzz().rempty())
{
const bool inc_x = vr.x < tr.z;
vr = (vr + GSVector4i(inc_x ? 0 : -1, 0, inc_x ? 1 : 0, 0)).rintersect(tr);
}
else if (vr.yyww().rempty())
{
const bool inc_y = vr.y < tr.w;
vr = (vr + GSVector4i(0, inc_y ? 0 : -1, 0, inc_y ? 1 : 0)).rintersect(tr);
}
return { vr, uses_border };
result->coverage = GSVector4i(uvi.left, uvi.top, uvi.right + 1, uvi.bottom + 1); // Use exclusive coordinates for right/bottom.
result->uses_boundary = 0;
result->uses_boundary |= left_boundary ? TextureMinMaxResult::USES_BOUNDARY_LEFT : 0;
result->uses_boundary |= top_boundary ? TextureMinMaxResult::USES_BOUNDARY_TOP : 0;
result->uses_boundary |= right_boundary ? TextureMinMaxResult::USES_BOUNDARY_RIGHT : 0;
result->uses_boundary |= bottom_boundary ? TextureMinMaxResult::USES_BOUNDARY_BOTTOM : 0;
return true;
}
void GSState::CalcAlphaMinMax(const int tex_alpha_min, const int tex_alpha_max)

View File

@ -22,9 +22,12 @@ public:
virtual ~GSState();
static constexpr int GetSaveStateSize(int version);
static void GetClampWrapMinMaxUVRegionRepeat(int MSK, int FIX, int min, int max, int* min_out, int* max_out, bool* min_boundary, bool* max_boundary);
static void GetClampWrapMinMaxUV(int SIZE, int WM, int MIN, int MAX, int min, int max, int* min_out, int* max_out, bool* min_boundary, bool* max_boundary);
private:
// RESTRICT prevents multiple loads of the same part of the register when accessing its bitfields (the compiler is happy to know that memory writes in-between will not go there)
// RESTRICT prevents multiple loads of the same part of the register when accessing its bitfields
// (the compiler is happy to know that memory writes in-between will not go there)
typedef void (GSState::*GIFPackedRegHandler)(const GIFPackedReg* RESTRICT r);
@ -133,6 +136,10 @@ protected:
GSVector4i m_scissor_cull_max = {};
GSVector4i m_xyof = {};
// head: first vertex of the current primitive
// points to "center" vertex of a triangle fan
// tail: last vertex of the current primitive + 1
// next: last vertex of the previous primitive + 1
struct
{
GSVertex* buff;
@ -199,7 +206,10 @@ protected:
GSVector4i coverage; ///< Part of the texture used
u8 uses_boundary; ///< Whether or not the usage touches the left, top, right, or bottom edge (and therefore needs wrap modes preserved)
};
TextureMinMaxResult GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, bool linear, bool clamp_to_tsize);
TextureMinMaxResult GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, GSVector4i scissor, bool linear, bool nowrap0);
TextureMinMaxResult GetTextureMinMaxApprox(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, bool linear, bool nowrap0);
void GetTextureMinMaxAxisAlignedHelper(GIFRegTEX0 TEX0, GSVector4i scissor, u32 fst, const GSVertex* vertex, u16 index0, u16 index1, GSVector4* minmax);
bool GetTextureMinMaxAxisAligned(GIFRegTEX0 TEX0, GIFRegCLAMP CLAMP, GSVector4i scissor, bool linear, bool nowrap0, GSState::TextureMinMaxResult* result);
bool TryAlphaTest(u32& fm, u32& zm);
bool IsOpaque();
bool IsMipMapDraw();

View File

@ -214,6 +214,180 @@ GSRendererType GSUtil::GetPreferredRenderer()
return preferred_renderer;
}
// Helper struct for IsTriangleRight and AreTrianglesRight
struct ComparisonResult
{
u8 value;
u8 FinalCmp() const { return value & 3; }
constexpr ComparisonResult(u8 final_cmp, u8 final_order)
: value(final_cmp | (final_order << 2))
{
}
GSUtil::TriangleOrdering FinalOrder() const
{
struct alignas(2) TriangleOrderingBC
{
u8 b;
u8 c;
};
alignas(16) static constexpr TriangleOrderingBC order_lut[6] =
{
TriangleOrderingBC{/*a=0,*/ 1, 2},
TriangleOrderingBC{/*a=0,*/ 2, 1},
TriangleOrderingBC{/*a=1,*/ 0, 2},
TriangleOrderingBC{/*a=1,*/ 2, 0},
TriangleOrderingBC{/*a=2,*/ 0, 1},
TriangleOrderingBC{/*a=2,*/ 1, 0},
};
u32 order = static_cast<u32>(value) >> 2;
TriangleOrderingBC bc = order_lut[order];
return {order >> 1, bc.b, bc.c};
}
};
// Helper table for IsTriangleRight/AreTrianglesRight functions
static constexpr ComparisonResult comparison_lut[16] =
{
ComparisonResult(0, 0), // 0000 => None equal, no sprite possible
ComparisonResult(2, 0), // 0001 => x0 = x1, requires y1 = y2
ComparisonResult(1, 5), // 0010 => y0 = y1, requires x1 = x2
ComparisonResult(2, 0), // 0011 => x0 = x1, y0 = y1, (no area) requires x1 = x2 or y1 = y2
ComparisonResult(2, 1), // 0100 => x0 = x2, requires y1 = y2
ComparisonResult(2, 0), // 0101 => x0 = x1, x0 = x2, (no area) requires y1 = y2
ComparisonResult(0, 4), // 0110 => y0 = y1, x0 = x2, requires nothing
ComparisonResult(0, 4), // 0111 => x0 = y1, y0 = y1, x0 = x2, (no area) requires nothing
ComparisonResult(1, 3), // 1000 => y0 = y2, requires x1 = x2
ComparisonResult(0, 2), // 1001 => x0 = x1, y0 = y2, requires nothing
ComparisonResult(1, 3), // 1010 => y0 = y1, y0 = y2, (no area) requires x1 = x2
ComparisonResult(0, 2), // 1011 => x0 = x1, y0 = y1, y0 = y2, (unlikely) requires nothing
ComparisonResult(2, 1), // 1100 => x0 = x2, y0 = y2, (no area) requires x1 = x2 or y1 = y2
ComparisonResult(0, 2), // 1101 => x0 = x1, x0 = x2, y0 = y2, (no area) requires nothing
ComparisonResult(0, 4), // 1110 => y0 = y1, x0 = x2, y0 = y2, (no area) requires nothing
ComparisonResult(0, 2), // 1111 => x0 = x1, y0 = y1, x0 = x2, y0 = y2, (no area) requires nothing
};
template <u32 tme, u32 fst>
bool GSUtil::AreTrianglesRight(const GSVertex* RESTRICT vin, const u16* index0, const u16* index1,
TriangleOrdering* out_triangle0, TriangleOrdering* out_triangle1)
{
GSVector4i mask;
if (tme && fst)
{
// Compare xy and uv together
mask = GSVector4i::cxpr8(
(s8)0, (s8)1, (s8)8, (s8)9,
(s8)2, (s8)3, (s8)10, (s8)11,
(s8)0, (s8)1, (s8)8, (s8)9,
(s8)2, (s8)3, (s8)10, (s8)11);
}
else
{
// ignore uv, compare st instead later
mask = GSVector4i::cxpr8(
(s8)0, (s8)1, (s8)0x80, (s8)0x80,
(s8)2, (s8)3, (s8)0x80, (s8)0x80,
(s8)0, (s8)1, (s8)0x80, (s8)0x80,
(s8)2, (s8)3, (s8)0x80, (s8)0x80);
}
GSVector4i xy0 = GSVector4i(vin[index0[0]].m[1]).shuffle8(mask); // Triangle 0 vertex 0
GSVector4i xy1 = GSVector4i(vin[index0[1]].m[1]).shuffle8(mask); // Triangle 0 vertex 1
GSVector4i xy2 = GSVector4i(vin[index0[2]].m[1]).shuffle8(mask); // Triangle 0 vertex 2
GSVector4i xy3 = GSVector4i(vin[index1[0]].m[1]).shuffle8(mask); // Triangle 1 vertex 0
GSVector4i xy4 = GSVector4i(vin[index1[1]].m[1]).shuffle8(mask); // Triangle 1 vertex 1
GSVector4i xy5 = GSVector4i(vin[index1[2]].m[1]).shuffle8(mask); // Triangle 1 vertex 2
GSVector4i vcmp0 = xy0.eq32(xy1.upl64(xy2));
GSVector4i vcmp1 = xy3.eq32(xy4.upl64(xy5));
GSVector4i vcmp2 = xy1.upl64(xy4).eq32(xy2.upl64(xy5));
if (tme && !fst)
{
// do the st comparisons
GSVector4 st0 = GSVector4::cast(GSVector4i(vin[index0[0]].m[0]));
GSVector4 st1 = GSVector4::cast(GSVector4i(vin[index0[1]].m[0]));
GSVector4 st2 = GSVector4::cast(GSVector4i(vin[index0[2]].m[0]));
GSVector4 st3 = GSVector4::cast(GSVector4i(vin[index1[0]].m[0]));
GSVector4 st4 = GSVector4::cast(GSVector4i(vin[index1[1]].m[0]));
GSVector4 st5 = GSVector4::cast(GSVector4i(vin[index1[2]].m[0]));
vcmp0 = vcmp0 & GSVector4i::cast(st0.xyxy() == st1.upld(st2));
vcmp1 = vcmp1 & GSVector4i::cast(st3.xyxy() == st4.upld(st5));
vcmp2 = vcmp2 & GSVector4i::cast(st1.upld(st4) == st2.upld(st5));
}
int cmp0 = GSVector4::cast(vcmp0).mask();
int cmp1 = GSVector4::cast(vcmp1).mask();
int cmp2 = GSVector4::cast(vcmp2).mask();
if (!cmp0 || !cmp1) // Either triangle 0 or triangle 1 isn't a right triangle
return false;
ComparisonResult triangle0cmp = comparison_lut[cmp0];
ComparisonResult triangle1cmp = comparison_lut[cmp1];
int required_cmp2 = triangle0cmp.FinalCmp() | (triangle1cmp.FinalCmp() << 2);
if ((cmp2 & required_cmp2) != required_cmp2)
return false;
// Both t0 and t1 are right triangles!
*out_triangle0 = triangle0cmp.FinalOrder();
*out_triangle1 = triangle1cmp.FinalOrder();
return true;
}
template <u32 tme, u32 fst>
bool GSUtil::IsTriangleRight(const GSVertex* RESTRICT vin, const u16* index, TriangleOrdering* out_triangle)
{
GSVector4i mask;
if (tme && fst)
{
// Compare xy and uv together
mask = GSVector4i::cxpr8(
(s8)0, (s8)1, (s8) 8, (s8) 9,
(s8)2, (s8)3, (s8)10, (s8)11,
(s8)0, (s8)1, (s8) 8, (s8) 9,
(s8)2, (s8)3, (s8)10, (s8)11);
}
else
{
// ignore uv, compare st instead later
mask = GSVector4i::cxpr8(
(s8)0, (s8)1, (s8)0x80, (s8)0x80,
(s8)2, (s8)3, (s8)0x80, (s8)0x80,
(s8)0, (s8)1, (s8)0x80, (s8)0x80,
(s8)2, (s8)3, (s8)0x80, (s8)0x80);
}
GSVector4i xy0 = GSVector4i(vin[index[0]].m[1]).shuffle8(mask); // Triangle 0 vertex 0
GSVector4i xy1 = GSVector4i(vin[index[1]].m[1]).shuffle8(mask); // Triangle 0 vertex 1
GSVector4i xy2 = GSVector4i(vin[index[2]].m[1]).shuffle8(mask); // Triangle 0 vertex 2
GSVector4i vcmp0 = xy0.eq32(xy1.upl64(xy2));
GSVector4i vcmp1 = xy1.eq32(xy2); // ignore top 64 bits
if (tme && !fst)
{
// do the st comparisons
GSVector4 st0 = GSVector4::cast(GSVector4i(vin[index[0]].m[0]));
GSVector4 st1 = GSVector4::cast(GSVector4i(vin[index[1]].m[0]));
GSVector4 st2 = GSVector4::cast(GSVector4i(vin[index[2]].m[0]));
vcmp0 = vcmp0 & GSVector4i::cast(st0.xyxy() == st1.upld(st2));
vcmp1 = vcmp1 & GSVector4i::cast(st1 == st2); // ignore top 64 bits
}
int cmp0 = GSVector4::cast(vcmp0).mask();
int cmp1 = GSVector4::cast(vcmp1).mask() & 0x3;
if (!cmp0) // Either triangle 0 or triangle 1 isn't a right triangle
return false;
ComparisonResult trianglecmp = comparison_lut[cmp0];
int required_cmp1 = trianglecmp.FinalCmp();
if (cmp1 != required_cmp1)
return false;
// Both t0 and t1 are right triangles!
*out_triangle = trianglecmp.FinalOrder();
return true;
}
// Instantiate the template functions for Is/AreTrianglesRight
template bool GSUtil::AreTrianglesRight<0, 0>(const GSVertex* RESTRICT, const u16*, const u16*, TriangleOrdering*, TriangleOrdering*);
template bool GSUtil::AreTrianglesRight<1, 0>(const GSVertex* RESTRICT, const u16*, const u16*, TriangleOrdering*, TriangleOrdering*);
template bool GSUtil::AreTrianglesRight<0, 1>(const GSVertex* RESTRICT, const u16*, const u16*, TriangleOrdering*, TriangleOrdering*);
template bool GSUtil::AreTrianglesRight<1, 1>(const GSVertex* RESTRICT, const u16*, const u16*, TriangleOrdering*, TriangleOrdering*);
template bool GSUtil::IsTriangleRight<0, 0>(const GSVertex* RESTRICT, const u16*, TriangleOrdering*);
template bool GSUtil::IsTriangleRight<1, 0>(const GSVertex* RESTRICT, const u16*, TriangleOrdering*);
template bool GSUtil::IsTriangleRight<0, 1>(const GSVertex* RESTRICT, const u16*, TriangleOrdering*);
template bool GSUtil::IsTriangleRight<1, 1>(const GSVertex* RESTRICT, const u16*, TriangleOrdering*);
const char* GSUtil::GetPSMName(int psm)
{
switch (psm)

View File

@ -5,6 +5,7 @@
#include "GS.h"
#include "GSRegs.h"
#include "GS/Renderers/Common/GSVertex.h"
class GSUtil
{
@ -63,4 +64,24 @@ public:
{
return GetClassVertexCount(GetPrimClass(prim));
}
// For returning order of vertices to form a right triangle
struct TriangleOrdering
{
// Describes a right triangle laid out in one of the following orientations
// b c | c b | a | a
// a | a | b c | c b
u32 a; // Same x as b
u32 b; // Same x as a, same y as c
u32 c; // Same y as b
};
// Determines ordering of two triangles in parallel if both are right.
// More efficient than calling IsTriangleRight twice.
template <u32 tme, u32 fst>
static bool AreTrianglesRight(const GSVertex* RESTRICT vin, const u16* index0, const u16* index1,
TriangleOrdering* out_triangle0, TriangleOrdering* out_triangle1);
// Determines ordering of a single triangle
template <u32 tme, u32 fst>
static bool IsTriangleRight(const GSVertex* RESTRICT vin, const u16* index, TriangleOrdering* out_triangle);
};

View File

@ -18,14 +18,68 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
if (i_count == 0)
return;
const GSDrawingContext* context = m_state->m_context;
m_primclass = primclass;
// Setup selector parameters for FindMinMax()
const u32 iip = m_state->PRIM->IIP;
const u32 tme = m_state->PRIM->TME;
const u32 fst = m_state->PRIM->FST;
const u32 color = !(m_state->PRIM->TME && m_state->m_context->TEX0.TFX == TFX_DECAL && m_state->m_context->TEX0.TCC);
const u32 color = !(m_state->PRIM->TME && context->TEX0.TFX == TFX_DECAL && context->TEX0.TCC);
m_fmm[color][fst][tme][iip][primclass](*this, vertex, index, i_count);
// Select correct FindMinMax() function to calculaute raw min/max values
GSVector4 tmin, tmax;
GSVector4i cmin, cmax, pmin, pmax;
m_fmm[color][fst][tme][iip][primclass](vertex, index, i_count, tmin, tmax, cmin, cmax, pmin, pmax);
// Set m_min and m_max values based on the raw min/max values
const GSVector4 offset(m_state->m_context->XYOFFSET);
const GSVector4 pscale(1.0f / 16, 1.0f / 16, 0.0f, 1.0f);
m_min.p = (GSVector4(pmin) - offset) * pscale;
m_max.p = (GSVector4(pmax) - offset) * pscale;
// Do Z separately, requires unsigned int conversion
m_min.p.z = static_cast<float>(static_cast<u32>(pmin.z));
m_max.p.z = static_cast<float>(static_cast<u32>(pmax.z));
m_min.t = GSVector4::zero();
m_max.t = GSVector4::zero();
m_min.c = GSVector4i::zero();
m_max.c = GSVector4i::zero();
if (tme)
{
GSVector4 tscale;
if (fst)
tscale = GSVector4(1.0f / 16, 1.0f / 16, 1.0f, 1.0f);
else
tscale = GSVector4(static_cast<float>(1 << context->TEX0.TW), static_cast<float>(1 << context->TEX0.TH), 1.0f, 1.0f);
m_min.t = tmin * tscale;
m_max.t = tmax * tscale;
}
if (color)
{
m_min.c = cmin.u8to32();
m_max.c = cmax.u8to32();
}
// AA1: Set alpha min max to coverage 128 when there is no alpha blending.
if (!m_state->PRIM->ABE && m_state->PRIM->AA1 && (primclass == GS_LINE_CLASS || primclass == GS_TRIANGLE_CLASS))
{
m_min.c.a = 128;
m_max.c.a = 128;
}
m_alpha.valid = false;
// Set m_eq flags for when vertex values are constant.
const u32 t_eq = (m_min.t == m_max.t).mask(); // GSVector4, 4 bit mask.
const u32 p_eq = GSVector4::cast(pmin == pmax).mask(); // Cast to GSVector4() for 4 bit mask.
const u32 c_eq = (m_min.c == m_max.c).mask(); // GSVector4i, 16 bit mask.
m_eq.value = c_eq | (p_eq << 16) | (t_eq << 20);
// Potential float overflow detected. Better uses the slower division instead
// Note: If Q is too big, 1/Q will end up as 0. 1e30 is a random number
@ -36,23 +90,7 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
m_accurate_stq = true;
}
// AA1: Set alpha min max to coverage 128 when there is no alpha blending.
if (!m_state->PRIM->ABE && m_state->PRIM->AA1 && (m_primclass == GS_LINE_CLASS || m_primclass == GS_TRIANGLE_CLASS))
{
m_min.c.a = 128;
m_max.c.a = 128;
}
m_eq.value = (m_min.c == m_max.c).mask() | ((m_min.p == m_max.p).mask() << 16) | ((m_min.t == m_max.t).mask() << 20);
m_alpha.valid = false;
// I'm not sure of the cost. In doubt let's do it only when depth is enabled
if (m_state->m_context->TEST.ZTE == 1 && m_state->m_context->TEST.ZTST > ZTST_ALWAYS)
{
CorrectDepthTrace(vertex, v_count);
}
// Determine mipmapping LOD and whether linear filter is used.
if (tme)
{
const GIFRegTEX1& TEX1 = m_state->m_context->TEX1;
@ -60,7 +98,7 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
m_filter.mmag = TEX1.IsMagLinear();
m_filter.mmin = TEX1.IsMinLinear();
if (TEX1.MXL == 0) // MXL == 0 => MMIN ignored, tested it on ps2
if (TEX1.MXL == 0) // MXL == 0 => MMIN ignored, tested it on ps2.
{
m_filter.linear = m_filter.mmag;
}
@ -68,7 +106,7 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
{
const float K = static_cast<float>(TEX1.K) / 16;
if (TEX1.LCM == 0 && m_state->PRIM->FST == 0) // FST == 1 => Q is not interpolated
if (TEX1.LCM == 0 && m_state->PRIM->FST == 0) // FST == 1 => Q is not interpolated.
{
// LOD = log2(1/|Q|) * (1 << L) + K
@ -108,7 +146,7 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
break;
case BiFiltering::Forced_But_Sprite:
// Special case to reduce the number of glitch when upscaling is enabled
// Special case to reduce the number of glitch when upscaling is enabled.
m_filter.opt_linear = (m_primclass == GS_SPRITE_CLASS) ? m_filter.linear : 1;
break;
@ -123,50 +161,3 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
}
}
}
void GSVertexTrace::CorrectDepthTrace(const void* vertex, int count)
{
if (m_eq.z == 0)
return;
// FindMinMax isn't accurate for the depth value. Lsb bit is always 0.
// The code below will check that depth value is really constant
// and will update m_min/m_max/m_eq accordingly
//
// Really impact Xenosaga3
//
// Hopefully function is barely called so AVX/SSE will be useless here
const GSVertex* RESTRICT v = (GSVertex*)vertex;
const int sprite_step = (m_primclass == GS_SPRITE_CLASS) ? 1 : 0;
u32 z = v[sprite_step].XYZ.Z;
if (z & 1)
{
// Check that first bit is always 1
for (int i = sprite_step; i < count; i += (sprite_step + 1))
{
z &= v[i].XYZ.Z;
}
}
else
{
// Check that first bit is always 0
for (int i = sprite_step; i < count; i += (sprite_step + 1))
{
z |= v[i].XYZ.Z;
}
}
if (z == v[sprite_step].XYZ.Z)
{
m_eq.z = 1;
}
else
{
m_eq.z = 0;
}
}

View File

@ -37,7 +37,9 @@ public:
protected:
const GSState* m_state;
typedef void (*FindMinMaxPtr)(GSVertexTrace& vt, const void* vertex, const u16* index, int count);
typedef void (*FindMinMaxPtr)(const void* vertex, const u16* index, int count,
GSVector4& tmin_out, GSVector4& tmax_out, GSVector4i& cmin_out, GSVector4i& cmax_out,
GSVector4i& pmin_out, GSVector4i& pmax_out);
FindMinMaxPtr m_fmm[2][2][2][2][4];

View File

@ -3,14 +3,15 @@
#include "GSVertexTrace.h"
#include "GS/GSState.h"
#include "GS/GSUtil.h"
#include <cfloat>
class CURRENT_ISA::GSVertexTraceFMM
{
static constexpr GSVector4 s_minmax = GSVector4::cxpr(FLT_MAX, -FLT_MAX, 0.f, 0.f);
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
static void FindMinMax(GSVertexTrace& vt, const void* vertex, const u16* index, int count);
static void FindMinMax(const void* vertex, const u16* index, int count,
GSVector4& tmin_out, GSVector4& tmax_out, GSVector4i& cmin_out, GSVector4i& cmax_out,
GSVector4i& pmin_out, GSVector4i& pmax_out);
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
static constexpr GSVertexTrace::FindMinMaxPtr GetFMM();
@ -29,8 +30,8 @@ void CURRENT_ISA::GSVertexTracePopulateFunctions(GSVertexTrace& vt)
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
constexpr GSVertexTrace::FindMinMaxPtr GSVertexTraceFMM::GetFMM()
{
constexpr bool real_iip = primclass == GS_SPRITE_CLASS ? false : iip;
constexpr bool real_fst = tme ? fst : false;
constexpr bool real_iip = (primclass != GS_POINT_CLASS) && (primclass != GS_SPRITE_CLASS) && iip;
constexpr bool real_fst = tme && fst;
return FindMinMax<primclass, real_iip, tme, real_fst, color>;
}
@ -56,31 +57,24 @@ void GSVertexTraceFMM::Populate(GSVertexTrace& vt)
InitUpdate(GS_LINE_CLASS);
InitUpdate(GS_TRIANGLE_CLASS);
InitUpdate(GS_SPRITE_CLASS);
#undef InitUpdate3
#undef InitUpdate2
#undef InitUpdate
}
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
void GSVertexTraceFMM::FindMinMax(GSVertexTrace& vt, const void* vertex, const u16* index, int count)
void GSVertexTraceFMM::FindMinMax(const void* vertex, const u16* index, int count,
GSVector4& tmin_out, GSVector4& tmax_out, GSVector4i& cmin_out, GSVector4i& cmax_out,
GSVector4i& pmin_out, GSVector4i& pmax_out)
{
const GSDrawingContext* context = vt.m_state->m_context;
constexpr int n = GSUtil::GetClassVertexCount(primclass);
int n = 1;
pxAssert(count % n == 0);
switch (primclass)
{
case GS_POINT_CLASS:
n = 1;
break;
case GS_LINE_CLASS:
case GS_SPRITE_CLASS:
n = 2;
break;
case GS_TRIANGLE_CLASS:
n = 3;
break;
}
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
GSVector4 tmin = s_minmax.xxxx();
GSVector4 tmax = s_minmax.yyyy();
GSVector4i cmin = GSVector4i::xffffffff();
GSVector4i cmax = GSVector4i::zero();
@ -89,67 +83,68 @@ void GSVertexTraceFMM::FindMinMax(GSVertexTrace& vt, const void* vertex, const u
const GSVertex* RESTRICT v = (GSVertex*)vertex;
// Process 2 vertices at a time for increased efficiency
auto processVertices = [&tmin, &tmax, &cmin, &cmax, &pmin, &pmax, n](const GSVertex& v0, const GSVertex& v1, bool finalVertex)
// Process 2 vertices at a time for increased efficiency.
const auto ProcessVertices = [&tmin, &tmax, &cmin, &cmax, &pmin, &pmax]<bool use_color>(
const GSVertex& v0, const GSVertex& v1)
{
if (color)
// Process color.
if constexpr (color && use_color)
{
GSVector4i c0 = GSVector4i::load(v0.RGBAQ.U32[0]);
GSVector4i c1 = GSVector4i::load(v1.RGBAQ.U32[0]);
if (iip || finalVertex)
const GSVector4i c0 = GSVector4i::load(v0.RGBAQ.U32[0]);
const GSVector4i c1 = GSVector4i::load(v1.RGBAQ.U32[0]);
if constexpr (primclass == GS_SPRITE_CLASS || (primclass == GS_LINE_CLASS && !iip))
{
// Flat shaded sprites or lines.
// Last color is provoking in flat-shaded primitives.
cmin = cmin.min_u8(c1);
cmax = cmax.max_u8(c1);
}
else
{
// Otherwise use color from both vertices.
cmin = cmin.min_u8(c0.min_u8(c1));
cmax = cmax.max_u8(c0.max_u8(c1));
}
else if (n == 2)
{
// For even n, we process v1 and v2 of the same prim
// (For odd n, we process one vertex from each of two prims)
GSVector4i c = c1; // second color is provoking in flat-shaded primitives
cmin = cmin.min_u8(c);
cmax = cmax.max_u8(c);
}
}
if (tme)
// Process UV or ST.
if constexpr (tme)
{
if (!fst)
if constexpr (!fst)
{
// Process ST.
GSVector4 stq0 = GSVector4::cast(GSVector4i(v0.m[0]));
GSVector4 stq1 = GSVector4::cast(GSVector4i(v1.m[0]));
GSVector4 q;
// Sprites always have indices == vertices, so we don't have to look at the index table here
if (primclass == GS_SPRITE_CLASS)
q = stq1.wwww();
else
q = stq0.wwww(stq1);
pxAssert(stq0.w == stq1.w); // Q should be fixed in vertex queue to be the same for both vertices.
// Note: If in the future this is changed in a way that causes parts of calculations to go unused,
// make sure to remove the z (rgba) field as it's often denormal.
// Then, use GSVector4::noopt() to prevent clang from optimizing out your "useless" shuffle
// e.g. stq = (stq.xyww() / stq.wwww()).noopt().xyww(stq);
GSVector4 st = stq0.xyxy(stq1) / q;
const GSVector4 q = stq0.wwww(stq1);
stq0 = st.xyww(primclass == GS_SPRITE_CLASS ? stq1 : stq0);
stq1 = st.zwww(stq1);
const GSVector4 st = stq0.xyxy(stq1) / q;
stq0 = st.xyyy(q);
stq1 = st.zwww(q);
tmin = tmin.min(stq0.min(stq1));
tmax = tmax.max(stq0.max(stq1));
}
else
{
GSVector4i uv0(v0.m[1]);
GSVector4i uv1(v1.m[1]);
// Process UV.
const GSVector4i uv0(v0.m[1]);
const GSVector4i uv1(v1.m[1]);
GSVector4 st0 = GSVector4(uv0.uph16()).xyxy();
GSVector4 st1 = GSVector4(uv1.uph16()).xyxy();
const GSVector4 st0 = GSVector4(uv0.uph16()).xyxy();
const GSVector4 st1 = GSVector4(uv1.uph16()).xyxy();
tmin = tmin.min(st0.min(st1));
tmax = tmax.max(st0.max(st1));
}
}
// Process XYZ
GSVector4i xyzf0(v0.m[1]);
GSVector4i xyzf1(v1.m[1]);
@ -158,94 +153,53 @@ void GSVertexTraceFMM::FindMinMax(GSVertexTrace& vt, const void* vertex, const u
GSVector4i xy1 = xyzf1.upl16();
GSVector4i zf1 = xyzf1.ywyw();
GSVector4i p0 = xy0.blend32<0xc>(primclass == GS_SPRITE_CLASS ? zf1 : zf0);
GSVector4i p1 = xy1.blend32<0xc>(zf1);
GSVector4i p0 = xy0.upl64(primclass == GS_SPRITE_CLASS ? zf1 : zf0);
GSVector4i p1 = xy1.upl64(zf1);
pmin = pmin.min_u32(p0.min_u32(p1));
pmax = pmax.max_u32(p0.max_u32(p1));
};
if (n == 2)
if constexpr (iip || primclass == GS_POINT_CLASS)
{
// Points or Gouraud shading; treat all vertices the same.
for (int i = 0; i < count - 1; i += 2) // 2x loop unroll
ProcessVertices.template operator()<true>(v[index[i]], v[index[i + 1]]);
// Process last vertex if we had an odd number of vertices.
if (count & 1)
ProcessVertices.template operator()<true>(v[index[count - 1]], v[index[count - 1]]);
}
else if constexpr (primclass == GS_TRIANGLE_CLASS)
{
// Flat shaded triangles.
// Only case where we process two primitives in parallel and control color with the use_color flag.
for (int i = 0; i < count - 3; i += 6)
{
ProcessVertices.template operator()<false>(v[index[i + 0]], v[index[i + 3]]);
ProcessVertices.template operator()<false>(v[index[i + 1]], v[index[i + 4]]);
ProcessVertices.template operator()<true>(v[index[i + 2]], v[index[i + 5]]);
}
// Process last triangle if we had an odd number of triangles.
if (count & 1)
{
ProcessVertices.template operator()<false>(v[index[count - 3]], v[index[count - 2]]);
ProcessVertices.template operator()<true>(v[index[count - 1]], v[index[count - 1]]);
}
}
else
{
// Flat shaded lines or sprites.
pxAssert(primclass == GS_LINE_CLASS || primclass == GS_SPRITE_CLASS);
for (int i = 0; i < count; i += 2)
{
processVertices(v[index[i + 0]], v[index[i + 1]], false);
}
}
else if (iip || n == 1) // iip means final and non-final vertexes are treated the same
{
int i = 0;
for (; i < (count - 1); i += 2) // 2x loop unroll
{
processVertices(v[index[i + 0]], v[index[i + 1]], true);
}
if (count & 1)
{
// Compiler optimizations go!
// (And if they don't, it's only one vertex out of many)
processVertices(v[index[i]], v[index[i]], true);
}
}
else if (n == 3)
{
int i = 0;
for (; i < (count - 3); i += 6)
{
processVertices(v[index[i + 0]], v[index[i + 3]], false);
processVertices(v[index[i + 1]], v[index[i + 4]], false);
processVertices(v[index[i + 2]], v[index[i + 5]], true);
}
if (count & 1)
{
processVertices(v[index[i + 0]], v[index[i + 1]], false);
// Compiler optimizations go!
// (And if they don't, it's only one vertex out of many)
processVertices(v[index[i + 2]], v[index[i + 2]], true);
}
}
else
{
pxAssertRel(0, "Bad n value");
ProcessVertices.template operator()<true>(v[index[i + 0]], v[index[i + 1]]);
}
GSVector4 o(context->XYOFFSET);
GSVector4 s(1.0f / 16, 1.0f / 16, 2.0f, 1.0f);
vt.m_min.p = (GSVector4(pmin) - o) * s;
vt.m_max.p = (GSVector4(pmax) - o) * s;
// Fix signed int conversion
vt.m_min.p = vt.m_min.p.insert32<0, 2>(GSVector4::load((float)(u32)pmin.extract32<2>()));
vt.m_max.p = vt.m_max.p.insert32<0, 2>(GSVector4::load((float)(u32)pmax.extract32<2>()));
if (tme)
{
if (fst)
{
s = GSVector4(1.0f / 16, 1.0f).xxyy();
}
else
{
s = GSVector4(1 << context->TEX0.TW, 1 << context->TEX0.TH, 1, 1);
}
vt.m_min.t = tmin * s;
vt.m_max.t = tmax * s;
}
else
{
vt.m_min.t = GSVector4::zero();
vt.m_max.t = GSVector4::zero();
}
if (color)
{
vt.m_min.c = cmin.u8to32();
vt.m_max.c = cmax.u8to32();
}
else
{
vt.m_min.c = GSVector4i::zero();
vt.m_max.c = GSVector4i::zero();
}
// Write out the results.
tmin_out = tmin;
tmax_out = tmax;
cmin_out = cmin;
cmax_out = cmax;
pmin_out = pmin;
pmax_out = pmax;
}

View File

@ -2904,7 +2904,7 @@ void GSRendererHW::Draw()
TEX0 = m_cached_ctx.TEX0;
}
tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear(), false);
tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_context->scissor.in, m_vt.IsLinear(), true);
// Snowblind games set TW/TH to 1024, and use UVs for smaller textures inside that.
// Such textures usually contain junk in local memory, so try to make them smaller based on UVs.
@ -4042,7 +4042,7 @@ void GSRendererHW::Draw()
m_vt.m_min.t *= 0.5f;
m_vt.m_max.t *= 0.5f;
tmm = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, m_vt.IsLinear(), true);
tmm = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, m_context->scissor.in, m_vt.IsLinear(), false);
src->UpdateLayer(MIP_TEX0, tmm.coverage, layer - m_lod.x);
}
@ -8158,7 +8158,7 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
if (m_process_texture)
{
// If we're using a texture to draw our CLUT/whatever, we need the GPU to write back dirty data we need.
const GSVector4i r = GetTextureMinMax(m_cached_ctx.TEX0, m_cached_ctx.CLAMP, m_vt.IsLinear(), false).coverage;
const GSVector4i r = GetTextureMinMax(m_cached_ctx.TEX0, m_cached_ctx.CLAMP, m_context->scissor.in, m_vt.IsLinear(), true).coverage;
// If we have GPU CLUT enabled, don't do a CPU draw when it would result in a download.
if (GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
@ -8299,7 +8299,7 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t
// If the EE has written over our sample area, we're fine to do this on the CPU, despite the target.
if (!src_target->m_dirty.empty())
{
const GSVector4i tr(GetTextureMinMax(m_cached_ctx.TEX0, m_cached_ctx.CLAMP, m_vt.IsLinear(), false).coverage);
const GSVector4i tr(GetTextureMinMax(m_cached_ctx.TEX0, m_cached_ctx.CLAMP, m_context->scissor.in, m_vt.IsLinear(), true).coverage);
const u32 start_bp = GSLocalMemory::GetStartBlockAddress(m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW, m_cached_ctx.TEX0.PSM, tr);
const u32 end_bp = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW, m_cached_ctx.TEX0.PSM, tr);

View File

@ -186,9 +186,9 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
bool mipmap = hw.IsMipMapActive();
GIFRegTEX0 TEX0 = context->GetSizeFixedTEX0(vt.m_min.t.xyxy(vt.m_max.t), vt.IsLinear(), mipmap);
const GSVector4i r = hw.GetTextureMinMax(context->TEX0, context->CLAMP, context->scissor.in, gd.sel.ltf, false).coverage;
const GSVector4i r = hw.GetTextureMinMax(TEX0, context->CLAMP, gd.sel.ltf, true).coverage;
GIFRegTEX0 TEX0 = context->GetSizeFixedTEX0(r, vt.IsLinear(), mipmap);
if (!hw.m_sw_texture[0])
hw.m_sw_texture[0] = std::make_unique<GSTextureCacheSW::Texture>(0, TEX0, env.TEXA);
@ -289,7 +289,7 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
else
hw.m_sw_texture[i]->Reset(gd.sel.tw + 3, MIP_TEX0, env.TEXA);
GSVector4i r = hw.GetTextureMinMax(MIP_TEX0, MIP_CLAMP, gd.sel.ltf, true).coverage;
GSVector4i r = hw.GetTextureMinMax(MIP_TEX0, MIP_CLAMP, context->scissor.in, gd.sel.ltf, false).coverage;
hw.m_sw_texture[i]->Update(r);
gd.tex[i] = hw.m_sw_texture[i]->m_buff;
}

View File

@ -947,34 +947,28 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
zm = 0xffffffff;
}
if (PRIM->TME)
if (PRIM->TME && GSLocalMemory::m_psm[context->TEX0.PSM].pal > 0)
{
if (GSLocalMemory::m_psm[context->TEX0.PSM].pal > 0)
{
m_mem.m_clut.Read32(context->TEX0, env.TEXA);
}
m_mem.m_clut.Read32(context->TEX0, env.TEXA);
}
if (context->TEST.ATE)
if (context->TEST.ATE && !TryAlphaTest(fm, zm))
{
if (!TryAlphaTest(fm, zm))
gd.sel.atst = context->TEST.ATST;
gd.sel.afail = context->TEST.GetAFAIL(context->FRAME.PSM);
gd.aref = GSVector4i((int)context->TEST.AREF);
switch (gd.sel.atst)
{
gd.sel.atst = context->TEST.ATST;
gd.sel.afail = context->TEST.GetAFAIL(context->FRAME.PSM);
gd.aref = GSVector4i((int)context->TEST.AREF);
switch (gd.sel.atst)
{
case ATST_LESS:
gd.sel.atst = ATST_LEQUAL;
gd.aref -= GSVector4i::x00000001();
break;
case ATST_GREATER:
gd.sel.atst = ATST_GEQUAL;
gd.aref += GSVector4i::x00000001();
break;
}
case ATST_LESS:
gd.sel.atst = ATST_LEQUAL;
gd.aref -= GSVector4i::x00000001();
break;
case ATST_GREATER:
gd.sel.atst = ATST_GEQUAL;
gd.aref += GSVector4i::x00000001();
break;
}
}
@ -1034,9 +1028,9 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
bool mipmap = IsMipMapActive();
GIFRegTEX0 TEX0 = m_context->GetSizeFixedTEX0(m_vt.m_min.t.xyxy(m_vt.m_max.t), m_vt.IsLinear(), mipmap);
GSVector4i r = GetTextureMinMax(context->TEX0, context->CLAMP, context->scissor.in, gd.sel.ltf, false).coverage;
GSVector4i r = GetTextureMinMax(TEX0, context->CLAMP, gd.sel.ltf, true).coverage;
const GIFRegTEX0 TEX0 = context->GetSizeFixedTEX0(r, m_vt.IsLinear(), mipmap);
GSTextureCacheSW::Texture* t = m_tc->Lookup(TEX0, env.TEXA);
@ -1142,7 +1136,7 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
return false;
}
GSVector4i r = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, gd.sel.ltf, true).coverage;
GSVector4i r = GetTextureMinMax(MIP_TEX0, MIP_CLAMP, context->scissor.in, gd.sel.ltf, false).coverage;
data->SetSource(t, r, i);
}

View File

@ -183,7 +183,7 @@ bool SysMemory::AllocateMemoryMap()
HostMemoryMap::EEmem = (uptr)(s_data_memory + HostMemoryMap::EEmemOffset);
HostMemoryMap::IOPmem = (uptr)(s_data_memory + HostMemoryMap::IOPmemOffset);
HostMemoryMap::VUmem = (uptr)(s_data_memory + HostMemoryMap::VUmemSize);
HostMemoryMap::VUmem = (uptr)(s_data_memory + HostMemoryMap::VUmemOffset);
DumpMemoryMap();
return true;
@ -198,8 +198,8 @@ void SysMemory::DumpMemoryMap()
DUMP_REGION("EE Main Memory", s_data_memory, HostMemoryMap::EEmemOffset, HostMemoryMap::EEmemSize);
DUMP_REGION("IOP Main Memory", s_data_memory, HostMemoryMap::IOPmemOffset, HostMemoryMap::IOPmemSize);
DUMP_REGION("VU0/1 On-Chip Memory", s_data_memory, HostMemoryMap::VUmemOffset, HostMemoryMap::VUmemSize);
DUMP_REGION("VTLB Virtual Map", s_data_memory, HostMemoryMap::VTLBAddressMapOffset, HostMemoryMap::VTLBVirtualMapSize);
DUMP_REGION("VTLB Address Map", s_data_memory, HostMemoryMap::VTLBAddressMapSize, HostMemoryMap::VTLBAddressMapSize);
DUMP_REGION("VTLB Virtual Map", s_data_memory, HostMemoryMap::VTLBVirtualMapOffset, HostMemoryMap::VTLBVirtualMapSize);
DUMP_REGION("VTLB Address Map", s_data_memory, HostMemoryMap::VTLBAddressMapOffset, HostMemoryMap::VTLBAddressMapSize);
DUMP_REGION("R5900 Recompiler Cache", s_code_memory, HostMemoryMap::EErecOffset, HostMemoryMap::EErecSize);
DUMP_REGION("R3000A Recompiler Cache", s_code_memory, HostMemoryMap::IOPrecOffset, HostMemoryMap::IOPrecSize);