diff options
162 files changed, 5804 insertions, 990 deletions
diff --git a/.appveyor/UtilityFunctions.ps1 b/.appveyor/UtilityFunctions.ps1 new file mode 100644 index 000000000..fd7476314 --- /dev/null +++ b/.appveyor/UtilityFunctions.ps1 @@ -0,0 +1,39 @@ +# Set-up Visual Studio Command Prompt environment for PowerShell +pushd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\" +cmd /c "VsDevCmd.bat -arch=x64 & set" | foreach { + if ($_ -match "=") { + $v = $_.split("="); Set-Item -Force -Path "ENV:\$($v[0])" -Value "$($v[1])" + } +} +popd + +function Which ($search_path, $name) { + ($search_path).Split(";") | Get-ChildItem -Filter $name | Select -First 1 -Exp FullName +} + +function GetDeps ($search_path, $binary) { + ((dumpbin /dependents $binary).Where({ $_ -match "dependencies:"}, "SkipUntil") | Select-String "[^ ]*\.dll").Matches | foreach { + Which $search_path $_.Value + } +} + +function RecursivelyGetDeps ($search_path, $binary) { + $final_deps = @() + $deps_to_process = GetDeps $search_path $binary + while ($deps_to_process.Count -gt 0) { + $current, $deps_to_process = $deps_to_process + if ($final_deps -contains $current) { continue } + + # Is this a system dll file? + # We use the same algorithm that cmake uses to determine this. + if ($current -match "$([regex]::Escape($env:SystemRoot))\\sys") { continue } + if ($current -match "$([regex]::Escape($env:WinDir))\\sys") { continue } + if ($current -match "\\msvc[^\\]+dll") { continue } + if ($current -match "\\api-ms-win-[^\\]+dll") { continue } + + $final_deps += $current + $new_deps = GetDeps $search_path $current + $deps_to_process += ($new_deps | ?{-not ($final_deps -contains $_)}) + } + return $final_deps +} diff --git a/CMakeLists.txt b/CMakeLists.txt index bcd169643..86d2423ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,7 +278,7 @@ endif() if (ENABLE_QT) if (YUZU_USE_BUNDLED_QT) if (MSVC14 AND ARCHITECTURE_x86_64) - set(QT_VER qt-5.7-msvc2015_64) + set(QT_VER qt-5.10.0-msvc2015_64) else() message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.") endif() diff --git a/appveyor.yml b/appveyor.yml index fe6b649f5..72cda26a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -121,23 +121,16 @@ after_build: Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST + # copy all the dll dependencies to the release folder - # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain. - $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll", - # QT dll dependencies - "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll", - "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll", - "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre2-16-*.dll","libpcre16-*.dll","libpng16-*.dll", - # Runtime/Other dependencies - "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll" + . "./.appveyor/UtilityFunctions.ps1" + $DLLSearchPath = "C:\msys64\mingw64\bin;$env:PATH" + $MingwDLLs = RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\yuzu.exe" + $MingwDLLs += RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\yuzu_cmd.exe" + Write-Host "Detected the following dependencies:" + Write-Host $MingwDLLs foreach ($file in $MingwDLLs) { - Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST" - } - # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!) - # so we can remove them by hardcoding another list of extra dlls to remove - $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll" - foreach ($file in $DebugDLLs) { - Remove-Item -path "$RELEASE_DIST/$file" + Copy-Item -path "$file" -force -destination "$RELEASE_DIST" } # copy the qt windows plugin dll to platforms diff --git a/dist/icons/icons.qrc b/dist/icons/icons.qrc deleted file mode 100644 index 575cd0861..000000000 --- a/dist/icons/icons.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="icons"> - <file>yuzu.png</file> - </qresource> -</RCC>
\ No newline at end of file diff --git a/dist/icons/yuzu.png b/dist/icons/yuzu.png Binary files differdeleted file mode 100644 index 81a745344..000000000 --- a/dist/icons/yuzu.png +++ /dev/null diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc new file mode 100644 index 000000000..14a0cf6f9 --- /dev/null +++ b/dist/qt_themes/default/default.qrc @@ -0,0 +1,11 @@ +<RCC> + <qresource prefix="icons/default"> + <file alias="index.theme">icons/index.theme</file> + + <file alias="16x16/checked.png">icons/16x16/checked.png</file> + + <file alias="16x16/failed.png">icons/16x16/failed.png</file> + + <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> + </qresource> +</RCC> diff --git a/dist/qt_themes/default/icons/16x16/checked.png b/dist/qt_themes/default/icons/16x16/checked.png Binary files differnew file mode 100644 index 000000000..c277e6b40 --- /dev/null +++ b/dist/qt_themes/default/icons/16x16/checked.png diff --git a/dist/qt_themes/default/icons/16x16/failed.png b/dist/qt_themes/default/icons/16x16/failed.png Binary files differnew file mode 100644 index 000000000..ac10f174a --- /dev/null +++ b/dist/qt_themes/default/icons/16x16/failed.png diff --git a/dist/qt_themes/default/icons/256x256/yuzu.png b/dist/qt_themes/default/icons/256x256/yuzu.png Binary files differnew file mode 100644 index 000000000..8fd4dc259 --- /dev/null +++ b/dist/qt_themes/default/icons/256x256/yuzu.png diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme new file mode 100644 index 000000000..ac67cb236 --- /dev/null +++ b/dist/qt_themes/default/icons/index.theme @@ -0,0 +1,10 @@ +[Icon Theme] +Name=default +Comment=default theme +Directories=16x16,256x256 + +[16x16] +Size=16 + +[256x256] +Size=256
\ No newline at end of file diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme new file mode 100644 index 000000000..558ece40b --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/index.theme @@ -0,0 +1,11 @@ +[Icon Theme] +Name=qdarkstyle +Comment=dark theme +Inherits=default +Directories=16x16,256x256 + +[16x16] +Size=16 + +[256x256] +Size=256
\ No newline at end of file diff --git a/dist/qt_themes/qdarkstyle/rc/Hmovetoolbar.png b/dist/qt_themes/qdarkstyle/rc/Hmovetoolbar.png Binary files differnew file mode 100644 index 000000000..cead99ed1 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/Hmovetoolbar.png diff --git a/dist/qt_themes/qdarkstyle/rc/Hsepartoolbar.png b/dist/qt_themes/qdarkstyle/rc/Hsepartoolbar.png Binary files differnew file mode 100644 index 000000000..7f183c8b3 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/Hsepartoolbar.png diff --git a/dist/qt_themes/qdarkstyle/rc/Vmovetoolbar.png b/dist/qt_themes/qdarkstyle/rc/Vmovetoolbar.png Binary files differnew file mode 100644 index 000000000..512edcecd --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/Vmovetoolbar.png diff --git a/dist/qt_themes/qdarkstyle/rc/Vsepartoolbar.png b/dist/qt_themes/qdarkstyle/rc/Vsepartoolbar.png Binary files differnew file mode 100644 index 000000000..d9dc1561b --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/Vsepartoolbar.png diff --git a/dist/qt_themes/qdarkstyle/rc/branch_closed-on.png b/dist/qt_themes/qdarkstyle/rc/branch_closed-on.png Binary files differnew file mode 100644 index 000000000..d081e9b3b --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/branch_closed-on.png diff --git a/dist/qt_themes/qdarkstyle/rc/branch_closed.png b/dist/qt_themes/qdarkstyle/rc/branch_closed.png Binary files differnew file mode 100644 index 000000000..d652159a3 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/branch_closed.png diff --git a/dist/qt_themes/qdarkstyle/rc/branch_open-on.png b/dist/qt_themes/qdarkstyle/rc/branch_open-on.png Binary files differnew file mode 100644 index 000000000..ec372b27d --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/branch_open-on.png diff --git a/dist/qt_themes/qdarkstyle/rc/branch_open.png b/dist/qt_themes/qdarkstyle/rc/branch_open.png Binary files differnew file mode 100644 index 000000000..66f8e1ac6 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/branch_open.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png Binary files differnew file mode 100644 index 000000000..830cfee65 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png Binary files differnew file mode 100644 index 000000000..cb63cc2fa --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png Binary files differnew file mode 100644 index 000000000..671be273b --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_checked_focus.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png Binary files differnew file mode 100644 index 000000000..41024f768 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png Binary files differnew file mode 100644 index 000000000..abdc01d90 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png Binary files differnew file mode 100644 index 000000000..415f9b6e1 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_indeterminate_focus.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked.png b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked.png Binary files differnew file mode 100644 index 000000000..2159aca9a --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_disabled.png b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_disabled.png Binary files differnew file mode 100644 index 000000000..ade721e81 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_focus.png b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_focus.png Binary files differnew file mode 100644 index 000000000..e4258cc47 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/checkbox_unchecked_focus.png diff --git a/dist/qt_themes/qdarkstyle/rc/close-hover.png b/dist/qt_themes/qdarkstyle/rc/close-hover.png Binary files differnew file mode 100644 index 000000000..657943a66 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/close-hover.png diff --git a/dist/qt_themes/qdarkstyle/rc/close-pressed.png b/dist/qt_themes/qdarkstyle/rc/close-pressed.png Binary files differnew file mode 100644 index 000000000..937d00598 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/close-pressed.png diff --git a/dist/qt_themes/qdarkstyle/rc/close.png b/dist/qt_themes/qdarkstyle/rc/close.png Binary files differnew file mode 100644 index 000000000..bc0f57610 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/close.png diff --git a/dist/qt_themes/qdarkstyle/rc/down_arrow.png b/dist/qt_themes/qdarkstyle/rc/down_arrow.png Binary files differnew file mode 100644 index 000000000..e271f7f90 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/down_arrow.png diff --git a/dist/qt_themes/qdarkstyle/rc/down_arrow_disabled.png b/dist/qt_themes/qdarkstyle/rc/down_arrow_disabled.png Binary files differnew file mode 100644 index 000000000..5805d9842 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/down_arrow_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/left_arrow.png b/dist/qt_themes/qdarkstyle/rc/left_arrow.png Binary files differnew file mode 100644 index 000000000..f808d2d72 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/left_arrow.png diff --git a/dist/qt_themes/qdarkstyle/rc/left_arrow_disabled.png b/dist/qt_themes/qdarkstyle/rc/left_arrow_disabled.png Binary files differnew file mode 100644 index 000000000..f5b9af8a3 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/left_arrow_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_checked.png b/dist/qt_themes/qdarkstyle/rc/radio_checked.png Binary files differnew file mode 100644 index 000000000..235e6b0ba --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_checked.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_checked_disabled.png b/dist/qt_themes/qdarkstyle/rc/radio_checked_disabled.png Binary files differnew file mode 100644 index 000000000..bf0051ede --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_checked_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_checked_focus.png b/dist/qt_themes/qdarkstyle/rc/radio_checked_focus.png Binary files differnew file mode 100644 index 000000000..700c6b525 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_checked_focus.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_unchecked.png b/dist/qt_themes/qdarkstyle/rc/radio_unchecked.png Binary files differnew file mode 100644 index 000000000..9a4def65c --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_unchecked.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_unchecked_disabled.png b/dist/qt_themes/qdarkstyle/rc/radio_unchecked_disabled.png Binary files differnew file mode 100644 index 000000000..6ece890e7 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_unchecked_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/radio_unchecked_focus.png b/dist/qt_themes/qdarkstyle/rc/radio_unchecked_focus.png Binary files differnew file mode 100644 index 000000000..564e022d3 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/radio_unchecked_focus.png diff --git a/dist/qt_themes/qdarkstyle/rc/right_arrow.png b/dist/qt_themes/qdarkstyle/rc/right_arrow.png Binary files differnew file mode 100644 index 000000000..9b0a4e6a7 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/right_arrow.png diff --git a/dist/qt_themes/qdarkstyle/rc/right_arrow_disabled.png b/dist/qt_themes/qdarkstyle/rc/right_arrow_disabled.png Binary files differnew file mode 100644 index 000000000..5c0bee402 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/right_arrow_disabled.png diff --git a/dist/qt_themes/qdarkstyle/rc/sizegrip.png b/dist/qt_themes/qdarkstyle/rc/sizegrip.png Binary files differnew file mode 100644 index 000000000..350583aaa --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/sizegrip.png diff --git a/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-end.png b/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-end.png Binary files differnew file mode 100644 index 000000000..cb5d3b51f --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-end.png diff --git a/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-more.png b/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-more.png Binary files differnew file mode 100644 index 000000000..62711409d --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/stylesheet-branch-more.png diff --git a/dist/qt_themes/qdarkstyle/rc/stylesheet-vline.png b/dist/qt_themes/qdarkstyle/rc/stylesheet-vline.png Binary files differnew file mode 100644 index 000000000..87536cce1 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/stylesheet-vline.png diff --git a/dist/qt_themes/qdarkstyle/rc/transparent.png b/dist/qt_themes/qdarkstyle/rc/transparent.png Binary files differnew file mode 100644 index 000000000..483df2513 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/transparent.png diff --git a/dist/qt_themes/qdarkstyle/rc/undock.png b/dist/qt_themes/qdarkstyle/rc/undock.png Binary files differnew file mode 100644 index 000000000..88691d779 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/undock.png diff --git a/dist/qt_themes/qdarkstyle/rc/up_arrow.png b/dist/qt_themes/qdarkstyle/rc/up_arrow.png Binary files differnew file mode 100644 index 000000000..abcc72452 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/up_arrow.png diff --git a/dist/qt_themes/qdarkstyle/rc/up_arrow_disabled.png b/dist/qt_themes/qdarkstyle/rc/up_arrow_disabled.png Binary files differnew file mode 100644 index 000000000..b9c8e3b53 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/rc/up_arrow_disabled.png diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc new file mode 100644 index 000000000..efbd0b9dc --- /dev/null +++ b/dist/qt_themes/qdarkstyle/style.qrc @@ -0,0 +1,49 @@ +<RCC> + <qresource prefix="icons/qdarkstyle"> + <file alias="index.theme">icons/index.theme</file> + </qresource> + <qresource prefix="qss_icons"> + <file>rc/up_arrow_disabled.png</file> + <file>rc/Hmovetoolbar.png</file> + <file>rc/stylesheet-branch-end.png</file> + <file>rc/branch_closed-on.png</file> + <file>rc/stylesheet-vline.png</file> + <file>rc/branch_closed.png</file> + <file>rc/branch_open-on.png</file> + <file>rc/transparent.png</file> + <file>rc/right_arrow_disabled.png</file> + <file>rc/sizegrip.png</file> + <file>rc/close.png</file> + <file>rc/close-hover.png</file> + <file>rc/close-pressed.png</file> + <file>rc/down_arrow.png</file> + <file>rc/Vmovetoolbar.png</file> + <file>rc/left_arrow.png</file> + <file>rc/stylesheet-branch-more.png</file> + <file>rc/up_arrow.png</file> + <file>rc/right_arrow.png</file> + <file>rc/left_arrow_disabled.png</file> + <file>rc/Hsepartoolbar.png</file> + <file>rc/branch_open.png</file> + <file>rc/Vsepartoolbar.png</file> + <file>rc/down_arrow_disabled.png</file> + <file>rc/undock.png</file> + <file>rc/checkbox_checked_disabled.png</file> + <file>rc/checkbox_checked_focus.png</file> + <file>rc/checkbox_checked.png</file> + <file>rc/checkbox_indeterminate.png</file> + <file>rc/checkbox_indeterminate_focus.png</file> + <file>rc/checkbox_unchecked_disabled.png</file> + <file>rc/checkbox_unchecked_focus.png</file> + <file>rc/checkbox_unchecked.png</file> + <file>rc/radio_checked_disabled.png</file> + <file>rc/radio_checked_focus.png</file> + <file>rc/radio_checked.png</file> + <file>rc/radio_unchecked_disabled.png</file> + <file>rc/radio_unchecked_focus.png</file> + <file>rc/radio_unchecked.png</file> + </qresource> + <qresource prefix="qdarkstyle"> + <file>style.qss</file> + </qresource> +</RCC> diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss new file mode 100644 index 000000000..c8e50312a --- /dev/null +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -0,0 +1,1268 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) <2013-2014> <Colin Duquesnoy> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +QToolTip +{ + border: 1px solid #76797C; + background-color: rgb(90, 102, 117);; + color: white; + padding: 5px; + opacity: 200; +} + +QWidget +{ + color: #eff0f1; + background-color: #31363b; + selection-background-color:#3daee9; + selection-color: #eff0f1; + background-clip: border; + border-image: none; + border: 0px transparent black; + outline: 0; +} + +QWidget:item:hover +{ + background-color: #18465d; + color: #eff0f1; +} + +QWidget:item:selected +{ + background-color: #18465d; +} + +QCheckBox +{ + spacing: 5px; + outline: none; + color: #eff0f1; + margin-bottom: 2px; +} + +QCheckBox:disabled +{ + color: #76797C; +} + +QCheckBox::indicator, +QGroupBox::indicator +{ + width: 18px; + height: 18px; +} +QGroupBox::indicator +{ + margin-left: 2px; +} + +QCheckBox::indicator:unchecked +{ + image: url(:/qss_icons/rc/checkbox_unchecked.png); +} + +QCheckBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:focus, +QCheckBox::indicator:unchecked:pressed, +QGroupBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:focus, +QGroupBox::indicator:unchecked:pressed +{ + border: none; + image: url(:/qss_icons/rc/checkbox_unchecked_focus.png); +} + +QCheckBox::indicator:checked +{ + image: url(:/qss_icons/rc/checkbox_checked.png); +} + +QCheckBox::indicator:checked:hover, +QCheckBox::indicator:checked:focus, +QCheckBox::indicator:checked:pressed, +QGroupBox::indicator:checked:hover, +QGroupBox::indicator:checked:focus, +QGroupBox::indicator:checked:pressed +{ + border: none; + image: url(:/qss_icons/rc/checkbox_checked_focus.png); +} + + +QCheckBox::indicator:indeterminate +{ + image: url(:/qss_icons/rc/checkbox_indeterminate.png); +} + +QCheckBox::indicator:indeterminate:focus, +QCheckBox::indicator:indeterminate:hover, +QCheckBox::indicator:indeterminate:pressed +{ + image: url(:/qss_icons/rc/checkbox_indeterminate_focus.png); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled +{ + image: url(:/qss_icons/rc/checkbox_checked_disabled.png); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled +{ + image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); +} + +QRadioButton +{ + spacing: 5px; + outline: none; + color: #eff0f1; + margin-bottom: 2px; +} + +QRadioButton:disabled +{ + color: #76797C; +} +QRadioButton::indicator +{ + width: 21px; + height: 21px; +} + +QRadioButton::indicator:unchecked +{ + image: url(:/qss_icons/rc/radio_unchecked.png); +} + + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:focus, +QRadioButton::indicator:unchecked:pressed +{ + border: none; + outline: none; + image: url(:/qss_icons/rc/radio_unchecked_focus.png); +} + +QRadioButton::indicator:checked +{ + border: none; + outline: none; + image: url(:/qss_icons/rc/radio_checked.png); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed +{ + border: none; + outline: none; + image: url(:/qss_icons/rc/radio_checked_focus.png); +} + +QRadioButton::indicator:checked:disabled +{ + outline: none; + image: url(:/qss_icons/rc/radio_checked_disabled.png); +} + +QRadioButton::indicator:unchecked:disabled +{ + image: url(:/qss_icons/rc/radio_unchecked_disabled.png); +} + + +QMenuBar +{ + background-color: #31363b; + color: #eff0f1; +} + +QMenuBar::item +{ + background: transparent; +} + +QMenuBar::item:selected +{ + background: transparent; + border: 1px solid #76797C; +} + +QMenuBar::item:pressed +{ + border: 1px solid #76797C; + background-color: #3daee9; + color: #eff0f1; + margin-bottom:-1px; + padding-bottom:1px; +} + +QMenu +{ + border: 1px solid #76797C; + color: #eff0f1; + margin: 2px; +} + +QMenu::icon +{ + margin: 5px; +} + +QMenu::item +{ + padding: 5px 30px 5px 30px; + margin-left: 5px; + border: 1px solid transparent; /* reserve space for selection border */ +} + +QMenu::item:selected +{ + color: #eff0f1; +} + +QMenu::separator { + height: 2px; + background: lightblue; + margin-left: 10px; + margin-right: 5px; +} + +QMenu::indicator { + width: 18px; + height: 18px; +} + +/* non-exclusive indicator = check box style indicator + (see QActionGroup::setExclusive) */ +QMenu::indicator:non-exclusive:unchecked { + image: url(:/qss_icons/rc/checkbox_unchecked.png); +} + +QMenu::indicator:non-exclusive:unchecked:selected { + image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); +} + +QMenu::indicator:non-exclusive:checked { + image: url(:/qss_icons/rc/checkbox_checked.png); +} + +QMenu::indicator:non-exclusive:checked:selected { + image: url(:/qss_icons/rc/checkbox_checked_disabled.png); +} + +/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ +QMenu::indicator:exclusive:unchecked { + image: url(:/qss_icons/rc/radio_unchecked.png); +} + +QMenu::indicator:exclusive:unchecked:selected { + image: url(:/qss_icons/rc/radio_unchecked_disabled.png); +} + +QMenu::indicator:exclusive:checked { + image: url(:/qss_icons/rc/radio_checked.png); +} + +QMenu::indicator:exclusive:checked:selected { + image: url(:/qss_icons/rc/radio_checked_disabled.png); +} + +QMenu::right-arrow { + margin: 5px; + image: url(:/qss_icons/rc/right_arrow.png) +} + + +QWidget:disabled +{ + color: #454545; + background-color: #31363b; +} + +QAbstractItemView +{ + alternate-background-color: #31363b; + color: #eff0f1; + border: 1px solid 3A3939; + border-radius: 2px; +} + +QWidget:focus, QMenuBar:focus +{ + border: 1px solid #3daee9; +} + +QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus +{ + border: none; +} + +QLineEdit +{ + background-color: #232629; + padding: 5px; + border-style: solid; + border: 1px solid #76797C; + border-radius: 2px; + color: #eff0f1; +} + +QAbstractItemView QLineEdit +{ + padding: 0; +} + +QGroupBox { + border:1px solid #76797C; + border-radius: 2px; + margin-top: 20px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; +} + +QAbstractScrollArea +{ + border-radius: 2px; + border: 1px solid #76797C; + background-color: transparent; +} + +QScrollBar:horizontal +{ + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent #2A2929; + border-radius: 4px; + background-color: #2A2929; +} + +QScrollBar::handle:horizontal +{ + background-color: #605F5F; + min-width: 5px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal +{ + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/right_arrow_disabled.png); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal +{ + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/left_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on +{ + border-image: url(:/qss_icons/rc/right_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + + +QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on +{ + border-image: url(:/qss_icons/rc/left_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal +{ + background: none; +} + + +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ + background: none; +} + +QScrollBar:vertical +{ + background-color: #2A2929; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent #2A2929; + border-radius: 4px; +} + +QScrollBar::handle:vertical +{ + background-color: #605F5F; + min-height: 5px; + border-radius: 4px; +} + +QScrollBar::sub-line:vertical +{ + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/up_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical +{ + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/down_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on +{ + + border-image: url(:/qss_icons/rc/up_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on +{ + border-image: url(:/qss_icons/rc/down_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical +{ + background: none; +} + + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical +{ + background: none; +} + +QTextEdit +{ + background-color: #232629; + color: #eff0f1; + border: 1px solid #76797C; +} + +QPlainTextEdit +{ + background-color: #232629;; + color: #eff0f1; + border-radius: 2px; + border: 1px solid #76797C; +} + +QHeaderView::section +{ + background-color: #76797C; + color: #eff0f1; + padding: 5px; + border: 1px solid #76797C; +} + +QSizeGrip { + image: url(:/qss_icons/rc/sizegrip.png); + width: 12px; + height: 12px; +} + + +QMainWindow::separator +{ + background-color: #31363b; + color: white; + padding-left: 4px; + spacing: 2px; + border: 1px dashed #76797C; +} + +QMainWindow::separator:hover +{ + + background-color: #787876; + color: white; + padding-left: 4px; + border: 1px solid #76797C; + spacing: 2px; +} + + +QMenu::separator +{ + height: 1px; + background-color: #76797C; + color: white; + padding-left: 4px; + margin-left: 10px; + margin-right: 5px; +} + + +QFrame +{ + border-radius: 2px; + border: 1px solid #76797C; +} + +QFrame[frameShape="0"] +{ + border-radius: 2px; + border: 1px transparent #76797C; +} + +QStackedWidget +{ + border: 1px transparent black; +} + +QToolBar { + border: 1px transparent #393838; + background: 1px solid #31363b; + font-weight: bold; +} + +QToolBar::handle:horizontal { + image: url(:/qss_icons/rc/Hmovetoolbar.png); +} +QToolBar::handle:vertical { + image: url(:/qss_icons/rc/Vmovetoolbar.png); +} +QToolBar::separator:horizontal { + image: url(:/qss_icons/rc/Hsepartoolbar.png); +} +QToolBar::separator:vertical { + image: url(:/qss_icons/rc/Vsepartoolbar.png); +} +QToolButton#qt_toolbar_ext_button { + background: #58595a +} + +QPushButton +{ + color: #eff0f1; + background-color: #31363b; + border-width: 1px; + border-color: #76797C; + border-style: solid; + padding: 5px; + border-radius: 2px; + outline: none; +} + +QPushButton:disabled +{ + background-color: #31363b; + border-width: 1px; + border-color: #454545; + border-style: solid; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; + border-radius: 2px; + color: #454545; +} + +QPushButton:focus { + background-color: #3daee9; + color: white; +} + +QPushButton:pressed +{ + background-color: #3daee9; + padding-top: -15px; + padding-bottom: -17px; +} + +QComboBox +{ + selection-background-color: #3daee9; + border-style: solid; + border: 1px solid #76797C; + border-radius: 2px; + padding: 5px; + min-width: 75px; +} + +QPushButton:checked{ + background-color: #76797C; + border-color: #6A6969; +} + +QComboBox:hover,QPushButton:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:hover,QTreeView:hover +{ + border: 1px solid #3daee9; + color: #eff0f1; +} + +QComboBox:on +{ + padding-top: 3px; + padding-left: 4px; + selection-background-color: #4a4a4a; +} + +QComboBox QAbstractItemView +{ + background-color: #232629; + border-radius: 2px; + border: 1px solid #76797C; + selection-background-color: #18465d; +} + +QComboBox::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow +{ + image: url(:/qss_icons/rc/down_arrow_disabled.png); +} + +QComboBox::down-arrow:on, QComboBox::down-arrow:hover, +QComboBox::down-arrow:focus +{ + image: url(:/qss_icons/rc/down_arrow.png); +} + +QAbstractSpinBox { + padding: 5px; + border: 1px solid #76797C; + background-color: #232629; + color: #eff0f1; + border-radius: 2px; + min-width: 75px; +} + +QAbstractSpinBox:up-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; +} + +QAbstractSpinBox:down-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; +} + +QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off { + image: url(:/qss_icons/rc/up_arrow_disabled.png); + width: 10px; + height: 10px; +} +QAbstractSpinBox::up-arrow:hover +{ + image: url(:/qss_icons/rc/up_arrow.png); +} + + +QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off +{ + image: url(:/qss_icons/rc/down_arrow_disabled.png); + width: 10px; + height: 10px; +} +QAbstractSpinBox::down-arrow:hover +{ + image: url(:/qss_icons/rc/down_arrow.png); +} + + +QLabel +{ + border: 0px solid black; +} + +QTabWidget{ + border: 0px transparent black; +} + +QTabWidget::pane { + border: 1px solid #76797C; + padding: 5px; + margin: 0px; +} + +QTabWidget::tab-bar { + left: 5px; /* move to the right by 5px */ +} + +QTabBar +{ + qproperty-drawBase: 0; + border-radius: 3px; +} + +QTabBar:focus +{ + border: 0px transparent black; +} + +QTabBar::close-button { + image: url(:/qss_icons/rc/close.png); + background: transparent; +} + +QTabBar::close-button:hover +{ + image: url(:/qss_icons/rc/close-hover.png); + background: transparent; +} + +QTabBar::close-button:pressed { + image: url(:/qss_icons/rc/close-pressed.png); + background: transparent; +} + +/* TOP TABS */ +QTabBar::tab:top { + color: #eff0f1; + border: 1px solid #76797C; + border-bottom: 1px transparent black; + background-color: #31363b; + padding: 5px; + min-width: 50px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-bottom: 1px transparent black; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected:hover { + background-color: #3daee9; +} + +/* BOTTOM TABS */ +QTabBar::tab:bottom { + color: #eff0f1; + border: 1px solid #76797C; + border-top: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + min-width: 50px; +} + +QTabBar::tab:bottom:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-top: 1px transparent black; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:!selected:hover { + background-color: #3daee9; +} + +/* LEFT TABS */ +QTabBar::tab:left { + color: #eff0f1; + border: 1px solid #76797C; + border-left: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + min-height: 50px; +} + +QTabBar::tab:left:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-left: 1px transparent black; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:!selected:hover { + background-color: #3daee9; +} + + +/* RIGHT TABS */ +QTabBar::tab:right { + color: #eff0f1; + border: 1px solid #76797C; + border-right: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + min-height: 50px; +} + +QTabBar::tab:right:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-right: 1px transparent black; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:!selected:hover { + background-color: #3daee9; +} + +QTabBar QToolButton::right-arrow:enabled { + image: url(:/qss_icons/rc/right_arrow.png); + } + + QTabBar QToolButton::left-arrow:enabled { + image: url(:/qss_icons/rc/left_arrow.png); + } + +QTabBar QToolButton::right-arrow:disabled { + image: url(:/qss_icons/rc/right_arrow_disabled.png); + } + + QTabBar QToolButton::left-arrow:disabled { + image: url(:/qss_icons/rc/left_arrow_disabled.png); + } + + +QDockWidget { + background: #31363b; + border: 1px solid #403F3F; + titlebar-close-icon: url(:/qss_icons/rc/close.png); + titlebar-normal-icon: url(:/qss_icons/rc/undock.png); +} + +QDockWidget::close-button, QDockWidget::float-button { + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background: rgba(255, 255, 255, 10); +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + padding: 1px -1px -1px 1px; + background: rgba(255, 255, 255, 10); +} + +QTreeView, QListView +{ + border: 1px solid #76797C; + background-color: #232629; +} + +QTreeView:branch:selected, QTreeView:branch:hover +{ + background: url(:/qss_icons/rc/transparent.png); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/qss_icons/rc/transparent.png); +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/qss_icons/rc/transparent.png); +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/qss_icons/rc/transparent.png); +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url(:/qss_icons/rc/branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url(:/qss_icons/rc/branch_open.png); +} + +QTreeView::branch:has-children:!has-siblings:closed:hover, +QTreeView::branch:closed:has-children:has-siblings:hover { + image: url(:/qss_icons/rc/branch_closed-on.png); + } + +QTreeView::branch:open:has-children:!has-siblings:hover, +QTreeView::branch:open:has-children:has-siblings:hover { + image: url(:/qss_icons/rc/branch_open-on.png); + } + +QListView::item:!selected:hover, QTreeView::item:!selected:hover { + background: #18465d; + outline: 0; + color: #eff0f1 +} + +QListView::item:selected:hover, QTreeView::item:selected:hover { + background: #287399; + color: #eff0f1; +} + +QSlider::groove:horizontal { + border: 1px solid #565a5e; + height: 4px; + background: #565a5e; + margin: 0px; + border-radius: 2px; +} + +QSlider::handle:horizontal { + background: #232629; + border: 1px solid #565a5e; + width: 16px; + height: 16px; + margin: -8px 0; + border-radius: 9px; +} + +QSlider::groove:vertical { + border: 1px solid #565a5e; + width: 4px; + background: #565a5e; + margin: 0px; + border-radius: 3px; +} + +QSlider::handle:vertical { + background: #232629; + border: 1px solid #565a5e; + width: 16px; + height: 16px; + margin: 0 -8px; + border-radius: 9px; +} + +QToolButton { + background-color: transparent; + border: 1px transparent #76797C; + border-radius: 2px; + margin: 3px; + padding: 5px; +} + +QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ + padding-right: 20px; /* make way for the popup button */ + border: 1px #76797C; + border-radius: 5px; +} + +QToolButton[popupMode="2"] { /* only for InstantPopup */ + padding-right: 10px; /* make way for the popup button */ + border: 1px #76797C; +} + + +QToolButton:hover, QToolButton::menu-button:hover { + background-color: transparent; + border: 1px solid #3daee9; + padding: 5px; +} + +QToolButton:checked, QToolButton:pressed, + QToolButton::menu-button:pressed { + background-color: #3daee9; + border: 1px solid #3daee9; + padding: 5px; +} + +/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ +QToolButton::menu-indicator { + image: url(:/qss_icons/rc/down_arrow.png); + top: -7px; left: -2px; /* shift it a bit */ +} + +/* the subcontrols below are used only in the MenuButtonPopup mode */ +QToolButton::menu-button { + border: 1px transparent #76797C; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border = 20px allocated above */ + width: 16px; + outline: none; +} + +QToolButton::menu-arrow { + image: url(:/qss_icons/rc/down_arrow.png); +} + +QToolButton::menu-arrow:open { + border: 1px solid #76797C; +} + +QPushButton::menu-indicator { + subcontrol-origin: padding; + subcontrol-position: bottom right; + left: 8px; +} + +QTableView +{ + border: 1px solid #76797C; + gridline-color: #31363b; + background-color: #232629; +} + + +QTableView, QHeaderView +{ + border-radius: 0px; +} + +QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { + background: #18465d; + color: #eff0f1; +} + +QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { + background: #287399; + color: #eff0f1; +} + + +QHeaderView +{ + background-color: #31363b; + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; + +} + +QHeaderView::section { + background-color: #31363b; + color: #eff0f1; + padding: 5px; + border: 1px solid #76797C; + border-radius: 0px; + text-align: center; +} + +QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one +{ + border-top: 1px solid #76797C; +} + +QHeaderView::section::vertical +{ + border-top: transparent; +} + +QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one +{ + border-left: 1px solid #76797C; +} + +QHeaderView::section::horizontal +{ + border-left: transparent; +} + + +QHeaderView::section:checked + { + color: white; + background-color: #334e5e; + } + + /* style the sort indicator */ +QHeaderView::down-arrow { + image: url(:/qss_icons/rc/down_arrow.png); +} + +QHeaderView::up-arrow { + image: url(:/qss_icons/rc/up_arrow.png); +} + + +QTableCornerButton::section { + background-color: #31363b; + border: 1px transparent #76797C; + border-radius: 0px; +} + +QToolBox { + padding: 5px; + border: 1px transparent black; +} + +QToolBox::tab { + color: #eff0f1; + background-color: #31363b; + border: 1px solid #76797C; + border-bottom: 1px transparent #31363b; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +QToolBox::tab:selected { /* italicize selected tabs */ + font: italic; + background-color: #31363b; + border-color: #3daee9; + } + +QStatusBar::item { + border: 0px transparent dark; + } + + +QFrame[height="3"], QFrame[width="3"] { + background-color: #76797C; +} + + +QSplitter::handle { + border: 1px dashed #76797C; +} + +QSplitter::handle:hover { + background-color: #787876; + border: 1px solid #76797C; +} + +QSplitter::handle:horizontal { + width: 1px; +} + +QSplitter::handle:vertical { + height: 1px; +} + +QProgressBar { + border: 1px solid #76797C; + border-radius: 5px; + text-align: center; +} + +QProgressBar::chunk { + background-color: #05B8CC; +} + +QDateEdit +{ + selection-background-color: #3daee9; + border-style: solid; + border: 1px solid #3375A3; + border-radius: 2px; + padding: 1px; + min-width: 75px; +} + +QDateEdit:on +{ + padding-top: 3px; + padding-left: 4px; + selection-background-color: #4a4a4a; +} + +QDateEdit QAbstractItemView +{ + background-color: #232629; + border-radius: 2px; + border: 1px solid #3375A3; + selection-background-color: #3daee9; +} + +QDateEdit::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QDateEdit::down-arrow +{ + image: url(:/qss_icons/rc/down_arrow_disabled.png); +} + +QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, +QDateEdit::down-arrow:focus +{ + image: url(:/qss_icons/rc/down_arrow.png); +} diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index ec3c70779..d2b0652a5 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -17,6 +17,7 @@ endif() # libfmt add_subdirectory(fmt) +add_library(fmt::fmt ALIAS fmt) # getopt if (MSVC) diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 6b4c6b06a94290690d2132adfa45a8087958c2c +Subproject 9cc12d80b984be5a383af8f471e65b52cc1895f diff --git a/externals/fmt b/externals/fmt -Subproject ac5484c4e7365b59d8c7e14db6778de26635e42 +Subproject 4d35f94133ed14794e53c9f8627d047b408e0dc diff --git a/hooks/pre-commit b/hooks/pre-commit index 098a99216..e376ed4d8 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -1,6 +1,6 @@ #!/bin/sh -# Enforce citra's whitespace policy +# Enforce yuzu's whitespace policy git config --local core.whitespace tab-in-indent,trailing-space paths_to_check="src/ CMakeLists.txt" diff --git a/src/common/file_util.h b/src/common/file_util.h index 630232a25..143f099eb 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path); // Set the current directory to given directory bool SetCurrentDir(const std::string& directory); -// Returns a pointer to a string with a Citra data dir in the user's home +// Returns a pointer to a string with a yuzu data dir in the user's home // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = ""); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 8d5225f62..a763f4abf 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -42,6 +42,7 @@ namespace Log { SUB(Service, FS) \ SUB(Service, HID) \ SUB(Service, LM) \ + SUB(Service, NFP) \ SUB(Service, NIFM) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ @@ -49,6 +50,7 @@ namespace Log { SUB(Service, SET) \ SUB(Service, SM) \ SUB(Service, SPL) \ + SUB(Service, SSL) \ SUB(Service, Time) \ SUB(Service, VI) \ CLS(HW) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 6298a4906..7f079b20f 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -59,6 +59,7 @@ enum class Class : ClassType { Service_FS, ///< The FS (Filesystem) service Service_HID, ///< The HID (Human interface device) service Service_LM, ///< The LM (Logger) service + Service_NFP, ///< The NFP service Service_NIFM, ///< The NIFM (Network interface) service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service @@ -66,6 +67,7 @@ enum class Class : ClassType { Service_SET, ///< The SET (Settings) service Service_SM, ///< The SM (Service manager) service Service_SPL, ///< The SPL service + Service_SSL, ///< The SSL service Service_Time, ///< The time service Service_VI, ///< The VI (Video interface) service HW, ///< Low-level hardware emulation @@ -84,7 +86,7 @@ enum class Class : ClassType { Loader, ///< ROM loader Input, ///< Input emulation Network, ///< Network emulation - WebService, ///< Interface to Citra Web Services + WebService, ///< Interface to yuzu Web Services Count ///< Total number of logging classes }; diff --git a/src/common/swap.h b/src/common/swap.h index d94cbe6b2..4a4012d1a 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -103,7 +103,19 @@ inline __attribute__((always_inline)) u64 swap64(u64 _data) { return __builtin_bswap64(_data); } #elif defined(__Bitrig__) || defined(__OpenBSD__) -// swap16, swap32, swap64 are left as is +// redefine swap16, swap32, swap64 as inline functions +#undef swap16 +#undef swap32 +#undef swap64 +inline u16 swap16(u16 _data) { + return __swap16(_data); +} +inline u32 swap32(u32 _data) { + return __swap32(_data); +} +inline u64 swap64(u64 _data) { + return __swap64(_data); +} #elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) inline u16 swap16(u16 _data) { return bswap16(_data); diff --git a/src/common/telemetry.h b/src/common/telemetry.h index 3694c76f2..7a09df0a7 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -15,7 +15,7 @@ namespace Telemetry { /// Field type, used for grouping fields together in the final submitted telemetry log enum class FieldType : u8 { None = 0, ///< No specified field group - App, ///< Citra application fields (e.g. version, branch, etc.) + App, ///< yuzu application fields (e.g. version, branch, etc.) Session, ///< Emulated session fields (e.g. title ID, log, etc.) Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.) UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.) diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 62f17fbb5..2dfcd39c8 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -54,7 +54,7 @@ static CPUCaps Detect() { caps.num_cores = std::thread::hardware_concurrency(); // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support - // Citra at all anyway + // yuzu at all anyway int cpu_id[4]; memset(caps.brand_string, 0, sizeof(caps.brand_string)); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6c0911070..6f8104516 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -22,6 +22,8 @@ add_library(core STATIC file_sys/romfs_filesystem.h file_sys/savedata_factory.cpp file_sys/savedata_factory.h + file_sys/sdmc_factory.cpp + file_sys/sdmc_factory.h file_sys/storage.h frontend/emu_window.cpp frontend/emu_window.h @@ -140,6 +142,10 @@ add_library(core STATIC hle/service/nifm/nifm_s.h hle/service/nifm/nifm_u.cpp hle/service/nifm/nifm_u.h + hle/service/nfp/nfp.cpp + hle/service/nfp/nfp.h + hle/service/nfp/nfp_user.cpp + hle/service/nfp/nfp_user.h hle/service/ns/ns.cpp hle/service/ns/ns.h hle/service/ns/pl_u.cpp @@ -187,8 +193,10 @@ add_library(core STATIC hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h - hle/service/sockets/bsd_u.cpp - hle/service/sockets/bsd_u.h + hle/service/sockets/bsd.cpp + hle/service/sockets/bsd.h + hle/service/sockets/nsd.cpp + hle/service/sockets/nsd.h hle/service/sockets/sfdnsres.cpp hle/service/sockets/sfdnsres.h hle/service/sockets/sockets.cpp @@ -199,6 +207,8 @@ add_library(core STATIC hle/service/spl/module.h hle/service/spl/spl.cpp hle/service/spl/spl.h + hle/service/ssl/ssl.cpp + hle/service/ssl/ssl.h hle/service/time/time.cpp hle/service/time/time.h hle/service/time/time_s.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7d83f9717..6afad0e0c 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -86,21 +86,17 @@ public: } void AddTicks(u64 ticks) override { - if (ticks > ticks_remaining) { - ticks_remaining = 0; - return; - } - ticks -= ticks_remaining; + CoreTiming::AddTicks(ticks - num_interpreted_instructions); + num_interpreted_instructions = 0; } u64 GetTicksRemaining() override { - return ticks_remaining; + return std::max(CoreTiming::GetDowncount(), 0); } u64 GetCNTPCT() override { return CoreTiming::GetTicks(); } ARM_Dynarmic& parent; - size_t ticks_remaining = 0; size_t num_interpreted_instructions = 0; u64 tpidrro_el0 = 0; u64 tpidr_el0 = 0; diff --git a/src/core/core.cpp b/src/core/core.cpp index d55621de8..11654d4da 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -148,19 +148,15 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { current_process = Kernel::Process::Create("main"); - switch (Settings::values.cpu_core) { - case Settings::CpuCore::Unicorn: - cpu_core = std::make_shared<ARM_Unicorn>(); - break; - case Settings::CpuCore::Dynarmic: - default: + if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 cpu_core = std::make_shared<ARM_Dynarmic>(); #else cpu_core = std::make_shared<ARM_Unicorn>(); LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); #endif - break; + } else { + cpu_core = std::make_shared<ARM_Unicorn>(); } gpu_core = std::make_unique<Tegra::GPU>(); diff --git a/src/core/core.h b/src/core/core.h index 552c8f5ee..ade456cfc 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -13,6 +13,7 @@ #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" class EmuWindow; @@ -135,6 +136,14 @@ public: return *app_loader; } + void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { + debug_context = std::move(context); + } + + std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const { + return debug_context; + } + private: /** * Initialize the emulated system. @@ -154,6 +163,8 @@ private: std::unique_ptr<Kernel::Scheduler> scheduler; std::unique_ptr<Tegra::GPU> gpu_core; + std::shared_ptr<Tegra::DebugContext> debug_context; + Kernel::SharedPtr<Kernel::Process> current_process; /// When true, signals that a reschedule should happen diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 5a40bf472..c7639795e 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -6,34 +6,28 @@ #include <array> #include <cstddef> +#include "common/common_funcs.h" #include "common/common_types.h" +#include "core/file_sys/filesystem.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // FileSys namespace namespace FileSys { -// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format -const size_t FILENAME_LENGTH = 0x20C / 2; +// Structure of a directory entry, from +// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry +const size_t FILENAME_LENGTH = 0x300; struct Entry { - char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated) - std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated) - char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD) - std::array<char, 4> - extension; // 8.3 file extension (set to spaces for directories, null-terminated) - char unknown2; // unknown (always 0x01) - char unknown3; // unknown (0x00 or 0x08) - char is_directory; // directory flag - char is_hidden; // hidden flag - char is_archive; // archive flag - char is_read_only; // read-only flag - u64 file_size; // file size (for files only) + char filename[FILENAME_LENGTH]; + INSERT_PADDING_BYTES(4); + EntryType type; + INSERT_PADDING_BYTES(3); + u64 file_size; }; -static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!"); -static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry."); -static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry."); -static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry."); -static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry."); +static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!"); +static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry."); +static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry."); class DirectoryBackend : NonCopyable { public: @@ -46,7 +40,10 @@ public: * @param entries Buffer to read data into * @return Number of entries listed */ - virtual u32 Read(const u32 count, Entry* entries) = 0; + virtual u64 Read(const u64 count, Entry* entries) = 0; + + /// Returns the number of entries still left to read. + virtual u64 GetEntryCount() const = 0; /** * Close the directory diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp index 22b17ba04..4235f3935 100644 --- a/src/core/file_sys/disk_filesystem.cpp +++ b/src/core/file_sys/disk_filesystem.cpp @@ -11,16 +11,43 @@ namespace FileSys { +static std::string ModeFlagsToString(Mode mode) { + std::string mode_str; + u32 mode_flags = static_cast<u32>(mode); + + // Calculate the correct open mode for the file. + if ((mode_flags & static_cast<u32>(Mode::Read)) && + (mode_flags & static_cast<u32>(Mode::Write))) { + if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a+"; + else + mode_str = "r+"; + } else { + if (mode_flags & static_cast<u32>(Mode::Read)) + mode_str = "r"; + else if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a"; + else if (mode_flags & static_cast<u32>(Mode::Write)) + mode_str = "w"; + } + + mode_str += "b"; + + return mode_str; +} + std::string Disk_FileSystem::GetName() const { return "Disk"; } ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path, Mode mode) const { - ASSERT_MSG(mode == Mode::Read || mode == Mode::Write, "Other file modes are not supported"); + + // Calculate the correct open mode for the file. + std::string mode_str = ModeFlagsToString(mode); std::string full_path = base_directory + path; - auto file = std::make_shared<FileUtil::IOFile>(full_path, mode == Mode::Read ? "rb" : "wb"); + auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str()); if (!file->IsOpen()) { return ERROR_PATH_NOT_FOUND; @@ -75,8 +102,15 @@ ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const return ResultCode(-1); } -ResultCode Disk_FileSystem::CreateDirectory(const Path& path) const { - LOG_WARNING(Service_FS, "(STUBBED) called"); +ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const { + // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox. + std::string full_path = base_directory + path; + + if (FileUtil::CreateDir(full_path)) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str()); // TODO(wwylele): Use correct error code return ResultCode(-1); } @@ -88,8 +122,17 @@ ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& de } ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory( - const Path& path) const { - return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<Disk_Directory>()); + const std::string& path) const { + + std::string full_path = base_directory + path; + + if (!FileUtil::IsDirectory(full_path)) { + // TODO(Subv): Find the correct error code for this. + return ResultCode(-1); + } + + auto directory = std::make_unique<Disk_Directory>(full_path); + return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); } u64 Disk_FileSystem::GetFreeSpaceSize() const { @@ -103,8 +146,10 @@ ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& p return ERROR_PATH_NOT_FOUND; } - // TODO(Subv): Find out the EntryType values - UNIMPLEMENTED_MSG("Unimplemented GetEntryType"); + if (FileUtil::IsDirectory(full_path)) + return MakeResult(EntryType::Directory); + + return MakeResult(EntryType::File); } ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { @@ -129,18 +174,55 @@ u64 Disk_Storage::GetSize() const { } bool Disk_Storage::SetSize(const u64 size) const { - LOG_WARNING(Service_FS, "(STUBBED) called"); - return false; + file->Resize(size); + file->Flush(); + return true; } -u32 Disk_Directory::Read(const u32 count, Entry* entries) { - LOG_WARNING(Service_FS, "(STUBBED) called"); - return 0; +Disk_Directory::Disk_Directory(const std::string& path) : directory() { + unsigned size = FileUtil::ScanDirectoryTree(path, directory); + directory.size = size; + directory.isDirectory = true; + children_iterator = directory.children.begin(); } -bool Disk_Directory::Close() const { - LOG_WARNING(Service_FS, "(STUBBED) called"); - return true; +u64 Disk_Directory::Read(const u64 count, Entry* entries) { + u64 entries_read = 0; + + while (entries_read < count && children_iterator != directory.children.cend()) { + const FileUtil::FSTEntry& file = *children_iterator; + const std::string& filename = file.virtualName; + Entry& entry = entries[entries_read]; + + LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, + file.isDirectory); + + // TODO(Link Mauve): use a proper conversion to UTF-16. + for (size_t j = 0; j < FILENAME_LENGTH; ++j) { + entry.filename[j] = filename[j]; + if (!filename[j]) + break; + } + + if (file.isDirectory) { + entry.file_size = 0; + entry.type = EntryType::Directory; + } else { + entry.file_size = file.size; + entry.type = EntryType::File; + } + + ++entries_read; + ++children_iterator; + } + return entries_read; +} + +u64 Disk_Directory::GetEntryCount() const { + // We convert the children iterator into a const_iterator to allow template argument deduction + // in std::distance. + std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator; + return std::distance(current, directory.children.end()); } } // namespace FileSys diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h index 53767b949..742d7db1a 100644 --- a/src/core/file_sys/disk_filesystem.h +++ b/src/core/file_sys/disk_filesystem.h @@ -30,9 +30,10 @@ public: ResultCode DeleteDirectory(const Path& path) const override; ResultCode DeleteDirectoryRecursively(const Path& path) const override; ResultCode CreateFile(const std::string& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; + ResultCode CreateDirectory(const std::string& path) const override; ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; u64 GetFreeSpaceSize() const override; ResultVal<EntryType> GetEntryType(const std::string& path) const override; @@ -59,8 +60,26 @@ private: class Disk_Directory : public DirectoryBackend { public: - u32 Read(const u32 count, Entry* entries) override; - bool Close() const override; + Disk_Directory(const std::string& path); + + ~Disk_Directory() override { + Close(); + } + + u64 Read(const u64 count, Entry* entries) override; + u64 GetEntryCount() const override; + + bool Close() const override { + return true; + } + +protected: + u32 total_entries_in_directory; + FileUtil::FSTEntry directory; + + // We need to remember the last entry we returned, so a subsequent call to Read will continue + // from the next one. This iterator will always point to the next unread entry. + std::vector<FileUtil::FSTEntry>::iterator children_iterator; }; } // namespace FileSys diff --git a/src/core/file_sys/filesystem.h b/src/core/file_sys/filesystem.h index 94ad2abf2..399427ca2 100644 --- a/src/core/file_sys/filesystem.h +++ b/src/core/file_sys/filesystem.h @@ -27,7 +27,7 @@ enum LowPathType : u32 { Wchar = 4, }; -enum EntryType : u32 { +enum EntryType : u8 { Directory = 0, File = 1, }; @@ -35,6 +35,7 @@ enum EntryType : u32 { enum class Mode : u32 { Read = 1, Write = 2, + Append = 4, }; class Path { @@ -103,7 +104,7 @@ public: * @param path Path relative to the archive * @return Result of the operation */ - virtual ResultCode CreateDirectory(const Path& path) const = 0; + virtual ResultCode CreateDirectory(const std::string& path) const = 0; /** * Delete a directory specified by its path @@ -149,7 +150,8 @@ public: * @param path Path relative to the archive * @return Opened directory, or error code */ - virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; + virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const = 0; /** * Get the free space diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp index f1f9b4d04..0c6cc3157 100644 --- a/src/core/file_sys/romfs_filesystem.cpp +++ b/src/core/file_sys/romfs_filesystem.cpp @@ -55,7 +55,7 @@ ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const return ResultCode(-1); } -ResultCode RomFS_FileSystem::CreateDirectory(const Path& path) const { +ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const { LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).", GetName().c_str()); // TODO(wwylele): Use correct error code @@ -70,7 +70,8 @@ ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& d } ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory( - const Path& path) const { + const std::string& path) const { + LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive"); return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>()); } diff --git a/src/core/file_sys/romfs_filesystem.h b/src/core/file_sys/romfs_filesystem.h index cedd70645..3f94c04d0 100644 --- a/src/core/file_sys/romfs_filesystem.h +++ b/src/core/file_sys/romfs_filesystem.h @@ -36,9 +36,10 @@ public: ResultCode DeleteDirectory(const Path& path) const override; ResultCode DeleteDirectoryRecursively(const Path& path) const override; ResultCode CreateFile(const std::string& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; + ResultCode CreateDirectory(const std::string& path) const override; ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; u64 GetFreeSpaceSize() const override; ResultVal<EntryType> GetEntryType(const std::string& path) const override; @@ -70,7 +71,10 @@ private: class ROMFSDirectory : public DirectoryBackend { public: - u32 Read(const u32 count, Entry* entries) override { + u64 Read(const u64 count, Entry* entries) override { + return 0; + } + u64 GetEntryCount() const override { return 0; } bool Close() const override { diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp new file mode 100644 index 000000000..00e80d2a7 --- /dev/null +++ b/src/core/file_sys/sdmc_factory.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/sdmc_factory.h" + +namespace FileSys { + +SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {} + +ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) { + // Create the SD Card directory if it doesn't already exist. + if (!FileUtil::IsDirectory(sd_directory)) { + FileUtil::CreateFullPath(sd_directory); + } + + auto archive = std::make_unique<Disk_FileSystem>(sd_directory); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode SDMC_Factory::Format(const Path& path) { + LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str()); + // TODO(Subv): Find the right error code for this + return ResultCode(-1); +} + +ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const { + LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +} // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h new file mode 100644 index 000000000..93becda25 --- /dev/null +++ b/src/core/file_sys/sdmc_factory.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" + +namespace FileSys { + +/// File system interface to the SDCard archive +class SDMC_Factory final : public FileSystemFactory { +public: + explicit SDMC_Factory(std::string sd_directory); + + std::string GetName() const override { + return "SDMC_Factory"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::string sd_directory; +}; + +} // namespace FileSys diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index ffcbfe64f..bef4f15f5 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -268,8 +268,11 @@ std::vector<u8> HLERequestContext::ReadBuffer() const { size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const { const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()}; - - ASSERT_MSG(size <= GetWriteBufferSize(), "Size %lx is too big", size); + const size_t buffer_size{GetWriteBufferSize()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size (%016zx) is greater than buffer_size (%016zx)", size, buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } if (is_buffer_b) { Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size); diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 3694afc60..2cffec198 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -121,8 +121,9 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part // of the user address space. vm_manager - .MapMemoryBlock(Memory::STACK_VADDR, std::make_shared<std::vector<u8>>(stack_size, 0), 0, - stack_size, MemoryState::Mapped) + .MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size, + std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, + MemoryState::Mapped) .Unwrap(); misc_memory_used += stack_size; memory_region->used += stack_size; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 311ab4187..171bbd956 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -756,8 +756,16 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 return RESULT_SUCCESS; } -static ResultCode SetThreadCoreMask(u64, u64, u64) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called"); +static ResultCode GetThreadCoreMask(Handle handle, u32* mask, u64* unknown) { + LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X", handle); + *mask = 0x0; + *unknown = 0xf; + return RESULT_SUCCESS; +} + +static ResultCode SetThreadCoreMask(Handle handle, u32 mask, u64 unknown) { + LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, mask=0x%08X, unknown=0x%lx", handle, + mask, unknown); return RESULT_SUCCESS; } @@ -809,7 +817,7 @@ static const FunctionDef SVC_Table[] = { {0x0B, SvcWrap<SleepThread>, "SleepThread"}, {0x0C, SvcWrap<GetThreadPriority>, "GetThreadPriority"}, {0x0D, SvcWrap<SetThreadPriority>, "SetThreadPriority"}, - {0x0E, nullptr, "GetThreadCoreMask"}, + {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"}, {0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"}, {0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"}, {0x11, nullptr, "SignalEvent"}, diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index b224f5e67..5da4f5269 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -70,6 +70,21 @@ void SvcWrap() { FuncReturn(retval); } +template <ResultCode func(u32, u32, u64)> +void SvcWrap() { + FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw); +} + +template <ResultCode func(u32, u32*, u64*)> +void SvcWrap() { + u32 param_1 = 0; + u64 param_2 = 0; + ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); + Core::CPU().SetReg(1, param_1); + Core::CPU().SetReg(2, param_2); + FuncReturn(retval.raw); +} + template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 145f50887..f3a8aa4aa 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -342,7 +342,7 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority, // Initialize new "main" thread auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, - Memory::STACK_VADDR_END, owner_process); + Memory::STACK_AREA_VADDR_END, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 97fef7a48..052f49979 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -200,6 +200,9 @@ public: } ResultVal& operator=(const ResultVal& o) { + if (this == &o) { + return *this; + } if (!empty()) { if (!o.empty()) { object = o.object; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d9f003ed4..bab338205 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -12,6 +12,7 @@ #include "core/hle/service/apm/apm.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/settings.h" namespace Service { namespace AM { @@ -241,17 +242,20 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { } void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u8>(OperationMode::Handheld)); + rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld)); LOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld)); + rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked + : APM::PerformanceMode::Handheld)); LOG_WARNING(Service_AM, "(STUBBED) called"); } diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 1cbca6c4b..6d0461bbc 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -59,12 +59,12 @@ private: AudioRendererResponseData response_data{}; response_data.section_0_size = - response_data.state_entries.size() * sizeof(AudioRendererStateEntry); - response_data.section_1_size = response_data.section_1.size(); - response_data.section_2_size = response_data.section_2.size(); - response_data.section_3_size = response_data.section_3.size(); - response_data.section_4_size = response_data.section_4.size(); - response_data.section_5_size = response_data.section_5.size(); + static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry)); + response_data.section_1_size = static_cast<u32>(response_data.section_1.size()); + response_data.section_2_size = static_cast<u32>(response_data.section_2.size()); + response_data.section_3_size = static_cast<u32>(response_data.section_3.size()); + response_data.section_4_size = static_cast<u32>(response_data.section_4.size()); + response_data.section_5_size = static_cast<u32>(response_data.section_5.size()); response_data.total_size = sizeof(AudioRendererResponseData); for (unsigned i = 0; i < response_data.state_entries.size(); i++) { @@ -151,12 +151,80 @@ private: Kernel::SharedPtr<Kernel::Event> system_event; }; +class IAudioDevice final : public ServiceFramework<IAudioDevice> { +public: + IAudioDevice() : ServiceFramework("IAudioDevice") { + static const FunctionInfo functions[] = { + {0x0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, + {0x1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, + {0x2, nullptr, "GetAudioDeviceOutputVolume"}, + {0x3, nullptr, "GetActiveAudioDeviceName"}, + {0x4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, + {0x5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, + {0x6, nullptr, "ListAudioDeviceNameAuto"}, + {0x7, nullptr, "SetAudioDeviceOutputVolumeAuto"}, + {0x8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, + {0x10, nullptr, "GetActiveAudioDeviceNameAuto"}, + {0x11, nullptr, "QueryAudioDeviceInputEvent"}, + {0x12, nullptr, "QueryAudioDeviceOutputEvent"}}; + RegisterHandlers(functions); + + buffer_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent"); + } + +private: + void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const std::string audio_interface = "AudioInterface"; + ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size()); + + IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::RequestParser rp{ctx}; + f32 volume = static_cast<f32>(rp.Pop<u32>()); + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } + + void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + buffer_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(buffer_event); + } + + void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + Kernel::SharedPtr<Kernel::Event> buffer_event; + +}; // namespace Audio + AudRenU::AudRenU() : ServiceFramework("audren:u") { static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"}, - {2, &AudRenU::GetAudioRenderersProcessMasterVolume, "GetAudioRenderersProcessMasterVolume"}, - {3, nullptr, "SetAudioRenderersProcessMasterVolume"}, + {2, &AudRenU::GetAudioDevice, "GetAudioDevice"}, }; RegisterHandlers(functions); } @@ -179,12 +247,13 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); } -void AudRenU::GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 3}; +void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(100); - LOG_WARNING(Service_Audio, "(STUBBED) called"); + rb.PushIpcInterface<Audio::IAudioDevice>(); + + LOG_DEBUG(Service_Audio, "called"); } } // namespace Audio diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 939d353a9..f59d1627d 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -21,7 +21,7 @@ public: private: void OpenAudioRenderer(Kernel::HLERequestContext& ctx); void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); - void GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx); + void GetAudioDevice(Kernel::HLERequestContext& ctx); }; } // namespace Audio diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index ef05955b9..945832e98 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -6,6 +6,7 @@ #include "common/file_util.h" #include "core/file_sys/filesystem.h" #include "core/file_sys/savedata_factory.h" +#include "core/file_sys/sdmc_factory.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_srv.h" @@ -60,9 +61,13 @@ void RegisterFileSystems() { filesystem_map.clear(); std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX); auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory)); RegisterFileSystem(std::move(savedata), Type::SaveData); + + auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory)); + RegisterFileSystem(std::move(sdcard), Type::SDMC); } void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 8d30e94a1..56d26146e 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -26,6 +26,7 @@ namespace FileSystem { enum class Type { RomFS = 1, SaveData = 2, + SDMC = 3, }; /** diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index e5ce41671..89fa70ae6 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -5,6 +5,7 @@ #include <cinttypes> #include "common/logging/log.h" #include "core/core.h" +#include "core/file_sys/directory.h" #include "core/file_sys/filesystem.h" #include "core/file_sys/storage.h" #include "core/hle/ipc_helpers.h" @@ -71,8 +72,8 @@ public: explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend) : ServiceFramework("IFile"), backend(std::move(backend)) { static const FunctionInfo functions[] = { - {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, nullptr, "Flush"}, - {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, + {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, nullptr, "Flush"}, + {3, &IFile::SetSize, "SetSize"}, {4, &IFile::GetSize, "GetSize"}, }; RegisterHandlers(functions); } @@ -149,6 +150,75 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + void SetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 size = rp.Pop<u64>(); + backend->SetSize(size); + LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(size); + } +}; + +class IDirectory final : public ServiceFramework<IDirectory> { +public: + explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend) + : ServiceFramework("IDirectory"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IDirectory::Read, "Read"}, + {1, &IDirectory::GetEntryCount, "GetEntryCount"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::DirectoryBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + + LOG_DEBUG(Service_FS, "called, unk=0x%llx", unk); + + // Calculate how many entries we can fit in the output buffer + u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); + + // Read the data from the Directory backend + std::vector<FileSys::Entry> entries(count_entries); + u64 read_entries = backend->Read(count_entries, entries.data()); + + // Convert the data into a byte array + std::vector<u8> output(entries.size() * sizeof(FileSys::Entry)); + std::memcpy(output.data(), entries.data(), output.size()); + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(read_entries); + } + + void GetEntryCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + u64 count = backend->GetEntryCount(); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(count); + } }; class IFileSystem final : public ServiceFramework<IFileSystem> { @@ -157,8 +227,10 @@ public: : ServiceFramework("IFileSystem"), backend(std::move(backend)) { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, + {2, &IFileSystem::CreateDirectory, "CreateDirectory"}, {7, &IFileSystem::GetEntryType, "GetEntryType"}, {8, &IFileSystem::OpenFile, "OpenFile"}, + {9, &IFileSystem::OpenDirectory, "OpenDirectory"}, {10, &IFileSystem::Commit, "Commit"}, }; RegisterHandlers(functions); @@ -182,6 +254,20 @@ public: rb.Push(backend->CreateFile(name, size)); } + void CreateDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + LOG_DEBUG(Service_FS, "called directory %s", name.c_str()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->CreateDirectory(name)); + } + void OpenFile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -208,6 +294,33 @@ public: rb.PushIpcInterface<IFile>(std::move(file)); } + void OpenDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + // TODO(Subv): Implement this filter. + u32 filter_flags = rp.Pop<u32>(); + + LOG_DEBUG(Service_FS, "called directory %s filter %u", name.c_str(), filter_flags); + + auto result = backend->OpenDirectory(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + auto directory = std::move(result.Unwrap()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDirectory>(std::move(directory)); + } + void GetEntryType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -274,10 +387,14 @@ void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) { } void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + LOG_DEBUG(Service_FS, "called"); - IPC::ResponseBuilder rb{ctx, 2}; + FileSys::Path unused; + auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); } void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 7e04ad8d4..019a09444 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -65,13 +65,14 @@ private: } void UpdatePadCallback(u64 userdata, int cycles_late) { - SharedMemory* mem = reinterpret_cast<SharedMemory*>(shared_mem->GetPointer()); + SharedMemory mem{}; + std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory)); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); // Set up controllers as neon red+blue Joy-Con attached to console - ControllerHeader& controller_header = mem->controllers[Controller_Handheld].header; + ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header; controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair; controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent; controller_header.right_color_body = JOYCON_BODY_NEON_RED; @@ -79,8 +80,8 @@ private: controller_header.left_color_body = JOYCON_BODY_NEON_BLUE; controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE; - for (int layoutIdx = 0; layoutIdx < HID_NUM_LAYOUTS; layoutIdx++) { - ControllerLayout& layout = mem->controllers[Controller_Handheld].layouts[layoutIdx]; + for (int index = 0; index < HID_NUM_LAYOUTS; index++) { + ControllerLayout& layout = mem.controllers[Controller_Handheld].layouts[index]; layout.header.num_entries = HID_NUM_ENTRIES; layout.header.max_entry_index = HID_NUM_ENTRIES - 1; @@ -136,10 +137,25 @@ private: // layouts) } - // TODO(shinyquagsire23): Update touch info + // TODO(bunnei): Properly implement the touch screen, the below will just write empty data + + TouchScreen& touchscreen = mem.touchscreen; + const u64 last_entry = touchscreen.header.latest_entry; + const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size(); + const u64 timestamp = CoreTiming::GetTicks(); + const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1; + touchscreen.header.timestamp_ticks = timestamp; + touchscreen.header.num_entries = touchscreen.entries.size(); + touchscreen.header.latest_entry = curr_entry; + touchscreen.header.max_entry_index = touchscreen.entries.size(); + touchscreen.header.timestamp = timestamp; + touchscreen.entries[curr_entry].header.timestamp = sample_counter; + touchscreen.entries[curr_entry].header.num_touches = 0; // TODO(shinyquagsire23): Signal events + std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory)); + // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } @@ -185,6 +201,7 @@ public: {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, + {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, {103, &Hid::ActivateNpad, "ActivateNpad"}, {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, @@ -193,7 +210,7 @@ public: {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"}, - {124, nullptr, "SetNpadJoyAssignmentModeDual"}, + {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"}, {201, &Hid::SendVibrationValue, "SendVibrationValue"}, @@ -265,6 +282,13 @@ private: LOG_WARNING(Service_HID, "(STUBBED) called"); } + void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -315,6 +339,12 @@ private: LOG_WARNING(Service_HID, "(STUBBED) called"); } + void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp new file mode 100644 index 000000000..49870841c --- /dev/null +++ b/src/core/hle/service/nfp/nfp.cpp @@ -0,0 +1,28 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/nfp/nfp.h" +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service { +namespace NFP { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<NFP_User>(module)->InstallAsService(service_manager); +} + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h new file mode 100644 index 000000000..1163e9954 --- /dev/null +++ b/src/core/hle/service/nfp/nfp.h @@ -0,0 +1,28 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service { +namespace NFP { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void Unknown(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp new file mode 100644 index 000000000..14e5647c4 --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service { +namespace NFP { + +NFP_User::NFP_User(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "nfp:user") { + static const FunctionInfo functions[] = { + {0, &NFP_User::Unknown, "Unknown"}, + }; + RegisterHandlers(functions); +} + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h new file mode 100644 index 000000000..1606444ca --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nfp/nfp.h" + +namespace Service { +namespace NFP { + +class NFP_User final : public Module::Interface { +public: + explicit NFP_User(std::shared_ptr<Module> module); +}; + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 695e295ca..ef3c7799a 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -33,6 +33,7 @@ enum class LoadState : u32 { PL_U::PL_U() : ServiceFramework("pl:u") { static const FunctionInfo functions[] = { + {0, &PL_U::RequestLoad, "RequestLoad"}, {1, &PL_U::GetLoadState, "GetLoadState"}, {2, &PL_U::GetSize, "GetSize"}, {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, @@ -54,6 +55,15 @@ PL_U::PL_U() : ServiceFramework("pl:u") { } } +void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 shared_font_type{rp.Pop<u32>()}; + + LOG_DEBUG(Service_NS, "called, shared_font_type=%d", shared_font_type); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index 7a4766338..360482d13 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -17,6 +17,7 @@ public: ~PL_U() = default; private: + void RequestLoad(Kernel::HLERequestContext& ctx); void GetLoadState(Kernel::HLERequestContext& ctx); void GetSize(Kernel::HLERequestContext& ctx); void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 94530724e..87b3a2d74 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -26,14 +26,13 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3 "Drawing from address %lx offset %08X Width %u Height %u Stride %u Format %u", addr, offset, width, height, stride, format); - using PixelFormat = RendererBase::FramebufferInfo::PixelFormat; - using Flags = NVFlinger::BufferQueue::BufferTransformFlags; - const bool flip_vertical = static_cast<u32>(transform) & static_cast<u32>(Flags::FlipV); - const RendererBase::FramebufferInfo framebuffer_info{ - addr, offset, width, height, stride, static_cast<PixelFormat>(format), flip_vertical}; + using PixelFormat = Tegra::FramebufferConfig::PixelFormat; + const Tegra::FramebufferConfig framebuffer{ + addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform}; Core::System::GetInstance().perf_stats.EndGameFrame(); - VideoCore::g_renderer->SwapBuffers(framebuffer_info); + + VideoCore::g_renderer->SwapBuffers(framebuffer); } } // namespace Devices diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 686eadca7..1de5767cb 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -47,6 +47,8 @@ public: ~BufferQueue() = default; enum class BufferTransformFlags : u32 { + /// No transform flags are set + Unset = 0x00, /// Flip source image horizontally (around the vertical axis) FlipH = 0x01, /// Flip source image vertically (around the horizontal axis) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 005cc1e0b..c5490c1ae 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -25,6 +25,7 @@ #include "core/hle/service/friend/friend.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/lm/lm.h" +#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" @@ -35,6 +36,7 @@ #include "core/hle/service/sm/sm.h" #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/spl/module.h" +#include "core/hle/service/ssl/ssl.h" #include "core/hle/service/time/time.h" #include "core/hle/service/vi/vi.h" @@ -110,15 +112,15 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext auto cmd_buf = ctx.CommandBuffer(); std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name; - fmt::MemoryWriter w; - w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name, - cmd_buf[0]); + fmt::memory_buffer buf; + fmt::format_to(buf, "function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name, + cmd_buf[0]); for (int i = 1; i <= 8; ++i) { - w.write(", [{}]={:#x}", i, cmd_buf[i]); + fmt::format_to(buf, ", [{}]={:#x}", i, cmd_buf[i]); } - w << '}'; + buf.push_back('}'); - LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); + LOG_ERROR(Service, "unknown / unimplemented %s", fmt::to_string(buf).c_str()); UNIMPLEMENTED(); } @@ -186,12 +188,14 @@ void Init() { Friend::InstallInterfaces(*SM::g_service_manager); HID::InstallInterfaces(*SM::g_service_manager); LM::InstallInterfaces(*SM::g_service_manager); + NFP::InstallInterfaces(*SM::g_service_manager); NIFM::InstallInterfaces(*SM::g_service_manager); NS::InstallInterfaces(*SM::g_service_manager); Nvidia::InstallInterfaces(*SM::g_service_manager); PCTL::InstallInterfaces(*SM::g_service_manager); Sockets::InstallInterfaces(*SM::g_service_manager); SPL::InstallInterfaces(*SM::g_service_manager); + SSL::InstallInterfaces(*SM::g_service_manager); Time::InstallInterfaces(*SM::g_service_manager); VI::InstallInterfaces(*SM::g_service_manager, nv_flinger); Set::InstallInterfaces(*SM::g_service_manager); diff --git a/src/core/hle/service/sockets/bsd_u.cpp b/src/core/hle/service/sockets/bsd.cpp index 2ca1000ca..790ff82b3 100644 --- a/src/core/hle/service/sockets/bsd_u.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -3,12 +3,12 @@ // Refer to the license.txt file included. #include "core/hle/ipc_helpers.h" -#include "core/hle/service/sockets/bsd_u.h" +#include "core/hle/service/sockets/bsd.h" namespace Service { namespace Sockets { -void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) { +void BSD::RegisterClient(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; @@ -17,7 +17,7 @@ void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) { +void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; @@ -26,7 +26,7 @@ void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -void BSD_U::Socket(Kernel::HLERequestContext& ctx) { +void BSD::Socket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u32 domain = rp.Pop<u32>(); @@ -44,7 +44,7 @@ void BSD_U::Socket(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -void BSD_U::Connect(Kernel::HLERequestContext& ctx) { +void BSD::Connect(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -54,7 +54,7 @@ void BSD_U::Connect(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -void BSD_U::SendTo(Kernel::HLERequestContext& ctx) { +void BSD::SendTo(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -64,7 +64,7 @@ void BSD_U::SendTo(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -void BSD_U::Close(Kernel::HLERequestContext& ctx) { +void BSD::Close(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -74,13 +74,15 @@ void BSD_U::Close(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); // bsd errno } -BSD_U::BSD_U() : ServiceFramework("bsd:u") { - static const FunctionInfo functions[] = {{0, &BSD_U::RegisterClient, "RegisterClient"}, - {1, &BSD_U::StartMonitoring, "StartMonitoring"}, - {2, &BSD_U::Socket, "Socket"}, - {11, &BSD_U::SendTo, "SendTo"}, - {14, &BSD_U::Connect, "Connect"}, - {26, &BSD_U::Close, "Close"}}; +BSD::BSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {0, &BSD::RegisterClient, "RegisterClient"}, + {1, &BSD::StartMonitoring, "StartMonitoring"}, + {2, &BSD::Socket, "Socket"}, + {11, &BSD::SendTo, "SendTo"}, + {14, &BSD::Connect, "Connect"}, + {26, &BSD::Close, "Close"}, + }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/sockets/bsd_u.h b/src/core/hle/service/sockets/bsd.h index 4e1252e9d..32d949e95 100644 --- a/src/core/hle/service/sockets/bsd_u.h +++ b/src/core/hle/service/sockets/bsd.h @@ -10,10 +10,10 @@ namespace Service { namespace Sockets { -class BSD_U final : public ServiceFramework<BSD_U> { +class BSD final : public ServiceFramework<BSD> { public: - BSD_U(); - ~BSD_U() = default; + explicit BSD(const char* name); + ~BSD() = default; private: void RegisterClient(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp new file mode 100644 index 000000000..e3542d325 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.cpp @@ -0,0 +1,34 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/sockets/nsd.h" + +namespace Service { +namespace Sockets { + +NSD::NSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {10, nullptr, "GetSettingName"}, + {11, nullptr, "GetEnvironmentIdentifier"}, + {12, nullptr, "GetDeviceId"}, + {13, nullptr, "DeleteSettings"}, + {14, nullptr, "ImportSettings"}, + {20, nullptr, "Resolve"}, + {21, nullptr, "ResolveEx"}, + {30, nullptr, "GetNasServiceSetting"}, + {31, nullptr, "GetNasServiceSettingEx"}, + {40, nullptr, "GetNasRequestFqdn"}, + {41, nullptr, "GetNasRequestFqdnEx"}, + {42, nullptr, "GetNasApiFqdn"}, + {43, nullptr, "GetNasApiFqdnEx"}, + {50, nullptr, "GetCurrentSetting"}, + {60, nullptr, "ReadSaveDataFromFsForTest"}, + {61, nullptr, "WriteSaveDataToFsForTest"}, + {62, nullptr, "DeleteSaveDataOfFsForTest"}, + }; + RegisterHandlers(functions); +} + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h new file mode 100644 index 000000000..a7c15a860 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.h @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace Sockets { + +class NSD final : public ServiceFramework<NSD> { +public: + explicit NSD(const char* name); + ~NSD() = default; +}; + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 4d7bc7c3e..eb4b5fa57 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -19,16 +19,18 @@ void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) { } SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { - static const FunctionInfo functions[] = {{0, nullptr, "SetDnsAddressesPrivate"}, - {1, nullptr, "GetDnsAddressPrivate"}, - {2, nullptr, "GetHostByName"}, - {3, nullptr, "GetHostByAddr"}, - {4, nullptr, "GetHostStringError"}, - {5, nullptr, "GetGaiStringError"}, - {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"}, - {7, nullptr, "GetNameInfo"}, - {8, nullptr, "RequestCancelHandle"}, - {9, nullptr, "CancelSocketCall"}}; + static const FunctionInfo functions[] = { + {0, nullptr, "SetDnsAddressesPrivate"}, + {1, nullptr, "GetDnsAddressPrivate"}, + {2, nullptr, "GetHostByName"}, + {3, nullptr, "GetHostByAddr"}, + {4, nullptr, "GetHostStringError"}, + {5, nullptr, "GetGaiStringError"}, + {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"}, + {7, nullptr, "GetNameInfo"}, + {8, nullptr, "RequestCancelHandle"}, + {9, nullptr, "CancelSocketCall"}, + }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index b726a30fd..c07cc1594 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -12,7 +12,7 @@ namespace Sockets { class SFDNSRES final : public ServiceFramework<SFDNSRES> { public: - SFDNSRES(); + explicit SFDNSRES(); ~SFDNSRES() = default; private: diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index f1396eaa1..cedc276d9 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -2,7 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/sockets/bsd_u.h" +#include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/sockets/nsd.h" #include "core/hle/service/sockets/sfdnsres.h" #include "core/hle/service/sockets/sockets.h" @@ -10,7 +11,10 @@ namespace Service { namespace Sockets { void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<BSD_U>()->InstallAsService(service_manager); + std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager); + std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager); std::make_shared<SFDNSRES>()->InstallAsService(service_manager); } diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp new file mode 100644 index 000000000..afa8d5d79 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.cpp @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ssl/ssl.h" + +namespace Service { +namespace SSL { + +SSL::SSL() : ServiceFramework("ssl") {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<SSL>()->InstallAsService(service_manager); +} + +} // namespace SSL +} // namespace Service diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h new file mode 100644 index 000000000..645dad003 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.h @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service { +namespace SSL { + +class SSL final : public ServiceFramework<SSL> { +public: + explicit SSL(); + ~SSL() = default; +}; + +/// Registers all SSL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace SSL +} // namespace Service diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index aa09ed323..8b4ee970f 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -76,7 +76,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& fil } else if (Common::ToLower(virtual_name) == "sdk") { is_sdk_found = true; } else { - // Contrinue searching + // Continue searching return true; } diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 0ba8c6fd2..e9f462196 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -414,7 +414,7 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(codeset->entrypoint, 48, Memory::STACK_SIZE); + process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 6dcd1ce48..b5133e4d6 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -137,7 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(base_addr, 48, Memory::STACK_SIZE); + process->Run(base_addr, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 100aa022e..3bc10ed0d 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -165,7 +165,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::STACK_SIZE); + process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index a9beccb95..291bf066f 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -15,6 +15,7 @@ #include "core/core.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" +#include "core/hle/lock.h" #include "core/memory.h" #include "core/memory_setup.h" #include "video_core/renderer_base.h" @@ -41,6 +42,9 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); + RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, + FlushMode::FlushAndInvalidate); + VAddr end = base + size; while (base != end) { ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base); @@ -112,91 +116,120 @@ static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) { return GetSpecialHandlers(page_table, vaddr, size); } -template <typename T> -boost::optional<T> ReadSpecial(VAddr addr); +/** + * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) + * using a VMA from the current process + */ +static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { + u8* direct_pointer = nullptr; + + auto& vm_manager = process.vm_manager; + + auto it = vm_manager.FindVMA(vaddr); + ASSERT(it != vm_manager.vma_map.end()); + + auto& vma = it->second; + switch (vma.type) { + case Kernel::VMAType::AllocatedMemoryBlock: + direct_pointer = vma.backing_block->data() + vma.offset; + break; + case Kernel::VMAType::BackingMemory: + direct_pointer = vma.backing_memory; + break; + case Kernel::VMAType::Free: + return nullptr; + default: + UNREACHABLE(); + } + + return direct_pointer + (vaddr - vma.base); +} + +/** + * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) + * using a VMA from the current process. + */ +static u8* GetPointerFromVMA(VAddr vaddr) { + return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); +} template <typename T> T Read(const VAddr vaddr) { - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) { - LOG_ERROR(HW_Memory, "Read%lu after page table @ 0x%016" PRIX64, sizeof(T) * 8, vaddr); - return 0; + const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + T value; + std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); + return value; } - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + + PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { case PageType::Unmapped: - LOG_ERROR(HW_Memory, "unmapped Read%zu @ 0x%016" PRIX64, sizeof(T) * 8, vaddr); + LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%08X", sizeof(T) * 8, vaddr); return 0; - case PageType::Special: { - if (auto result = ReadSpecial<T>(vaddr)) - return *result; - [[fallthrough]]; - } - case PageType::Memory: { - const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr); + case PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); + break; + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); T value; - std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); + std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); return value; } + default: + UNREACHABLE(); } - UNREACHABLE(); - return 0; } template <typename T> -bool WriteSpecial(VAddr addr, const T data); - -template <typename T> void Write(const VAddr vaddr, const T data) { - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) { - LOG_ERROR(HW_Memory, "Write%lu after page table 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8, - (u32)data, vaddr); + u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); return; } - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + + PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { case PageType::Unmapped: - LOG_ERROR(HW_Memory, "unmapped Write%zu 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8, - static_cast<u32>(data), vaddr); - return; - case PageType::Special: { - if (WriteSpecial<T>(vaddr, data)) - return; - [[fallthrough]]; - } - case PageType::Memory: { - u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; - ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr); - std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); + LOG_ERROR(HW_Memory, "unmapped Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, + vaddr); return; + case PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); + break; + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate); + std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); + break; } + default: + UNREACHABLE(); } - UNREACHABLE(); } bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { auto& page_table = process.vm_manager.page_table; - if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) - return false; + const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS]; + if (page_pointer) + return true; - const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; - switch (type) { - case PageType::Unmapped: - return false; - case PageType::Memory: + if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) return true; - case PageType::Special: { - for (auto handler : GetSpecialHandlers(page_table, vaddr, 1)) - if (auto result = handler->IsValidAddress(vaddr)) - return *result; - return current_page_table->pointers[vaddr >> PAGE_BITS] != nullptr; - } - } - UNREACHABLE(); + + if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) + return false; + return false; } @@ -214,7 +247,11 @@ u8* GetPointer(const VAddr vaddr) { return page_pointer + (vaddr & PAGE_MASK); } - LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%016" PRIx64, vaddr); + if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) { + return GetPointerFromVMA(vaddr); + } + + LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%08x", vaddr); return nullptr; } @@ -288,6 +325,95 @@ u8* GetPhysicalPointer(PAddr address) { return target_pointer; } +void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached) { + if (start == 0) { + return; + } + + u64 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1; + VAddr vaddr = start; + + for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { + PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + + if (cached) { + // Switch page type to cached if now cached + switch (page_type) { + case PageType::Unmapped: + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case PageType::Memory: + page_type = PageType::RasterizerCachedMemory; + current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; + break; + default: + UNREACHABLE(); + } + } else { + // Switch page type to uncached if now uncached + switch (page_type) { + case PageType::Unmapped: + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case PageType::RasterizerCachedMemory: { + u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); + if (pointer == nullptr) { + // It's possible that this function has been called while updating the pagetable + // after unmapping a VMA. In that case the underlying VMA will no longer exist, + // and we should just leave the pagetable entry blank. + page_type = PageType::Unmapped; + } else { + page_type = PageType::Memory; + current_page_table->pointers[vaddr >> PAGE_BITS] = pointer; + } + break; + } + default: + UNREACHABLE(); + } + } + } +} + +void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) { + // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be + // null here + if (VideoCore::g_renderer == nullptr) { + return; + } + + VAddr end = start + size; + + auto CheckRegion = [&](VAddr region_start, VAddr region_end) { + if (start >= region_end || end <= region_start) { + // No overlap with region + return; + } + + VAddr overlap_start = std::max(start, region_start); + VAddr overlap_end = std::min(end, region_end); + u64 overlap_size = overlap_end - overlap_start; + + auto* rasterizer = VideoCore::g_renderer->Rasterizer(); + switch (mode) { + case FlushMode::Flush: + rasterizer->FlushRegion(overlap_start, overlap_size); + break; + case FlushMode::Invalidate: + rasterizer->InvalidateRegion(overlap_start, overlap_size); + break; + case FlushMode::FlushAndInvalidate: + rasterizer->FlushAndInvalidateRegion(overlap_start, overlap_size); + break; + } + }; + + CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END); + CheckRegion(HEAP_VADDR, HEAP_VADDR_END); +} + u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -304,17 +430,6 @@ u64 Read64(const VAddr addr) { return Read<u64_le>(addr); } -static bool ReadSpecialBlock(const Kernel::Process& process, const VAddr src_addr, - void* dest_buffer, const size_t size) { - auto& page_table = process.vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, src_addr, size)) { - if (handler->ReadBlock(src_addr, dest_buffer, size)) { - return true; - } - } - return false; -} - void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer, const size_t size) { auto& page_table = process.vm_manager.page_table; @@ -324,21 +439,16 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped ReadBlock @ 0x%016" PRIX64 " (start address = 0x%" PRIx64 - ", size = %zu)", + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); break; - case PageType::Special: { - if (ReadSpecialBlock(process, current_vaddr, dest_buffer, copy_amount)) - break; - [[fallthrough]]; } case PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); @@ -347,6 +457,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ std::memcpy(dest_buffer, src_ptr, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Flush); + std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount); + break; + } default: UNREACHABLE(); } @@ -378,17 +494,6 @@ void Write64(const VAddr addr, const u64 data) { Write<u64_le>(addr, data); } -static bool WriteSpecialBlock(const Kernel::Process& process, const VAddr dest_addr, - const void* src_buffer, const size_t size) { - auto& page_table = process.vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, dest_addr, size)) { - if (handler->WriteBlock(dest_addr, src_buffer, size)) { - return true; - } - } - return false; -} - void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer, const size_t size) { auto& page_table = process.vm_manager.page_table; @@ -397,20 +502,17 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi size_t page_offset = dest_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: + case PageType::Unmapped: { LOG_ERROR(HW_Memory, - "unmapped WriteBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", + "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size); break; - case PageType::Special: - if (WriteSpecialBlock(process, current_vaddr, src_buffer, copy_amount)) - break; - [[fallthrough]]; + } case PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); @@ -418,6 +520,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi std::memcpy(dest_ptr, src_buffer, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Invalidate); + std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); + break; + } default: UNREACHABLE(); } @@ -433,9 +541,8 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); } -void ZeroBlock(const VAddr dest_addr, const size_t size) { - const auto& process = *Core::CurrentProcess(); - +void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = dest_addr >> PAGE_BITS; size_t page_offset = dest_addr & PAGE_MASK; @@ -443,27 +550,29 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { static const std::array<u8, PAGE_SIZE> zeros = {}; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped ZeroBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", + switch (page_table.attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size); break; - case PageType::Special: - if (WriteSpecialBlock(process, current_vaddr, zeros.data(), copy_amount)) - break; - [[fallthrough]]; + } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); + DEBUG_ASSERT(page_table.pointers[page_index]); - u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; + u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memset(dest_ptr, 0, copy_amount); break; } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Invalidate); + std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount); + break; + } default: UNREACHABLE(); } @@ -474,37 +583,34 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { } } -void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { - const auto& process = *Core::CurrentProcess(); - +void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = src_addr >> PAGE_BITS; size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { - case PageType::Unmapped: - LOG_ERROR(HW_Memory, - "unmapped CopyBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64 - ", size = %zu)", + switch (page_table.attributes[page_index]) { + case PageType::Unmapped: { + LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); - ZeroBlock(dest_addr, copy_amount); + ZeroBlock(process, dest_addr, copy_amount); break; - case PageType::Special: { - std::vector<u8> buffer(copy_amount); - if (ReadSpecialBlock(process, current_vaddr, buffer.data(), buffer.size())) { - WriteBlock(dest_addr, buffer.data(), buffer.size()); - break; - } - [[fallthrough]]; } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); - const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; - WriteBlock(dest_addr, src_ptr, copy_amount); + DEBUG_ASSERT(page_table.pointers[page_index]); + const u8* src_ptr = page_table.pointers[page_index] + page_offset; + WriteBlock(process, dest_addr, src_ptr, copy_amount); + break; + } + case PageType::RasterizerCachedMemory: { + RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), + FlushMode::Flush); + WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount); break; } default: @@ -519,78 +625,6 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { } } -template <> -boost::optional<u8> ReadSpecial<u8>(VAddr addr) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8))) - if (auto result = handler->Read8(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u16> ReadSpecial<u16>(VAddr addr) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16))) - if (auto result = handler->Read16(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u32> ReadSpecial<u32>(VAddr addr) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32))) - if (auto result = handler->Read32(addr)) - return *result; - return {}; -} - -template <> -boost::optional<u64> ReadSpecial<u64>(VAddr addr) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64))) - if (auto result = handler->Read64(addr)) - return *result; - return {}; -} - -template <> -bool WriteSpecial<u8>(VAddr addr, const u8 data) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8))) - if (handler->Write8(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u16>(VAddr addr, const u16 data) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16))) - if (handler->Write16(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u32>(VAddr addr, const u32 data) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32))) - if (handler->Write32(addr, data)) - return true; - return false; -} - -template <> -bool WriteSpecial<u64>(VAddr addr, const u64 data) { - const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; - for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64))) - if (handler->Write64(addr, data)) - return true; - return false; -} - boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { if (addr == 0) { return 0; diff --git a/src/core/memory.h b/src/core/memory.h index f5bf0141f..e9b8ca873 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -36,7 +36,10 @@ enum class PageType : u8 { Unmapped, /// Page is mapped to regular memory. This is the only type you can get pointers to. Memory, - /// Page is mapped to a memory hook, which intercepts read and write requests. + /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and + /// invalidation + RasterizerCachedMemory, + /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. Special, }; @@ -159,12 +162,13 @@ enum : VAddr { TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END, TLS_ENTRY_SIZE = 0x200, TLS_AREA_SIZE = 0x10000000, - TLS_ADREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, + TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, /// Application stack - STACK_VADDR = TLS_ADREA_VADDR_END, - STACK_SIZE = 0x10000, - STACK_VADDR_END = STACK_VADDR + STACK_SIZE, + STACK_AREA_VADDR = TLS_AREA_VADDR_END, + STACK_AREA_SIZE = 0x10000000, + STACK_AREA_VADDR_END = STACK_AREA_VADDR + STACK_AREA_SIZE, + DEFAULT_STACK_SIZE = 0x100000, /// Application heap /// Size is confirmed to be a static value on fw 3.0.0 @@ -242,4 +246,24 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr); */ u8* GetPhysicalPointer(PAddr address); +enum class FlushMode { + /// Write back modified surfaces to RAM + Flush, + /// Remove region from the cache + Invalidate, + /// Write back modified surfaces to RAM, and also remove them from the cache + FlushAndInvalidate, +}; + +/** + * Mark each page touching the region as cached. + */ +void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached); + +/** + * Flushes and invalidates any externally cached rasterizer resources touching the given virtual + * address region. + */ +void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode); + } // namespace Memory diff --git a/src/core/settings.h b/src/core/settings.h index 6f8cd0f03..2c94caab7 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -105,12 +105,10 @@ static const std::array<const char*, NumAnalogs> mapping = {{ }}; } // namespace NativeAnalog -enum class CpuCore { - Unicorn, - Dynarmic, -}; - struct Values { + // System + bool use_docked_mode; + // Controls std::array<std::string, NativeButton::NumButtons> buttons; std::array<std::string, NativeAnalog::NumAnalogs> analogs; @@ -118,7 +116,7 @@ struct Values { std::string touch_device; // Core - CpuCore cpu_core; + bool use_cpu_jit; // Data Storage bool use_virtual_sd; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index bea05a09b..cecf0a5cb 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -87,8 +87,8 @@ TelemetrySession::TelemetrySession() { #ifdef ENABLE_WEB_SERVICE if (Settings::values.enable_telemetry) { backend = std::make_unique<WebService::TelemetryJson>( - Settings::values.telemetry_endpoint_url, Settings::values.citra_username, - Settings::values.citra_token); + Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username, + Settings::values.yuzu_token); } else { backend = std::make_unique<Telemetry::NullVisitor>(); } @@ -154,12 +154,13 @@ TelemetrySession::TelemetrySession() { #endif // Log user configuration information - AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore", - static_cast<int>(Settings::values.cpu_core)); + AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", Settings::values.resolution_factor); AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit", Settings::values.toggle_framelimit); + AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode", + Settings::values.use_docked_mode); } TelemetrySession::~TelemetrySession() { diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index 550c6ea2d..dbc4f8bd4 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -50,8 +50,8 @@ u64 RegenerateTelemetryId(); /** * Verifies the username and token. - * @param username Citra username to use for authentication. - * @param token Citra token to use for authentication. + * @param username yuzu username to use for authentication. + * @param token yuzu token to use for authentication. * @param func A function that gets exectued when the verification is finished * @returns Future with bool indicating whether the verification succeeded */ diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e56253c4c..a710c4bc5 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(video_core STATIC command_processor.cpp command_processor.h + debug_utils/debug_utils.cpp + debug_utils/debug_utils.h engines/fermi_2d.cpp engines/fermi_2d.h engines/maxwell_3d.cpp @@ -9,6 +11,8 @@ add_library(video_core STATIC engines/maxwell_compute.h gpu.cpp gpu.h + macro_interpreter.cpp + macro_interpreter.h memory_manager.cpp memory_manager.h rasterizer_interface.h @@ -29,8 +33,12 @@ add_library(video_core STATIC renderer_opengl/gl_state.h renderer_opengl/gl_stream_buffer.cpp renderer_opengl/gl_stream_buffer.h + renderer_opengl/maxwell_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h + textures/decoders.cpp + textures/decoders.h + textures/texture.h utils.h video_core.cpp video_core.h diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp new file mode 100644 index 000000000..22d44aab2 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -0,0 +1,64 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <algorithm> +#include <condition_variable> +#include <cstdint> +#include <cstring> +#include <fstream> +#include <map> +#include <mutex> +#include <string> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/debug_utils/debug_utils.h" + +namespace Tegra { + +void DebugContext::DoOnEvent(Event event, void* data) { + { + std::unique_lock<std::mutex> lock(breakpoint_mutex); + + // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will + // show on debug widgets + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::lock_guard<std::mutex> lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +} // namespace Tegra diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h new file mode 100644 index 000000000..bbba8e380 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h @@ -0,0 +1,163 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> +#include <condition_variable> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Tegra { + +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + MaxwellCommandLoaded = FirstEvent, + MaxwellCommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state + * access + * @todo Evaluate an alternative interface, in which there is only one managing observer and + * multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr<DebugContext> debug_context) + : context_weak(debug_context) { + std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock<std::mutex> lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper yuzu + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellBreakPointHit(Event event, void* data) {} + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellResume() {} + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr<DebugContext> context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr<DebugContext> Construct() { + return std::shared_ptr<DebugContext>(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until + * Resume() is called. + */ + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. + * Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + for (auto& bp : breakpoints) { + bp.enabled = false; + } + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::array<BreakPoint, (int)Event::NumEvents> breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list<BreakPointObserver*> breakpoint_observers; +}; + +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 4d9745e48..124753032 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -2,8 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "common/assert.h" +#include "core/core.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/video_core.h" namespace Tegra { namespace Engines { @@ -11,41 +19,29 @@ namespace Engines { /// First register id that is actually a Macro call. constexpr u32 MacroRegistersStart = 0xE00; -const std::unordered_map<u32, Maxwell3D::MethodInfo> Maxwell3D::method_handlers = { - {0xE1A, {"BindTextureInfoBuffer", 1, &Maxwell3D::BindTextureInfoBuffer}}, - {0xE24, {"SetShader", 5, &Maxwell3D::SetShader}}, - {0xE2A, {"BindStorageBuffer", 1, &Maxwell3D::BindStorageBuffer}}, -}; - -Maxwell3D::Maxwell3D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +Maxwell3D::Maxwell3D(MemoryManager& memory_manager) + : memory_manager(memory_manager), macro_interpreter(*this) {} void Maxwell3D::SubmitMacroCode(u32 entry, std::vector<u32> code) { uploaded_macros[entry * 2 + MacroRegistersStart] = std::move(code); } -void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) { - // TODO(Subv): Write an interpreter for the macros uploaded via registers 0x45 and 0x47 - +void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { + auto macro_code = uploaded_macros.find(method); // The requested macro must have been uploaded already. - ASSERT_MSG(uploaded_macros.find(method) != uploaded_macros.end(), "Macro %08X was not uploaded", - method); - - auto itr = method_handlers.find(method); - ASSERT_MSG(itr != method_handlers.end(), "Unhandled method call %08X", method); - - ASSERT(itr->second.arguments == parameters.size()); + ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method); - (this->*itr->second.handler)(parameters); - - // Reset the current macro and its parameters. + // Reset the current macro and execute it. executing_macro = 0; - macro_params.clear(); + macro_interpreter.Execute(macro_code->second, std::move(parameters)); } void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + // It is an error to write to a register other than the current macro's ARG register before it // has finished execution. if (executing_macro != 0) { @@ -67,11 +63,15 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { // Call the macro when there are no more parameters in the command buffer if (remaining_params == 0) { - CallMacroMethod(executing_macro, macro_params); + CallMacroMethod(executing_macro, std::move(macro_params)); } return; } + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); + } + regs.reg_array[method] = value; #define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) @@ -137,6 +137,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { } #undef MAXWELL3D_REG_INDEX + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); + } } void Maxwell3D::ProcessQueryGet() { @@ -159,85 +163,20 @@ void Maxwell3D::ProcessQueryGet() { } void Maxwell3D::DrawArrays() { - LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); -} + LOG_DEBUG(HW_GPU, "called, topology=%d, count=%d", regs.draw.topology.Value(), + regs.vertex_buffer.count); -void Maxwell3D::BindTextureInfoBuffer(const std::vector<u32>& parameters) { - /** - * Parameters description: - * [0] = Shader stage, usually 4 for FragmentShader - */ + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); - u32 stage = parameters[0]; - - // Perform the same operations as the real macro code. - GPUVAddr address = static_cast<GPUVAddr>(regs.tex_info_buffers.address[stage]) << 8; - u32 size = regs.tex_info_buffers.size[stage]; - - regs.const_buffer.cb_size = size; - regs.const_buffer.cb_address_high = address >> 32; - regs.const_buffer.cb_address_low = address & 0xFFFFFFFF; -} - -void Maxwell3D::SetShader(const std::vector<u32>& parameters) { - /** - * Parameters description: - * [0] = Shader Program. - * [1] = Unknown, presumably the shader id. - * [2] = Offset to the start of the shader, after the 0x30 bytes header. - * [3] = Shader Stage. - * [4] = Const Buffer Address >> 8. - */ - auto shader_program = static_cast<Regs::ShaderProgram>(parameters[0]); - // TODO(Subv): This address is probably an offset from the CODE_ADDRESS register. - GPUVAddr address = parameters[2]; - auto shader_stage = static_cast<Regs::ShaderStage>(parameters[3]); - GPUVAddr cb_address = parameters[4] << 8; - - auto& shader = state.shader_programs[static_cast<size_t>(shader_program)]; - shader.program = shader_program; - shader.stage = shader_stage; - shader.address = address; - - // Perform the same operations as the real macro code. - // TODO(Subv): Early exit if register 0xD1C + shader_program contains the same as params[1]. - auto& shader_regs = regs.shader_config[static_cast<size_t>(shader_program)]; - shader_regs.start_id = address; - // TODO(Subv): Write params[1] to register 0xD1C + shader_program. - // TODO(Subv): Write params[2] to register 0xD22 + shader_program. - - // Note: This value is hardcoded in the macro's code. - static constexpr u32 DefaultCBSize = 0x10000; - regs.const_buffer.cb_size = DefaultCBSize; - regs.const_buffer.cb_address_high = cb_address >> 32; - regs.const_buffer.cb_address_low = cb_address & 0xFFFFFFFF; - - // Write a hardcoded 0x11 to CB_BIND, this binds the current const buffer to buffer c1[] in the - // shader. It's likely that these are the constants for the shader. - regs.cb_bind[static_cast<size_t>(shader_stage)].valid.Assign(1); - regs.cb_bind[static_cast<size_t>(shader_stage)].index.Assign(1); - - ProcessCBBind(shader_stage); -} - -void Maxwell3D::BindStorageBuffer(const std::vector<u32>& parameters) { - /** - * Parameters description: - * [0] = Buffer offset >> 2 - */ - - u32 buffer_offset = parameters[0] << 2; - - // Perform the same operations as the real macro code. - // Note: This value is hardcoded in the macro's code. - static constexpr u32 DefaultCBSize = 0x5F00; - regs.const_buffer.cb_size = DefaultCBSize; + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); + } - GPUVAddr address = regs.ssbo_info.BufferAddress(); - regs.const_buffer.cb_address_high = address >> 32; - regs.const_buffer.cb_address_low = address & 0xFFFFFFFF; + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } - regs.const_buffer.cb_pos = buffer_offset; + VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(false /*is_indexed*/); } void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { @@ -270,5 +209,95 @@ void Maxwell3D::ProcessCBData(u32 value) { regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; } +Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry); + VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); + + Texture::TICEntry tic_entry; + Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + + ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear, + "TIC versions other than BlockLinear are unimplemented"); + + ASSERT_MSG(tic_entry.texture_type == Texture::TextureType::Texture2D, + "Texture types other than Texture2D are unimplemented"); + + auto r_type = tic_entry.r_type.Value(); + auto g_type = tic_entry.g_type.Value(); + auto b_type = tic_entry.b_type.Value(); + auto a_type = tic_entry.a_type.Value(); + + // TODO(Subv): Different data types for separate components are not supported + ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + return tic_entry; +} + +Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { + GPUVAddr tsc_base_address = regs.tsc.TSCAddress(); + + GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry); + VAddr tsc_address_cpu = memory_manager.PhysicalToVirtualAddress(tsc_address_gpu); + + Texture::TSCEntry tsc_entry; + Memory::ReadBlock(tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; +} + +std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const { + std::vector<Texture::FullTextureInfo> textures; + + auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; + + // Offset into the texture constbuffer where the texture info begins. + static constexpr size_t TextureInfoOffset = 0x20; + + for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; + current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) { + + Texture::TextureHandle tex_handle{ + Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; + + Texture::FullTextureInfo tex_info{}; + // TODO(Subv): Use the shader to determine which textures are actually accessed. + tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) / + sizeof(Texture::TextureHandle); + + // Load the TIC data. + if (tex_handle.tic_id != 0) { + tex_info.enabled = true; + + auto tic_entry = GetTICEntry(tex_handle.tic_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry)); + } + + // Load the TSC data + if (tex_handle.tsc_id != 0) { + auto tsc_entry = GetTSCEntry(tex_handle.tsc_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry)); + } + + if (tex_info.enabled) + textures.push_back(tex_info); + } + + return textures; +} + +u32 Maxwell3D::GetRegisterValue(u32 method) const { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register"); + return regs.reg_array[method]; +} + } // namespace Engines } // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index aab282b77..98b39b2ff 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -7,10 +7,15 @@ #include <array> #include <unordered_map> #include <vector> +#include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/macro_interpreter.h" #include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" namespace Tegra { namespace Engines { @@ -20,18 +25,13 @@ public: explicit Maxwell3D(MemoryManager& memory_manager); ~Maxwell3D() = default; - /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value, u32 remaining_params); - - /// Uploads the code for a GPU macro program associated with the specified entry. - void SubmitMacroCode(u32 entry, std::vector<u32> code); - /// Register structure of the Maxwell3D engine. /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. struct Regs { static constexpr size_t NUM_REGS = 0xE36; static constexpr size_t NumRenderTargets = 8; + static constexpr size_t NumViewports = 16; static constexpr size_t NumCBData = 16; static constexpr size_t NumVertexArrays = 32; static constexpr size_t NumVertexAttributes = 32; @@ -62,6 +62,192 @@ public: Fragment = 4, }; + struct VertexAttribute { + enum class Size : u32 { + Size_32_32_32_32 = 0x01, + Size_32_32_32 = 0x02, + Size_16_16_16_16 = 0x03, + Size_32_32 = 0x04, + Size_16_16_16 = 0x05, + Size_8_8_8_8 = 0x0a, + Size_16_16 = 0x0f, + Size_32 = 0x12, + Size_8_8_8 = 0x13, + Size_8_8 = 0x18, + Size_16 = 0x1b, + Size_8 = 0x1d, + Size_10_10_10_2 = 0x30, + Size_11_11_10 = 0x31, + }; + + enum class Type : u32 { + SignedNorm = 1, + UnsignedNorm = 2, + SignedInt = 3, + UnsignedInt = 4, + UnsignedScaled = 5, + SignedScaled = 6, + Float = 7, + }; + + union { + BitField<0, 5, u32> buffer; + BitField<6, 1, u32> constant; + BitField<7, 14, u32> offset; + BitField<21, 6, Size> size; + BitField<27, 3, Type> type; + BitField<31, 1, u32> bgra; + }; + + u32 ComponentCount() const { + switch (size) { + case Size::Size_32_32_32_32: + return 4; + case Size::Size_32_32_32: + return 3; + case Size::Size_16_16_16_16: + return 4; + case Size::Size_32_32: + return 2; + case Size::Size_16_16_16: + return 3; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 2; + case Size::Size_32: + return 1; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 1; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 3; + default: + UNREACHABLE(); + } + } + + u32 SizeInBytes() const { + switch (size) { + case Size::Size_32_32_32_32: + return 16; + case Size::Size_32_32_32: + return 12; + case Size::Size_16_16_16_16: + return 8; + case Size::Size_32_32: + return 8; + case Size::Size_16_16_16: + return 6; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 4; + case Size::Size_32: + return 4; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 2; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 4; + default: + UNREACHABLE(); + } + } + + std::string SizeString() const { + switch (size) { + case Size::Size_32_32_32_32: + return "32_32_32_32"; + case Size::Size_32_32_32: + return "32_32_32"; + case Size::Size_16_16_16_16: + return "16_16_16_16"; + case Size::Size_32_32: + return "32_32"; + case Size::Size_16_16_16: + return "16_16_16"; + case Size::Size_8_8_8_8: + return "8_8_8_8"; + case Size::Size_16_16: + return "16_16"; + case Size::Size_32: + return "32"; + case Size::Size_8_8_8: + return "8_8_8"; + case Size::Size_8_8: + return "8_8"; + case Size::Size_16: + return "16"; + case Size::Size_8: + return "8"; + case Size::Size_10_10_10_2: + return "10_10_10_2"; + case Size::Size_11_11_10: + return "11_11_10"; + } + UNREACHABLE(); + return {}; + } + + std::string TypeString() const { + switch (type) { + case Type::SignedNorm: + return "SNORM"; + case Type::UnsignedNorm: + return "UNORM"; + case Type::SignedInt: + return "SINT"; + case Type::UnsignedInt: + return "UINT"; + case Type::UnsignedScaled: + return "USCALED"; + case Type::SignedScaled: + return "SSCALED"; + case Type::Float: + return "FLOAT"; + } + UNREACHABLE(); + return {}; + } + + bool IsNormalized() const { + return (type == Type::SignedNorm) || (type == Type::UnsignedNorm); + } + }; + + enum class PrimitiveTopology : u32 { + Points = 0x0, + Lines = 0x1, + LineLoop = 0x2, + LineStrip = 0x3, + Triangles = 0x4, + TriangleStrip = 0x5, + TriangleFan = 0x6, + Quads = 0x7, + QuadStrip = 0x8, + Polygon = 0x9, + LinesAdjacency = 0xa, + LineStripAdjacency = 0xb, + TrianglesAdjacency = 0xc, + TriangleStripAdjacency = 0xd, + Patches = 0xe, + }; + union { struct { INSERT_PADDING_WORDS(0x200); @@ -69,9 +255,9 @@ public: struct { u32 address_high; u32 address_low; - u32 horiz; - u32 vert; - u32 format; + u32 width; + u32 height; + Tegra::RenderTargetFormat format; u32 block_dimensions; u32 array_mode; u32 layer_stride; @@ -84,7 +270,31 @@ public: } } rt[NumRenderTargets]; - INSERT_PADDING_WORDS(0xDD); + INSERT_PADDING_WORDS(0x80); + + struct { + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> width; + }; + union { + BitField<0, 16, u32> y; + BitField<16, 16, u32> height; + }; + float depth_range_near; + float depth_range_far; + + MathUtil::Rectangle<s32> GetRect() const { + return { + static_cast<s32>(x), // left + static_cast<s32>(y + height), // top + static_cast<s32>(x + width), // right + static_cast<s32>(y) // bottom + }; + }; + } viewport[NumViewports]; + + INSERT_PADDING_WORDS(0x1D); struct { u32 first; @@ -108,14 +318,7 @@ public: INSERT_PADDING_WORDS(0x5B); - union { - BitField<0, 5, u32> buffer; - BitField<6, 1, u32> constant; - BitField<7, 14, u32> offset; - BitField<21, 6, u32> size; - BitField<27, 3, u32> type; - BitField<31, 1, u32> bgra; - } vertex_attrib_format[NumVertexAttributes]; + VertexAttribute vertex_attrib_format[NumVertexAttributes]; INSERT_PADDING_WORDS(0xF); @@ -163,13 +366,15 @@ public: } } code_address; INSERT_PADDING_WORDS(1); + struct { u32 vertex_end_gl; union { u32 vertex_begin_gl; - BitField<0, 16, u32> topology; + BitField<0, 16, PrimitiveTopology> topology; }; } draw; + INSERT_PADDING_WORDS(0x139); struct { u32 query_address_high; @@ -294,22 +499,27 @@ public: bool enabled; }; - struct ShaderProgramInfo { - Regs::ShaderStage stage; - Regs::ShaderProgram program; - GPUVAddr address; - }; - struct ShaderStageInfo { std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers; }; std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages; - std::array<ShaderProgramInfo, Regs::MaxShaderProgram> shader_programs; }; State state{}; + /// Reads a register value located at the input method address + u32 GetRegisterValue(u32 method) const; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value, u32 remaining_params); + + /// Uploads the code for a GPU macro program associated with the specified entry. + void SubmitMacroCode(u32 entry, std::vector<u32> code); + + /// Returns a list of enabled textures for the specified shader stage. + std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const; + private: MemoryManager& memory_manager; @@ -320,12 +530,21 @@ private: /// Parameters that have been submitted to the macro call so far. std::vector<u32> macro_params; + /// Interpreter for the macro codes uploaded to the GPU. + MacroInterpreter macro_interpreter; + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; + /** * Call a macro on this engine. * @param method Method to call * @param parameters Arguments to the method call */ - void CallMacroMethod(u32 method, const std::vector<u32>& parameters); + void CallMacroMethod(u32 method, std::vector<u32> parameters); /// Handles a write to the QUERY_GET register. void ProcessQueryGet(); @@ -338,19 +557,6 @@ private: /// Handles a write to the VERTEX_END_GL register, triggering a draw. void DrawArrays(); - - /// Method call handlers - void BindTextureInfoBuffer(const std::vector<u32>& parameters); - void SetShader(const std::vector<u32>& parameters); - void BindStorageBuffer(const std::vector<u32>& parameters); - - struct MethodInfo { - const char* name; - u32 arguments; - void (Maxwell3D::*handler)(const std::vector<u32>& parameters); - }; - - static const std::unordered_map<u32, MethodInfo> method_handlers; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -358,6 +564,7 @@ private: "Field " #field_name " has invalid position") ASSERT_REG_POSITION(rt, 0x200); +ASSERT_REG_POSITION(viewport, 0x300); ASSERT_REG_POSITION(vertex_buffer, 0x35D); ASSERT_REG_POSITION(zeta, 0x3F8); ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458); diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index c384d236e..9463cd5d6 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -18,4 +18,8 @@ GPU::GPU() { GPU::~GPU() = default; +const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const { + return *maxwell_3d; +} + } // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 2a9064ba3..71a8661b4 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -8,10 +8,49 @@ #include <unordered_map> #include <vector> #include "common/common_types.h" +#include "core/hle/service/nvflinger/buffer_queue.h" #include "video_core/memory_manager.h" namespace Tegra { +enum class RenderTargetFormat : u32 { + NONE = 0x0, + RGBA8_UNORM = 0xD5, +}; + +class DebugContext; + +/** + * Struct describing framebuffer configuration + */ +struct FramebufferConfig { + enum class PixelFormat : u32 { + ABGR8 = 1, + }; + + /** + * Returns the number of bytes per pixel. + */ + static u32 BytesPerPixel(PixelFormat format) { + switch (format) { + case PixelFormat::ABGR8: + return 4; + } + + UNREACHABLE(); + } + + VAddr address; + u32 offset; + u32 width; + u32 height; + u32 stride; + PixelFormat pixel_format; + + using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags; + TransformFlags transform_flags; +}; + namespace Engines { class Fermi2D; class Maxwell3D; @@ -34,8 +73,15 @@ public: /// Processes a command list stored at the specified address in GPU memory. void ProcessCommandList(GPUVAddr address, u32 size); + /// Returns a reference to the Maxwell3D GPU engine. + const Engines::Maxwell3D& Get3DEngine() const; + std::unique_ptr<MemoryManager> memory_manager; + Engines::Maxwell3D& Maxwell3D() { + return *maxwell_3d; + } + private: static constexpr u32 InvalidGraphMacroEntry = 0xFFFFFFFF; diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp new file mode 100644 index 000000000..993a67746 --- /dev/null +++ b/src/video_core/macro_interpreter.cpp @@ -0,0 +1,257 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/macro_interpreter.h" + +namespace Tegra { + +MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} + +void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) { + Reset(); + registers[1] = parameters[0]; + this->parameters = std::move(parameters); + + // Execute the code until we hit an exit condition. + bool keep_executing = true; + while (keep_executing) { + keep_executing = Step(code, false); + } + + // Assert the the macro used all the input parameters + ASSERT(next_parameter_index == this->parameters.size()); +} + +void MacroInterpreter::Reset() { + registers = {}; + pc = 0; + delayed_pc = boost::none; + method_address.raw = 0; + parameters.clear(); + // The next parameter index starts at 1, because $r1 already has the value of the first + // parameter. + next_parameter_index = 1; +} + +bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) { + u32 base_address = pc; + + Opcode opcode = GetOpcode(code); + pc += 4; + + // Update the program counter if we were delayed + if (delayed_pc != boost::none) { + ASSERT(is_delay_slot); + pc = *delayed_pc; + delayed_pc = boost::none; + } + + switch (opcode.operation) { + case Operation::ALU: { + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), + GetRegister(opcode.src_b)); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::AddImmediate: { + ProcessResult(opcode.result_operation, opcode.dst, + GetRegister(opcode.src_a) + opcode.immediate); + break; + } + case Operation::ExtractInsert: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); + dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); + dst |= src << opcode.bf_dst_bit; + ProcessResult(opcode.result_operation, opcode.dst, dst); + break; + } + case Operation::ExtractShiftLeftImmediate: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::ExtractShiftLeftRegister: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Read: { + u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Branch: { + ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); + u32 value = GetRegister(opcode.src_a); + bool taken = EvaluateBranchCondition(opcode.branch_condition, value); + if (taken) { + // Ignore the delay slot if the branch has the annul bit. + if (opcode.branch_annul) { + pc = base_address + (opcode.immediate << 2); + return true; + } + + delayed_pc = base_address + (opcode.immediate << 2); + // Execute one more instruction due to the delay slot. + return Step(code, true); + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented macro operation %u", + static_cast<u32>(opcode.operation.Value())); + } + + if (opcode.is_exit) { + // Exit has a delay slot, execute the next instruction + // Note: Executing an exit during a branch delay slot will cause the instruction at the + // branch target to be executed before exiting. + Step(code, true); + return false; + } + + return true; +} + +MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const { + ASSERT((pc % sizeof(u32)) == 0); + ASSERT(pc < code.size() * sizeof(u32)); + return {code[pc / sizeof(u32)]}; +} + +u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const { + switch (operation) { + case ALUOperation::Add: + return src_a + src_b; + // TODO(Subv): Implement AddWithCarry + case ALUOperation::Subtract: + return src_a - src_b; + // TODO(Subv): Implement SubtractWithBorrow + case ALUOperation::Xor: + return src_a ^ src_b; + case ALUOperation::Or: + return src_a | src_b; + case ALUOperation::And: + return src_a & src_b; + case ALUOperation::AndNot: + return src_a & ~src_b; + case ALUOperation::Nand: + return ~(src_a & src_b); + + default: + UNIMPLEMENTED_MSG("Unimplemented ALU operation %u", static_cast<u32>(operation)); + } +} + +void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) { + switch (operation) { + case ResultOperation::IgnoreAndFetch: + // Fetch parameter and ignore result. + SetRegister(reg, FetchParameter()); + break; + case ResultOperation::Move: + // Move result. + SetRegister(reg, result); + break; + case ResultOperation::MoveAndSetMethod: + // Move result and use as Method Address. + SetRegister(reg, result); + SetMethodAddress(result); + break; + case ResultOperation::FetchAndSend: + // Fetch parameter and send result. + SetRegister(reg, FetchParameter()); + Send(result); + break; + case ResultOperation::MoveAndSend: + // Move and send result. + SetRegister(reg, result); + Send(result); + break; + case ResultOperation::FetchAndSetMethod: + // Fetch parameter and use result as Method Address. + SetRegister(reg, FetchParameter()); + SetMethodAddress(result); + break; + case ResultOperation::MoveAndSetMethodFetchAndSend: + // Move result and use as Method Address, then fetch and send parameter. + SetRegister(reg, result); + SetMethodAddress(result); + Send(FetchParameter()); + break; + case ResultOperation::MoveAndSetMethodSend: + // Move result and use as Method Address, then send bits 12:17 of result. + SetRegister(reg, result); + SetMethodAddress(result); + Send((result >> 12) & 0b111111); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented result operation %u", static_cast<u32>(operation)); + } +} + +u32 MacroInterpreter::FetchParameter() { + ASSERT(next_parameter_index < parameters.size()); + return parameters[next_parameter_index++]; +} + +u32 MacroInterpreter::GetRegister(u32 register_id) const { + // Register 0 is supposed to always return 0. + if (register_id == 0) + return 0; + + ASSERT(register_id < registers.size()); + return registers[register_id]; +} + +void MacroInterpreter::SetRegister(u32 register_id, u32 value) { + // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero + // register. + if (register_id == 0) + return; + + ASSERT(register_id < registers.size()); + registers[register_id] = value; +} + +void MacroInterpreter::SetMethodAddress(u32 address) { + method_address.raw = address; +} + +void MacroInterpreter::Send(u32 value) { + maxwell3d.WriteReg(method_address.address, value, 0); + // Increment the method address by the method increment. + method_address.address.Assign(method_address.address.Value() + + method_address.increment.Value()); +} + +u32 MacroInterpreter::Read(u32 method) const { + return maxwell3d.GetRegisterValue(method); +} + +bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const { + switch (cond) { + case BranchCondition::Zero: + return value == 0; + case BranchCondition::NotZero: + return value != 0; + } + UNREACHABLE(); +} + +} // namespace Tegra diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h new file mode 100644 index 000000000..a71e359d8 --- /dev/null +++ b/src/video_core/macro_interpreter.h @@ -0,0 +1,164 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include <boost/optional.hpp> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Tegra { +namespace Engines { +class Maxwell3D; +} + +class MacroInterpreter final { +public: + explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d); + + /** + * Executes the macro code with the specified input parameters. + * @param code The macro byte code to execute + * @param parameters The parameters of the macro + */ + void Execute(const std::vector<u32>& code, std::vector<u32> parameters); + +private: + enum class Operation : u32 { + ALU = 0, + AddImmediate = 1, + ExtractInsert = 2, + ExtractShiftLeftImmediate = 3, + ExtractShiftLeftRegister = 4, + Read = 5, + Unused = 6, // This operation doesn't seem to be a valid encoding. + Branch = 7, + }; + + enum class ALUOperation : u32 { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + // Operations 4-7 don't seem to be valid encodings. + Xor = 8, + Or = 9, + And = 10, + AndNot = 11, + Nand = 12 + }; + + enum class ResultOperation : u32 { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMethod = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMethod = 5, + MoveAndSetMethodFetchAndSend = 6, + MoveAndSetMethodSend = 7 + }; + + enum class BranchCondition : u32 { + Zero = 0, + NotZero = 1, + }; + + union Opcode { + u32 raw; + BitField<0, 3, Operation> operation; + BitField<4, 3, ResultOperation> result_operation; + BitField<4, 1, BranchCondition> branch_condition; + BitField<5, 1, u32> + branch_annul; // If set on a branch, then the branch doesn't have a delay slot. + BitField<7, 1, u32> is_exit; + BitField<8, 3, u32> dst; + BitField<11, 3, u32> src_a; + BitField<14, 3, u32> src_b; + // The signed immediate overlaps the second source operand and the alu operation. + BitField<14, 18, s32> immediate; + + BitField<17, 5, ALUOperation> alu_operation; + + // Bitfield instructions data + BitField<17, 5, u32> bf_src_bit; + BitField<22, 5, u32> bf_size; + BitField<27, 5, u32> bf_dst_bit; + + u32 GetBitfieldMask() const { + return (1 << bf_size) - 1; + } + }; + + union MethodAddress { + u32 raw; + BitField<0, 12, u32> address; + BitField<12, 6, u32> increment; + }; + + /// Resets the execution engine state, zeroing registers, etc. + void Reset(); + + /** + * Executes a single macro instruction located at the current program counter. Returns whether + * the interpreter should keep running. + * @param code The macro code to execute. + * @param is_delay_slot Whether the current step is being executed due to a delay slot in a + * previous instruction. + */ + bool Step(const std::vector<u32>& code, bool is_delay_slot); + + /// Calculates the result of an ALU operation. src_a OP src_b; + u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const; + + /// Performs the result operation on the input result and stores it in the specified register + /// (if necessary). + void ProcessResult(ResultOperation operation, u32 reg, u32 result); + + /// Evaluates the branch condition and returns whether the branch should be taken or not. + bool EvaluateBranchCondition(BranchCondition cond, u32 value) const; + + /// Reads an opcode at the current program counter location. + Opcode GetOpcode(const std::vector<u32>& code) const; + + /// Returns the specified register's value. Register 0 is hardcoded to always return 0. + u32 GetRegister(u32 register_id) const; + + /// Sets the register to the input value. + void SetRegister(u32 register_id, u32 value); + + /// Sets the method address to use for the next Send instruction. + void SetMethodAddress(u32 address); + + /// Calls a GPU Engine method with the input parameter. + void Send(u32 value); + + /// Reads a GPU register located at the method address. + u32 Read(u32 method) const; + + /// Returns the next parameter in the parameter queue. + u32 FetchParameter(); + + Engines::Maxwell3D& maxwell3d; + + u32 pc; ///< Current program counter + boost::optional<u32> + delayed_pc; ///< Program counter to execute at after the delay slot is executed. + + static constexpr size_t NumMacroRegisters = 8; + + /// General purpose macro registers. + std::array<u32, NumMacroRegisters> registers = {}; + + /// Method address to use for the next Send instruction. + MethodAddress method_address = {}; + + /// Input parameters of the current macro. + std::vector<u32> parameters; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; +}; +} // namespace Tegra diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 6c7bd0826..8239f9aad 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "video_core/gpu.h" struct ScreenInfo; @@ -14,8 +15,8 @@ class RasterizerInterface { public: virtual ~RasterizerInterface() {} - /// Draw the current batch of triangles - virtual void DrawTriangles() = 0; + /// Draw the current batch of vertex arrays + virtual void DrawArrays() = 0; /// Notify rasterizer that the specified Maxwell register has been changed virtual void NotifyMaxwellRegisterChanged(u32 id) = 0; @@ -24,14 +25,14 @@ public: virtual void FlushAll() = 0; /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory - virtual void FlushRegion(PAddr addr, u32 size) = 0; + virtual void FlushRegion(VAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be invalidated - virtual void InvalidateRegion(PAddr addr, u32 size) = 0; + virtual void InvalidateRegion(VAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory /// and invalidated - virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0; + virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0 virtual bool AccelerateDisplayTransfer(const void* config) { @@ -49,7 +50,8 @@ public: } /// Attempt to use a faster method to display the framebuffer to screen - virtual bool AccelerateDisplay(const void* config, PAddr framebuffer_addr, u32 pixel_stride, + virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { return false; } diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 51e1d45f9..30075b23c 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -5,6 +5,11 @@ #include <atomic> #include <memory> #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/video_core.h" -void RendererBase::RefreshRasterizerSetting() {} +void RendererBase::RefreshRasterizerSetting() { + if (rasterizer == nullptr) { + rasterizer = std::make_unique<RasterizerOpenGL>(); + } +} diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 2aba50eda..89a960eaf 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -8,6 +8,8 @@ #include <boost/optional.hpp> #include "common/assert.h" #include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" class EmuWindow; @@ -16,40 +18,10 @@ public: /// Used to reference a framebuffer enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; - /** - * Struct describing framebuffer metadata - * TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet. - */ - struct FramebufferInfo { - enum class PixelFormat : u32 { - ABGR8 = 1, - }; - - /** - * Returns the number of bytes per pixel. - */ - static u32 BytesPerPixel(PixelFormat format) { - switch (format) { - case PixelFormat::ABGR8: - return 4; - } - - UNREACHABLE(); - } - - VAddr address; - u32 offset; - u32 width; - u32 height; - u32 stride; - PixelFormat pixel_format; - bool flip_vertical; - }; - virtual ~RendererBase() {} /// Swap buffers (render frame) - virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0; + virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0; /** * Set the emulator window to use for renderer @@ -74,12 +46,16 @@ public: return m_current_frame; } + VideoCore::RasterizerInterface* Rasterizer() const { + return rasterizer.get(); + } + void RefreshRasterizerSetting(); protected: + std::unique_ptr<VideoCore::RasterizerInterface> rasterizer; f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer int m_current_frame = 0; ///< Current frame, should be set by the renderer private: - bool opengl_rasterizer_active = false; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 24cfff229..911890f16 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -14,11 +14,16 @@ #include "common/microprofile.h" #include "common/scope_exit.h" #include "common/vector_math.h" +#include "core/core.h" +#include "core/hle/kernel/process.h" #include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/maxwell_to_gl.h" #include "video_core/renderer_opengl/renderer_opengl.h" +using Maxwell = Tegra::Engines::Maxwell3D::Regs; using PixelFormat = SurfaceParams::PixelFormat; using SurfaceType = SurfaceParams::SurfaceType; @@ -54,6 +59,8 @@ static void SetShaderUniformBlockBindings(GLuint shader) { } RasterizerOpenGL::RasterizerOpenGL() { + shader_dirty = true; + has_ARB_buffer_storage = false; has_ARB_direct_state_access = false; has_ARB_separate_shader_objects = false; @@ -106,8 +113,6 @@ RasterizerOpenGL::RasterizerOpenGL() { state.draw.vertex_buffer = stream_buffer->GetHandle(); pipeline.Create(); - vs_input_index_min = 0; - vs_input_index_max = 0; state.draw.program_pipeline = pipeline.handle; state.draw.shader_program = 0; state.draw.vertex_array = hw_vao.handle; @@ -120,20 +125,14 @@ RasterizerOpenGL::RasterizerOpenGL() { glBufferData(GL_UNIFORM_BUFFER, sizeof(VSUniformData), nullptr, GL_STREAM_COPY); glBindBufferBase(GL_UNIFORM_BUFFER, 1, vs_uniform_buffer.handle); } else { - UNIMPLEMENTED(); + UNREACHABLE(); } accelerate_draw = AccelDraw::Disabled; glEnable(GL_BLEND); - // Sync fixed function OpenGL state - SyncClipEnabled(); - SyncClipCoef(); - SyncCullMode(); - SyncBlendEnabled(); - SyncBlendFuncs(); - SyncBlendColor(); + LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!"); } RasterizerOpenGL::~RasterizerOpenGL() { @@ -144,47 +143,235 @@ RasterizerOpenGL::~RasterizerOpenGL() { } } -static constexpr std::array<GLenum, 4> vs_attrib_types{ - GL_BYTE, // VertexAttributeFormat::BYTE - GL_UNSIGNED_BYTE, // VertexAttributeFormat::UBYTE - GL_SHORT, // VertexAttributeFormat::SHORT - GL_FLOAT // VertexAttributeFormat::FLOAT -}; - void RasterizerOpenGL::AnalyzeVertexArray(bool is_indexed) { - UNIMPLEMENTED(); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + if (is_indexed) { + UNREACHABLE(); + } + + // TODO(bunnei): Add support for 1+ vertex arrays + vs_input_size = regs.vertex_buffer.count * regs.vertex_array[0].stride; } void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset) { MICROPROFILE_SCOPE(OpenGL_VAO); - UNIMPLEMENTED(); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager; + + state.draw.vertex_array = hw_vao.handle; + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + // TODO(bunnei): Add support for 1+ vertex arrays + const auto& vertex_array{regs.vertex_array[0]}; + ASSERT_MSG(vertex_array.enable, "vertex array 0 is disabled?"); + ASSERT_MSG(!vertex_array.divisor, "vertex array 0 divisor is unimplemented!"); + for (unsigned index = 1; index < Maxwell::NumVertexArrays; ++index) { + ASSERT_MSG(!regs.vertex_array[index].enable, "vertex array %d is unimplemented!", index); + } + + // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. + // Enables the first 16 vertex attributes always, as we don't know which ones are actually used + // until shader time. Note, Tegra technically supports 32, but we're cappinig this to 16 for now + // to avoid OpenGL errors. + for (unsigned index = 0; index < 16; ++index) { + auto& attrib = regs.vertex_attrib_format[index]; + glVertexAttribPointer(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, vertex_array.stride, + reinterpret_cast<GLvoid*>(buffer_offset + attrib.offset)); + glEnableVertexAttribArray(index); + hw_vao_enabled_attributes[index] = true; + } + + // Copy vertex array data + const u32 data_size{vertex_array.stride * regs.vertex_buffer.count}; + const VAddr data_addr{memory_manager->PhysicalToVirtualAddress(vertex_array.StartAddress())}; + res_cache.FlushRegion(data_addr, data_size, nullptr); + Memory::ReadBlock(data_addr, array_ptr, data_size); + + array_ptr += data_size; + buffer_offset += data_size; } void RasterizerOpenGL::SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset) { MICROPROFILE_SCOPE(OpenGL_VS); - UNIMPLEMENTED(); + LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader."); + glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_shader->shader.handle); } void RasterizerOpenGL::SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset) { MICROPROFILE_SCOPE(OpenGL_FS); - UNIMPLEMENTED(); + UNREACHABLE(); } bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { if (!has_ARB_separate_shader_objects) { - UNIMPLEMENTED(); + UNREACHABLE(); return false; } accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; - DrawTriangles(); + DrawArrays(); return true; } -void RasterizerOpenGL::DrawTriangles() { +void RasterizerOpenGL::DrawArrays() { + if (accelerate_draw == AccelDraw::Disabled) + return; + MICROPROFILE_SCOPE(OpenGL_Drawing); - UNIMPLEMENTED(); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + // TODO(bunnei): Implement these + const bool has_stencil = false; + const bool using_color_fb = true; + const bool using_depth_fb = false; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()}; + + const bool write_color_fb = + state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || + state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; + + const bool write_depth_fb = + (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) || + (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0); + + Surface color_surface; + Surface depth_surface; + MathUtil::Rectangle<u32> surfaces_rect; + std::tie(color_surface, depth_surface, surfaces_rect) = + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect); + + const u16 res_scale = color_surface != nullptr + ? color_surface->res_scale + : (depth_surface == nullptr ? 1u : depth_surface->res_scale); + + MathUtil::Rectangle<u32> draw_rect{ + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) + + viewport_rect.left * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Left + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + + viewport_rect.top * res_scale, + surfaces_rect.bottom, surfaces_rect.top)), // Top + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) + + viewport_rect.right * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Right + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + + viewport_rect.bottom * res_scale, + surfaces_rect.bottom, surfaces_rect.top))}; // Bottom + + // Bind the framebuffer surfaces + BindFramebufferSurfaces(color_surface, depth_surface, has_stencil); + + // Sync the viewport + SyncViewport(surfaces_rect, res_scale); + + // TODO(bunnei): Sync framebuffer_scale uniform here + // TODO(bunnei): Sync scissorbox uniform(s) here + // TODO(bunnei): Sync and bind the texture surfaces + + // Sync and bind the shader + if (shader_dirty) { + SetShader(); + shader_dirty = false; + } + + // Sync the uniform data + if (uniform_block_data.dirty) { + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformData), &uniform_block_data.data); + uniform_block_data.dirty = false; + } + + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable + // scissor test to prevent drawing outside of the framebuffer region + state.scissor.enabled = true; + state.scissor.x = draw_rect.left; + state.scissor.y = draw_rect.bottom; + state.scissor.width = draw_rect.GetWidth(); + state.scissor.height = draw_rect.GetHeight(); + state.Apply(); + + // Draw the vertex batch + const bool is_indexed = accelerate_draw == AccelDraw::Indexed; + AnalyzeVertexArray(is_indexed); + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + size_t buffer_size = static_cast<size_t>(vs_input_size); + if (is_indexed) { + UNREACHABLE(); + } + buffer_size += sizeof(VSUniformData); + + size_t ptr_pos = 0; + u8* buffer_ptr; + GLintptr buffer_offset; + std::tie(buffer_ptr, buffer_offset) = + stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4); + + SetupVertexArray(buffer_ptr, buffer_offset); + ptr_pos += vs_input_size; + + GLintptr index_buffer_offset = 0; + if (is_indexed) { + UNREACHABLE(); + } + + SetupVertexShader(reinterpret_cast<VSUniformData*>(&buffer_ptr[ptr_pos]), + buffer_offset + static_cast<GLintptr>(ptr_pos)); + const GLintptr vs_ubo_offset = buffer_offset + static_cast<GLintptr>(ptr_pos); + ptr_pos += sizeof(VSUniformData); + + stream_buffer->Unmap(); + + const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) { + if (has_ARB_direct_state_access) { + glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size); + } else { + glBindBuffer(GL_COPY_WRITE_BUFFER, handle); + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size); + } + }; + + copy_buffer(vs_uniform_buffer.handle, vs_ubo_offset, sizeof(VSUniformData)); + + glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_shader->shader.handle); + + if (is_indexed) { + UNREACHABLE(); + } else { + glDrawArrays(MaxwellToGL::PrimitiveTopology(regs.draw.topology), 0, + regs.vertex_buffer.count); + } + + // Disable scissor test + state.scissor.enabled = false; + + accelerate_draw = AccelDraw::Disabled; + + // Unbind textures for potential future use as framebuffer attachments + for (auto& texture_unit : state.texture_units) { + texture_unit.texture_2d = 0; + } + state.Apply(); + + // Mark framebuffer surfaces as dirty + MathUtil::Rectangle<u32> draw_rect_unscaled{ + draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale, + draw_rect.bottom / res_scale}; + + if (color_surface != nullptr && write_color_fb) { + auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + color_surface); + } + if (depth_surface != nullptr && write_depth_fb) { + auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + depth_surface); + } } void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 id) {} @@ -194,17 +381,17 @@ void RasterizerOpenGL::FlushAll() { res_cache.FlushAll(); } -void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { +void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.FlushRegion(addr, size); } -void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { +void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.InvalidateRegion(addr, size, nullptr); } -void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { +void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.FlushRegion(addr, size); res_cache.InvalidateRegion(addr, size, nullptr); @@ -212,58 +399,180 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { MICROPROFILE_SCOPE(OpenGL_Blits); - UNIMPLEMENTED(); + UNREACHABLE(); return true; } bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) { - UNIMPLEMENTED(); + UNREACHABLE(); return true; } bool RasterizerOpenGL::AccelerateFill(const void* config) { - UNIMPLEMENTED(); + UNREACHABLE(); return true; } -bool RasterizerOpenGL::AccelerateDisplay(const void* config, PAddr framebuffer_addr, - u32 pixel_stride, ScreenInfo& screen_info) { - UNIMPLEMENTED(); +bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, + ScreenInfo& screen_info) { + if (framebuffer_addr == 0) { + return false; + } + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + + SurfaceParams src_params; + src_params.addr = framebuffer_addr; + src_params.width = std::min(framebuffer.width, pixel_stride); + src_params.height = framebuffer.height; + src_params.stride = pixel_stride; + src_params.is_tiled = false; + src_params.pixel_format = + SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format); + src_params.UpdateParams(); + + MathUtil::Rectangle<u32> src_rect; + Surface src_surface; + std::tie(src_surface, src_rect) = + res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true); + + if (src_surface == nullptr) { + return false; + } + + u32 scaled_width = src_surface->GetScaledWidth(); + u32 scaled_height = src_surface->GetScaledHeight(); + + screen_info.display_texcoords = MathUtil::Rectangle<float>( + (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width, + (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width); + + screen_info.display_texture = src_surface->texture.handle; + return true; } void RasterizerOpenGL::SetShader() { - UNIMPLEMENTED(); + // TODO(bunnei): The below sets up a static test shader for passing untransformed vertices to + // OpenGL for rendering. This should be removed/replaced when we start emulating Maxwell + // shaders. + + static constexpr char vertex_shader[] = R"( +#version 150 core + +in vec2 vert_position; +in vec2 vert_tex_coord; +out vec2 frag_tex_coord; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)) * vert_position + mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} +)"; + + static constexpr char fragment_shader[] = R"( +#version 150 core + +in vec2 frag_tex_coord; +out vec4 color; + +uniform sampler2D color_texture; + +void main() { + color = vec4(1.0, 0.0, 1.0, 0.0); +} +)"; + + if (current_shader) { + return; + } + + LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader."); + + current_shader = &test_shader; + if (has_ARB_separate_shader_objects) { + test_shader.shader.Create(vertex_shader, nullptr, fragment_shader, {}, true); + glActiveShaderProgram(pipeline.handle, test_shader.shader.handle); + } else { + UNREACHABLE(); + } + + state.draw.shader_program = test_shader.shader.handle; + state.Apply(); + + if (has_ARB_separate_shader_objects) { + state.draw.shader_program = 0; + state.Apply(); + } +} + +void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface, + const Surface& depth_surface, bool has_stencil) { + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + color_surface != nullptr ? color_surface->texture.handle : 0, 0); + if (depth_surface != nullptr) { + if (has_stencil) { + // attach both depth and stencil + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + } else { + // attach depth + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + // clear stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + } else { + // clear both depth and stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } +} + +void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()}; + + state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale; + state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale; + state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale); + state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale); } void RasterizerOpenGL::SyncClipEnabled() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncClipCoef() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncCullMode() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncDepthScale() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncDepthOffset() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncBlendEnabled() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncBlendFuncs() { - UNIMPLEMENTED(); + UNREACHABLE(); } void RasterizerOpenGL::SyncBlendColor() { - UNIMPLEMENTED(); + UNREACHABLE(); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 893fc530f..fd53e94cd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -29,19 +29,25 @@ public: RasterizerOpenGL(); ~RasterizerOpenGL() override; - void DrawTriangles() override; + void DrawArrays() override; void NotifyMaxwellRegisterChanged(u32 id) override; void FlushAll() override; - void FlushRegion(PAddr addr, u32 size) override; - void InvalidateRegion(PAddr addr, u32 size) override; - void FlushAndInvalidateRegion(PAddr addr, u32 size) override; + void FlushRegion(VAddr addr, u64 size) override; + void InvalidateRegion(VAddr addr, u64 size) override; + void FlushAndInvalidateRegion(VAddr addr, u64 size) override; bool AccelerateDisplayTransfer(const void* config) override; bool AccelerateTextureCopy(const void* config) override; bool AccelerateFill(const void* config) override; - bool AccelerateDisplay(const void* config, PAddr framebuffer_addr, u32 pixel_stride, - ScreenInfo& screen_info) override; + bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr, + u32 pixel_stride, ScreenInfo& screen_info) override; bool AccelerateDrawBatch(bool is_indexed) override; + /// OpenGL shader generated for a given Maxwell register state + struct MaxwellShader { + /// OpenGL shader resource + OGLShader shader; + }; + struct VertexShader { OGLShader shader; }; @@ -81,6 +87,13 @@ public: private: struct SamplerInfo {}; + /// Binds the framebuffer color and depth surface + void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface, + bool has_stencil); + + /// Syncs the viewport to match the guest state + void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale); + /// Syncs the clip enabled status to match the guest state void SyncClipEnabled(); @@ -117,6 +130,12 @@ private: RasterizerCacheOpenGL res_cache; + /// Shader used for test renderering - to be removed once we have emulated shaders + MaxwellShader test_shader{}; + + const MaxwellShader* current_shader{}; + bool shader_dirty{}; + struct { UniformData data; bool dirty; @@ -127,7 +146,7 @@ private: OGLVertexArray hw_vao; std::array<bool, 16> hw_vao_enabled_attributes; - std::array<SamplerInfo, 3> texture_samplers; + std::array<SamplerInfo, 32> texture_samplers; static constexpr size_t VERTEX_BUFFER_SIZE = 128 * 1024 * 1024; std::unique_ptr<OGLStreamBuffer> vertex_buffer; OGLBuffer uniform_buffer; @@ -136,8 +155,6 @@ private: static constexpr size_t STREAM_BUFFER_SIZE = 4 * 1024 * 1024; std::unique_ptr<OGLStreamBuffer> stream_buffer; - GLint vs_input_index_min; - GLint vs_input_index_max; GLsizeiptr vs_input_size; void AnalyzeVertexArray(bool is_indexed); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 884637ca5..2ffbd3bab 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -21,9 +21,13 @@ #include "common/microprofile.h" #include "common/scope_exit.h" #include "common/vector_math.h" +#include "core/core.h" #include "core/frontend/emu_window.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" #include "core/memory.h" #include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/utils.h" @@ -107,67 +111,28 @@ static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) { } template <bool morton_to_gl, PixelFormat format> -static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) { +static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, VAddr base, VAddr start, VAddr end) { constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8; - constexpr u32 tile_size = bytes_per_pixel * 64; - constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); - static_assert(gl_bytes_per_pixel >= bytes_per_pixel, ""); - gl_buffer += gl_bytes_per_pixel - bytes_per_pixel; - - const PAddr aligned_down_start = base + Common::AlignDown(start - base, tile_size); - const PAddr aligned_start = base + Common::AlignUp(start - base, tile_size); - const PAddr aligned_end = base + Common::AlignDown(end - base, tile_size); - - ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end)); - - const u64 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel; - u32 x = static_cast<u32>((begin_pixel_index % (stride * 8)) / 8); - u32 y = static_cast<u32>((begin_pixel_index / (stride * 8)) * 8); - - gl_buffer += ((height - 8 - y) * stride + x) * gl_bytes_per_pixel; - - auto glbuf_next_tile = [&] { - x = (x + 8) % stride; - gl_buffer += 8 * gl_bytes_per_pixel; - if (!x) { - y += 8; - gl_buffer -= stride * 9 * gl_bytes_per_pixel; - } - }; - u8* tile_buffer = Memory::GetPhysicalPointer(start); - - if (start < aligned_start && !morton_to_gl) { - std::array<u8, tile_size> tmp_buf; - MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer); - std::memcpy(tile_buffer, &tmp_buf[start - aligned_down_start], - std::min(aligned_start, end) - start); - - tile_buffer += aligned_start - start; - glbuf_next_tile(); - } - - const u8* const buffer_end = tile_buffer + aligned_end - aligned_start; - while (tile_buffer < buffer_end) { - MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer); - tile_buffer += tile_size; - glbuf_next_tile(); - } - - if (end > std::max(aligned_start, aligned_end) && !morton_to_gl) { - std::array<u8, tile_size> tmp_buf; - MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer); - std::memcpy(tile_buffer, &tmp_buf[0], end - aligned_end); - } + // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check the + // configuration for this and perform more generic un/swizzle + LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); + VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel, + Memory::GetPointer(base), gl_buffer, morton_to_gl); } -static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = { - MortonCopy<true, PixelFormat::RGBA8>, // 0 - MortonCopy<true, PixelFormat::RGB8>, // 1 - MortonCopy<true, PixelFormat::RGB5A1>, // 2 - MortonCopy<true, PixelFormat::RGB565>, // 3 - MortonCopy<true, PixelFormat::RGBA4>, // 4 +static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> morton_to_gl_fns = { + MortonCopy<true, PixelFormat::RGBA8>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, nullptr, nullptr, nullptr, @@ -176,19 +141,19 @@ static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> mo nullptr, nullptr, nullptr, - nullptr, // 5 - 13 - MortonCopy<true, PixelFormat::D16>, // 14 - nullptr, // 15 - MortonCopy<true, PixelFormat::D24>, // 16 - MortonCopy<true, PixelFormat::D24S8> // 17 }; -static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl_to_morton_fns = { - MortonCopy<false, PixelFormat::RGBA8>, // 0 - MortonCopy<false, PixelFormat::RGB8>, // 1 - MortonCopy<false, PixelFormat::RGB5A1>, // 2 - MortonCopy<false, PixelFormat::RGB565>, // 3 - MortonCopy<false, PixelFormat::RGBA4>, // 4 +static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> gl_to_morton_fns = { + MortonCopy<false, PixelFormat::RGBA8>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, nullptr, nullptr, nullptr, @@ -197,11 +162,6 @@ static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl nullptr, nullptr, nullptr, - nullptr, // 5 - 13 - MortonCopy<false, PixelFormat::D16>, // 14 - nullptr, // 15 - MortonCopy<false, PixelFormat::D24>, // 16 - MortonCopy<false, PixelFormat::D24S8> // 17 }; // Allocate an uninitialized texture of appropriate size and format for the surface @@ -290,17 +250,17 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rec static bool FillSurface(const Surface& surface, const u8* fill_data, const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) { - UNIMPLEMENTED(); - return true; + UNREACHABLE(); + return {}; } SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const { SurfaceParams params = *this; const u32 tiled_size = is_tiled ? 8 : 1; const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size); - PAddr aligned_start = + VAddr aligned_start = addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes); - PAddr aligned_end = + VAddr aligned_end = addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes); if (aligned_end - aligned_start > stride_tiled_bytes) { @@ -527,10 +487,10 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac } MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); -void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { +void CachedSurface::LoadGLBuffer(VAddr load_start, VAddr load_end) { ASSERT(type != SurfaceType::Fill); - const u8* const texture_src_data = Memory::GetPhysicalPointer(addr); + u8* const texture_src_data = Memory::GetPointer(addr); if (texture_src_data == nullptr) return; @@ -539,35 +499,30 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { gl_buffer.reset(new u8[gl_buffer_size]); } - // TODO: Should probably be done in ::Memory:: and check for other regions too - if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END) - load_end = Memory::VRAM_VADDR_END; - - if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR) - load_start = Memory::VRAM_VADDR; - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); ASSERT(load_start >= addr && load_end <= end); - const u32 start_offset = load_start - addr; + const u64 start_offset = load_start - addr; if (!is_tiled) { ASSERT(type == SurfaceType::Color); - std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset, - load_end - load_start); + const u32 bytes_per_pixel{GetFormatBpp() >> 3}; + + // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check + // the configuration for this and perform more generic un/swizzle + LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); + VideoCore::MortonCopyPixels128(width, height, bytes_per_pixel, 4, + texture_src_data + start_offset, &gl_buffer[start_offset], + true); } else { - if (type == SurfaceType::Texture) { - UNIMPLEMENTED(); - } else { - morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, - load_start, load_end); - } + morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, + load_start, load_end); } } MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); -void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { - u8* const dst_buffer = Memory::GetPhysicalPointer(addr); +void CachedSurface::FlushGLBuffer(VAddr flush_start, VAddr flush_end) { + u8* const dst_buffer = Memory::GetPointer(addr); if (dst_buffer == nullptr) return; @@ -1102,18 +1057,106 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& } Surface RasterizerCacheOpenGL::GetTextureSurface(const void* config) { - UNIMPLEMENTED(); + UNREACHABLE(); return {}; } SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( - bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport_rect) { - UNIMPLEMENTED(); - return {}; + bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager; + const auto& config = regs.rt[0]; + + // TODO(bunnei): This is hard corded to use just the first render buffer + LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!"); + + // update resolution_scale_factor and reset cache if changed + // TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We + // need to fix this before making the renderer multi-threaded. + static u16 resolution_scale_factor = GetResolutionScaleFactor(); + if (resolution_scale_factor != GetResolutionScaleFactor()) { + resolution_scale_factor = GetResolutionScaleFactor(); + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); + } + + MathUtil::Rectangle<u32> viewport_clamped{ + static_cast<u32>(MathUtil::Clamp(viewport.left, 0, static_cast<s32>(config.width))), + static_cast<u32>(MathUtil::Clamp(viewport.top, 0, static_cast<s32>(config.height))), + static_cast<u32>(MathUtil::Clamp(viewport.right, 0, static_cast<s32>(config.width))), + static_cast<u32>(MathUtil::Clamp(viewport.bottom, 0, static_cast<s32>(config.height)))}; + + // get color and depth surfaces + SurfaceParams color_params; + color_params.is_tiled = true; + color_params.res_scale = resolution_scale_factor; + color_params.width = config.width; + color_params.height = config.height; + SurfaceParams depth_params = color_params; + + color_params.addr = memory_manager->PhysicalToVirtualAddress(config.Address()); + color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format); + color_params.UpdateParams(); + + ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented"); + // depth_params.addr = config.GetDepthBufferPhysicalAddress(); + // depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format); + // depth_params.UpdateParams(); + + auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); + auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); + + // Make sure that framebuffers don't overlap if both color and depth are being used + if (using_color_fb && using_depth_fb && + boost::icl::length(color_vp_interval & depth_vp_interval)) { + LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " + "overlapping framebuffers not supported!"); + using_depth_fb = false; + } + + MathUtil::Rectangle<u32> color_rect{}; + Surface color_surface = nullptr; + if (using_color_fb) + std::tie(color_surface, color_rect) = + GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> depth_rect{}; + Surface depth_surface = nullptr; + if (using_depth_fb) + std::tie(depth_surface, depth_rect) = + GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> fb_rect{}; + if (color_surface != nullptr && depth_surface != nullptr) { + fb_rect = color_rect; + // Color and Depth surfaces must have the same dimensions and offsets + if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top || + color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) { + color_surface = GetSurface(color_params, ScaleMatch::Exact, false); + depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); + fb_rect = color_surface->GetScaledRect(); + } + } else if (color_surface != nullptr) { + fb_rect = color_rect; + } else if (depth_surface != nullptr) { + fb_rect = depth_rect; + } + + if (color_surface != nullptr) { + ValidateSurface(color_surface, boost::icl::first(color_vp_interval), + boost::icl::length(color_vp_interval)); + } + if (depth_surface != nullptr) { + ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), + boost::icl::length(depth_vp_interval)); + } + + return std::make_tuple(color_surface, depth_surface, fb_rect); } Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) { - UNIMPLEMENTED(); + UNREACHABLE(); return {}; } @@ -1167,7 +1210,7 @@ void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, } } -void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u64 size) { +void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, VAddr addr, u64 size) { if (size == 0) return; @@ -1227,7 +1270,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, } } -void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u64 size, Surface flush_surface) { +void RasterizerCacheOpenGL::FlushRegion(VAddr addr, u64 size, Surface flush_surface) { if (size == 0) return; @@ -1260,10 +1303,10 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u64 size, Surface flush_surf } void RasterizerCacheOpenGL::FlushAll() { - FlushRegion(0, 0xFFFFFFFF); + FlushRegion(0, Kernel::VMManager::MAX_ADDRESS); } -void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner) { +void RasterizerCacheOpenGL::InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner) { if (size == 0) return; @@ -1356,6 +1399,34 @@ void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) { surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}}); } -void RasterizerCacheOpenGL::UpdatePagesCachedCount(PAddr addr, u64 size, int delta) { - UNIMPLEMENTED(); +void RasterizerCacheOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { + const u64 num_pages = + ((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1; + const u64 page_start = addr >> Memory::PAGE_BITS; + const u64 page_end = page_start + num_pages; + + // Interval maps will erase segments if count reaches 0, so if delta is negative we have to + // subtract after iterating + const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end); + if (delta > 0) + cached_pages.add({pages_interval, delta}); + + for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) { + const auto interval = pair.first & pages_interval; + const int count = pair.second; + + const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS; + const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS; + const u64 interval_size = interval_end_addr - interval_start_addr; + + if (delta > 0 && count == delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true); + else if (delta < 0 && count == -delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false); + else + ASSERT(count >= 0); + } + + if (delta < 0) + cached_pages.add({pages_interval, delta}); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 17ce0fee7..1f660d30c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -22,15 +22,16 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/math_util.h" +#include "video_core/gpu.h" #include "video_core/renderer_opengl/gl_resource_manager.h" struct CachedSurface; using Surface = std::shared_ptr<CachedSurface>; using SurfaceSet = std::set<Surface>; -using SurfaceRegions = boost::icl::interval_set<PAddr>; -using SurfaceMap = boost::icl::interval_map<PAddr, Surface>; -using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet>; +using SurfaceRegions = boost::icl::interval_set<VAddr>; +using SurfaceMap = boost::icl::interval_map<VAddr, Surface>; +using SurfaceCache = boost::icl::interval_map<VAddr, SurfaceSet>; using SurfaceInterval = SurfaceCache::interval_type; static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() && @@ -40,7 +41,7 @@ static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>; using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>; -using PageMap = boost::icl::interval_map<u32, int>; +using PageMap = boost::icl::interval_map<u64, int>; enum class ScaleMatch { Exact, // only accept same res scale @@ -115,6 +116,24 @@ struct SurfaceParams { return GetFormatBpp(pixel_format); } + static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) { + switch (format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return PixelFormat::RGBA8; + default: + UNREACHABLE(); + } + } + + static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) { + switch (format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: + return PixelFormat::RGBA8; + default: + UNREACHABLE(); + } + } + static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { SurfaceType a_type = GetFormatType(pixel_format_a); SurfaceType b_type = GetFormatType(pixel_format_b); @@ -211,8 +230,8 @@ struct SurfaceParams { MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const; MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const; - PAddr addr = 0; - PAddr end = 0; + VAddr addr = 0; + VAddr end = 0; u64 size = 0; u32 width = 0; @@ -257,9 +276,9 @@ struct CachedSurface : SurfaceParams { std::unique_ptr<u8[]> gl_buffer; size_t gl_buffer_size = 0; - // Read/Write data in 3DS memory to/from gl_buffer - void LoadGLBuffer(PAddr load_start, PAddr load_end); - void FlushGLBuffer(PAddr flush_start, PAddr flush_end); + // Read/Write data in Switch memory to/from gl_buffer + void LoadGLBuffer(VAddr load_start, VAddr load_end); + void FlushGLBuffer(VAddr flush_start, VAddr flush_end); // Upload/Download data in gl_buffer in/to this surface's texture void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, @@ -298,7 +317,7 @@ public: /// Get the color and depth surfaces based on the framebuffer configuration SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, - const MathUtil::Rectangle<s32>& viewport_rect); + const MathUtil::Rectangle<s32>& viewport); /// Get a surface that matches the fill config Surface GetFillSurface(const void* config); @@ -307,10 +326,10 @@ public: SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); /// Write any cached resources overlapping the region back to memory (if dirty) - void FlushRegion(PAddr addr, u64 size, Surface flush_surface = nullptr); + void FlushRegion(VAddr addr, u64 size, Surface flush_surface = nullptr); /// Mark region as being invalidated by region_owner (nullptr if 3DS memory) - void InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner); + void InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner); /// Flush all cached resources tracked by this cache manager void FlushAll(); @@ -319,7 +338,7 @@ private: void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface); /// Update surface's texture for given region when necessary - void ValidateSurface(const Surface& surface, PAddr addr, u64 size); + void ValidateSurface(const Surface& surface, VAddr addr, u64 size); /// Create a new surface Surface CreateSurface(const SurfaceParams& params); @@ -331,7 +350,7 @@ private: void UnregisterSurface(const Surface& surface); /// Increase/decrease the number of surface in pages touching the specified region - void UpdatePagesCachedCount(PAddr addr, u64 size, int delta); + void UpdatePagesCachedCount(VAddr addr, u64 size, int delta); SurfaceCache surface_cache; PageMap cached_pages; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 0e0ef18cc..564ea8f9e 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -26,7 +26,7 @@ public: sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {} std::string Decompile() { - UNIMPLEMENTED(); + UNREACHABLE(); return {}; } diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index f242bce1d..8f3c98800 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -8,12 +8,12 @@ namespace GLShader { std::string GenerateVertexShader(const MaxwellVSConfig& config) { - UNIMPLEMENTED(); + UNREACHABLE(); return {}; } std::string GenerateFragmentShader(const MaxwellFSConfig& config) { - UNIMPLEMENTED(); + UNREACHABLE(); return {}; } diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index a3ba16761..a6c6204d5 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -38,8 +38,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, if (result == GL_TRUE) { LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]); } else { - LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s", - &vertex_shader_error[0]); + LOG_CRITICAL(Render_OpenGL, "Error compiling vertex shader:\n%s", + &vertex_shader_error[0]); } } } @@ -62,8 +62,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, if (result == GL_TRUE) { LOG_DEBUG(Render_OpenGL, "%s", &geometry_shader_error[0]); } else { - LOG_ERROR(Render_OpenGL, "Error compiling geometry shader:\n%s", - &geometry_shader_error[0]); + LOG_CRITICAL(Render_OpenGL, "Error compiling geometry shader:\n%s", + &geometry_shader_error[0]); } } } @@ -86,8 +86,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, if (result == GL_TRUE) { LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]); } else { - LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s", - &fragment_shader_error[0]); + LOG_CRITICAL(Render_OpenGL, "Error compiling fragment shader:\n%s", + &fragment_shader_error[0]); } } } @@ -128,20 +128,20 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, if (result == GL_TRUE) { LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]); } else { - LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]); + LOG_CRITICAL(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]); } } // If the program linking failed at least one of the shaders was probably bad if (result == GL_FALSE) { if (vertex_shader) { - LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader); + LOG_CRITICAL(Render_OpenGL, "Vertex shader:\n%s", vertex_shader); } if (geometry_shader) { - LOG_ERROR(Render_OpenGL, "Geometry shader:\n%s", geometry_shader); + LOG_CRITICAL(Render_OpenGL, "Geometry shader:\n%s", geometry_shader); } if (fragment_shader) { - LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader); + LOG_CRITICAL(Render_OpenGL, "Fragment shader:\n%s", fragment_shader); } } ASSERT_MSG(result == GL_TRUE, "Shader not linked"); diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 940575dfa..c1f4efc8c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -85,7 +85,7 @@ public: struct { GLuint texture_2d; // GL_TEXTURE_BINDING_2D GLuint sampler; // GL_SAMPLER_BINDING - } texture_units[3]; + } texture_units[32]; struct { GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h new file mode 100644 index 000000000..d847317ac --- /dev/null +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <glad/glad.h> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" + +namespace MaxwellToGL { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +inline GLenum VertexType(Maxwell::VertexAttribute attrib) { + switch (attrib.type) { + case Maxwell::VertexAttribute::Type::UnsignedNorm: { + + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_8_8_8_8: + return GL_UNSIGNED_BYTE; + } + + LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size=%s", attrib.SizeString().c_str()); + UNREACHABLE(); + return {}; + } + + case Maxwell::VertexAttribute::Type::Float: + return GL_FLOAT; + } + + LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type=%s", attrib.TypeString().c_str()); + UNREACHABLE(); + return {}; +} + +inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { + switch (topology) { + case Maxwell::PrimitiveTopology::TriangleStrip: + return GL_TRIANGLE_STRIP; + } + LOG_CRITICAL(Render_OpenGL, "Unimplemented primitive topology=%d", topology); + UNREACHABLE(); + return {}; +} + +} // namespace MaxwellToGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 65d38ade5..78b50b227 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -20,6 +20,7 @@ #include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/utils.h" #include "video_core/video_core.h" static const char vertex_shader[] = R"( @@ -98,197 +99,88 @@ RendererOpenGL::RendererOpenGL() = default; RendererOpenGL::~RendererOpenGL() = default; /// Swap buffers (render frame) -void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) { +void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) { + Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); - if (framebuffer_info != boost::none) { - // If framebuffer_info is provided, reload it from memory to a texture - if (screen_info.texture.width != (GLsizei)framebuffer_info->width || - screen_info.texture.height != (GLsizei)framebuffer_info->height || - screen_info.texture.pixel_format != framebuffer_info->pixel_format) { + if (framebuffer != boost::none) { + // If framebuffer is provided, reload it from memory to a texture + if (screen_info.texture.width != (GLsizei)framebuffer->width || + screen_info.texture.height != (GLsizei)framebuffer->height || + screen_info.texture.pixel_format != framebuffer->pixel_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info); + ConfigureFramebufferTexture(screen_info.texture, *framebuffer); } - LoadFBToScreenInfo(*framebuffer_info, screen_info); - } - - DrawScreens(); - Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Load the framebuffer from memory, draw it to the screen, and swap buffers + LoadFBToScreenInfo(*framebuffer, screen_info); + DrawScreen(); + render_window->SwapBuffers(); + } - // Swap buffers render_window->PollEvents(); - render_window->SwapBuffers(); Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); Core::System::GetInstance().perf_stats.BeginSystemFrame(); + // Restore the rasterizer state prev_state.Apply(); RefreshRasterizerSetting(); } -static inline u32 MortonInterleave128(u32 x, u32 y) { - // 128x128 Z-Order coordinate from 2D coordinates - static constexpr u32 xlut[] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, - 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, - 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, - 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, - 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, - 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, - 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, - 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, - 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, - 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, - 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, - 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, - 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, - 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, - 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, - 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, - 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, - 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, - 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, - 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, - 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, - 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, - 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, - 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, - 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, - 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, - 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, - 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, - 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, - 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, - 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, - 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, - 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, - 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, - }; - static constexpr u32 ylut[] = { - 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, - 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, - 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, - 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, - 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, - 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, - 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, - 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, - 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, - 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, - 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, - 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, - 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, - 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, - 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, - 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, - 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, - 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, - 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, - 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, - 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, - 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, - 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, - 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, - 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, - 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, - 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, - 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, - 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, - 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, - 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, - 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, - 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, - 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, - 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, - }; - return xlut[x % 128] + ylut[y % 128]; -} - -static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { - // Calculates the offset of the position of the pixel in Morton order - // Framebuffer images are split into 128x128 tiles. - - const unsigned int block_height = 128; - const unsigned int coarse_x = x & ~127; - - u32 i = MortonInterleave128(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - -static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, - u8* morton_data, u8* gl_data, bool morton_to_gl) { - u8* data_ptrs[2]; - for (unsigned y = 0; y < height; ++y) { - for (unsigned x = 0; x < width; ++x) { - const u32 coarse_y = y & ~127; - u32 morton_offset = - GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; - u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; - - data_ptrs[morton_to_gl] = morton_data + morton_offset; - data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; - - memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - /** * Loads framebuffer from emulated memory into the active OpenGL texture. */ -void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, +void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info) { - const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)}; - const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp}; + const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)}; + const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; + const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; - MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4, - Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(), - true); - - LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes, - framebuffer_info.address, framebuffer_info.width, framebuffer_info.height, - (int)framebuffer_info.pixel_format); + // Framebuffer orientation handling + framebuffer_transform_flags = framebuffer.transform_flags; // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default // only allows rows to have a memory alignement of 4. - ASSERT(framebuffer_info.stride % 4 == 0); + ASSERT(framebuffer.stride % 4 == 0); - framebuffer_flip_vertical = framebuffer_info.flip_vertical; + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride, + screen_info)) { + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; + screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); - // Reset the screen info's display texture to its own permanent texture - screen_info.display_texture = screen_info.texture.resource.handle; - screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); + Rasterizer()->FlushRegion(framebuffer_addr, size_in_bytes); - // Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes); + VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4, + Memory::GetPointer(framebuffer_addr), + gl_framebuffer_data.data(), true); - state.texture_units[0].texture_2d = screen_info.texture.resource.handle; - state.Apply(); + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride); + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that - // they differ from the LCD resolution. - // TODO: Applications could theoretically crash Citra here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height, - screen_info.texture.gl_format, screen_info.texture.gl_type, - gl_framebuffer_data.data()); + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that + // they differ from the LCD resolution. + // TODO: Applications could theoretically crash yuzu here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, + gl_framebuffer_data.data()); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - state.texture_units[0].texture_2d = 0; - state.Apply(); + state.texture_units[0].texture_2d = 0; + state.Apply(); + } } /** @@ -372,14 +264,14 @@ void RendererOpenGL::InitOpenGLObjects() { } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, - const FramebufferInfo& framebuffer_info) { + const Tegra::FramebufferConfig& framebuffer) { - texture.width = framebuffer_info.width; - texture.height = framebuffer_info.height; + texture.width = framebuffer.width; + texture.height = framebuffer.height; GLint internal_format; - switch (framebuffer_info.pixel_format) { - case FramebufferInfo::PixelFormat::ABGR8: + switch (framebuffer.pixel_format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: // Use RGBA8 and swap in the fragment shader internal_format = GL_RGBA; texture.gl_format = GL_RGBA; @@ -387,7 +279,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, gl_framebuffer_data.resize(texture.width * texture.height * 4); break; default: - UNIMPLEMENTED(); + UNREACHABLE(); } state.texture_units[0].texture_2d = texture.resource.handle; @@ -401,11 +293,22 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, state.Apply(); } -void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, - float h) { +void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, + float h) { const auto& texcoords = screen_info.display_texcoords; - const auto& left = framebuffer_flip_vertical ? texcoords.right : texcoords.left; - const auto& right = framebuffer_flip_vertical ? texcoords.left : texcoords.right; + auto left = texcoords.left; + auto right = texcoords.right; + if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset) + if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) { + // Flip the framebuffer vertically + left = texcoords.right; + right = texcoords.left; + } else { + // Other transformations are unsupported + LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags=%d", + framebuffer_transform_flags); + UNIMPLEMENTED(); + } std::array<ScreenRectVertex, 4> vertices = {{ ScreenRectVertex(x, y, texcoords.top, right), @@ -427,7 +330,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl /** * Draws the emulated screens to the emulator window. */ -void RendererOpenGL::DrawScreens() { +void RendererOpenGL::DrawScreen() { const auto& layout = render_window->GetFramebufferLayout(); const auto& screen = layout.screen; @@ -443,8 +346,8 @@ void RendererOpenGL::DrawScreens() { glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(), - (float)screen.GetHeight()); + DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top, + (float)screen.GetWidth(), (float)screen.GetHeight()); m_current_frame++; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 05bb3c5cf..fffd0f9f4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -21,7 +21,7 @@ struct TextureInfo { GLsizei height; GLenum gl_format; GLenum gl_type; - RendererBase::FramebufferInfo::PixelFormat pixel_format; + Tegra::FramebufferConfig::PixelFormat pixel_format; }; /// Structure used for storing information about the display target for each 3DS screen @@ -37,7 +37,7 @@ public: ~RendererOpenGL() override; /// Swap buffers (render frame) - void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override; + void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override; /** * Set the emulator window to use for renderer @@ -53,13 +53,14 @@ public: private: void InitOpenGLObjects(); - void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info); - void DrawScreens(); - void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h); + void ConfigureFramebufferTexture(TextureInfo& texture, + const Tegra::FramebufferConfig& framebuffer); + void DrawScreen(); + void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); // Loads framebuffer from emulated memory into the display information structure - void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info); + void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info); // Fills active OpenGL texture with the given RGBA color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture); @@ -87,6 +88,6 @@ private: GLuint attrib_position; GLuint attrib_tex_coord; - /// Flips the framebuffer vertically when true - bool framebuffer_flip_vertical; + /// Used for transforming the framebuffer orientation + Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; }; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp new file mode 100644 index 000000000..2e87281eb --- /dev/null +++ b/src/video_core/textures/decoders.cpp @@ -0,0 +1,105 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Calculates the offset of an (x, y) position within a swizzled texture. + * Taken from the Tegra X1 TRM. + */ +static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { + u32 image_width_in_gobs = image_width * bytes_per_pixel / 64; + u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (x * bytes_per_pixel / 64) * 512 * block_height + + (y % (8 * block_height) / 8) * 512; + x *= bytes_per_pixel; + u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); + + return address; +} + +static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, + u32 block_height) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); + u32 pixel_index = (x + y * width) * out_bytes_per_pixel; + + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +u32 BytesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::DXT1: + // In this case a 'pixel' actually refers to a 4x4 tile. + return 8; + case TextureFormat::A8R8G8B8: + return 4; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } +} + +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) { + u8* data = Memory::GetPointer(address); + u32 bytes_per_pixel = BytesPerPixel(format); + + static constexpr u32 DefaultBlockHeight = 16; + + std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); + + switch (format) { + case TextureFormat::DXT1: + // In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values. + CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; + case TextureFormat::A8R8G8B8: + CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return unswizzled_data; +} + +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height) { + std::vector<u8> rgba_data; + + // TODO(Subv): Implement. + switch (format) { + case TextureFormat::DXT1: + case TextureFormat::A8R8G8B8: + // TODO(Subv): For the time being just forward the same data without any decoding. + rgba_data = texture_data; + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return rgba_data; +} + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h new file mode 100644 index 000000000..0c21694ff --- /dev/null +++ b/src/video_core/textures/decoders.h @@ -0,0 +1,26 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Unswizzles a swizzled texture without changing its format. + */ +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height); + +/** + * Decodes an unswizzled texture into a A8R8G8B8 texture. + */ +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h new file mode 100644 index 000000000..07936f8a3 --- /dev/null +++ b/src/video_core/textures/texture.h @@ -0,0 +1,137 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +namespace Texture { + +enum class TextureFormat : u32 { + A8R8G8B8 = 8, + DXT1 = 0x24, +}; + +enum class TextureType : u32 { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCubemap = 3, + Texture1DArray = 4, + Texture2DArray = 5, + Texture1DBuffer = 6, + Texture2DNoMipmap = 7, + TextureCubeArray = 8, +}; + +enum class TICHeaderVersion : u32 { + OneDBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4, +}; + +union TextureHandle { + u32 raw; + BitField<0, 20, u32> tic_id; + BitField<20, 12, u32> tsc_id; +}; +static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); + +struct TICEntry { + union { + u32 raw; + BitField<0, 7, TextureFormat> format; + BitField<7, 3, u32> r_type; + BitField<10, 3, u32> g_type; + BitField<13, 3, u32> b_type; + BitField<16, 3, u32> a_type; + }; + u32 address_low; + union { + BitField<0, 16, u32> address_high; + BitField<21, 3, TICHeaderVersion> header_version; + }; + INSERT_PADDING_BYTES(4); + union { + BitField<0, 16, u32> width_minus_1; + BitField<23, 4, TextureType> texture_type; + }; + u16 height_minus_1; + INSERT_PADDING_BYTES(10); + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); + } + + u32 Width() const { + return width_minus_1 + 1; + } + + u32 Height() const { + return height_minus_1 + 1; + } +}; +static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); + +enum class WrapMode : u32 { + Wrap = 0, + Mirror = 1, + ClampToEdge = 2, + Border = 3, + ClampOGL = 4, + MirrorOnceClampToEdge = 5, + MirrorOnceBorder = 6, + MirrorOnceClampOGL = 7, +}; + +enum class TextureFilter : u32 { + Nearest = 1, + Linear = 2, +}; + +enum class TextureMipmapFilter : u32 { + None = 1, + Nearest = 2, + Linear = 3, +}; + +struct TSCEntry { + union { + BitField<0, 3, WrapMode> wrap_u; + BitField<3, 3, WrapMode> wrap_v; + BitField<6, 3, WrapMode> wrap_p; + BitField<9, 1, u32> depth_compare_enabled; + BitField<10, 3, u32> depth_compare_func; + }; + union { + BitField<0, 2, TextureFilter> mag_filter; + BitField<4, 2, TextureFilter> min_filter; + BitField<6, 2, TextureMipmapFilter> mip_filter; + }; + INSERT_PADDING_BYTES(8); + u32 border_color_r; + u32 border_color_g; + u32 border_color_b; + u32 border_color_a; +}; +static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size"); + +struct FullTextureInfo { + u32 index; + TICEntry tic; + TSCEntry tsc; + bool enabled; +}; + +/// Returns the number of bytes per pixel of the input texture format. +u32 BytesPerPixel(TextureFormat format); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/utils.h b/src/video_core/utils.h index d94a10417..be0f7e22b 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { return (i + offset) * bytes_per_pixel; } +static inline u32 MortonInterleave128(u32 x, u32 y) { + // 128x128 Z-Order coordinate from 2D coordinates + static constexpr u32 xlut[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, + 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, + 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, + 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, + 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, + 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, + 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, + 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, + 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, + 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, + 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, + 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, + 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, + 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, + 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, + 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, + 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, + 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, + 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, + 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, + 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, + 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, + 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, + 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, + 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, + 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, + 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, + 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, + 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, + 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, + 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, + 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, + 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, + 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, + }; + static constexpr u32 ylut[] = { + 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, + 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, + 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, + 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, + 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, + 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, + 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, + 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, + 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, + 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, + 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, + 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, + 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, + 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, + 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, + 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, + 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, + 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, + 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, + 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, + 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, + 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, + 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, + 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, + 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, + 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, + 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, + 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, + 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, + 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, + 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, + 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, + 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, + 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, + 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, + }; + return xlut[x % 128] + ylut[y % 128]; +} + +static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { + // Calculates the offset of the position of the pixel in Morton order + // Framebuffer images are split into 128x128 tiles. + + const unsigned int block_height = 128; + const unsigned int coarse_x = x & ~127; + + u32 i = MortonInterleave128(x, y); + + const unsigned int offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + +static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, + u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, + bool morton_to_gl) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~127; + u32 morton_offset = + GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + } // namespace VideoCore diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 864691baa..289140f31 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -26,7 +26,7 @@ bool Init(EmuWindow* emu_window) { if (g_renderer->Init()) { LOG_DEBUG(Render, "initialized OK"); } else { - LOG_ERROR(Render, "initialization failed !"); + LOG_CRITICAL(Render, "initialization failed !"); return false; } return true; diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 1fd90b9d0..37da62436 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -15,6 +15,8 @@ class RendererBase; namespace VideoCore { +enum class Renderer { Software, OpenGL }; + extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin extern EmuWindow* g_emu_window; ///< Emu window diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0c4056c49..5af3154d7 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -23,6 +23,13 @@ add_executable(yuzu configuration/configure_input.h configuration/configure_system.cpp configuration/configure_system.h + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_surface.h debugger/profiler.cpp debugger/profiler.h debugger/registers.cpp diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index da3429822..d6647eeea 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QIcon> #include "common/scm_rev.h" #include "ui_aboutdialog.h" #include "yuzu/about_dialog.h" AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); + ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 71dc58e5d..8843f2078 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -77,8 +77,7 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 1).toInt()); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -94,6 +93,10 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); + qt_config->beginGroup("System"); + Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); qt_config->endGroup(); @@ -104,6 +107,8 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("UI"); + UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -171,7 +176,7 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core)); + qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -188,6 +193,10 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); + qt_config->beginGroup("System"); + qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 92fd6ab02..2d73fc5aa 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,9 +13,14 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->setupUi(this); + for (auto theme : UISettings::themes) { + ui->theme_combobox->addItem(theme.first, theme.second); + } + this->setConfiguration(); - ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } ConfigureGeneral::~ConfigureGeneral() {} @@ -23,13 +28,18 @@ ConfigureGeneral::~ConfigureGeneral() {} void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); - ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core)); + ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex()); + UISettings::values.theme = + ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + + Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 573c4cb0e..1775c4d40 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -13,17 +13,17 @@ <property name="windowTitle"> <string>Form</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="HorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="VerticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="GeneralGroupBox"> <property name="title"> <string>General</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> @@ -44,40 +44,80 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>CPU Core</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QComboBox" name="cpu_core_combobox"> - <item> - <property name="text"> - <string>Unicorn</string> - </property> - </item> - <item> - <property name="text"> - <string>Dynarmic</string> - </property> - </item> - </widget> - </item> - </layout> - </item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_cpu_jit"> + <property name="text"> + <string>Enable CPU JIT</string> + </property> + </widget> + </item> </layout> - </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="EmulationGroupBox"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="EmulationVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Enable docked mode</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="theme_group_box"> + <property name="title"> + <string>Theme</string> + </property> + <layout class="QHBoxLayout" name="theme_qhbox_layout"> + <item> + <layout class="QVBoxLayout" name="theme_qvbox_layout"> + <item> + <layout class="QHBoxLayout" name="theme_qhbox_layout_2"> + <item> + <widget class="QLabel" name="theme_label"> + <property name="text"> + <string>Theme:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="theme_combobox"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> + <widget class="QGroupBox" name="HotKeysGroupBox"> <property name="title"> <string>Hotkeys</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <layout class="QHBoxLayout" name="HotKeysHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="HotKeysVerticalLayout"> <item> <widget class="GHotkeysDialog" name="widget" native="true"/> </item> diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..d6d61a739 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp @@ -0,0 +1,27 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMetaType> +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) { + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnMaxwellResume() { + emit Resumed(); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h new file mode 100644 index 000000000..9d05493cf --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. + * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, + protected Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +private slots: + virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp new file mode 100644 index 000000000..f98cc8152 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -0,0 +1,212 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_breakpoints_p.h" + +BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context, + QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) {} + +int BreakPointModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const { + return static_cast<int>(Tegra::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::DisplayRole: { + if (index.column() == 0) { + static const std::map<Tegra::DebugContext::Event, QString> map = { + {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, + {Tegra::DebugContext::Event::MaxwellCommandProcessed, + tr("Maxwell command processed")}, + {Tegra::DebugContext::Event::IncomingPrimitiveBatch, + tr("Incoming primitive batch")}, + {Tegra::DebugContext::Event::FinishedPrimitiveBatch, + tr("Finished primitive batch")}, + }; + + DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); + return (map.find(event) != map.end()) ? map.at(event) : QString(); + } + + break; + } + + case Qt::CheckStateRole: { + if (index.column() == 0) + return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; + break; + } + + case Qt::BackgroundRole: { + if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: { + auto context = context_weak.lock(); + return context && context->breakpoints[(int)event].enabled; + } + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { + if (!index.isValid()) + return 0; + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.column() == 0) + flags |= Qt::ItemIsUserCheckable; + return flags; +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::CheckStateRole: { + if (index.column() != 0) + return false; + + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[(int)event].enabled = value == Qt::Checked; + QModelIndex changed_index = createIndex(index.row(), 0); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + +void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(event), 0), + createIndex(static_cast<int>(event), 0)); +} + +void BreakPointModel::OnResumed() { + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), + createIndex(static_cast<int>(active_breakpoint), 0)); + active_breakpoint = context->active_breakpoint; +} + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( + std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) + : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( + debug_context) { + setObjectName("TegraBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setRootIsDecorated(false); + breakpoint_list->setHeaderHidden(true); + breakpoint_list->setModel(breakpoint_model); + + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, + SLOT(OnItemDoubleClicked(const QModelIndex&))); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnMaxwellResume() { + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() { + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() { + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { + if (!index.isValid()) + return; + + QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); + QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); + QVariant new_state = Qt::Unchecked; + if (enabled == Qt::Unchecked) + new_state = Qt::Checked; + breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h new file mode 100644 index 000000000..ae0ede2e8 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h @@ -0,0 +1,46 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + +public: + explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnItemDoubleClicked(const QModelIndex&); + void OnResumeRequested(); + void OnResumed(); + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + QLabel* status_text; + QPushButton* resume_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h new file mode 100644 index 000000000..35a6876ae --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h @@ -0,0 +1,36 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QAbstractListModel> +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr<Tegra::DebugContext> context_weak; + bool at_breakpoint; + Tegra::DebugContext::Event active_breakpoint; +}; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..1e4844b57 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,451 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QFileDialog> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QScrollArea> +#include <QSpinBox> +#include "core/core.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/utils.h" +#include "yuzu/debugger/graphics/graphics_surface.h" +#include "yuzu/util/spinbox.h" + +static Tegra::Texture::TextureFormat ConvertToTextureFormat( + Tegra::RenderTargetFormat render_target_format) { + switch (render_target_format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return Tegra::Texture::TextureFormat::A8R8G8B8; + default: + UNIMPLEMENTED_MSG("Unimplemented RT format"); + } +} + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) + : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +void SurfacePicture::mousePressEvent(QMouseEvent* event) { + // Only do something while the left mouse button is held down + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (pixmap() == nullptr) + return; + + if (surface_widget) + surface_widget->Pick(event->x() * pixmap()->width() / width(), + event->y() * pixmap()->height() / height()); +} + +void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { + // We also want to handle the event if the user moves the mouse while holding down the LMB + mousePressEvent(event); +} + +GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), + surface_source(Source::RenderTarget0) { + setObjectName("MaxwellSurface"); + + surface_source_list = new QComboBox; + surface_source_list->addItem(tr("Render Target 0")); + surface_source_list->addItem(tr("Render Target 1")); + surface_source_list->addItem(tr("Render Target 2")); + surface_source_list->addItem(tr("Render Target 3")); + surface_source_list->addItem(tr("Render Target 4")); + surface_source_list->addItem(tr("Render Target 5")); + surface_source_list->addItem(tr("Render Target 6")); + surface_source_list->addItem(tr("Render Target 7")); + surface_source_list->addItem(tr("Z Buffer")); + surface_source_list->addItem(tr("Custom")); + surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); + + surface_address_control = new CSpinBox; + surface_address_control->SetBase(16); + surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); + surface_address_control->SetPrefix("0x"); + + unsigned max_dimension = 16384; // TODO: Find actual maximum + + surface_width_control = new QSpinBox; + surface_width_control->setRange(0, max_dimension); + + surface_height_control = new QSpinBox; + surface_height_control->setRange(0, max_dimension); + + surface_picker_x_control = new QSpinBox; + surface_picker_x_control->setRange(0, max_dimension - 1); + + surface_picker_y_control = new QSpinBox; + surface_picker_y_control->setRange(0, max_dimension - 1); + + surface_format_control = new QComboBox; + + // Color formats sorted by Maxwell texture format index + surface_format_control->addItem(tr("None")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("A8R8G8B8")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("DXT1")); + surface_format_control->addItem(tr("DXT23")); + surface_format_control->addItem(tr("DXT45")); + surface_format_control->addItem(tr("DXN1")); + surface_format_control->addItem(tr("DXN2")); + + surface_info_label = new QLabel(); + surface_info_label->setWordWrap(true); + + surface_picture_label = new SurfacePicture(0, this); + surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + surface_picture_label->setScaledContents(false); + + auto scroll_area = new QScrollArea(); + scroll_area->setBackgroundRole(QPalette::Dark); + scroll_area->setWidgetResizable(false); + scroll_area->setWidget(surface_picture_label); + + save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceSourceChanged(int))); + connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, + SLOT(OnSurfaceAddressChanged(qint64))); + connect(surface_width_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceWidthChanged(int))); + connect(surface_height_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceHeightChanged(int))); + connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceFormatChanged(int))); + connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerXChanged(int))); + connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerYChanged(int))); + connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(surface_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("GPU Address:"))); + sub_layout->addWidget(surface_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(surface_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(surface_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(surface_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(scroll_area); + + auto info_layout = new QHBoxLayout; + { + auto xy_layout = new QVBoxLayout; + { + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("X:"))); + sub_layout->addWidget(surface_picker_x_control); + xy_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Y:"))); + sub_layout->addWidget(surface_picker_y_control); + xy_layout->addLayout(sub_layout); + } + } + info_layout->addLayout(xy_layout); + surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + info_layout->addWidget(surface_info_label); + } + main_layout->addLayout(info_layout); + + main_layout->addWidget(save_surface); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + if (debug_context && debug_context->at_breakpoint) { + emit Update(); + widget()->setEnabled(debug_context->at_breakpoint); + } else { + widget()->setEnabled(false); + } +} + +void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsSurfaceWidget::OnResumed() { + widget()->setEnabled(false); +} + +void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { + surface_source = static_cast<Source>(new_value); + emit Update(); +} + +void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { + if (surface_address != new_value) { + surface_address = static_cast<Tegra::GPUVAddr>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { + if (surface_width != static_cast<unsigned>(new_value)) { + surface_width = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { + if (surface_height != static_cast<unsigned>(new_value)) { + surface_height = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { + if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { + surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { + if (surface_picker_x != new_value) { + surface_picker_x = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { + if (surface_picker_y != new_value) { + surface_picker_y = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::Pick(int x, int y) { + surface_picker_x_control->setValue(x); + surface_picker_y_control->setValue(y); + + if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || + y >= static_cast<int>(surface_height)) { + surface_info_label->setText(tr("Pixel out of bounds")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + return; + } + + surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); +} + +void GraphicsSurfaceWidget::OnUpdate() { + auto& gpu = Core::System::GetInstance().GPU(); + + QPixmap pixmap; + + switch (surface_source) { + case Source::RenderTarget0: + case Source::RenderTarget1: + case Source::RenderTarget2: + case Source::RenderTarget3: + case Source::RenderTarget4: + case Source::RenderTarget5: + case Source::RenderTarget6: + case Source::RenderTarget7: { + // TODO: Store a reference to the registers in the debug context instead of accessing them + // directly... + + auto& registers = gpu.Get3DEngine().regs; + auto& rt = registers.rt[static_cast<size_t>(surface_source) - + static_cast<size_t>(Source::RenderTarget0)]; + + surface_address = rt.Address(); + surface_width = rt.width; + surface_height = rt.height; + if (rt.format != Tegra::RenderTargetFormat::NONE) { + surface_format = ConvertToTextureFormat(rt.format); + } + + break; + } + + case Source::Custom: { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast<int>(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); + + if (surface_address == 0) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + auto unswizzled_data = + Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height); + + auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, + surface_width, surface_height); + + surface_picture_label->show(); + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + Math::Vec4<u8> color; + color[0] = texture_data[x + y * surface_width + 0]; + color[1] = texture_data[x + y * surface_width + 1]; + color[2] = texture_data[x + y * surface_width + 2]; + color[3] = texture_data[x + y * surface_width + 3]; + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + pixmap = QPixmap::fromImage(decoded_image); + surface_picture_label->setPixmap(pixmap); + surface_picture_label->resize(pixmap.size()); + + // Update the info with pixel data + surface_picker_x_control->setEnabled(true); + surface_picker_y_control->setEnabled(true); + Pick(surface_picker_x, surface_picker_y); + + // Enable saving the converted pixmap to file + save_surface->setEnabled(true); +} + +void GraphicsSurfaceWidget::SaveSurface() { + QString png_filter = tr("Portable Network Graphic (*.png)"); + QString bin_filter = tr("Binary data (*.bin)"); + + QString selectedFilter; + QString filename = QFileDialog::getSaveFileName( + this, tr("Save Surface"), + QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), + QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + + if (filename.isEmpty()) { + // If the user canceled the dialog, don't save anything. + return; + } + + if (selectedFilter == png_filter) { + const QPixmap* pixmap = surface_picture_label->pixmap(); + ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + if (pixmap) + pixmap->save(&file, "PNG"); + } else if (selectedFilter == bin_filter) { + auto& gpu = Core::System::GetInstance().GPU(); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + const u8* buffer = Memory::GetPointer(address); + ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); + QByteArray data(reinterpret_cast<const char*>(buffer), size); + file.write(data); + } else { + UNREACHABLE_MSG("Unhandled filter selected"); + } +} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h new file mode 100644 index 000000000..6a344bdfc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -0,0 +1,97 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QLabel> +#include <QPushButton> +#include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +class QComboBox; +class QSpinBox; +class CSpinBox; + +class GraphicsSurfaceWidget; + +class SurfacePicture : public QLabel { + Q_OBJECT + +public: + explicit SurfacePicture(QWidget* parent = nullptr, + GraphicsSurfaceWidget* surface_widget = nullptr); + ~SurfacePicture(); + +protected slots: + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + GraphicsSurfaceWidget* surface_widget; +}; + +class GraphicsSurfaceWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + + enum class Source { + RenderTarget0 = 0, + RenderTarget1 = 1, + RenderTarget2 = 2, + RenderTarget3 = 3, + RenderTarget4 = 4, + RenderTarget5 = 5, + RenderTarget6 = 6, + RenderTarget7 = 7, + ZBuffer = 8, + Custom = 9, + }; + +public: + explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + void Pick(int x, int y); + +public slots: + void OnSurfaceSourceChanged(int new_value); + void OnSurfaceAddressChanged(qint64 new_value); + void OnSurfaceWidthChanged(int new_value); + void OnSurfaceHeightChanged(int new_value); + void OnSurfaceFormatChanged(int new_value); + void OnSurfacePickerXChanged(int new_value); + void OnSurfacePickerYChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnResumed() override; + + void SaveSurface(); + +signals: + void Update(); + +private: + QComboBox* surface_source_list; + CSpinBox* surface_address_control; + QSpinBox* surface_width_control; + QSpinBox* surface_height_control; + QComboBox* surface_format_control; + + SurfacePicture* surface_picture_label; + QSpinBox* surface_picker_x_control; + QSpinBox* surface_picker_y_control; + QLabel* surface_info_label; + QPushButton* save_surface; + + Source surface_source; + Tegra::GPUVAddr surface_address; + unsigned surface_width; + unsigned surface_height; + Tegra::Texture::TextureFormat surface_format; + int surface_picker_x = 0; + int surface_picker_y = 0; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cd799869c..0bdb63a0b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -25,10 +25,13 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" @@ -68,10 +71,16 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + + debug_context = Tegra::DebugContext::Construct(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); + default_theme_paths = QIcon::themeSearchPaths(); + UpdateUITheme(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -160,6 +169,16 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + + graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); + graphicsSurfaceWidget->hide(); + debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); + waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); @@ -324,6 +343,8 @@ bool GMainWindow::LoadROM(const QString& filename) { Core::System& system{Core::System::GetInstance()}; + system.SetGPUDebugContext(debug_context); + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); @@ -635,6 +656,7 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); + UpdateUITheme(); config->Save(); } } @@ -815,6 +837,32 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +void GMainWindow::UpdateUITheme() { + QStringList theme_paths(default_theme_paths); + if (UISettings::values.theme != UISettings::themes[0].second && + !UISettings::values.theme.isEmpty()) { + QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + QFile f(theme_uri); + if (!f.exists()) { + LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); + } else { + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + GMainWindow::setStyleSheet(ts.readAll()); + } + theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); + QIcon::setThemeName(":/icons/" + UISettings::values.theme); + } else { + qApp->setStyleSheet(""); + GMainWindow::setStyleSheet(""); + theme_paths.append(QStringList{":/icons/default"}); + QIcon::setThemeName(":/icons/default"); + } + QIcon::setThemeSearchPaths(theme_paths); + emit UpdateThemedIcons(); +} + #ifdef main #undef main #endif diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..20ff65314 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -2,8 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#ifndef _CITRA_QT_MAIN_HXX_ -#define _CITRA_QT_MAIN_HXX_ +#pragma once #include <memory> #include <QMainWindow> @@ -15,17 +14,18 @@ class Config; class EmuThread; class GameList; class GImageInfo; -class GPUCommandStreamWidget; -class GPUCommandListWidget; class GraphicsBreakPointsWidget; -class GraphicsTracingWidget; -class GraphicsVertexShaderWidget; +class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; class RegistersWidget; class WaitTreeWidget; +namespace Tegra { +class DebugContext; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -64,6 +64,9 @@ signals: */ void EmulationStopping(); + // Signal that tells widgets to update icons to use the current theme + void UpdateThemedIcons(); + private: void InitializeWidgets(); void InitializeDebugWidgets(); @@ -138,6 +141,8 @@ private: Ui::MainWindow ui; + std::shared_ptr<Tegra::DebugContext> debug_context; + GRenderWindow* render_window; GameList* game_list; @@ -150,7 +155,7 @@ private: std::unique_ptr<Config> config; - // Whether emulation is currently running in Citra. + // Whether emulation is currently running in yuzu. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; @@ -158,14 +163,17 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + // stores default icon theme search paths for the platform + QStringList default_theme_paths; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; }; - -#endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 9036ce2c1..8e215a002 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,6 +15,10 @@ namespace UISettings { using ContextualShortcut = std::pair<QString, int>; using Shortcut = std::pair<QString, ContextualShortcut>; +static const std::array<std::pair<QString, QString>, 2> themes = { + {std::make_pair(QString("Default"), QString("default")), + std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; + struct Values { QByteArray geometry; QByteArray state; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 342ad3850..8b479bc6d 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -90,8 +90,7 @@ void Config::ReadValues() { sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); // Core - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 1)); + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); // Renderer Settings::values.resolution_factor = @@ -107,6 +106,9 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + // System + Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true); + // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 7fa7015ae..895a42c39 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -76,9 +76,9 @@ motion_device= touch_device= [Core] -# Which CPU core to use for CPU emulation -# 0: Unicorn (slow), 1 (default): Dynarmic (faster) -cpu_core = +# Whether to use the Just-In-Time (JIT) compiler for CPU emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_cpu_jit = [Renderer] # Whether to use software or hardware rendering. @@ -154,9 +154,9 @@ output_device = use_virtual_sd = [System] -# The system model that Citra will try to emulate -# 0: Old 3DS (default), 1: New 3DS -is_new_3ds = +# Whether the system is docked +# 1 (default): Yes, 0: No +use_docked_mode = # The system region that Citra will use during emulation # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan @@ -177,12 +177,12 @@ gdbstub_port=24689 # 0: No, 1 (default): Yes enable_telemetry = # Endpoint URL for submitting telemetry data -telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry +telemetry_endpoint_url = # Endpoint URL to verify the username and token -verify_endpoint_url = https://services.citra-emu.org/api/profile -# Username and token for Citra Web Service +verify_endpoint_url = +# Username and token for yuzu Web Service # See https://services.citra-emu.org/ for more info -citra_username = -citra_token = +yuzu_username = +yuzu_token = )"; } diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc index 7cb8a14e1..7de8ef3d9 100644 --- a/src/yuzu_cmd/yuzu.rc +++ b/src/yuzu_cmd/yuzu.rc @@ -6,7 +6,7 @@ // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -CITRA_ICON ICON "../../dist/yuzu.ico" +YUZU_ICON ICON "../../dist/yuzu.ico" ///////////////////////////////////////////////////////////////////////////// |