mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-18 04:55:42 +02:00
Compare commits
29
Commits
@@ -63,6 +63,9 @@ oprofile_data/
|
||||
*.kdev4
|
||||
/.kdev4*
|
||||
|
||||
# Kate Projects
|
||||
.kateproject*
|
||||
|
||||
# Resources and docs in /bin are tracked
|
||||
/bin/**/*.dll
|
||||
/bin/**/*.dmp
|
||||
|
||||
@@ -15134,6 +15134,14 @@ SLES-50276:
|
||||
name: "The Gift"
|
||||
name-sort: "Gift, The"
|
||||
region: "PAL-E"
|
||||
patches:
|
||||
CC76CD02:
|
||||
content: |-
|
||||
// Sound RPC races because of shared send/receive buffers with badly
|
||||
// placed wait loop, fix by forcing all sound rpc to be synchronous.
|
||||
// Ideal fix would be moving the rpc wait loop though.
|
||||
author=Ziemas
|
||||
patch=0,EE,002159e0,word,00000000
|
||||
SLES-50277:
|
||||
name: "Red Faction"
|
||||
region: "PAL-E"
|
||||
|
||||
@@ -196,6 +196,7 @@ target_sources(pcsx2-qt PRIVATE
|
||||
Debugger/ModuleModel.h
|
||||
Debugger/ModuleView.cpp
|
||||
Debugger/ModuleView.h
|
||||
Debugger/NavigationHistoryStack.h
|
||||
Debugger/RegisterView.cpp
|
||||
Debugger/RegisterView.h
|
||||
Debugger/RegisterView.ui
|
||||
|
||||
@@ -206,6 +206,29 @@ void DebuggerView::updateStyleSheet()
|
||||
setStyleSheet(stylesheet);
|
||||
}
|
||||
|
||||
bool DebuggerView::supportsNavigation()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebuggerView::canNavigateBack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebuggerView::canNavigateForward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void DebuggerView::navigateBack()
|
||||
{
|
||||
}
|
||||
|
||||
void DebuggerView::navigateForward()
|
||||
{
|
||||
}
|
||||
|
||||
void DebuggerView::goToInDisassembler(u32 address, bool switch_to_tab)
|
||||
{
|
||||
DebuggerEvents::GoToAddress event;
|
||||
@@ -268,7 +291,7 @@ std::vector<QAction*> DebuggerView::createEventActionsImplementation(
|
||||
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
|
||||
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
|
||||
|
||||
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
|
||||
return QtHost::LocaleSensitiveCompare(lhs->displayNameWithoutSuffix(), rhs->displayNameWithoutSuffix()) < 0;
|
||||
});
|
||||
|
||||
QMenu* submenu = nullptr;
|
||||
|
||||
@@ -153,6 +153,12 @@ public:
|
||||
|
||||
void updateStyleSheet();
|
||||
|
||||
virtual bool supportsNavigation();
|
||||
virtual bool canNavigateBack();
|
||||
virtual bool canNavigateForward();
|
||||
virtual void navigateBack();
|
||||
virtual void navigateForward();
|
||||
|
||||
static void goToInDisassembler(u32 address, bool switch_to_tab);
|
||||
static void goToInMemoryView(u32 address, bool switch_to_tab);
|
||||
|
||||
|
||||
@@ -115,6 +115,28 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
|
||||
|
||||
setMenuWidget(m_dock_manager->createMenuBar(menu_bar));
|
||||
|
||||
connect(m_dock_manager, &DockManager::focusedViewForNavigationChanged,
|
||||
this, &DebuggerWindow::updateNavigationButtons);
|
||||
|
||||
m_ui.actionNavigateBack->setEnabled(false);
|
||||
m_ui.actionNavigateForward->setEnabled(false);
|
||||
|
||||
connect(m_ui.actionNavigateBack, &QAction::triggered, this, [this]() {
|
||||
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
|
||||
if (!view)
|
||||
return;
|
||||
|
||||
view->navigateBack();
|
||||
});
|
||||
|
||||
connect(m_ui.actionNavigateForward, &QAction::triggered, this, [this]() {
|
||||
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
|
||||
if (!view)
|
||||
return;
|
||||
|
||||
view->navigateForward();
|
||||
});
|
||||
|
||||
updateTheme();
|
||||
|
||||
Host::RunOnCPUThread([]() {
|
||||
@@ -307,6 +329,21 @@ void DebuggerWindow::updateFromSettings()
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::updateNavigationButtons()
|
||||
{
|
||||
DebuggerView* view = m_dock_manager->focusedViewForNavigation();
|
||||
if (view)
|
||||
{
|
||||
m_ui.actionNavigateBack->setEnabled(view->canNavigateBack());
|
||||
m_ui.actionNavigateForward->setEnabled(view->canNavigateForward());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.actionNavigateBack->setEnabled(false);
|
||||
m_ui.actionNavigateForward->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::onVMStarting()
|
||||
{
|
||||
m_ui.actionRun->setEnabled(true);
|
||||
|
||||
@@ -40,7 +40,9 @@ public:
|
||||
|
||||
void updateFromSettings();
|
||||
|
||||
public slots:
|
||||
void updateNavigationButtons();
|
||||
|
||||
private slots:
|
||||
void onVMStarting();
|
||||
void onVMPaused();
|
||||
void onVMResumed();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1000</width>
|
||||
<height>750</height>
|
||||
<width>1322</width>
|
||||
<height>974</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -23,8 +23,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1000</width>
|
||||
<height>21</height>
|
||||
<width>1322</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
@@ -58,6 +58,9 @@
|
||||
</property>
|
||||
<addaction name="actionOnTop"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionNavigateBack"/>
|
||||
<addaction name="actionNavigateForward"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionIncreaseFontSize"/>
|
||||
<addaction name="actionDecreaseFontSize"/>
|
||||
<addaction name="actionResetFontSize"/>
|
||||
@@ -82,12 +85,25 @@
|
||||
<addaction name="menuWindows"/>
|
||||
<addaction name="menuLayouts"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBarNavigation">
|
||||
<property name="windowTitle">
|
||||
<string>Navigation</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionNavigateBack"/>
|
||||
<addaction name="actionNavigateForward"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBarDebug">
|
||||
<property name="windowTitle">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
@@ -105,7 +121,7 @@
|
||||
<string>File</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
@@ -122,7 +138,7 @@
|
||||
<string>System</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
@@ -138,7 +154,7 @@
|
||||
<string>View</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
@@ -237,7 +253,7 @@
|
||||
<string>Shut Down</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset">
|
||||
@@ -248,7 +264,7 @@
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
@@ -259,7 +275,7 @@
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionIncreaseFontSize">
|
||||
@@ -270,7 +286,7 @@
|
||||
<string>Increase Font Size</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDecreaseFontSize">
|
||||
@@ -284,7 +300,7 @@
|
||||
<string>Ctrl+-</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResetFontSize">
|
||||
@@ -295,7 +311,7 @@
|
||||
<string>Reset Font Size</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSettings">
|
||||
@@ -306,7 +322,7 @@
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGameSettings">
|
||||
@@ -317,7 +333,7 @@
|
||||
<string>Game Settings</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolsDummy">
|
||||
@@ -330,6 +346,37 @@
|
||||
<string/>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNavigateBack">
|
||||
<property name="icon">
|
||||
<iconset theme="arrow-left-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Navigate Back</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Navigate Back</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+Left</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNavigateForward">
|
||||
<property name="icon">
|
||||
<iconset theme="arrow-right-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Navigate Forward</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+Right</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -76,7 +76,11 @@ bool DisassemblyView::fromJson(const JsonValueWrapper& json)
|
||||
|
||||
auto start_address = json.value().FindMember("startAddress");
|
||||
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
|
||||
{
|
||||
m_visibleStart = start_address->value.GetUint() & ~3;
|
||||
m_navigation_history.clear();
|
||||
m_navigation_history.pushInstantly(m_visibleStart);
|
||||
}
|
||||
|
||||
auto go_to_pc_on_pause = json.value().FindMember("goToPCOnPause");
|
||||
if (go_to_pc_on_pause != json.value().MemberEnd() && go_to_pc_on_pause->value.IsBool())
|
||||
@@ -388,6 +392,41 @@ QString DisassemblyView::GetLineDisasm(u32 address)
|
||||
return QString("%1 %2").arg(lineInfo.name.c_str()).arg(lineInfo.params.c_str());
|
||||
};
|
||||
|
||||
bool DisassemblyView::supportsNavigation()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisassemblyView::canNavigateBack()
|
||||
{
|
||||
return m_navigation_history.canGoBack();
|
||||
}
|
||||
|
||||
bool DisassemblyView::canNavigateForward()
|
||||
{
|
||||
return m_navigation_history.canGoForward();
|
||||
}
|
||||
|
||||
void DisassemblyView::navigateBack()
|
||||
{
|
||||
std::optional<u32> address = m_navigation_history.back();
|
||||
if (!address.has_value())
|
||||
return;
|
||||
|
||||
m_visibleStart = *address;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisassemblyView::navigateForward()
|
||||
{
|
||||
std::optional<u32> address = m_navigation_history.forward();
|
||||
if (!address.has_value())
|
||||
return;
|
||||
|
||||
m_visibleStart = *address;
|
||||
update();
|
||||
}
|
||||
|
||||
// Here we go!
|
||||
void DisassemblyView::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
@@ -629,19 +668,21 @@ void DisassemblyView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
|
||||
void DisassemblyView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
if (event->angleDelta().y() < 0) // todo: max address bounds check?
|
||||
{
|
||||
if (event->angleDelta().y() < 0)
|
||||
m_visibleStart += 4;
|
||||
}
|
||||
else if (event->angleDelta().y() && m_visibleStart > 0)
|
||||
{
|
||||
m_visibleStart -= 4;
|
||||
}
|
||||
|
||||
m_navigation_history.pushWithDelay(m_visibleStart);
|
||||
update();
|
||||
}
|
||||
|
||||
void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
// Alt is used for the global navigation shortcuts.
|
||||
if (event->modifiers() & Qt::AltModifier)
|
||||
return;
|
||||
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
@@ -653,15 +694,19 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
// Auto scroll
|
||||
if (m_visibleStart > m_selectedAddressStart)
|
||||
m_visibleStart -= 4;
|
||||
|
||||
m_navigation_history.pushWithDelay(m_visibleStart);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Qt::Key_PageUp:
|
||||
{
|
||||
m_selectedAddressStart -= m_visibleRows * 4;
|
||||
m_selectedAddressEnd = m_selectedAddressStart;
|
||||
m_visibleStart -= m_visibleRows * 4;
|
||||
|
||||
m_navigation_history.pushWithDelay(m_visibleStart);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Down:
|
||||
{
|
||||
m_selectedAddressEnd += 4;
|
||||
@@ -673,6 +718,7 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
if (m_visibleStart + ((m_visibleRows - 1) * 4) < m_selectedAddressEnd)
|
||||
m_visibleStart += 4;
|
||||
|
||||
m_navigation_history.pushWithDelay(m_visibleStart);
|
||||
break;
|
||||
}
|
||||
case Qt::Key_PageDown:
|
||||
@@ -680,6 +726,7 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
m_selectedAddressStart += m_visibleRows * 4;
|
||||
m_selectedAddressEnd = m_selectedAddressStart;
|
||||
m_visibleStart += m_visibleRows * 4;
|
||||
m_navigation_history.pushWithDelay(m_visibleStart);
|
||||
break;
|
||||
}
|
||||
case Qt::Key_G:
|
||||
@@ -1014,6 +1061,8 @@ void DisassemblyView::gotoAddress(u32 address, bool should_set_focus)
|
||||
m_selectedAddressStart = destAddress;
|
||||
m_selectedAddressEnd = destAddress;
|
||||
|
||||
m_navigation_history.pushInstantly(m_visibleStart);
|
||||
|
||||
update();
|
||||
if (should_set_focus)
|
||||
setFocus();
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
#include "ui_DisassemblyView.h"
|
||||
|
||||
#include "DebuggerView.h"
|
||||
#include "Debugger/DebuggerView.h"
|
||||
#include "Debugger/NavigationHistoryStack.h"
|
||||
|
||||
#include "pcsx2/DebugTools/DisassemblyManager.h"
|
||||
|
||||
@@ -26,6 +27,12 @@ public:
|
||||
// Required for the breakpoint list (ugh wtf)
|
||||
QString GetLineDisasm(u32 address);
|
||||
|
||||
bool supportsNavigation() override;
|
||||
bool canNavigateBack() override;
|
||||
bool canNavigateForward() override;
|
||||
void navigateBack() override;
|
||||
void navigateForward() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
@@ -68,7 +75,7 @@ public slots:
|
||||
private:
|
||||
Ui::DisassemblyView m_ui;
|
||||
|
||||
u32 m_visibleStart = 0x100000; // The address of the first instruction shown.
|
||||
u32 m_visibleStart = STARTING_ADDRESS; // The address of the first instruction shown.
|
||||
u32 m_visibleRows;
|
||||
u32 m_selectedAddressStart = 0;
|
||||
u32 m_selectedAddressEnd = 0;
|
||||
@@ -81,6 +88,8 @@ private:
|
||||
bool m_goToProgramCounterOnPause = true;
|
||||
DisassemblyManager m_disassemblyManager;
|
||||
|
||||
NavigationHistoryStack<u32> m_navigation_history{STARTING_ADDRESS};
|
||||
|
||||
QString GetDisassemblyTitleLine();
|
||||
QColor GetDisassemblyTitleLineColor();
|
||||
inline QString DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected);
|
||||
@@ -96,4 +105,6 @@ private:
|
||||
void setInstructions(u32 start, u32 end, u32 value);
|
||||
bool AddressCanRestore(u32 start, u32 end);
|
||||
bool FunctionCanRestore(u32 address);
|
||||
|
||||
static constexpr u32 STARTING_ADDRESS = 0x100000;
|
||||
};
|
||||
|
||||
@@ -339,15 +339,28 @@ void DockManager::createToolsMenu(QMenu* menu)
|
||||
if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window)
|
||||
return;
|
||||
|
||||
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
|
||||
std::vector<QToolBar*> toolbars;
|
||||
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
|
||||
toolbars.emplace_back(toolbar);
|
||||
|
||||
std::sort(toolbars.begin(), toolbars.end(), [](QToolBar* lhs, QToolBar* rhs) {
|
||||
return QtHost::LocaleSensitiveCompare(lhs->windowTitle(), rhs->windowTitle()) < 0;
|
||||
});
|
||||
|
||||
for (QToolBar* toolbar : toolbars)
|
||||
{
|
||||
QAction* action = menu->addAction(widget->windowTitle());
|
||||
action->setText(widget->windowTitle());
|
||||
QAction* action = menu->addAction(toolbar->windowTitle());
|
||||
action->setText(toolbar->windowTitle());
|
||||
action->setCheckable(true);
|
||||
action->setChecked(widget->isVisible());
|
||||
connect(action, &QAction::triggered, this, [widget]() {
|
||||
widget->setVisible(!widget->isVisible());
|
||||
action->setChecked(toolbar->isVisible());
|
||||
|
||||
connect(action, &QAction::triggered, this, [toolbar = QPointer<QToolBar>(toolbar)]() {
|
||||
if (!toolbar)
|
||||
return;
|
||||
|
||||
toolbar->setVisible(!toolbar->isVisible());
|
||||
});
|
||||
|
||||
menu->addAction(action);
|
||||
}
|
||||
}
|
||||
@@ -383,7 +396,7 @@ void DockManager::createWindowsMenu(QMenu* menu)
|
||||
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
|
||||
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
|
||||
|
||||
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
|
||||
return QtHost::LocaleSensitiveCompare(lhs->displayNameWithoutSuffix(), rhs->displayNameWithoutSuffix()) < 0;
|
||||
});
|
||||
|
||||
for (DebuggerView* widget : add_another_widgets)
|
||||
@@ -472,7 +485,7 @@ void DockManager::createWindowsMenu(QMenu* menu)
|
||||
if (lhs.display_name == rhs.display_name)
|
||||
return lhs.suffix_number < rhs.suffix_number;
|
||||
|
||||
return lhs.display_name < rhs.display_name;
|
||||
return QtHost::LocaleSensitiveCompare(lhs.display_name, rhs.display_name) < 0;
|
||||
});
|
||||
|
||||
for (const DebuggerViewToggle& toggle : toggles)
|
||||
@@ -862,6 +875,24 @@ std::optional<BreakPointCpu> DockManager::cpu()
|
||||
return m_layouts.at(m_current_layout).cpu();
|
||||
}
|
||||
|
||||
DebuggerView* DockManager::focusedViewForNavigation()
|
||||
{
|
||||
if (!m_focused_view_for_navigation)
|
||||
return nullptr;
|
||||
|
||||
return m_focused_view_for_navigation;
|
||||
}
|
||||
|
||||
void DockManager::onFocusedDockWidgetChanged(KDDockWidgets::QtWidgets::DockWidget* widget)
|
||||
{
|
||||
DebuggerView* view = qobject_cast<DebuggerView*>(widget->widget());
|
||||
if (view && view->supportsNavigation() && view != m_focused_view_for_navigation)
|
||||
{
|
||||
m_focused_view_for_navigation = view;
|
||||
emit focusedViewForNavigationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
|
||||
{
|
||||
if (!g_debugger_window)
|
||||
|
||||
@@ -98,6 +98,14 @@ public:
|
||||
|
||||
std::optional<BreakPointCpu> cpu();
|
||||
|
||||
/// Returns the last focused view that supports back/forward navigation.
|
||||
DebuggerView* focusedViewForNavigation();
|
||||
|
||||
void onFocusedDockWidgetChanged(KDDockWidgets::QtWidgets::DockWidget* widget);
|
||||
|
||||
Q_SIGNALS:
|
||||
void focusedViewForNavigationChanged();
|
||||
|
||||
private:
|
||||
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);
|
||||
static bool dragAboutToStart(KDDockWidgets::Core::Draggable* draggable);
|
||||
@@ -108,4 +116,6 @@ private:
|
||||
DockMenuBar* m_menu_bar = nullptr;
|
||||
|
||||
bool m_layout_locked = true;
|
||||
|
||||
QPointer<DebuggerView> m_focused_view_for_navigation;
|
||||
};
|
||||
|
||||
@@ -82,6 +82,7 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
|
||||
{"MemorySearchView", DefaultDockGroup::TOP_LEFT},
|
||||
},
|
||||
.toolbars = {
|
||||
"toolBarNavigation",
|
||||
"toolBarDebug",
|
||||
"toolBarFile",
|
||||
},
|
||||
@@ -113,6 +114,7 @@ const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUT
|
||||
{"MemorySearchView", DefaultDockGroup::TOP_LEFT},
|
||||
},
|
||||
.toolbars = {
|
||||
"toolBarNavigation",
|
||||
"toolBarDebug",
|
||||
"toolBarFile",
|
||||
},
|
||||
|
||||
@@ -82,6 +82,7 @@ DockWidget::DockWidget(
|
||||
: KDDockWidgets::QtWidgets::DockWidget(unique_name, options, layout_saver_options, window_flags)
|
||||
{
|
||||
connect(this, &DockWidget::isOpenChanged, this, &DockWidget::openStateChanged);
|
||||
connect(this, &DockWidget::isFocusedChanged, this, &DockWidget::focusStateChanged);
|
||||
}
|
||||
|
||||
void DockWidget::openStateChanged(bool open)
|
||||
@@ -95,6 +96,12 @@ void DockWidget::openStateChanged(bool open)
|
||||
g_debugger_window->dockManager().destroyDebuggerView(uniqueName());
|
||||
}
|
||||
|
||||
void DockWidget::focusStateChanged(bool focused)
|
||||
{
|
||||
if (focused && g_debugger_window)
|
||||
g_debugger_window->dockManager().onFocusedDockWidgetChanged(this);
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent)
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
|
||||
protected:
|
||||
void openStateChanged(bool open);
|
||||
void focusStateChanged(bool focused);
|
||||
};
|
||||
|
||||
class DockTitleBar : public KDDockWidgets::QtWidgets::TitleBar
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
@@ -19,7 +25,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>nameEditor</cstring>
|
||||
@@ -29,7 +35,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="cpuLabel">
|
||||
<property name="text">
|
||||
<string>Target</string>
|
||||
<string>Target:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>cpuEditor</cstring>
|
||||
@@ -39,7 +45,7 @@
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="initialStateLabel">
|
||||
<property name="text">
|
||||
<string>Initial State</string>
|
||||
<string>Initial State:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>initialStateEditor</cstring>
|
||||
|
||||
@@ -19,9 +19,11 @@ using namespace QtUtils;
|
||||
/*
|
||||
MemoryViewTable
|
||||
*/
|
||||
void MemoryViewTable::UpdateStartAddress(u32 start)
|
||||
void MemoryViewTable::UpdateStartAddress(u32 start, NavigationHistoryOperation history_operation)
|
||||
{
|
||||
startAddress = start & ~0xF;
|
||||
navigation_history.push(startAddress, history_operation);
|
||||
parent->update();
|
||||
}
|
||||
|
||||
void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
|
||||
@@ -41,6 +43,13 @@ void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
|
||||
else
|
||||
startAddress += 0x10;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
navigation_history.pushWithDelay(startAddress);
|
||||
parent->update();
|
||||
}
|
||||
|
||||
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
|
||||
@@ -684,7 +693,7 @@ MemoryView::MemoryView(const DebuggerViewParameters& parameters)
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &MemoryView::customContextMenuRequested, this, &MemoryView::openContextMenu);
|
||||
|
||||
m_table.UpdateStartAddress(0x100000);
|
||||
m_table.UpdateStartAddress(0x100000, NavigationHistoryOperation::INSTANT_PUSH);
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
@@ -723,7 +732,10 @@ bool MemoryView::fromJson(const JsonValueWrapper& json)
|
||||
|
||||
auto start_address = json.value().FindMember("startAddress");
|
||||
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
|
||||
m_table.UpdateStartAddress(start_address->value.GetUint());
|
||||
{
|
||||
m_table.navigation_history.clear();
|
||||
m_table.UpdateStartAddress(start_address->value.GetUint(), NavigationHistoryOperation::INSTANT_PUSH);
|
||||
}
|
||||
|
||||
auto view_type = json.value().FindMember("viewType");
|
||||
if (view_type != json.value().MemberEnd() && view_type->value.IsInt())
|
||||
@@ -746,6 +758,40 @@ bool MemoryView::fromJson(const JsonValueWrapper& json)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryView::supportsNavigation()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryView::canNavigateBack()
|
||||
{
|
||||
return m_table.navigation_history.canGoBack();
|
||||
}
|
||||
|
||||
bool MemoryView::canNavigateForward()
|
||||
{
|
||||
return m_table.navigation_history.canGoForward();
|
||||
}
|
||||
|
||||
void MemoryView::navigateBack()
|
||||
{
|
||||
std::optional<u32> address = m_table.navigation_history.back();
|
||||
if (!address.has_value())
|
||||
return;
|
||||
|
||||
m_table.UpdateStartAddress(*address, NavigationHistoryOperation::NO_PUSH);
|
||||
}
|
||||
|
||||
void MemoryView::navigateForward()
|
||||
{
|
||||
std::optional<u32> address = m_table.navigation_history.forward();
|
||||
if (!address.has_value())
|
||||
return;
|
||||
|
||||
m_table.UpdateStartAddress(*address, NavigationHistoryOperation::NO_PUSH);
|
||||
}
|
||||
|
||||
|
||||
void MemoryView::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
@@ -928,17 +974,20 @@ void MemoryView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
if (event->angleDelta().y() < 0)
|
||||
{
|
||||
m_table.UpdateStartAddress(m_table.startAddress + 0x10);
|
||||
m_table.UpdateStartAddress(m_table.startAddress + 0x10, NavigationHistoryOperation::DELAYED_PUSH);
|
||||
}
|
||||
else if (event->angleDelta().y() > 0)
|
||||
{
|
||||
m_table.UpdateStartAddress(m_table.startAddress - 0x10);
|
||||
m_table.UpdateStartAddress(m_table.startAddress - 0x10, NavigationHistoryOperation::DELAYED_PUSH);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
// Alt is used for the global navigation shortcuts.
|
||||
if (event->modifiers() & Qt::AltModifier)
|
||||
return;
|
||||
|
||||
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
|
||||
{
|
||||
switch (event->key())
|
||||
@@ -960,9 +1009,8 @@ void MemoryView::keyPressEvent(QKeyEvent* event)
|
||||
|
||||
void MemoryView::gotoAddress(u32 address)
|
||||
{
|
||||
m_table.UpdateStartAddress(address & ~0xF);
|
||||
m_table.UpdateStartAddress(address & ~0xF, NavigationHistoryOperation::INSTANT_PUSH);
|
||||
m_table.selectedAddress = address;
|
||||
update();
|
||||
setFocus();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "ui_MemoryView.h"
|
||||
|
||||
#include "Debugger/DebuggerView.h"
|
||||
#include "Debugger/NavigationHistoryStack.h"
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/DisassemblyManager.h"
|
||||
@@ -93,7 +94,9 @@ public:
|
||||
u32 selectedAddress = 0;
|
||||
s32 selectedIndex = 0;
|
||||
|
||||
void UpdateStartAddress(u32 start);
|
||||
NavigationHistoryStack<u32> navigation_history;
|
||||
|
||||
void UpdateStartAddress(u32 start, NavigationHistoryOperation history_operation);
|
||||
void UpdateSelectedAddress(u32 selected, bool page = false);
|
||||
void DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu);
|
||||
void SelectAt(QPoint pos);
|
||||
@@ -136,6 +139,12 @@ public:
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
bool supportsNavigation() override;
|
||||
bool canNavigateBack() override;
|
||||
bool canNavigateForward() override;
|
||||
void navigateBack() override;
|
||||
void navigateForward() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debugger/DebuggerWindow.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <deque>
|
||||
|
||||
enum class NavigationHistoryOperation
|
||||
{
|
||||
INSTANT_PUSH,
|
||||
DELAYED_PUSH,
|
||||
NO_PUSH
|
||||
};
|
||||
|
||||
/// Data structure for storing navigation history, to be used for the back and
|
||||
/// forward buttons.
|
||||
template <typename Element>
|
||||
class NavigationHistoryStack
|
||||
{
|
||||
public:
|
||||
NavigationHistoryStack()
|
||||
: m_position(m_elements.end())
|
||||
{
|
||||
m_timer.setInterval(DELAY_MILLISECONDS);
|
||||
|
||||
QObject::connect(&m_timer, &QTimer::timeout, [this]() {
|
||||
pushInstantly(std::move(m_pending));
|
||||
m_timer.stop();
|
||||
});
|
||||
}
|
||||
|
||||
NavigationHistoryStack(Element element)
|
||||
: NavigationHistoryStack()
|
||||
{
|
||||
pushInstantly(std::move(element));
|
||||
}
|
||||
|
||||
NavigationHistoryStack(const NavigationHistoryStack<Element>&) = delete;
|
||||
NavigationHistoryStack<Element>& operator=(const NavigationHistoryStack<Element>&) = delete;
|
||||
|
||||
NavigationHistoryStack(NavigationHistoryStack<Element>&&) = delete;
|
||||
NavigationHistoryStack<Element>& operator=(NavigationHistoryStack<Element>&&) = delete;
|
||||
|
||||
/// Push an element onto the stack.
|
||||
void push(Element element, NavigationHistoryOperation operation)
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case NavigationHistoryOperation::INSTANT_PUSH:
|
||||
pushInstantly(std::move(element));
|
||||
break;
|
||||
case NavigationHistoryOperation::DELAYED_PUSH:
|
||||
pushWithDelay(std::move(element));
|
||||
break;
|
||||
case NavigationHistoryOperation::NO_PUSH:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait a second, and then push an element onto the stack. If another
|
||||
/// element is pushed, then that will take priority. This is useful if we're
|
||||
/// storing a scroll position, and don't want the stack to be spammed with
|
||||
/// new values when the user scrolls, for example.
|
||||
void pushWithDelay(Element element)
|
||||
{
|
||||
m_pending = std::move(element);
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
/// Push an element onto the stack instantly.
|
||||
void pushInstantly(Element element)
|
||||
{
|
||||
m_timer.stop();
|
||||
|
||||
if (m_position != m_elements.end() && element == *(m_position - 1))
|
||||
return;
|
||||
|
||||
updateNavigationButtons();
|
||||
|
||||
m_elements.erase(m_position, m_elements.end());
|
||||
m_elements.push_back(std::move(element));
|
||||
|
||||
if (m_elements.size() > MAX_ELEMENTS)
|
||||
m_elements.pop_front();
|
||||
|
||||
m_position = m_elements.end();
|
||||
}
|
||||
|
||||
/// Should to user have an option to go back?
|
||||
bool canGoBack() const
|
||||
{
|
||||
return m_elements.begin() != m_elements.end() && m_position > m_elements.begin() + 1;
|
||||
}
|
||||
|
||||
/// Should the user have an option to go forward?
|
||||
bool canGoForward() const
|
||||
{
|
||||
return m_position != m_elements.end();
|
||||
}
|
||||
|
||||
// Retrieve the current element.
|
||||
std::optional<Element> current() const
|
||||
{
|
||||
if (m_position == m_elements.begin())
|
||||
return std::nullopt;
|
||||
|
||||
return *(m_position - 1);
|
||||
}
|
||||
|
||||
/// Go back one.
|
||||
std::optional<Element> back()
|
||||
{
|
||||
updateNavigationButtons();
|
||||
|
||||
if (!canGoBack())
|
||||
return std::nullopt;
|
||||
|
||||
m_position--;
|
||||
return *(m_position - 1);
|
||||
}
|
||||
|
||||
/// Go forward one.
|
||||
std::optional<Element> forward()
|
||||
{
|
||||
updateNavigationButtons();
|
||||
|
||||
if (!canGoForward())
|
||||
return std::nullopt;
|
||||
|
||||
Element element = *m_position;
|
||||
m_position++;
|
||||
return element;
|
||||
}
|
||||
|
||||
/// Remove all elements.
|
||||
void clear()
|
||||
{
|
||||
m_elements.clear();
|
||||
m_position = m_elements.end();
|
||||
m_timer.stop();
|
||||
}
|
||||
|
||||
private:
|
||||
void updateNavigationButtons()
|
||||
{
|
||||
QTimer::singleShot(0, []() {
|
||||
if (g_debugger_window)
|
||||
g_debugger_window->updateNavigationButtons();
|
||||
});
|
||||
}
|
||||
|
||||
std::deque<Element> m_elements;
|
||||
std::deque<Element>::iterator m_position;
|
||||
|
||||
QTimer m_timer;
|
||||
Element m_pending;
|
||||
|
||||
static constexpr int DELAY_MILLISECONDS = 1000;
|
||||
static constexpr size_t MAX_ELEMENTS = 1000;
|
||||
};
|
||||
@@ -216,7 +216,7 @@ void LogWindow::createUi()
|
||||
m_newline_on_enter = state == Qt::CheckState::Checked;
|
||||
});
|
||||
|
||||
m_input_hbox = new QHBoxLayout(this);
|
||||
m_input_hbox = new QHBoxLayout;
|
||||
m_input_hbox->addWidget(m_line_input, 1);
|
||||
m_input_hbox->addWidget(m_local_echo_checkbox);
|
||||
m_input_hbox->addWidget(m_newline_on_enter_checkbox);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,7 @@
|
||||
<QtMoc Include="Debugger\StackView.h" />
|
||||
<QtMoc Include="Debugger\ModuleModel.h" />
|
||||
<QtMoc Include="Debugger\ModuleView.h" />
|
||||
<QtMoc Include="Debugger\NavigationHistoryStack.h" />
|
||||
<QtMoc Include="Debugger\ThreadModel.h" />
|
||||
<QtMoc Include="Debugger\ThreadView.h" />
|
||||
<ClInclude Include="Debugger\DebuggerSettingsManager.h" />
|
||||
|
||||
@@ -529,6 +529,9 @@
|
||||
<QtMoc Include="Debugger\ModuleView.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\NavigationHistoryStack.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
<QtMoc Include="Debugger\ModuleModel.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</QtMoc>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000"><path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000"><path d="M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff"><path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff"><path d="M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 222 B |
@@ -2,12 +2,14 @@
|
||||
<qresource>
|
||||
<file>icons/AppIcon64.png</file>
|
||||
<file>icons/black/index.theme</file>
|
||||
<file>icons/black/svg/arrow-left-line.svg</file>
|
||||
<file>icons/black/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/black/svg/arrow-right-line.svg</file>
|
||||
<file>icons/black/svg/artboard-2-line.svg</file>
|
||||
<file>icons/black/svg/at.svg</file>
|
||||
<file>icons/black/svg/band-aid-line.svg</file>
|
||||
<file>icons/black/svg/booklet.svg</file>
|
||||
<file>icons/black/svg/book.svg</file>
|
||||
<file>icons/black/svg/booklet.svg</file>
|
||||
<file>icons/black/svg/brush-line.svg</file>
|
||||
<file>icons/black/svg/bug-line.svg</file>
|
||||
<file>icons/black/svg/buzz-controller-line.svg</file>
|
||||
@@ -113,12 +115,14 @@
|
||||
<file>icons/QT.png</file>
|
||||
<file>icons/update.png</file>
|
||||
<file>icons/white/index.theme</file>
|
||||
<file>icons/white/svg/arrow-left-line.svg</file>
|
||||
<file>icons/white/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/white/svg/arrow-right-line.svg</file>
|
||||
<file>icons/white/svg/artboard-2-line.svg</file>
|
||||
<file>icons/white/svg/at.svg</file>
|
||||
<file>icons/white/svg/band-aid-line.svg</file>
|
||||
<file>icons/white/svg/booklet.svg</file>
|
||||
<file>icons/white/svg/book.svg</file>
|
||||
<file>icons/white/svg/booklet.svg</file>
|
||||
<file>icons/white/svg/brush-line.svg</file>
|
||||
<file>icons/white/svg/bug-line.svg</file>
|
||||
<file>icons/white/svg/buzz-controller-line.svg</file>
|
||||
|
||||
+28
-1
@@ -385,7 +385,13 @@ void GSClut::Read32(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA)
|
||||
case PSMT4HH:
|
||||
clut += (TEX0.CSA & 15) << 4;
|
||||
// TODO: merge these functions
|
||||
ReadCLUT_T32_I4(clut, m_buff32);
|
||||
|
||||
// This is for a situation with Breath of Fire Dragon Quarter where it reinterprets the buffer under CSM2 after reading as CSM1.
|
||||
if (TEX0.CSM == 1 && m_write.TEX0.CSM == 0)
|
||||
ReadCLUT_T32_I4_Swizzled(clut, m_buff32);
|
||||
else
|
||||
ReadCLUT_T32_I4(clut, m_buff32);
|
||||
|
||||
ExpandCLUT64_T32_I8(m_buff32, (u64*)m_buff64); // sw renderer does not need m_buff64 anymore
|
||||
break;
|
||||
}
|
||||
@@ -632,6 +638,27 @@ __forceinline void GSClut::WriteCLUT_T16_I4_CSM1(const u16* RESTRICT src, u16* R
|
||||
}
|
||||
}
|
||||
|
||||
// These functions are only used if the CLUT is in 32bit mode and it swaps to CSM2, a very obscure setup used by Breath of Fire Dragon Quarter.
|
||||
// This doesn't handle offsetting the CLUT or anything crazy, that would be a lot more work
|
||||
void GSClut::ReadCLUT_T32_I4_Swizzled(const u16* RESTRICT clut, u32* RESTRICT dst)
|
||||
{
|
||||
// Point to the base of the palette block
|
||||
GSVector4i* s = (GSVector4i*)clut;
|
||||
GSVector4i* d = (GSVector4i*)dst;
|
||||
|
||||
GSVector4i v0 = s[0];
|
||||
GSVector4i v1 = s[2];
|
||||
GSVector4i v2 = s[32];
|
||||
GSVector4i v3 = s[34];
|
||||
|
||||
GSVector4i::sw16(v0, v2, v1, v3);
|
||||
|
||||
d[0] = v0;
|
||||
d[1] = v1;
|
||||
d[2] = v2;
|
||||
d[3] = v3;
|
||||
}
|
||||
|
||||
void GSClut::ReadCLUT_T32_I8(const u16* RESTRICT clut, u32* RESTRICT dst, int offset)
|
||||
{
|
||||
// Okay this deserves a small explanation
|
||||
|
||||
@@ -78,6 +78,7 @@ class alignas(32) GSClut final : public GSAlignedClass<32>
|
||||
static void WriteCLUT_T32_I4_CSM1(const u32* RESTRICT src, u16* RESTRICT clut);
|
||||
static void WriteCLUT_T16_I8_CSM1(const u16* RESTRICT src, u16* RESTRICT clut);
|
||||
static void WriteCLUT_T16_I4_CSM1(const u16* RESTRICT src, u16* RESTRICT clut);
|
||||
static void ReadCLUT_T32_I4_Swizzled(const u16* RESTRICT clut, u32* RESTRICT dst);
|
||||
static void ReadCLUT_T32_I8(const u16* RESTRICT clut, u32* RESTRICT dst, int offset);
|
||||
static void ReadCLUT_T32_I4(const u16* RESTRICT clut, u32* RESTRICT dst);
|
||||
//static void ReadCLUT_T32_I4(const u16* RESTRICT clut, u32* RESTRICT dst32, u64* RESTRICT dst64);
|
||||
|
||||
+11
-11
@@ -5,23 +5,23 @@
|
||||
|
||||
// clang-format off
|
||||
|
||||
#define VM_SIZE 4194304u
|
||||
#define HALF_VM_SIZE (VM_SIZE / 2u)
|
||||
#define GS_PAGE_SIZE 8192u
|
||||
#define GS_BLOCK_SIZE 256u
|
||||
#define GS_COLUMN_SIZE 64u
|
||||
#define GS_BLOCKS_PER_PAGE (GS_PAGE_SIZE / GS_BLOCK_SIZE)
|
||||
|
||||
#define GS_MAX_PAGES (VM_SIZE / GS_PAGE_SIZE)
|
||||
#define GS_MAX_BLOCKS (VM_SIZE / GS_BLOCK_SIZE)
|
||||
#define GS_MAX_COLUMNS (VM_SIZE / GS_COLUMN_SIZE)
|
||||
|
||||
//if defined, will send much info in reply to the API title info queri from PCSX2
|
||||
//default should be undefined
|
||||
//#define GSTITLEINFO_API_FORCE_VERBOSE
|
||||
|
||||
#include "GSVector.h"
|
||||
|
||||
constexpr u32 VM_SIZE = 4194304u;
|
||||
constexpr u32 HALF_VM_SIZE = (VM_SIZE / 2u);
|
||||
constexpr u32 GS_PAGE_SIZE = 8192u;
|
||||
constexpr u32 GS_BLOCK_SIZE = 256u;
|
||||
constexpr u32 GS_COLUMN_SIZE = 64u;
|
||||
constexpr u32 GS_BLOCKS_PER_PAGE = (GS_PAGE_SIZE / GS_BLOCK_SIZE);
|
||||
|
||||
constexpr u32 GS_MAX_PAGES = (VM_SIZE / GS_PAGE_SIZE);
|
||||
constexpr u32 GS_MAX_BLOCKS = (VM_SIZE / GS_BLOCK_SIZE);
|
||||
constexpr u32 GS_MAX_COLUMNS = (VM_SIZE / GS_COLUMN_SIZE);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
enum GS_PRIM
|
||||
|
||||
+205
-194
@@ -289,55 +289,58 @@ void GSState::ResetDrawBufferIdx()
|
||||
{
|
||||
int entry_ptr = 0;
|
||||
|
||||
for (int i = 0; i < m_used_buffers_idx; i++)
|
||||
if (GSConfig.UserHacks_DrawBuffering && m_used_buffers_idx > 1)
|
||||
{
|
||||
// There can be situations like VSync where it won't purge the draws, this is bad for us!
|
||||
if (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx)
|
||||
for (int i = 0; i < m_used_buffers_idx; i++)
|
||||
{
|
||||
if (m_index_buffers[i].tail == 0)
|
||||
m_env_buffers[i].draw_rect = GSVector4i::zero();
|
||||
|
||||
if (entry_ptr == i && (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx))
|
||||
// There can be situations like VSync where it won't purge the draws, this is bad for us!
|
||||
if (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx)
|
||||
{
|
||||
if (m_index_buffers[i].tail == 0)
|
||||
m_env_buffers[i].draw_rect = GSVector4i::zero();
|
||||
|
||||
if (entry_ptr == i && (m_index_buffers[i].tail > 0 || i == m_current_buffer_idx))
|
||||
{
|
||||
entry_ptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(m_vertex_buffers[entry_ptr].buff, m_vertex_buffers[i].buff, sizeof(GSVertex) * m_vertex_buffers[i].tail);
|
||||
|
||||
m_vertex_buffers[entry_ptr].head = m_vertex_buffers[i].head;
|
||||
m_vertex_buffers[entry_ptr].tail = m_vertex_buffers[i].tail;
|
||||
m_vertex_buffers[entry_ptr].next = m_vertex_buffers[i].next;
|
||||
|
||||
memcpy(m_index_buffers[entry_ptr].buff, m_index_buffers[i].buff, sizeof(u16) * m_index_buffers[i].tail);
|
||||
m_index_buffers[entry_ptr].tail = m_index_buffers[i].tail;
|
||||
|
||||
if (m_vertex_buffers[entry_ptr].tail != 0)
|
||||
{
|
||||
memcpy(m_vertex_buffers[entry_ptr].xy, m_vertex_buffers[i].xy, sizeof(m_vertex_buffers[i].xy));
|
||||
m_vertex_buffers[entry_ptr].xyhead = m_vertex_buffers[i].xyhead;
|
||||
m_vertex_buffers[entry_ptr].xy_tail = m_vertex_buffers[i].xy_tail;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vertex_buffers[entry_ptr].xy_tail = 0;
|
||||
}
|
||||
|
||||
memcpy(&m_env_buffers[entry_ptr], &m_env_buffers[i], sizeof(m_env_buffers[i]));
|
||||
|
||||
|
||||
if (i == m_current_buffer_idx)
|
||||
m_current_buffer_idx = entry_ptr;
|
||||
|
||||
entry_ptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(m_vertex_buffers[entry_ptr].buff, m_vertex_buffers[i].buff, sizeof(GSVertex) * m_vertex_buffers[i].tail);
|
||||
|
||||
m_vertex_buffers[entry_ptr].head = m_vertex_buffers[i].head;
|
||||
m_vertex_buffers[entry_ptr].tail = m_vertex_buffers[i].tail;
|
||||
m_vertex_buffers[entry_ptr].next = m_vertex_buffers[i].next;
|
||||
|
||||
memcpy(m_index_buffers[entry_ptr].buff, m_index_buffers[i].buff, sizeof(u16) * m_index_buffers[i].tail);
|
||||
m_index_buffers[entry_ptr].tail = m_index_buffers[i].tail;
|
||||
|
||||
if (m_vertex_buffers[entry_ptr].tail != 0)
|
||||
if (i != (entry_ptr - 1))
|
||||
{
|
||||
memcpy(m_vertex_buffers[entry_ptr].xy, m_vertex_buffers[i].xy, sizeof(m_vertex_buffers[i].xy));
|
||||
m_vertex_buffers[entry_ptr].xyhead = m_vertex_buffers[i].xyhead;
|
||||
m_vertex_buffers[entry_ptr].xy_tail = m_vertex_buffers[i].xy_tail;
|
||||
m_index_buffers[i].tail = 0;
|
||||
memset(&m_env_buffers[i], 0, sizeof(GSDrawBufferEnv));
|
||||
m_vertex_buffers[i].head = m_vertex_buffers[i].tail = m_vertex_buffers[i].next = 0;
|
||||
m_vertex_buffers[i].xy_tail = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vertex_buffers[entry_ptr].xy_tail = 0;
|
||||
}
|
||||
|
||||
memcpy(&m_env_buffers[entry_ptr], &m_env_buffers[i], sizeof(m_env_buffers[i]));
|
||||
|
||||
|
||||
if (i == m_current_buffer_idx)
|
||||
m_current_buffer_idx = entry_ptr;
|
||||
|
||||
entry_ptr++;
|
||||
}
|
||||
|
||||
if (i != (entry_ptr - 1))
|
||||
{
|
||||
m_index_buffers[i].tail = 0;
|
||||
memset(&m_env_buffers[i], 0, sizeof(GSDrawBufferEnv));
|
||||
m_vertex_buffers[i].head = m_vertex_buffers[i].tail = m_vertex_buffers[i].next = 0;
|
||||
m_vertex_buffers[i].xy_tail = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,18 +392,11 @@ void GSState::ResetDrawBuffers()
|
||||
// exclude_current is used if there is a flush for a reason other than the normal context change.
|
||||
void GSState::FlushBuffers(bool flush_base_only, bool use_flush_reason, GSFlushReason flush_reason)
|
||||
{
|
||||
const int current_idx = m_current_buffer_idx;
|
||||
bool restore_env = false;
|
||||
|
||||
if (m_used_buffers_idx > 0)
|
||||
if (m_used_buffers_idx > 1)
|
||||
{
|
||||
if (m_used_buffers_idx > 1)
|
||||
{
|
||||
restore_env = true;
|
||||
memcpy(&m_temp_env, &m_env, sizeof(m_env));
|
||||
}
|
||||
else if (m_index_buffers[0].tail == 0)
|
||||
return;
|
||||
const int current_idx = m_current_buffer_idx;
|
||||
bool restore_env = true;
|
||||
memcpy(&m_temp_env, &m_env, sizeof(m_env));
|
||||
|
||||
const int max_flushes = flush_base_only ? 1 : m_used_buffers_idx;
|
||||
for (int i = 0; i < max_flushes; i++)
|
||||
@@ -440,19 +436,26 @@ void GSState::FlushBuffers(bool flush_base_only, bool use_flush_reason, GSFlushR
|
||||
else
|
||||
FlushDraw(GSFlushReason::CONTEXTCHANGE);
|
||||
}
|
||||
|
||||
// Restore the environment
|
||||
m_current_buffer_idx = current_idx;
|
||||
m_index = &m_index_buffers[m_current_buffer_idx];
|
||||
m_vertex = &m_vertex_buffers[m_current_buffer_idx];
|
||||
|
||||
const int ctx = m_env_buffers[m_current_buffer_idx].m_backed_up_ctx;
|
||||
std::memcpy(&m_prev_env, &m_env_buffers[m_current_buffer_idx].m_env, 88);
|
||||
std::memcpy(&m_prev_env.CTXT[0], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[0], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[1], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[1], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset));
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (use_flush_reason || flush_reason == VSYNC)
|
||||
FlushDraw(flush_reason);
|
||||
else
|
||||
FlushDraw(GSFlushReason::CONTEXTCHANGE);
|
||||
}
|
||||
|
||||
// Restore the environment
|
||||
m_current_buffer_idx = current_idx;
|
||||
m_index = &m_index_buffers[m_current_buffer_idx];
|
||||
m_vertex = &m_vertex_buffers[m_current_buffer_idx];
|
||||
|
||||
const int ctx = m_env_buffers[m_current_buffer_idx].m_backed_up_ctx;
|
||||
std::memcpy(&m_prev_env, &m_env_buffers[m_current_buffer_idx].m_env, 88);
|
||||
std::memcpy(&m_prev_env.CTXT[0], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[0], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[1], &m_env_buffers[m_current_buffer_idx].m_env.CTXT[1], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].offset));
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor, sizeof(m_env_buffers[m_current_buffer_idx].m_env.CTXT[ctx].scissor));
|
||||
}
|
||||
|
||||
void GSState::PushBuffer()
|
||||
@@ -504,6 +507,18 @@ void GSState::PushBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
void GSState::SetDrawBufferEnv()
|
||||
{
|
||||
memcpy(&m_env_buffers[m_current_buffer_idx].m_env, &m_env, sizeof(GSDrawingEnvironment));
|
||||
m_env_buffers[m_current_buffer_idx].m_backed_up_ctx = m_backed_up_ctx;
|
||||
}
|
||||
|
||||
void GSState::SetDrawBuffDirty()
|
||||
{
|
||||
m_env_buffers[m_current_buffer_idx].m_dirty_regs = m_dirty_gs_regs;
|
||||
m_env_buffers[m_current_buffer_idx].draw_rect = temp_draw_rect;
|
||||
}
|
||||
|
||||
bool GSState::CanBufferNewDraw()
|
||||
{
|
||||
if (!GSConfig.UserHacks_DrawBuffering)
|
||||
@@ -692,18 +707,6 @@ bool GSState::CanBufferNewDraw()
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSState::SetDrawBufferEnv()
|
||||
{
|
||||
memcpy(&m_env_buffers[m_current_buffer_idx].m_env, &m_env, sizeof(GSDrawingEnvironment));
|
||||
m_env_buffers[m_current_buffer_idx].m_backed_up_ctx = m_backed_up_ctx;
|
||||
}
|
||||
|
||||
void GSState::SetDrawBuffDirty()
|
||||
{
|
||||
m_env_buffers[m_current_buffer_idx].m_dirty_regs = m_dirty_gs_regs;
|
||||
m_env_buffers[m_current_buffer_idx].draw_rect = temp_draw_rect;
|
||||
}
|
||||
|
||||
void GSState::ResetHandlers()
|
||||
{
|
||||
std::fill(std::begin(m_fpGIFPackedRegHandlers), std::end(m_fpGIFPackedRegHandlers), &GSState::GIFPackedRegHandlerNull);
|
||||
@@ -1673,7 +1676,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
|
||||
{
|
||||
for (int b = 0; b < m_used_buffers_idx; b++)
|
||||
{
|
||||
GSDrawingEnvironment& buffered_env = m_env_buffers[b].m_env;
|
||||
GSDrawingEnvironment& buffered_env = (m_current_buffer_idx == b) ? m_prev_env : m_env_buffers[b].m_env;
|
||||
if ((buffered_env.PRIM.TME && (buffered_env.CTXT[buffered_env.PRIM.CTXT].TEX0.PSM & 0x7) >= 3) || (m_mem.m_clut.IsInvalid() & 2))
|
||||
Flush(GSFlushReason::CLUTCHANGE);
|
||||
}
|
||||
@@ -2561,9 +2564,11 @@ void GSState::FlushPrim()
|
||||
Console.Warning("GS: Possible invalid draw, Frame PSM %x ZPSM %x", m_context->FRAME.PSM, m_context->ZBUF.PSM);
|
||||
}
|
||||
#endif
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
// Update scissor, it may have been modified by a previous draw
|
||||
m_env.CTXT[PRIM->CTXT].UpdateScissor();
|
||||
m_vt.Update(m_vertex->buff, m_index->buff, m_vertex->tail, m_index->tail, GSUtil::GetPrimClass(PRIM->PRIM));
|
||||
m_vt.Update(vtx_buff.buff, idx_buff.buff, vtx_buff.tail, idx_buff.tail, GSUtil::GetPrimClass(PRIM->PRIM));
|
||||
|
||||
// Texel coordinate rounding
|
||||
// Helps Manhunt (lights shining through objects).
|
||||
@@ -2575,13 +2580,13 @@ void GSState::FlushPrim()
|
||||
{
|
||||
const bool is_sprite = GSUtil::GetPrimClass(PRIM->PRIM) == GS_PRIM_CLASS::GS_SPRITE_CLASS;
|
||||
// ST's have the lowest 9 bits (or greater depending on exponent difference) rounding down (from hardware tests).
|
||||
for (int i = m_index->tail - 1; i >= 0; i--)
|
||||
for (int i = idx_buff.tail - 1; i >= 0; i--)
|
||||
{
|
||||
GSVertex* v = &m_vertex->buff[m_index->buff[i]];
|
||||
GSVertex* v = &vtx_buff.buff[idx_buff.buff[i]];
|
||||
|
||||
// Only Q on the second vertex is valid
|
||||
if (!(i & 1) && is_sprite)
|
||||
v->RGBAQ.Q = m_vertex->buff[m_index->buff[i + 1]].RGBAQ.Q;
|
||||
v->RGBAQ.Q = vtx_buff.buff[idx_buff.buff[i + 1]].RGBAQ.Q;
|
||||
|
||||
int T = std::bit_cast<int>(v->ST.T);
|
||||
int Q = std::bit_cast<int>(v->RGBAQ.Q);
|
||||
@@ -2634,7 +2639,7 @@ void GSState::FlushPrim()
|
||||
Draw();
|
||||
|
||||
g_perfmon.Put(GSPerfMon::Draw, 1);
|
||||
g_perfmon.Put(GSPerfMon::Prim, m_index->tail / GSUtil::GetVertexCount(PRIM->PRIM));
|
||||
g_perfmon.Put(GSPerfMon::Prim, idx_buff.tail / GSUtil::GetVertexCount(PRIM->PRIM));
|
||||
|
||||
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
{
|
||||
@@ -2646,15 +2651,15 @@ void GSState::FlushPrim()
|
||||
}
|
||||
}
|
||||
|
||||
m_index->tail = 0;
|
||||
m_vertex->head = 0;
|
||||
idx_buff.tail = 0;
|
||||
vtx_buff.head = 0;
|
||||
|
||||
if (unused > 0)
|
||||
{
|
||||
memcpy(m_vertex->buff, buff, sizeof(GSVertex) * unused);
|
||||
memcpy(vtx_buff.buff, buff, sizeof(GSVertex) * unused);
|
||||
|
||||
m_vertex->tail = unused;
|
||||
m_vertex->next = next > head ? next - head : 0;
|
||||
vtx_buff.tail = unused;
|
||||
vtx_buff.next = next > head ? next - head : 0;
|
||||
|
||||
// If it's a Triangle fan the XY buffer needs to be updated to point to the correct head vert
|
||||
// Jak 3 shadows get spikey (with autoflush) if you don't.
|
||||
@@ -2662,22 +2667,22 @@ void GSState::FlushPrim()
|
||||
{
|
||||
for (u32 i = 0; i < unused; i++)
|
||||
{
|
||||
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex->buff[i];
|
||||
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&vtx_buff.buff[i];
|
||||
GSVector4i v = vert_ptr[1];
|
||||
v = v.xxxx().u16to32().sub32(m_xyof);
|
||||
m_vertex->xy[i & 3] = v;
|
||||
m_vertex->xy_tail = unused;
|
||||
vtx_buff.xy[i & 3] = v;
|
||||
vtx_buff.xy_tail = unused;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vertex->tail = 0;
|
||||
m_vertex->next = 0;
|
||||
vtx_buff.tail = 0;
|
||||
vtx_buff.next = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
GSVector4i GSState::GetTEX0Rect(GSDrawingContext prev_ctx)
|
||||
GSVector4i GSState::GetTEX0Rect(const GSDrawingContext& prev_ctx)
|
||||
{
|
||||
GSVector4i ret = GSVector4i::zero();
|
||||
|
||||
@@ -2723,27 +2728,19 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
|
||||
const GIFRegBITBLTBUF& blit = m_env.BITBLTBUF;
|
||||
|
||||
const GSVector4i write_rect = GSVector4i(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, m_env.TRXPOS.DSAX + w, m_env.TRXPOS.DSAY + h);
|
||||
const u32 write_start_bp = GSLocalMemory::GetStartBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect);
|
||||
const u32 write_end_bp = ((GSLocalMemory::GetEndBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
|
||||
for (int i = 0; i < m_used_buffers_idx; i++)
|
||||
{
|
||||
GSIndexBuff* cur_index_buff = &m_index_buffers[i];
|
||||
GSVertexBuff* cur_vertex_buff = &m_vertex_buffers[i];
|
||||
const GSDrawingContext& prev_ctx = m_env_buffers[i].m_env.CTXT[m_env_buffers[i].m_backed_up_ctx];
|
||||
const GSDrawingEnvironment& prev_env = m_env_buffers[i].m_env;
|
||||
GSVector4i tex_rect = prev_env.PRIM.TME ? GetTEX0Rect(prev_ctx) : GSVector4i::zero();
|
||||
const GSDrawingContext& prev_ctx = m_current_buffer_idx == i ? m_prev_env.CTXT[m_backed_up_ctx] :m_env_buffers[i].m_env.CTXT[m_env_buffers[i].m_backed_up_ctx];
|
||||
const GSDrawingEnvironment& prev_env = m_current_buffer_idx == i ? m_prev_env : m_env_buffers[i].m_env;
|
||||
|
||||
if (cur_index_buff->tail > 0)
|
||||
{
|
||||
// Only flush on a NEW transfer if a pending one is using the same address or overlap.
|
||||
// Check Fast & Furious (Hardare mode) and Assault Suits Valken (either renderer) and Tomb Raider - Angel of Darkness menu (TBP != DBP but overlaps).
|
||||
// Cartoon Network overwrites its own Z buffer in the middle of a draw.
|
||||
// Alias wraps its transfers, so be careful
|
||||
const GSVector4i read_rect = GSVector4i(m_env.TRXPOS.SSAX, m_env.TRXPOS.SSAY, m_env.TRXPOS.SSAX + w, m_env.TRXPOS.SSAY + h);
|
||||
|
||||
if (req_write && prev_env.PRIM.TME)
|
||||
{
|
||||
GSVector4i tex_rect = prev_env.PRIM.TME ? GetTEX0Rect(prev_ctx) : GSVector4i::zero();
|
||||
// Tex rect could be invalid showing 1024x1024 when it isn't. If the frame is only 1 page wide, it's either a big strip or a single page draw.
|
||||
// This large texture causes misdetection of overlapping writes, causing our heuristics in the hardware renderer for future draws to be missing.
|
||||
// Either way if we check the queued up coordinates, it should give us a fair idea. (Cabela's Trophy Bucks)
|
||||
@@ -2842,6 +2839,11 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
|
||||
}
|
||||
}
|
||||
|
||||
// Only flush on a NEW transfer if a pending one is using the same address or overlap.
|
||||
// Check Fast & Furious (Hardare mode) and Assault Suits Valken (either renderer) and Tomb Raider - Angel of Darkness menu (TBP != DBP but overlaps).
|
||||
// Cartoon Network overwrites its own Z buffer in the middle of a draw.
|
||||
// Alias wraps its transfers, so be careful
|
||||
const GSVector4i read_rect = GSVector4i(m_env.TRXPOS.SSAX, m_env.TRXPOS.SSAY, m_env.TRXPOS.SSAX + w, m_env.TRXPOS.SSAY + h);
|
||||
const u32 frame_mask = GSLocalMemory::m_psm[prev_ctx.FRAME.PSM].fmsk;
|
||||
const bool frame_required = (!(prev_ctx.TEST.ATE && prev_ctx.TEST.ATST == 0 && (prev_ctx.TEST.AFAIL == 2 || prev_ctx.TEST.AFAIL == 0)) && ((prev_ctx.FRAME.FBMSK & frame_mask) != frame_mask)) || prev_ctx.TEST.DATE;
|
||||
const GSVector4i draw_rect = (m_current_buffer_idx == i) ? temp_draw_rect : m_env_buffers[i].draw_rect;
|
||||
@@ -2874,6 +2876,9 @@ void GSState::CheckWriteOverlap(bool req_write, bool req_read)
|
||||
|
||||
if (req_write)
|
||||
{
|
||||
const u32 write_start_bp = GSLocalMemory::GetStartBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect);
|
||||
const u32 write_end_bp = ((GSLocalMemory::GetEndBlockAddress(blit.DBP, blit.DBW, blit.DPSM, write_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
|
||||
// Invalid the CLUT if it crosses paths.
|
||||
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
|
||||
}
|
||||
@@ -2976,7 +2981,7 @@ void GSState::InitReadFIFO(u8* mem, int len)
|
||||
// Read the image all in one go.
|
||||
m_mem.ReadImageX(m_tr.x, m_tr.y, m_tr.buff, m_tr.total, m_env.BITBLTBUF, m_env.TRXPOS, m_env.TRXREG);
|
||||
|
||||
if (GSConfig.SaveRT && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
if (GSConfig.SaveTransferImages && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
{
|
||||
const std::string s(GetDrawDumpPath(
|
||||
"%05lld_read_%05x_%d_%s_%d_%d_%d_%d.bmp",
|
||||
@@ -5405,7 +5410,7 @@ __forceinline void GSState::CheckCLUTValidity(u32 prim)
|
||||
|
||||
for (int i = 0; i < m_used_buffers_idx; i++)
|
||||
{
|
||||
GSDrawingEnvironment& buffered_env = m_env_buffers[i].m_env;
|
||||
GSDrawingEnvironment& buffered_env = (m_current_buffer_idx == i) ? m_prev_env : m_env_buffers[i].m_env;
|
||||
const GSDrawingContext& ctx = buffered_env.CTXT[buffered_env.PRIM.CTXT];
|
||||
if ((m_index_buffers[i].tail > 0 || (m_vertex_buffers[i].tail == n - 1)) && (GSLocalMemory::m_psm[ctx.TEX0.PSM].pal == 0 || !buffered_env.PRIM.TME))
|
||||
{
|
||||
@@ -5421,7 +5426,8 @@ __forceinline void GSState::CheckCLUTValidity(u32 prim)
|
||||
if (prim != GS_POINTLIST || (m_index_buffers[i].tail > 1))
|
||||
endbp = fpsm.info.bn(temp_draw_rect.z - 1, temp_draw_rect.w - 1, ctx.FRAME.Block(), ctx.FRAME.FBW);
|
||||
|
||||
m_mem.m_clut.InvalidateRange(startbp, endbp, true);
|
||||
if (m_mem.m_clut.InvalidateRange(startbp, endbp, true))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5434,6 +5440,8 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
if ((m_index->tail & 1) && (prim == GS_TRIANGLESTRIP || prim == GS_TRIANGLEFAN) && !m_texflush_flag)
|
||||
return;
|
||||
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
// To briefly explain what's going on here, what we are checking for is draws over a texture when the source and destination are themselves.
|
||||
// Because one page of the texture gets buffered in the Texture Cache (the PS2's one) if any of those pixels are overwritten, you still read the old data.
|
||||
// So we need to calculate if a page boundary is being crossed for the format it is in and if the same part of the texture being written and read inside the draw.
|
||||
@@ -5442,8 +5450,8 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
{
|
||||
int n = 1;
|
||||
u32 buff[3];
|
||||
const u32 head = m_vertex->head;
|
||||
const u32 tail = m_vertex->tail;
|
||||
const u32 head = vtx_buff.head;
|
||||
const u32 tail = vtx_buff.tail;
|
||||
|
||||
switch (prim)
|
||||
{
|
||||
@@ -5510,7 +5518,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
// Get the rest of the rect.
|
||||
for (int i = 0; i < (n - 1); i++)
|
||||
{
|
||||
const GSVertex* v = &m_vertex->buff[buff[i]];
|
||||
const GSVertex* v = &vtx_buff.buff[buff[i]];
|
||||
|
||||
xy_coord.x = (static_cast<int>(v->XYZ.X) - static_cast<int>(m_context->XYOFFSET.OFX)) >> 4;
|
||||
xy_coord.y = (static_cast<int>(v->XYZ.Y) - static_cast<int>(m_context->XYOFFSET.OFY)) >> 4;
|
||||
@@ -5562,7 +5570,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
return;
|
||||
|
||||
// Get the last texture position from the last draw.
|
||||
const GSVertex* v = &m_vertex->buff[m_index->buff[m_index->tail - 1]];
|
||||
const GSVertex* v = &vtx_buff.buff[idx_buff.buff[idx_buff.tail - 1]];
|
||||
|
||||
if (PRIM->FST)
|
||||
{
|
||||
@@ -5652,13 +5660,13 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
const GSVector2i offset = GSVector2i(m_context->XYOFFSET.OFX, m_context->XYOFFSET.OFY);
|
||||
const GSVector4i scissor = m_context->scissor.in;
|
||||
GSVector4i old_draw_rect = GSVector4i::zero();
|
||||
int current_draw_end = m_index->tail;
|
||||
int current_draw_end = idx_buff.tail;
|
||||
|
||||
while (current_draw_end >= n)
|
||||
{
|
||||
for (int i = current_draw_end - 1; i >= current_draw_end - n; i--)
|
||||
{
|
||||
const GSVertex* v = &m_vertex->buff[m_index->buff[i]];
|
||||
const GSVertex* v = &vtx_buff.buff[idx_buff.buff[i]];
|
||||
|
||||
if (prim == GS_SPRITE && (i & 1))
|
||||
{
|
||||
@@ -5740,18 +5748,18 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
}
|
||||
}
|
||||
|
||||
bool GSState::CheckOverlapVerts(u32 n)
|
||||
__inline bool GSState::CheckOverlapVerts(u32 n)
|
||||
{
|
||||
if (!GSConfig.UserHacks_DrawBuffering)
|
||||
return false;
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
|
||||
if (m_recent_buffer_switch && ((m_vertex->tail + 1) - m_vertex->head) == n)
|
||||
if (m_recent_buffer_switch && ((vtx_buff.tail + 1) - vtx_buff.head) == n)
|
||||
{
|
||||
m_recent_buffer_switch = false;
|
||||
|
||||
if (m_used_buffers_idx > 1)
|
||||
{
|
||||
const GSVertex* v = &m_vertex->buff[0];
|
||||
const GSVertex* v = &vtx_buff.buff[0];
|
||||
GSVector2i cur_verts[3];
|
||||
|
||||
GSVector4i new_area = GSVector4i(m_v.XYZ.X - m_context->XYOFFSET.OFX, m_v.XYZ.Y - m_context->XYOFFSET.OFY).xyxy();
|
||||
@@ -5759,11 +5767,11 @@ bool GSState::CheckOverlapVerts(u32 n)
|
||||
|
||||
for (u32 i = 0; i < (n - 1); i++)
|
||||
{
|
||||
const int pos = (m_vertex->tail - 1) - i;
|
||||
const int pos = (vtx_buff.tail - 1) - i;
|
||||
|
||||
GSVector2i prev_vert;
|
||||
if (m_env.PRIM.PRIM == GS_TRIANGLEFAN && i == (n - 2))
|
||||
prev_vert = GSVector2i(v[m_vertex->head].XYZ.X - m_context->XYOFFSET.OFX, v[m_vertex->head].XYZ.X - m_context->XYOFFSET.OFY);
|
||||
prev_vert = GSVector2i(v[vtx_buff.head].XYZ.X - m_context->XYOFFSET.OFX, v[vtx_buff.head].XYZ.X - m_context->XYOFFSET.OFY);
|
||||
else
|
||||
prev_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
|
||||
|
||||
@@ -5775,12 +5783,12 @@ bool GSState::CheckOverlapVerts(u32 n)
|
||||
new_area.w = std::max(new_area.w, prev_vert.y);
|
||||
}
|
||||
|
||||
if (m_index->tail > 0)
|
||||
if (idx_buff.tail > 0)
|
||||
{
|
||||
u32 matching_verts = 0;
|
||||
for (u32 i = 0; i < n; i++)
|
||||
{
|
||||
const u32 pos = m_index->buff[(m_index->tail - n) + i];
|
||||
const u32 pos = idx_buff.buff[(idx_buff.tail - n) + i];
|
||||
const GSVector2i prev_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
|
||||
|
||||
for (u32 j = 0; j < n; j++)
|
||||
@@ -5811,12 +5819,12 @@ bool GSState::CheckOverlapVerts(u32 n)
|
||||
}
|
||||
}
|
||||
|
||||
/*const GSVertex* v = &m_vertex->buff[0];
|
||||
/*const GSVertex* v = &vtx_buff.buff[0];
|
||||
|
||||
GSVector4i new_area = GSVector4i(m_v.XYZ.X - m_context->XYOFFSET.OFX, m_v.XYZ.Y - m_context->XYOFFSET.OFY).xyxy();
|
||||
for (u32 i = 0; i < (n - 1); i++)
|
||||
{
|
||||
const int pos = m_index->buff[(m_index->tail - 1) - i];
|
||||
const int pos = idx_buff.buff[(idx_buff.tail - 1) - i];
|
||||
GSVector2i pre_vert = GSVector2i(v[pos].XYZ.X - m_context->XYOFFSET.OFX, v[pos].XYZ.Y - m_context->XYOFFSET.OFY);
|
||||
new_area.x = std::min(new_area.x, pre_vert.x);
|
||||
new_area.z = std::max(new_area.z, pre_vert.x);
|
||||
@@ -5827,11 +5835,11 @@ bool GSState::CheckOverlapVerts(u32 n)
|
||||
|
||||
if (new_area.rintersect(temp_draw_rect).eq(new_area))
|
||||
{
|
||||
const int end_pos = m_index->tail - (n - 1);
|
||||
const int end_pos = idx_buff.tail - (n - 1);
|
||||
//Need to check if it's already drawn at this vector with this setup, if it has, it means one of the other draws might be drawing over it, which is a bad time for us, so best check.
|
||||
for (int j = 0; j < end_pos; j+=n)
|
||||
{
|
||||
if (v[m_index->buff[j]].XYZ.X == m_v.XYZ.X && v[m_index->buff[j]].XYZ.Y == m_v.XYZ.Y)
|
||||
if (v[idx_buff.buff[j]].XYZ.X == m_v.XYZ.X && v[idx_buff.buff[j]].XYZ.Y == m_v.XYZ.Y)
|
||||
{
|
||||
int min_point = std::max(j - 2, 0);
|
||||
int match = 0;
|
||||
@@ -5841,9 +5849,9 @@ bool GSState::CheckOverlapVerts(u32 n)
|
||||
if (k == j)
|
||||
continue;
|
||||
|
||||
if (v[m_index->buff[k]].XYZ.X == v[m_vertex->tail - 2].XYZ.X && v[m_index->buff[k]].XYZ.Y == v[m_vertex->tail - 2].XYZ.Y)
|
||||
if (v[idx_buff.buff[k]].XYZ.X == v[vtx_buff.tail - 2].XYZ.X && v[idx_buff.buff[k]].XYZ.Y == v[vtx_buff.tail - 2].XYZ.Y)
|
||||
match |= 1;
|
||||
if (v[m_index->buff[k]].XYZ.X == v[m_vertex->tail - 1].XYZ.X && v[m_index->buff[k]].XYZ.Y == v[m_vertex->tail - 1].XYZ.Y)
|
||||
if (v[idx_buff.buff[k]].XYZ.X == v[vtx_buff.tail - 1].XYZ.X && v[idx_buff.buff[k]].XYZ.Y == v[vtx_buff.tail - 1].XYZ.Y)
|
||||
match |= 2;
|
||||
}
|
||||
|
||||
@@ -5861,28 +5869,30 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
{
|
||||
constexpr u32 n = NumIndicesForPrim(prim);
|
||||
constexpr int primclass = GSUtil::GetPrimClass(prim);
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
static_assert(n > 0);
|
||||
|
||||
pxAssert(m_vertex->tail < m_vertex->maxcount + 3);
|
||||
pxAssert(vtx_buff.tail < vtx_buff. maxcount + 3);
|
||||
|
||||
if constexpr (prim == GS_INVALID)
|
||||
{
|
||||
m_vertex->tail = m_vertex->head;
|
||||
vtx_buff.tail = vtx_buff.head;
|
||||
return;
|
||||
}
|
||||
|
||||
if (CheckOverlapVerts(n))
|
||||
Flush(CONTEXTCHANGE);
|
||||
if (GSConfig.UserHacks_DrawBuffering)
|
||||
if (CheckOverlapVerts(n))
|
||||
Flush(CONTEXTCHANGE);
|
||||
|
||||
if (auto_flush && skip == 0 && m_index->tail > 0 && ((m_vertex->tail + 1) - m_vertex->head) >= n)
|
||||
if (auto_flush && skip == 0 && idx_buff.tail > 0 && ((vtx_buff.tail + 1) - vtx_buff.head) >= n)
|
||||
{
|
||||
HandleAutoFlush<prim>();
|
||||
}
|
||||
|
||||
u32 head = m_vertex->head;
|
||||
u32 tail = m_vertex->tail;
|
||||
u32 next = m_vertex->next;
|
||||
u32 xy_tail = m_vertex->xy_tail;
|
||||
u32 head = vtx_buff.head;
|
||||
u32 tail = vtx_buff.tail;
|
||||
u32 next = vtx_buff.next;
|
||||
u32 xy_tail = vtx_buff.xy_tail;
|
||||
|
||||
if (GSIsHardwareRenderer() && GSLocalMemory::m_psm[m_context->ZBUF.PSM].bpp == 32)
|
||||
{
|
||||
@@ -5897,7 +5907,7 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
const GSVector4i new_v0(m_v.m[0]);
|
||||
const GSVector4i new_v1(m_v.m[1]);
|
||||
|
||||
GSVector4i* RESTRICT tailptr = (GSVector4i*)&m_vertex->buff[tail];
|
||||
GSVector4i* RESTRICT tailptr = (GSVector4i*)&vtx_buff.buff[tail];
|
||||
|
||||
tailptr[0] = new_v0;
|
||||
tailptr[1] = new_v1;
|
||||
@@ -5905,41 +5915,29 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
// We maintain the X/Y coordinates for the last 4 vertices, as well as the head for triangle fans, so we can compute
|
||||
// the min/max, and cull degenerate triangles, which saves draws in some cases. Why 4? Mod 4 is cheaper than Mod 3.
|
||||
const GSVector4i xy = new_v1.xxxx().u16to32().sub32(m_xyof);
|
||||
m_vertex->xy[xy_tail & 3] = xy;
|
||||
vtx_buff.xy[xy_tail & 3] = xy;
|
||||
|
||||
// Backup head for triangle fans so we can read it later, otherwise it'll get lost after the 4th vertex.
|
||||
if (prim == GS_TRIANGLEFAN && tail == head)
|
||||
m_vertex->xyhead = xy;
|
||||
vtx_buff.xyhead = xy;
|
||||
|
||||
m_vertex->tail = ++tail;
|
||||
m_vertex->xy_tail = ++xy_tail;
|
||||
vtx_buff.tail = ++tail;
|
||||
vtx_buff.xy_tail = ++xy_tail;
|
||||
|
||||
const u32 m = tail - head;
|
||||
|
||||
if (m < n)
|
||||
return;
|
||||
|
||||
if (m_index->tail == 0/* && ((m_backed_up_ctx != m_env.PRIM.CTXT) || m_dirty_gs_regs)*/)
|
||||
{
|
||||
const int ctx = m_env.PRIM.CTXT;
|
||||
std::memcpy(&m_prev_env, &m_env, 88);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx], &m_env.CTXT[ctx], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env.CTXT[ctx].offset, sizeof(m_env.CTXT[ctx].offset));
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env.CTXT[ctx].scissor, sizeof(m_env.CTXT[ctx].scissor));
|
||||
m_dirty_gs_regs = 0;
|
||||
m_backed_up_ctx = m_env.PRIM.CTXT;
|
||||
SetDrawBufferEnv();
|
||||
}
|
||||
|
||||
// Skip draws when scissor is out of range (i.e. bottom-right is less than top-left), since everything will get clipped.
|
||||
skip |= static_cast<u32>(m_scissor_invalid);
|
||||
|
||||
GSVector4i bbox;
|
||||
if (skip == 0)
|
||||
{
|
||||
const GSVector4i v0 = m_vertex->xy[(xy_tail - 1) & 3];
|
||||
const GSVector4i v1 = m_vertex->xy[(xy_tail - 2) & 3];
|
||||
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? m_vertex->xyhead : m_vertex->xy[(xy_tail - 3) & 3];
|
||||
const GSVector4i v0 = vtx_buff.xy[(xy_tail - 1) & 3];
|
||||
const GSVector4i v1 = vtx_buff.xy[(xy_tail - 2) & 3];
|
||||
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? vtx_buff.xyhead : vtx_buff.xy[(xy_tail - 3) & 3];
|
||||
|
||||
if constexpr (n == 1)
|
||||
{
|
||||
@@ -6004,14 +6002,14 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
case GS_LINELIST:
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_SPRITE:
|
||||
m_vertex->tail = head; // no need to check or grow the buffer length
|
||||
vtx_buff.tail = head; // no need to check or grow the buffer length
|
||||
break;
|
||||
case GS_LINESTRIP:
|
||||
case GS_TRIANGLESTRIP:
|
||||
m_vertex->head = head + 1;
|
||||
vtx_buff.head = head + 1;
|
||||
[[fallthrough]];
|
||||
case GS_TRIANGLEFAN:
|
||||
if (tail >= m_vertex->maxcount)
|
||||
if (tail >= vtx_buff.maxcount)
|
||||
GrowVertexBuffer(); // in case too many vertices were skipped
|
||||
break;
|
||||
default:
|
||||
@@ -6021,71 +6019,85 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
return;
|
||||
}
|
||||
|
||||
if (tail >= m_vertex->maxcount)
|
||||
if (idx_buff.tail == 0 /* && ((m_backed_up_ctx != m_env.PRIM.CTXT) || m_dirty_gs_regs)*/)
|
||||
{
|
||||
const int ctx = m_env.PRIM.CTXT;
|
||||
std::memcpy(&m_prev_env, &m_env, 88);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx], &m_env.CTXT[ctx], 96);
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].offset, &m_env.CTXT[ctx].offset, sizeof(m_env.CTXT[ctx].offset));
|
||||
std::memcpy(&m_prev_env.CTXT[ctx].scissor, &m_env.CTXT[ctx].scissor, sizeof(m_env.CTXT[ctx].scissor));
|
||||
m_dirty_gs_regs = 0;
|
||||
m_backed_up_ctx = m_env.PRIM.CTXT;
|
||||
|
||||
if (GSConfig.UserHacks_DrawBuffering)
|
||||
SetDrawBufferEnv();
|
||||
}
|
||||
|
||||
if (tail >= vtx_buff.maxcount)
|
||||
GrowVertexBuffer();
|
||||
|
||||
u16* RESTRICT buff = &m_index->buff[m_index->tail];
|
||||
u16* RESTRICT buff = &idx_buff.buff[idx_buff.tail];
|
||||
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
m_vertex->head = head + 1;
|
||||
m_vertex->next = head + 1;
|
||||
m_index->tail += 1;
|
||||
vtx_buff.head = head + 1;
|
||||
vtx_buff.next = head + 1;
|
||||
idx_buff.tail += 1;
|
||||
break;
|
||||
case GS_LINELIST:
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
m_vertex->head = head + 2;
|
||||
m_vertex->next = head + 2;
|
||||
m_index->tail += 2;
|
||||
vtx_buff.head = head + 2;
|
||||
vtx_buff.next = head + 2;
|
||||
idx_buff.tail += 2;
|
||||
break;
|
||||
case GS_LINESTRIP:
|
||||
if (next < head)
|
||||
{
|
||||
m_vertex->buff[next + 0] = m_vertex->buff[head + 0];
|
||||
m_vertex->buff[next + 1] = m_vertex->buff[head + 1];
|
||||
vtx_buff.buff[next + 0] = vtx_buff.buff[head + 0];
|
||||
vtx_buff.buff[next + 1] = vtx_buff.buff[head + 1];
|
||||
head = next;
|
||||
m_vertex->tail = next + 2;
|
||||
vtx_buff.tail = next + 2;
|
||||
}
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
m_vertex->head = head + 1;
|
||||
m_vertex->next = head + 2;
|
||||
m_index->tail += 2;
|
||||
vtx_buff.head = head + 1;
|
||||
vtx_buff.next = head + 2;
|
||||
idx_buff.tail += 2;
|
||||
break;
|
||||
case GS_TRIANGLELIST:
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
buff[2] = static_cast<u16>(head + 2);
|
||||
m_vertex->head = head + 3;
|
||||
m_vertex->next = head + 3;
|
||||
m_index->tail += 3;
|
||||
vtx_buff.head = head + 3;
|
||||
vtx_buff.next = head + 3;
|
||||
idx_buff.tail += 3;
|
||||
break;
|
||||
case GS_TRIANGLESTRIP:
|
||||
if (next < head)
|
||||
{
|
||||
m_vertex->buff[next + 0] = m_vertex->buff[head + 0];
|
||||
m_vertex->buff[next + 1] = m_vertex->buff[head + 1];
|
||||
m_vertex->buff[next + 2] = m_vertex->buff[head + 2];
|
||||
vtx_buff.buff[next + 0] = vtx_buff.buff[head + 0];
|
||||
vtx_buff.buff[next + 1] = vtx_buff.buff[head + 1];
|
||||
vtx_buff.buff[next + 2] = vtx_buff.buff[head + 2];
|
||||
head = next;
|
||||
m_vertex->tail = next + 3;
|
||||
vtx_buff.tail = next + 3;
|
||||
}
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
buff[2] = static_cast<u16>(head + 2);
|
||||
m_vertex->head = head + 1;
|
||||
m_vertex->next = head + 3;
|
||||
m_index->tail += 3;
|
||||
vtx_buff.head = head + 1;
|
||||
vtx_buff.next = head + 3;
|
||||
idx_buff.tail += 3;
|
||||
break;
|
||||
case GS_TRIANGLEFAN:
|
||||
// TODO: remove gaps, next == head && head < tail - 3 || next > head && next < tail - 2 (very rare)
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(tail - 2);
|
||||
buff[2] = static_cast<u16>(tail - 1);
|
||||
m_vertex->next = tail;
|
||||
m_index->tail += 3;
|
||||
vtx_buff.next = tail;
|
||||
idx_buff.tail += 3;
|
||||
break;
|
||||
case GS_SPRITE:
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
@@ -6093,11 +6105,11 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
|
||||
// Update the first vert's Q for ease of doing Autoflush
|
||||
if (!m_env.PRIM.FST)
|
||||
m_vertex->buff[buff[0]].RGBAQ.Q = m_vertex->buff[buff[1]].RGBAQ.Q;
|
||||
vtx_buff.buff[buff[0]].RGBAQ.Q = vtx_buff.buff[buff[1]].RGBAQ.Q;
|
||||
|
||||
m_vertex->head = head + 2;
|
||||
m_vertex->next = head + 2;
|
||||
m_index->tail += 2;
|
||||
vtx_buff.head = head + 2;
|
||||
vtx_buff.next = head + 2;
|
||||
idx_buff.tail += 2;
|
||||
break;
|
||||
default:
|
||||
ASSUME(0);
|
||||
@@ -6105,14 +6117,14 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
|
||||
// Update rectangle for the current draw. Needs exclusive endpoints.
|
||||
const GSVector4i draw_rect = bbox.sra32<4>() + GSVector4i(0, 0, 1, 1);
|
||||
if (m_index->tail != n)
|
||||
if (idx_buff.tail != n)
|
||||
temp_draw_rect = temp_draw_rect.runion(draw_rect);
|
||||
else
|
||||
temp_draw_rect = draw_rect;
|
||||
temp_draw_rect = temp_draw_rect.rintersect(m_context->scissor.in);
|
||||
|
||||
constexpr u32 max_vertices = MaxVerticesForPrim(prim);
|
||||
if (max_vertices != 0 && m_vertex->tail >= max_vertices)
|
||||
if (max_vertices != 0 && vtx_buff.tail >= max_vertices)
|
||||
Flush(VERTEXCOUNT);
|
||||
}
|
||||
|
||||
@@ -6274,7 +6286,6 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL
|
||||
const GSVector4i scissored_rc(int_rc.rintersect(m_context->scissor.in));
|
||||
if (!int_rc.eq(scissored_rc))
|
||||
{
|
||||
|
||||
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]];
|
||||
|
||||
+1
-1
@@ -506,7 +506,7 @@ public:
|
||||
|
||||
virtual void Move();
|
||||
|
||||
GSVector4i GetTEX0Rect(GSDrawingContext prev_ctx);
|
||||
GSVector4i GetTEX0Rect(const GSDrawingContext& prev_ctx);
|
||||
void CheckWriteOverlap(bool req_write, bool req_read);
|
||||
void Write(const u8* mem, int len);
|
||||
void Read(u8* mem, int len);
|
||||
|
||||
@@ -805,7 +805,7 @@ GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::F
|
||||
{
|
||||
pxAssert(mipmap_levels != 0 && (mipmap_levels < 0 || mipmap_levels <= GetMipmapLevelsForSize(w, h)));
|
||||
const int levels = mipmap_levels < 0 ? GetMipmapLevelsForSize(w, h) : mipmap_levels;
|
||||
return FetchSurface(GSTexture::Texture, w, h, levels, format, false, m_features.prefer_new_textures && prefer_reuse);
|
||||
return FetchSurface(GSTexture::Texture, w, h, levels, format, false, !m_features.prefer_new_textures || prefer_reuse);
|
||||
}
|
||||
|
||||
GSTexture* GSDevice::CreateTexture(const GSVector2i& size, int mipmap_levels, GSTexture::Format format, bool prefer_reuse)
|
||||
|
||||
@@ -222,7 +222,9 @@ void GSRendererHW::Lines2Sprites()
|
||||
|
||||
// each sprite converted to quad needs twice the space
|
||||
|
||||
while (m_vertex->tail * 2 > m_vertex->maxcount)
|
||||
const int tail = m_vertex->tail * 2;
|
||||
const int max_count = m_vertex->maxcount;
|
||||
while (tail > max_count)
|
||||
{
|
||||
GrowVertexBuffer();
|
||||
}
|
||||
@@ -1428,7 +1430,8 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
|
||||
const u32 tail = m_vertex->tail & ~1;
|
||||
for (u32 i = 2; i < tail; i++)
|
||||
{
|
||||
bool unique_found = false;
|
||||
|
||||
@@ -1469,7 +1472,8 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
|
||||
const u32 tail = m_vertex->tail & ~1;
|
||||
for (u32 i = 2; i < tail; i++)
|
||||
{
|
||||
bool unique_found = false;
|
||||
|
||||
@@ -2235,16 +2239,16 @@ void GSRendererHW::HandleManualDeswizzle()
|
||||
// Check if it's doing manual deswizzling first (draws are 32x16), if they are, check if the Z is flat, if not,
|
||||
// we're gonna have to get creative and swap around the quandrants, but that's a TODO.
|
||||
GSVertex* v = &m_vertex->buff[0];
|
||||
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
// Check for page quadrant and compare it to the quadrant from the verts, if it does match then we need to do correction.
|
||||
const GSVector2i page_quadrant = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs / 2;
|
||||
|
||||
if (PRIM->FST)
|
||||
{
|
||||
for (u32 i = 0; i < m_index->tail; i += 2)
|
||||
for (u32 i = 0; i < idx_buff.tail; i += 2)
|
||||
{
|
||||
const u32 index_first = m_index->buff[i];
|
||||
const u32 index_last = m_index->buff[i + 1];
|
||||
const u32 index_first = idx_buff.buff[i];
|
||||
const u32 index_last = idx_buff.buff[i + 1];
|
||||
|
||||
if ((abs((v[index_last].U) - (v[index_first].U)) >> 4) != page_quadrant.x || (abs((v[index_last].V) - (v[index_first].V)) >> 4) != page_quadrant.y)
|
||||
return;
|
||||
@@ -2252,10 +2256,10 @@ void GSRendererHW::HandleManualDeswizzle()
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < m_index->tail; i += 2)
|
||||
for (u32 i = 0; i < idx_buff.tail; i += 2)
|
||||
{
|
||||
const u32 index_first = m_index->buff[i];
|
||||
const u32 index_last = m_index->buff[i + 1];
|
||||
const u32 index_first = idx_buff.buff[i];
|
||||
const u32 index_last = idx_buff.buff[i + 1];
|
||||
const u32 x = abs(((v[index_last].ST.S / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TW)) - ((v[index_first].ST.S / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TW)));
|
||||
const u32 y = abs(((v[index_last].ST.T / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TH)) - ((v[index_first].ST.T / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TH)));
|
||||
|
||||
@@ -3225,8 +3229,10 @@ void GSRendererHW::Draw()
|
||||
|
||||
// Be careful of being 1 pixel from filled.
|
||||
const bool page_aligned = (m_r.w % pgs.y) == (pgs.y - 1) || (m_r.w % pgs.y) == 0;
|
||||
const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && !preserve_rt_color && page_aligned);
|
||||
const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && !preserve_depth && page_aligned);
|
||||
const bool is_full_color_cover = !preserve_rt_color && page_aligned;
|
||||
const bool is_full_depth_cover = !preserve_depth && page_aligned;
|
||||
const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && is_full_color_cover);
|
||||
const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && is_full_depth_cover);
|
||||
bool gs_mem_cleared = false;
|
||||
// If it's an invalid-sized draw, do the mem clear on the CPU, we don't want to create huge targets.
|
||||
// If clearing to zero, don't bother creating the target. Games tend to clear more than they use, wasting VRAM/bandwidth.
|
||||
@@ -3261,8 +3267,8 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
gs_mem_cleared |= overwriting_whole_rt && overwriting_whole_ds && (!no_rt || !no_ds);
|
||||
if (overwriting_whole_rt && overwriting_whole_ds &&
|
||||
TryGSMemClear(no_rt, preserve_rt_color, is_zero_color_clear, rt_end_bp,
|
||||
no_ds, preserve_depth, is_zero_depth_clear, ds_end_bp))
|
||||
TryGSMemClear(no_rt, preserve_rt_color, is_full_color_cover, rt_end_bp,
|
||||
no_ds, preserve_depth, is_full_depth_cover, ds_end_bp))
|
||||
{
|
||||
GL_INS("HW: Skipping (%d,%d=>%d,%d) draw at FBP %x/ZBP %x due to invalid height or zero clear.", m_r.x, m_r.y,
|
||||
m_r.z, m_r.w, m_cached_ctx.FRAME.Block(), m_cached_ctx.ZBUF.Block());
|
||||
@@ -3285,8 +3291,14 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
}
|
||||
|
||||
CleanupDraw(false);
|
||||
return;
|
||||
no_rt |= is_zero_color_clear;
|
||||
no_ds |= is_zero_depth_clear;
|
||||
|
||||
if (no_rt && no_ds)
|
||||
{
|
||||
CleanupDraw(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3435,7 +3447,7 @@ void GSRendererHW::Draw()
|
||||
bool shuffle_target = false;
|
||||
const u32 page_alignment = GSLocalMemory::IsPageAlignedMasked(m_cached_ctx.TEX0.PSM, m_r);
|
||||
const bool page_aligned = (page_alignment & 0xF0F0) != 0; // Make sure Y is page aligned.
|
||||
if (!no_rt && page_aligned && m_cached_ctx.ZBUF.ZMSK && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
|
||||
if (!no_rt && page_aligned && m_cached_ctx.ZBUF.ZMSK && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 16 &&
|
||||
(m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index->tail % 6) == 0 && TrianglesAreQuads(true) && m_index->tail > 6)))
|
||||
{
|
||||
// Tail check is to make sure we have enough strips to go all the way across the page, or if it's using a region clamp could be used to draw strips.
|
||||
@@ -3822,8 +3834,9 @@ void GSRendererHW::Draw()
|
||||
if (vertical_offset || horizontal_offset)
|
||||
{
|
||||
GSVertex* v = &m_vertex->buff[0];
|
||||
const u32 tail = m_vertex->tail;
|
||||
|
||||
for (u32 i = 0; i < m_vertex->tail; i++)
|
||||
for (u32 i = 0; i < tail; i++)
|
||||
{
|
||||
v[i].XYZ.X += horizontal_offset << 4;
|
||||
v[i].XYZ.Y += vertical_offset << 4;
|
||||
@@ -4119,8 +4132,9 @@ void GSRendererHW::Draw()
|
||||
if (vertical_offset || horizontal_offset)
|
||||
{
|
||||
GSVertex* v = &m_vertex->buff[0];
|
||||
const u32 tail = m_vertex->tail;
|
||||
|
||||
for (u32 i = 0; i < m_vertex->tail; i++)
|
||||
for (u32 i = 0; i < tail; i++)
|
||||
{
|
||||
v[i].XYZ.X += horizontal_offset << 4;
|
||||
v[i].XYZ.Y += vertical_offset << 4;
|
||||
@@ -5296,33 +5310,35 @@ void GSRendererHW::Draw()
|
||||
/// Verifies assumptions we expect to hold about indices
|
||||
bool GSRendererHW::VerifyIndices()
|
||||
{
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
|
||||
switch (m_vt.m_primclass)
|
||||
{
|
||||
case GS_SPRITE_CLASS:
|
||||
if (m_index->tail % 2 != 0)
|
||||
if (idx_buff.tail % 2 != 0)
|
||||
return false;
|
||||
[[fallthrough]];
|
||||
case GS_POINT_CLASS:
|
||||
// Expect indices to be flat increasing
|
||||
for (u32 i = 0; i < m_index->tail; i++)
|
||||
for (u32 i = 0; i < idx_buff.tail; i++)
|
||||
{
|
||||
if (m_index->buff[i] != i)
|
||||
if (idx_buff.buff[i] != i)
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GS_LINE_CLASS:
|
||||
if (m_index->tail % 2 != 0)
|
||||
if (idx_buff.tail % 2 != 0)
|
||||
return false;
|
||||
// Expect each line to be a pair next to each other
|
||||
// VS expand relies on this!
|
||||
for (u32 i = 0; i < m_index->tail; i += 2)
|
||||
for (u32 i = 0; i < idx_buff.tail; i += 2)
|
||||
{
|
||||
if (m_index->buff[i] + 1 != m_index->buff[i + 1])
|
||||
if (idx_buff.buff[i] + 1 != idx_buff.buff[i + 1])
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GS_TRIANGLE_CLASS:
|
||||
if (m_index->tail % 3 != 0)
|
||||
if (idx_buff.tail % 3 != 0)
|
||||
return false;
|
||||
break;
|
||||
case GS_INVALID_CLASS:
|
||||
@@ -5343,15 +5359,17 @@ void GSRendererHW::HandleFlatShadedVertices()
|
||||
if (!maybe_fix_vertices || dont_fix_vertices)
|
||||
return;
|
||||
|
||||
const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
|
||||
const u32 n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
GSIndexBuff& idx_buff = *m_index;
|
||||
|
||||
// If all vertices of each prim have the same color there is nothing to do.
|
||||
bool prims_flat = true;
|
||||
for (u32 i = 0; i < m_index->tail; i += n)
|
||||
for (u32 i = 0; i < idx_buff.tail; i += n)
|
||||
{
|
||||
for (u32 j = 0; j < n - 1; j++)
|
||||
{
|
||||
if (m_vertex->buff[m_index->buff[i + j]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
|
||||
if (vtx_buff.buff[idx_buff.buff[i + j]].RGBAQ.U32[0] != vtx_buff.buff[idx_buff.buff[i + n - 1]].RGBAQ.U32[0])
|
||||
{
|
||||
prims_flat = false;
|
||||
break;
|
||||
@@ -5364,21 +5382,22 @@ void GSRendererHW::HandleFlatShadedVertices()
|
||||
return;
|
||||
|
||||
// De-index the vertices using the copy buffer
|
||||
while (m_vertex->maxcount < m_index->tail)
|
||||
while (vtx_buff.maxcount < idx_buff.tail)
|
||||
GrowVertexBuffer();
|
||||
for (int i = static_cast<int>(m_index->tail) - 1; i >= 0; i--)
|
||||
|
||||
for (int i = static_cast<int>(idx_buff.tail) - 1; i >= 0; i--)
|
||||
{
|
||||
m_vertex->buff_copy[i] = m_vertex->buff[m_index->buff[i]];
|
||||
m_index->buff[i] = static_cast<u16>(i);
|
||||
vtx_buff.buff_copy[i] = vtx_buff.buff[idx_buff.buff[i]];
|
||||
idx_buff.buff[i] = static_cast<u16>(i);
|
||||
}
|
||||
std::swap(m_vertex->buff, m_vertex->buff_copy);
|
||||
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
|
||||
std::swap(vtx_buff.buff, vtx_buff.buff_copy);
|
||||
vtx_buff.head = vtx_buff.next = vtx_buff.tail = idx_buff.tail;
|
||||
|
||||
// Make all vertices the same color to simplify handling in expand shaders.
|
||||
for (u32 i = 0; i < m_index->tail; i += n)
|
||||
for (u32 i = 0; i < idx_buff.tail; i += n)
|
||||
{
|
||||
for (u32 j = 0; j < n - 1; j++)
|
||||
m_vertex->buff[i + j].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
|
||||
vtx_buff.buff[i + j].RGBAQ.U32[0] = vtx_buff.buff[i + n - 1].RGBAQ.U32[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7838,7 +7857,7 @@ void GSRendererHW::ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool
|
||||
}
|
||||
|
||||
#if PCSX2_DEVBUILD
|
||||
new_tex->SetDebugName(tgt->m_texture->GetDebugName());
|
||||
new_tex->SetDebugName(old_tex->GetDebugName());
|
||||
#endif
|
||||
|
||||
if (tgt->m_texture == old_tex)
|
||||
@@ -7869,12 +7888,13 @@ void GSRendererHW::ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCa
|
||||
// Convert depth to the proper type/format.
|
||||
if (ds)
|
||||
{
|
||||
if (m_conf.ps.HasDepthROV() && !ds->m_texture->IsShaderWrite())
|
||||
// Note: we must use m_conf.ds because it might be the temporary Z texture.
|
||||
if (m_conf.ps.HasDepthROV() && !m_conf.ds->IsShaderWrite())
|
||||
{
|
||||
GL_PUSH("HW: Convert DepthStencil -> DepthColor for ROV.");
|
||||
ConvertTextureTypeROVSingle(ds, true);
|
||||
}
|
||||
else if (!m_conf.ps.HasDepthROV() && !ds->m_texture->IsDepthStencil())
|
||||
else if (!m_conf.ps.HasDepthROV() && !m_conf.ds->IsDepthStencil())
|
||||
{
|
||||
GL_PUSH("HW: Convert DepthColor -> DepthStencil for non-ROV.");
|
||||
ConvertTextureTypeROVSingle(ds, false);
|
||||
@@ -9860,11 +9880,13 @@ bool GSRendererHW::DetectStripedDoubleClear(bool& no_rt, bool& no_ds)
|
||||
// and I could cheat and stop when we get a size that matches, but that might be a lucky misdetection, I don't wanna risk it.
|
||||
int vertex_offset = 0;
|
||||
int last_vertex = m_vertex->buff[0].XYZ.X;
|
||||
const u32 tail = m_vertex->tail;
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
|
||||
for (u32 i = 1; i < m_vertex->tail; i++)
|
||||
for (u32 i = 1; i < tail; i++)
|
||||
{
|
||||
vertex_offset = std::max(static_cast<int>((m_vertex->buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
|
||||
last_vertex = m_vertex->buff[i].XYZ.X;
|
||||
vertex_offset = std::max(static_cast<int>((vtx_buff.buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
|
||||
last_vertex = vtx_buff.buff[i].XYZ.X;
|
||||
|
||||
// Found a gap which is much bigger, no point continuing to scan.
|
||||
if (vertex_offset > strip_size)
|
||||
@@ -9975,9 +9997,11 @@ bool GSRendererHW::DetectDoubleHalfClear(bool& no_rt, bool& no_ds)
|
||||
// path write out FRAME and Z separately, with their associated masks. Limit it to black to avoid false positives.
|
||||
if (write_color == 0)
|
||||
{
|
||||
// Don't check the *entire* size for the end, as some games (X-Men) decide it's done with Z and starts overwriting
|
||||
// the end with other things, which causes a resize, so the end point is no longer in the right place.
|
||||
// So let's just check there's at least one page over the half way point, at worst it means 2 targets overlap.
|
||||
const GSTextureCache::Target* base_tgt = g_texture_cache->GetExactTarget(base * GS_BLOCKS_PER_PAGE,
|
||||
m_cached_ctx.FRAME.FBW, clear_depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget,
|
||||
GSLocalMemory::GetEndBlockAddress(half * GS_BLOCKS_PER_PAGE, m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r));
|
||||
m_cached_ctx.FRAME.FBW, clear_depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget, (half + 1) * GS_BLOCKS_PER_PAGE);
|
||||
if (base_tgt)
|
||||
{
|
||||
GL_INS("HW: DetectDoubleHalfClear(): Invalidating targets at 0x%x/0x%x due to different formats, and clear to black.",
|
||||
@@ -10877,10 +10901,13 @@ void GSRendererHW::OffsetDraw(s32 fbp_offset, s32 zbp_offset, s32 xoffset, s32 y
|
||||
|
||||
const s32 fp_xoffset = xoffset << 4;
|
||||
const s32 fp_yoffset = yoffset << 4;
|
||||
for (u32 i = 0; i < m_vertex->next; i++)
|
||||
|
||||
GSVertexBuff& vtx_buff = *m_vertex;
|
||||
|
||||
for (u32 i = 0; i < vtx_buff.next; i++)
|
||||
{
|
||||
m_vertex->buff[i].XYZ.X += fp_xoffset;
|
||||
m_vertex->buff[i].XYZ.Y += fp_yoffset;
|
||||
vtx_buff.buff[i].XYZ.X += fp_xoffset;
|
||||
vtx_buff.buff[i].XYZ.Y += fp_yoffset;
|
||||
}
|
||||
|
||||
m_vt.m_min.p.x += static_cast<float>(xoffset);
|
||||
|
||||
@@ -3565,16 +3565,62 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
|
||||
// Make sure there's sufficient compatibility/overlap between the old target and the new target
|
||||
// to warrant loading from the old target.
|
||||
const u32 old_buffer_width = std::max(1U, old_dst->m_TEX0.TBW);
|
||||
|
||||
if (old_dst->m_TEX0.PSM != dst->m_TEX0.PSM ||
|
||||
!old_dst->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid))
|
||||
{
|
||||
if (old_dst->m_TEX0.TBP0 == dst->m_TEX0.TBP0 && dst_end_block >= old_dst->m_end_block && (!src || !src->m_target || src->m_from_target != old_dst))
|
||||
{
|
||||
InvalidateSourcesFromTarget(old_dst);
|
||||
i = list.erase(j);
|
||||
delete old_dst;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (old_dst->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst_valid))
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm_o = GSLocalMemory::m_psm[old_dst->m_TEX0.PSM];
|
||||
if (dst->m_TEX0.TBP0 > old_dst->m_TEX0.TBP0)
|
||||
{
|
||||
const int block_diff = dst->m_TEX0.TBP0 - old_dst->m_TEX0.TBP0;
|
||||
const u32 old_pages_wide = old_buffer_width * 64 / psm_o.pgs.x;
|
||||
if ((block_diff % (GS_BLOCKS_PER_PAGE * old_pages_wide)) == 0) // Check for left alignment.
|
||||
{
|
||||
const int new_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_o.pgs.y;
|
||||
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(old_dst->m_valid.x, old_dst->m_valid.y, old_dst->m_valid.z, new_height));
|
||||
old_dst->ResizeValidity(old_dst->m_valid);
|
||||
}
|
||||
}
|
||||
else // new target is behind the old one, so need to move the start of the old one to the end block of the new.
|
||||
{
|
||||
const int block_diff = dst_end_block - old_dst->m_TEX0.TBP0;
|
||||
const u32 old_pages_wide = old_buffer_width * 64 / psm_o.pgs.x;
|
||||
|
||||
if ((block_diff % (GS_BLOCKS_PER_PAGE * old_pages_wide)) == 0) // Check for left alignment.
|
||||
{
|
||||
const int change_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_o.pgs.y;
|
||||
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(old_dst->m_valid.x, old_dst->m_valid.y, old_dst->m_valid.z, old_dst->m_valid.w - change_height));
|
||||
old_dst->m_TEX0.TBP0 += block_diff;
|
||||
|
||||
const GSVector2i new_scaled_size = GSVector2i(old_dst->m_unscaled_size * old_dst->m_scale);
|
||||
if (GSTexture* tex = g_gs_device->CreateCompatible(dst->m_texture, new_scaled_size, true))
|
||||
{
|
||||
const int height_offset = change_height * old_dst->m_scale;
|
||||
g_gs_device->CopyRect(old_dst->m_texture, tex, GSVector4i(0, height_offset, old_dst->GetUnscaledWidth() * old_dst->m_scale, old_dst->GetUnscaledHeight() * old_dst->m_scale), 0, 0);
|
||||
|
||||
g_gs_device->Recycle(old_dst->m_texture);
|
||||
old_dst->m_texture = tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int page_diff = std::abs(static_cast<int>(old_dst->m_TEX0.TBP0 - dst->m_TEX0.TBP0)) >> 5;
|
||||
const u32 new_buffer_width = std::max(1U, dst->m_TEX0.TBW);
|
||||
const u32 old_buffer_width = std::max(1U, old_dst->m_TEX0.TBW);
|
||||
|
||||
// Handle cases where the buffer widths don't match.
|
||||
if (new_buffer_width != old_buffer_width)
|
||||
@@ -3592,7 +3638,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
const u32 old_pages_wide = old_buffer_width * 64 / psm_s.pgs.x;
|
||||
if ((block_diff % (32 * old_pages_wide)) == 0) // Check for left alignment.
|
||||
{
|
||||
const int new_height = block_diff / (32 * old_pages_wide) * psm_s.pgs.y;
|
||||
const int new_height = block_diff / (GS_BLOCKS_PER_PAGE * old_pages_wide) * psm_s.pgs.y;
|
||||
old_dst->m_valid = old_dst->m_valid.rintersect(GSVector4i(0, 0, old_dst->GetUnscaledWidth(), new_height));
|
||||
if (old_dst->m_valid.rempty())
|
||||
{
|
||||
@@ -4543,10 +4589,14 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
|
||||
|
||||
InvalidateSourcesFromTarget(t);
|
||||
|
||||
t->m_valid_alpha_low &= preserve_alpha;
|
||||
t->m_valid_alpha_high &= preserve_alpha;
|
||||
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0;
|
||||
t->m_was_dst_matched = false;
|
||||
if (type == DepthStencil || start_bp == t->m_TEX0.TBP0 || (start_bp < t->m_TEX0.TBP0 && t->UnwrappedEndBlock() <= end_bp))
|
||||
{
|
||||
const bool compatible_channel_swizzle = GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[write_psm].bpp;
|
||||
t->m_valid_alpha_low &= preserve_alpha && compatible_channel_swizzle;
|
||||
t->m_valid_alpha_high &= preserve_alpha && compatible_channel_swizzle;
|
||||
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0 && compatible_channel_swizzle;
|
||||
t->m_was_dst_matched = false;
|
||||
}
|
||||
|
||||
// Don't keep partial depth buffers around.
|
||||
if ((!t->m_valid_alpha_low && !t->m_valid_alpha_high && !t->m_valid_rgb) || type == DepthStencil)
|
||||
@@ -8178,8 +8228,9 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca
|
||||
{
|
||||
// Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other
|
||||
// APIs either. So use a fullscreen quad setting depth instead.
|
||||
// Use bilinear to avoid artifacts with upscaling. At native this is equivalent to nearest.
|
||||
g_gs_device->StretchRectAuto(m_texture, tex, GSVector4(rc), Biln);
|
||||
// Use bilinear to avoid artifacts with upscaling during native scaling.
|
||||
const bool req_bilinear = m_downscaled && m_scale < g_gs_renderer->GetUpscaleMultiplier();
|
||||
g_gs_device->StretchRectAuto(m_texture, tex, GSVector4(rc), req_bilinear ? Biln : Nearest);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -128,12 +128,13 @@ public:
|
||||
{
|
||||
PSSelector ps;
|
||||
VSSelector vs;
|
||||
u8 pad[3];
|
||||
u8 pad[15];
|
||||
|
||||
__fi bool operator==(const ProgramSelector& p) const { return BitEqual(*this, p); }
|
||||
__fi bool operator!=(const ProgramSelector& p) const { return !BitEqual(*this, p); }
|
||||
};
|
||||
static_assert(sizeof(ProgramSelector) == 32, "Program selector is 32 bytes");
|
||||
static_assert(offsetof(ProgramSelector, pad) + sizeof(ProgramSelector::pad) == sizeof(ProgramSelector));
|
||||
|
||||
struct ProgramSelectorHash
|
||||
{
|
||||
|
||||
@@ -380,8 +380,8 @@ bool FullscreenUI::HasActiveWindow()
|
||||
bool FullscreenUI::AreAnyDialogsOpen()
|
||||
{
|
||||
return (s_save_state_selector_open || s_about_window_open || s_cover_downloader_open ||
|
||||
s_input_binding_type != InputBindingInfo::Type::Unknown || ImGuiFullscreen::IsChoiceDialogOpen() ||
|
||||
ImGuiFullscreen::IsFileSelectorOpen());
|
||||
s_achievements_login_open || s_input_binding_type != InputBindingInfo::Type::Unknown ||
|
||||
ImGuiFullscreen::IsChoiceDialogOpen() || ImGuiFullscreen::IsFileSelectorOpen());
|
||||
}
|
||||
|
||||
void FullscreenUI::CheckForConfigChanges(const Pcsx2Config& old_config)
|
||||
|
||||
@@ -4349,6 +4349,8 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
|
||||
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
ResetFocusHere();
|
||||
|
||||
const float content_width = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
@@ -4448,6 +4450,8 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
|
||||
s_achievements_login_username[0] = '\0';
|
||||
s_achievements_login_password[0] = '\0';
|
||||
|
||||
QueueResetFocus(FocusResetType::PopupClosed);
|
||||
};
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
|
||||
@@ -4463,7 +4467,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
|
||||
|
||||
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
|
||||
if ((ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) || WantsToCloseMenu()) && !s_achievements_login_logging_in)
|
||||
CloseLoginPopup();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
@@ -4580,7 +4584,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
|
||||
if (ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
|
||||
if ((ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) || WantsToCloseMenu()) && !s_achievements_login_logging_in)
|
||||
{
|
||||
if (s_achievements_login_reason == Achievements::LoginRequestReason::TokenInvalid)
|
||||
{
|
||||
@@ -4803,6 +4807,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
|
||||
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
|
||||
s_achievements_login_show_dismiss = false;
|
||||
s_achievements_login_open = true;
|
||||
QueueResetFocus(FocusResetType::PopupOpened);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1635,8 +1635,8 @@ void SaveStateSelectorUI::Draw()
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
// Leave 2 lines for the legend
|
||||
const float legend_margin = ImGui::GetFontSize() * 3.0f + ImGui::GetStyle().ItemSpacing.y * 3.0f;
|
||||
// Leave room for the legend.
|
||||
const float legend_margin = ImGui::GetTextLineHeightWithSpacing() * 4.0f;
|
||||
const float padding = 10.0f * scale;
|
||||
|
||||
ImGui::BeginChild("##item_list", ImVec2(0, -legend_margin), false,
|
||||
|
||||
@@ -555,7 +555,7 @@ const Pad::ControllerInfo& PadDualshock2::GetInfo() const
|
||||
|
||||
void PadDualshock2::Set(u32 index, float value)
|
||||
{
|
||||
if (index > Inputs::LENGTH)
|
||||
if (index >= Inputs::LENGTH)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ const Pad::ControllerInfo& PadGuitar::GetInfo() const
|
||||
|
||||
void PadGuitar::Set(u32 index, float value)
|
||||
{
|
||||
if (index > Inputs::LENGTH)
|
||||
if (index >= Inputs::LENGTH)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ const Pad::ControllerInfo& PadJogcon::GetInfo() const
|
||||
|
||||
void PadJogcon::Set(u32 index, float value)
|
||||
{
|
||||
if (index > Inputs::LENGTH)
|
||||
if (index >= Inputs::LENGTH)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ const Pad::ControllerInfo& PadNegcon::GetInfo() const
|
||||
|
||||
void PadNegcon::Set(u32 index, float value)
|
||||
{
|
||||
if (index > Inputs::LENGTH)
|
||||
if (index >= Inputs::LENGTH)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ const Pad::ControllerInfo& PadPopn::GetInfo() const
|
||||
|
||||
void PadPopn::Set(u32 index, float value)
|
||||
{
|
||||
if (index > Inputs::LENGTH)
|
||||
if (index >= Inputs::LENGTH)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
/// Version number for GS and other shaders. Increment whenever any of the contents of the
|
||||
/// shaders change, to invalidate the cache.
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 107; // Last changed in PR 14634
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 108; // Last changed in PR 14688
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace usb_lightgun
|
||||
{"SLUS-20669", 90.25f, 93.5f, 420, 132, 640, 240}, // Resident Evil - Dead Aim (U)
|
||||
{"SLUS-20619", 90.25f, 91.75f, 453, 154, 640, 256}, // Starsky & Hutch (U)
|
||||
{"SCES-50300", 90.25f, 102.75f, 390, 138, 640, 256}, // Time Crisis II (E)
|
||||
{"SLPS-20122", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis II (J)
|
||||
{"SLPS-20113", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis II (with GunCon 2) (J)
|
||||
{"SLUS-20219", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis 2 (U)
|
||||
{"SCES-51844", 90.25f, 102.75f, 390, 138, 640, 256}, // Time Crisis 3 (E)
|
||||
{"SLUS-20645", 90.25f, 97.5f, 390, 154, 640, 240}, // Time Crisis 3 (U)
|
||||
@@ -90,6 +92,7 @@ namespace usb_lightgun
|
||||
{"SLPS-25077", 90.0f, 97.5f, 422, 118, 640, 240}, // Vampire Night (J)
|
||||
{"SLUS-20221", 89.8f, 102.5f, 422, 124, 640, 228}, // Vampire Night (U)
|
||||
{"SLES-51229", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop - Elite Edition (E,J) (480i)
|
||||
{"SLPM-62205", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop Re-Birth (J) (480i)
|
||||
// {"SLES-51229", 85.75f, 92.0f, 456, 164, 640, 256}, // Virtua Cop - Elite Edition (E,J) (480p)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user