mirror of https://github.com/PCSX2/pcsx2.git
Compare commits
10 Commits
9de7fba2e9
...
bbfb9b0848
Author | SHA1 | Date |
---|---|---|
![]() |
bbfb9b0848 | |
![]() |
20411aa8d6 | |
![]() |
be94aa97db | |
![]() |
f65c1dd5bc | |
![]() |
a92297ceec | |
![]() |
a8ea4e55ef | |
![]() |
3195befab1 | |
![]() |
6ca075497c | |
![]() |
0e6f790ef2 | |
![]() |
a0dabccad3 |
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -30,7 +30,6 @@ public:
|
|||
|
||||
protected:
|
||||
void setupSymbolSourceGrid();
|
||||
void symbolSourceCheckStateChanged();
|
||||
void saveSymbolSources();
|
||||
|
||||
void setupSymbolFileList();
|
||||
|
|
|
@ -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><i>Start this game to modify the symbol sources list.</i></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 '%1'?</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 '%1'?</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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue